Coroutine.resume() bug while used in RemoteFunction & BindableFunction

I was excited to use the changed coroutine.yield on my project while I came across this coroutine issue while using it on RemoteFunction(OnServerInvoke).

ServerScript:


print("Server>> Initialized");
local remote = game.ReplicatedStorage.Request;

remote.OnServerInvoke = function(player)
	local currentThread = coroutine.running();
	print("Server>> Server invoked, Thread:",currentThread);
	coroutine.wrap(function()
		wait(1);
		coroutine.resume(currentThread, "Hello");
	end)();
	
	print("Server>> Server yielding, Thread:",currentThread);
	local yieldValue = coroutine.yield();
	print("Server>> Server yielded:", yieldValue,", Thread:",currentThread);
	return yieldValue;
end

Client Script


print("Client>> Hello world!");

local remote = game.ReplicatedStorage:WaitForChild("Request");
local result;

print("Client>> Invoking server");
delay(2, function() print("Client>> Force print results:", result); end);
result = remote:InvokeServer();
print("Client>> Server results:",result);

Output


image


As you can see, server outputs yielded: Hello, but it doesn’t return the value to the invoked client and client is stuck yielding for the server’s return.

I’m not sure If I am doing something wrong or if this is a bug. Thanks in advance for reading.

yieldingIssue.rbxl (13.4 KB)

4 Likes

Wow, interesting find. I could reproduce this myself too. So using coroutine.yield within OnServerInvoke causes the return value to never get sent to the client, thus causing the client to yield indefinitely.

Perhaps this should be placed under Engine Bugs instead of Scripting Support?

6 Likes

Looks like you cannot return a value to the client unless the coroutine created for OnServerInvoke was resumed by roblox’s thread scheduler:

Adding a wait() on line 15 makes the value to be correctly returned again:

14	print("Server>> Server yielded:", yieldValue,", Thread:",currentThread);
15	wait()
16	return yieldValue;
2 Likes

Before the changes, my assumption is because roblox will take over threads that use any roblox asynchronous functions that yield the thread with the task manager. A problem however is that this effect is not applied beforehand to new threads. A simple example would be using coroutine.yield() before a wait() call and then one after. The second yield will output like a wait() call because of the task managing process. This is most likely a result of the task manager not being able to manage the process’s yield correctly because, as mentioned above, a wait call (which again asserts the thread into the task managing process) will fix the issue. It might have something to do with the change not functioning correctly for these functions in specific.

This behavior has been fixed a while ago.

Code such as this:

local t = coroutine.running()
coroutine.wrap(function()
	while coroutine.status(t) ~= "dead" do
		coroutine.resume(t, "Manual resume")
		wait(1)
	end
end)()
print(coroutine.yield())
print(wait())
print(coroutine.yield())

now prints:

--> Manual resume
--> 0.033623059261913 938.04577054351
--> Manual resume

As said above

I was able to reproduce this with the following code

local bindable = Instance.new("BindableFunction")

function bindable.OnInvoke()
	print("Invoked")
	local thread = coroutine.running()
	spawn(function ()
		print("Thread resuming")
		coroutine.resume(thread, "Howdy")
	end)
	print("Thread yielding")
	local result = coroutine.yield()
	print("Thread resumed")
	return result
end

local result

delay(1, function ()
	print("Force print:", result)
end)

print("Invoke")
result = bindable:Invoke()

print("Print:", result)

I have a feeling I know what is going on. I’ll verify this now and hopefully have a fix for this soon.

3 Likes

This seems to be unrelated to coroutine.yield and instead, it is related to coroutine.resume. You can repro this by replacing the call to yield in my example with wait(1e5).

I will continue looking into this.

2 Likes

Any updates on this issue may I ask?

Has this been fixed yet? I seem to still be having this issue in the part of my project where I left off back when I first encountered it. Guess I’ll have to use another method in the meantime.

Are there any updates on this?

I left a reply on another thread a few months back, that’s the most recent update I have for you. Hopefully we’ll be able to fix it soon.

1 Like

Have you made any progress on fixing this bug?

1 Like

Just a heads up to anyone that stumbles upon this or is still checking up on it:

While this bug still appears for me with coroutine.resume, you can safely replace it with task.spawn and it will not cause the same issue.

2 Likes

just bumping this to confirm that it is still a problem but @devSparkle’s solution fixes it. I should clarify exactly how to do it though:

for my use, I am making a custom signal class and am using coroutine.yield to mimic the wait function of RBXScriptSignals.

function Signal:Wait()
	local Running = coroutine.running()
	table.insert(self._Yielding, Running)
	return coroutine.yield()
end

in signal:Fire(), i was doing:

	for i,thread in pairs(self._Yielding) do
		coroutine.resume(thread, ...)
	end

to apply this fix, simply change coroutine.resume to task.spawn:

	for i,thread in pairs(self._Yielding) do
		task.spawn(thread, ...)
	end

and now it will not cause any problems. hope the engineers can figure this one out if it isnt intentional

1 Like

Bump, I ran into this issue, and it took a couple of hours to figure out what was happening

This is roughly how it was in my game (with the main exception that the task.delay() was a task.defer() with an asynchronous function afterwards)
Client

local RemoteFunction = game.ReplicatedStorage.RemoteFunction

warn("Server Invoked")
RemoteFunction:InvokeServer()
warn("Remote Returned") -- Client yeilds forever, this is never printed

Server

local RemoteFunction = game.ReplicatedStorage.RemoteFunction

RemoteFunction.OnServerInvoke = function(Player)
	local Coroutine = coroutine.running()
	
	task.delay(5,function() 
		coroutine.resume(Coroutine)
	end)
	
	warn("Server yeild started")
	
	coroutine.yield(Coroutine)
	
	warn("Server yeild ended")
	
	return 
end

1 Like