Applying a timeout to a call / Pseudo-callbacks using xEvents over xFunctions

Note: x = Remote or Bindable.

Right now, I am trying to create a pseudo callback system that avoids the use of RemoteFunctions (I’ve begun avoiding them like the plague due to the ability to infinitely hang if used incorrectly or exploited). Currently, the system I have set up is a RemoteFunction/BindableEvent (depending across which boundaries I require it in) that fires back to its caller.

local Remote = whatever

-- elsewhere
local Results do
    Remote:FireServer()
    Results = Remote.OnServerEvent:Wait() or wait(1)
end

Would this adequately work? I may encounter some troubles if my remotes are firing back several arguments, since I’m not sure how I’d be able to access those.

I guess the ternary operator you’re using has quite a different behavior than what you’d expect.

The outcome of your script is the following:

  • You fire the remote;
  • You then wait until the remote is fired back, no matter how long it will take.
  • If the event is fired with no arguments, you will further wait another second before proceeding. Plus, if you pass more than one argument, only the first one would make it to Results anyway.

One of the solutions is to have a parallel queue so that the timeout and callback yields can be ran at the same time. This can be done using a coroutine:

local Remote = whatever

-- elsewhere
local Results
local Completed = false

do
    local elapsedTime = 0
    local killQueue = false
    Remote:FireServer()
    -- parallel thread that will conduct the main incoming event
    local reciever = coroutine.create(function()
        local temp = {Remote.OnServerEvent:Wait()} -- wrap these on a table so that they're not truncated
        if not killQueue then
            Completed = true -- flag the main queue that the info got through sucessfully
            Results = temp -- translate this to the final veriable
        end
    end); coroutine.resume(reciever)

    repeat
        elapsedTime = elapsedTime + wait()
    until Completed or elapsedTime >= 1

    killQueue = true -- failsafe for the coroutine not to go through with applying the results if it didn't die already
end

Of course, it’s possible that a better solution might be going around. But this is a solution (if I didn’t miss anything) for implementing a timeout system :slight_smile:

1 Like

Yikes. I’m trying to avoid anything lengthy, but if that’s what it takes, so be it.

You can (and should) wrap the code block inside a function if you’re going to apply the timeout system more than once:

function CallBackTimeout(event, timeout, ...) -- variadic arguments
    local completed
    local results
    local elapsedTime = 0
    local killQueue = false

    event:FireServer(...)
    -- parallel thread that will conduct the main incoming event
    local reciever = coroutine.create(function()
        local temp = {event.OnServerEvent:Wait()} -- wrap these on a table so that they're not truncated
        if not killQueue then
            completed = true -- flag the main queue that the info got through sucessfully
            results = temp -- translate this to the final veriable
        end
    end); coroutine.resume(reciever)

    repeat
        elapsedTime = elapsedTime + wait()
    until completed or elapsedTime >= timeout

    killQueue = true -- failsafe for the coroutine not to go through with applying the results if it didn't die already

    -- returns: sucess, *args
    return completed, unpack(results) -- returns the args in multiple variables, if you still want a table, remove the unpack()
end

This allows you to deploy the timeout system without having to copy-paste code, which is generally a bad practice.

-- thisEvent
thisEvent.OnClientEvent:Connect(function(player, arg1, arg2)
    thisEvent:FireClient(player, arg1..arg2)
end)
-- thatEvent
thisEvent.OnClientEvent:Connect(function(player, argument)
    wait(3)
    thisEvent:FireClient(player, str.rep(argument, argument:len()))
end)

-- Client

CallBackTimeout(thisEvent, 2, "hi", "nub")
-- Returns: true, "hinub"
CallBackTimeout(thatEvent, 5, "oof")
-- Returns: true, "oofoofoof"
CallBackTimeout(thatEvent, 1, "ha u nub")
-- Returns: false (timed out)
2 Likes