-- some_module
print("a");
local c = coroutine.running();
task.delay(2, function()
coroutine.resume(c);
end)
coroutine.yield();
print("b");
print(coroutine.status(_G.cor)); -- 'suspended'
-- if you do resume the script's coroutine,
-- the value of require(some_module) will be nil,
-- regardless of what the module actually returns.
task.defer(_G.cor);
local module = ...;
return module;
note that this side effect doesn’t occur if the module yields through a task library function or roblox function (for example WaitForChild()).
Expected behavior
this is what docs says should happen, on the ModuleScript api reference page.
“Note that the first call to require() will not yield (halt) unless the ModuleScript yields (calls task.wait() for example), in which case the current thread that called require() will yield until the ModuleScript returns a value.”
the module does return a value, but the script doesn’t resume execution.
I believe the issue is caused by manually yielding and resuming the same coroutine that require() is executing on
require() appears to have its own continuation handling when the ModuleScript yields. When you do:
local c = coroutine.running()
task.delay(2, function()
coroutine.resume(c)
end)
coroutine.yield()
you are manually resuming the coroutine before Roblox’s require() continuation gets a chance to resume it normally
That’s also why print("b") executes, but print("c") never does. The ModuleScript itself continues correctly after the coroutine.resume(), but the caller’s require() continuation is left in a suspended state
You can see the difference by replacing the manual coroutine yield with a scheduler yield:
The same seems to apply to WaitForChild() and other Roblox/task scheduler yielding functions, which explains why the side effect doesn’t occur with those
So I don’t think the problem is that the ModuleScript failed to return. It does return. The problem seems to be that manually resuming the coroutine bypasses/breaks the continuation that require() uses to resume the caller
If this is reproducible consistently, I’d consider it an engine bug/edge case in the interaction between require() and manually resumed coroutines rather than a normal Luau coroutine behavior
and i didn’t say the problem, was that the module failed to return.
we couldn’t really know what causes this or how roblox manages the resumption of the require() caller’s execution, because that code is closed source.
In case, where you absolutely need to do coroutine.yield() during the require of a module, you would implement that inside a function instead of a module, to have it work normally, however that would be a workaround if the programmer, thought that putting that code inside a module script was more appropriate.
I consider this a bug, simply because it doesn’t follow the expected behavior as explained in the docs.
as for reproducing the bug consistently, for me every time i run the code, from the first example, i get the same result, is that different for you?