This is effectively a DoS vector. A single malicious client can stall server threads indefinitely by never returning from OnClientInvoke

From a production and security standpoint, an indefinitely blocking synchronous RPC against an untrusted peer is indistinguishable from a bug. Even if not documented as a timeout, this behavior enables trivial DoS. We request an engine‑level timeout/cancellation mechanism so developers don’t have to reinvent unreliable workarounds with RemoteEvents.

Why this should be considered a bug
The current behavior allows an untrusted endpoint to block a synchronous RPC indefinitely without any documented or configurable timeout. This is not just a missing feature — it directly enables a trivial denial-of-service vector against the game server. In every other synchronous I/O API in the engine (e.g., HTTP, DataStore), there is an error path or timeout for failure cases. RemoteFunction is the only synchronous API that can yield forever waiting for a peer that may never respond. This violates safe-by-default design principles and is inconsistent with other engine networking APIs. A single malicious client can stall server logic indefinitely with no recourse, which is a reliability and security defect, not merely a feature request.

Expected behavior

When invoking a RemoteFunction, if the callee does not return within a reasonable time, the invocation should automatically terminate and return an error (or a specific Enum.TeleportResult-like status) to the caller after a fixed, documented timeout period, regardless of whether the target client/server disconnects. This prevents indefinite blocking and potential server stalls.

7 Likes

Quite honestly, this looks like an AI DOS H1 like report (if you know, you know)

Even then, I believe there’s an incorrect reference to what counts for synchronous.

All APIs you mentioned, including :Invoke() on RemoteFunctions, are asynchronous. If they were synchronous, the thread would be locked. Not the Luau thread, but the OS thread.

This is not the case; all the functions laid out are I/O, yes, but they’re all asynchronous in nature.

“Reasonable time frame” is too broad. Is it defined on a property? A function call?

The server will never ‘stall’. It will simply yield, as written explicitly in the documentation, until the call completes, which may never happen, and it’s widely documented that so is the case. If anyone doesn’t want to check it out with a simple wrapper module around it, which would be very simple to create and would fix this issue completely, then I’d say it’s simply laziness.

While the engine sure is at fault for this, developers are capable of acknowledging the problem and fixing it ourselves, as we have always done.

3 Likes

I understand that :Invoke() doesn’t block an OS thread, but that’s not really the point in this context. In Luau, the call still behaves as a blocking operation — once you hit :Invoke(), that script thread is stuck waiting until it gets a response or the call ends. From a gameplay and logic standpoint, that’s synchronous behavior, because nothing after that line will run until the invoke finishes.

It’s true that the server process itself isn’t literally frozen, but that doesn’t mean there’s no impact. Roblox runs a large amount of game logic in Luau threads, and if you start stacking up a lot of invokes that never return, you end up with a lot of suspended threads sitting in memory. Those threads still consume resources, and as the number grows, you’ll start to see side effects — more garbage collection work, slower scheduling, and a general drop in responsiveness.

So while it’s not an OS-level stall, it can still become a noticeable stall in terms of gameplay logic and server performance. That’s the real concern.

3 Likes

A wrapper can help avoid the problem, but it’s not a real fix. The moment you call :Invoke() on a RemoteFunction, you’ve already given up control — there’s no way to cancel it from the calling side, no way to set a timeout, and no way to break out of that wait without the callee responding or disconnecting. That’s a limitation of the API itself.

You can work around it by avoiding RemoteFunction or building your own request/response system with RemoteEvents, but that’s essentially abandoning the built-in feature because it can’t be made safe in certain scenarios. When something is that easy to misuse and that hard to recover from, I’d argue it’s a design flaw that should be addressed at the engine level, not just patched over in user code.

2 Likes

If someone is using a modified or malicious client, they can deliberately exploit the way InvokeClient works to waste server resources. All they have to do is set up OnClientInvoke so it never returns, and every time the server calls them, that thread on the server side is stuck indefinitely. There’s no built-in way to cancel the call, so those suspended threads just pile up.

2 Likes

Why are you placing synchronously-performed, yielding network operations in gameplay code where it can stall core logic? Wouldn’t it be better overall if Clients periodically sent the data to the Server in a manner asynchronous to your gameplay logic so it never yields or pauses for however many hundreds of milliseconds, potentially? That’ll still be a likely unnecessary cost even if this timeout was implemented.

SomeRemoteEvent:FireClient(Player)
local Player, Result = SomeRemoteEvent.OnServerEvent:Wait()

This is a missing feature. It’s a caveat you accept when dealing with networking synchronously.

I agree that a configurable timeout feature should still exist.

This might not be backwards-compatible, it would likely have to (at least) be manually enabled by developers to avoid breaking games which rely on a thread being blocked for a very long period of time (such as waiting for the player to exit an interface or make a selection within it).

2 Likes

Sure, blocking until a response comes back is a known property of synchronous calls, but the problem here isn’t that the call blocks — it’s that there’s no built-in safeguard at all. In most environments that allow synchronous networking, there’s at least a timeout or cancellation mechanism so a dead connection or an unresponsive peer doesn’t hold a call open forever.

Saying “that’s just how synchronous networking works” is leaving out the fact that safe-by-default designs still exist for synchronous APIs. Even if the engine wants to keep Invoke() blocking, it can still provide a documented timeout, a way to cancel the call, or a clear error path. Without that, the moment you use Invoke() you’re committing to a wait that you have no control over, and in a multiplayer environment that’s an open door for abuse.

2 Likes

This is well-documented behavior and is not a bug.

If you really need a timeout, you should open a feature request.

Just because something is documented doesn’t automatically mean it’s not a bug. Imagine if an API wrote in its docs, “Calling this function might sometimes delete your save data without warning.” Even if that behavior was clearly written down, it would still be a problem worth fixing.

It’s the same here — the fact that Invoke() can hang forever is written in the docs doesn’t mean it’s good design. If anything, the documentation just confirms that this is an unsafe default. A safe-by-default API shouldn’t allow a single unresponsive peer to block execution indefinitely, especially in a live multiplayer environment.

2 Likes

Is it too much to ask for you to actually type out your responses without an AI babying you throughout the entire process? I watched this thread intently and you did not type for a single second. You immediately pasted in the entire reply. Quit copying and pasting what ChatGPT tells you.

On that ground, anything I say you’ll have a retort for because I’m arguing with a machine, but to humor you:
If a real function in the documentation for a program told me

Calling this function might sometimes delete your save data without warning

Then I wouldn’t use it. Notice how this make-believe nonsensical scenario doesn’t mention it is a bug, the expected behavior (at time of writing) is indeed that it will randomly delete your save data. Bad design, but not a bug. Once again, this is something you open a FEATURE REQUEST to get fixed. Don’t claim it’s a bug when it’s not.

I get the distinction you’re making between “bad design” and “bug,” but in practice the line isn’t that clean. There are plenty of cases where something was originally intended, documented, and shipped — and later reclassified as a bug because it caused unintended side effects or opened the door for abuse.

Security vulnerabilities are the clearest example. Many of them existed “by design” at the time, sometimes even documented, but they were still patched as bugs once their real-world impact became clear. The fact that a problem is intentional at launch doesn’t mean it can’t be a defect.

In this case, the lack of any safeguard in Invoke() isn’t just an aesthetic design choice — it’s something that can be exploited to tie up server resources indefinitely. That’s an operational flaw, and those fall squarely into bug territory, even if the documentation warns about it.

2 Likes

In networking, you should always design with the assumption that failure can happen at any time.

After requesting the client, it will be postponed indefinitely unless the client replies or leaves.
So I think the only solution for now is to:kick() the player if he doesn’t answer in time.

Not a bug, as pointed out above, the fact that the behavior is poor is documented.

Yes, if we were implementing this today we would probably include a timeout affordance by default for the reasons you outline.

However it was implemented more than 10 years ago. There are certainly many experiences out there relying on long client invocations (E.g.: The length of a round) that would break if we just decided to add a timeout.

This would a have to be addressed through a feature request for an additional timeout option, not a bug report.

1 Like