"cannot resume non-suspended coroutine", no stack trace

I’m using Google Analytics, and when I checked it, there was the error mentioned in the title with no stack trace.image
Anyone know any causes for that error? Should I just switch to spawn?

3 Likes

I think that it is caused by you trying to run coroutine.resume on a function on either an already running coroutine or a function that isn’t a coroutine

1 Like

Oops, forgot to mention the only thing i use in the game related to coroutines is coroutine.wrap. Every coroutine.wrap is written like this:

coroutine.wrap(FUNCTION)()

nothing is written like this (a common problem I’ve seen in other posts):
local d = coroutine.wrap(FUNCTION)
d()

1 Like

I haven’t used coroutine.wrap before so I am probably not the best person to be answering your question. However, with the wiki article I just read and prior experience with coroutines I think that
Coroutine.resume(coroutine.create(function))

Should work just as fine as coroutine.wrap()

1 Like

Can you provide a basic version or full version of your code? Switching to spawn isn’t recommended.

1 Like

I think this may be the problem function, I don’t really know. (simplified)

local function f()
     while 1 do
          m = m + 1
          if m >= 2100 then break end
          wait(1)
     end
     f()
end

coroutine.wrap(f)()
1 Like

Does it immediately break or when “m” reaches 2100? Also, I have never tried calling a coroutines function inside the function.

1 Like

That is basically the same as the first way you do it. Neither way will cause any issue by themselves.

1 Like

Oops, yeah. I think in the other problems they did

local d = coroutine.wrap(FUNCTION)()
d()
1 Like

Yes.

1 Like
local f = coroutine.wrap(func)

is similar to doing

local f
do
    local c = coroutine.create(func)
    f = function(...) return coroutine.resume(c,...) end
end

my point is that the coroutine itself is only created once

coroutines are essentially threads and the objects themselves are of the thread datatype. how threads in lua work is they can yield and they can terminate (with or without returns). once a thread terminates you cannot revive it unless you create a new one. ‘suspended’ coroutines are yielded threads. this function might be more of what youre looking for:

local wrap = function(func)
    return function(...)
        return coroutine.resume(coroutine.create(func),...)
    end
end

and a better implementation (without the extra call) would be:

local spawnf = function(func,...)
    return coroutine.resume(coroutine.create(func),...)
end
1 Like

Nothing in the code present definitely shows whats it causing the error. Maybe could we get the full error message? How long has the code been running for when this happens? Have you tried inserting calls to print to narrow down where it magically crashes? I don’t think you can use the Roblox debugger to see where the script execution stopped if a proper error message isn’t printed.

As @RedDuck765 said, I’ve only seen this when coroutine.resume is called on a coroutine when it is running. That said, it could be possible that a Roblox C++ function that yields could cause this when it tries to resume the coroutine that called it. I’d add a print before and after each external call as well.

In order to make sure that you catch every external call, you could use my sandbox module. It’ll capture everything your script does, and allow you to print everything so you can see the last state your script was in when it crashed.

Here is an edited version of it that prints every time you index a table/userdata that wasn’t created locally and whenever you call a function. If you see that the last print was a function call, then it likely caused the error. The tostring of it will be returned, so you need to follow it back to the last index print to find the name of it.

-- Localize global functions to prevent a changing environment
-- from messing with this code. This should allow multiple
-- instances of this code to run at once. (Don't do that...)
local setfenv = setfenv
local getfenv = getfenv
local setmetatable = setmetatable
local type = type
local select = select
local tostring = tostring
local newproxy = newproxy

-- Unfortunately metamethods have different ways of invoking
-- the default metamethods of values. This table provides
-- metamethods which does that, so they can be easily wrapped.
local Capsule = {}
Capsule.__metatable = 'This debug metatable is locked.'
function Capsule:__index(k)
	print("Indexed", k, "returned", self[k])
	return self[k]
end
function Capsule:__newindex(k, v)	self[k] = v 	end
function Capsule:__call(...)		self(...) 		end
function Capsule:__concat(v)		return self .. v end
function Capsule:__unm()			return -self 	end
function Capsule:__add(v)			return self + v end
function Capsule:__sub(v)			return self - v end
function Capsule:__mul(v)			return self * v end
function Capsule:__div(v)			return self / v end
function Capsule:__mod(v)			return self % v end
function Capsule:__pow(v)			return self ^ v end
function Capsule:__tostring()		return tostring(self) end
function Capsule:__eq(v)			return self == v end
function Capsule:__lt(v)			return self < v end
function Capsule:__le(v)			return self <= v end
function Capsule:__len()			return #self 	end

-- We don't want to prevent interfaces and 'data' from being
-- garbage collected. While unwrapped 'data' shouldn't be present
-- in lua, interfaces will always be present in lua for the
-- lifetime of the data. So, we remove the data when the interfaces
-- are garbage collected, and allow interfaces to be collected.
local original = setmetatable({}, {__mode = 'k'})
local wrapper = setmetatable({}, {__mode = 'v'})

-- Since RBXLua isn't multithreaded and wrap/unwrap cannot
-- yield, i and n are fine being passed as upvalues. This
-- allows a very simple implmentation of a function wrapper
-- without the use of tables and unpack, which does not
-- always preserve nil values.
local i, n = 1, 0

-- This function is called to make sure no wrappers get passed
-- outside the sandbox. The only data that leaves the sandbox is
-- through metamethod and function call arguments. These arguments
-- are always unwrapped.
local function unwrap(...)
	if i > n then
		i = 1
		n = select('#', ...)

		-- make sure we handle n = 0 correctly, otherwise we can break
		-- vararg functions like 'print()' because they see the number of
		-- inputs, including nil ones.
		if n == 0 then
			return
		end
	end

	-- value may be nil, we should proceed with caution.
	local value = select(i, ...)
	if value and wrapper[value] then
		value = original[wrapper[value]]
	end

	-- wrap the other values too
	i = i + 1
	if i <= n then
		return value, unwrap(...)
	else
		return value
	end
end

-- The return value of all function and metamethod calls is wrapped.
local function wrap(...)
	if i > n then
		i = 1
		n = select('#', ...)

		-- make sure we handle n = 0 correctly, otherwise we can break
		-- vararg functions like 'print()' because they see the number of
		-- inputs, including nil ones.
		if n == 0 then
			return
		end
	end

	-- value may be nil, we should proceed with caution.
	local value = select(i, ...)
	if value then
		local wrapped = wrapper[value]

		if not wrapped then
			local vType = type(value)
			if vType == 'function' then
				local func = value -- value will be changed to the wrapped version soon.
				wrapped = function(...)
					print("calling", func)
					return wrap(func(unwrap(...)))
				end
			elseif vType == 'table' then
				wrapped = setmetatable({}, Capsule)
			elseif vType == 'userdata' then
				wrapped = setmetatable(newproxy(true), Capsule)
			else
				wrapped = value
			end

			wrapper[value] = wrapped -- Same data, same wrapper. Preserves equality tests.
			wrapper[wrapped] = wrapped -- Prevent encapsulating capsules.
			original[wrapped] = value -- store the original value to perform operations on and unwrap
		end

		value = wrapped
	end

	-- wrap the other values too
	i = i + 1
	if i <= n then
		return value, wrap(...)
	else
		return value
	end
end

-- Here, we ensure each metamethod is wrapped.
for key, metamethod in next, Capsule do
	Capsule[key] = wrap(metamethod)
end

-- mwhaha, now even calling rawset is sandboxed...
-- the only functions not sandboxed are those included in the
-- locked string metatable (i.e. 'myStringVar:sub(i, j)')
return function()
	return setfenv(2, wrap(getfenv(2))
end

And don’t worry, it only utilizes function environments, metatables, upvalues as arguments, recursive functions, varargs, and weak tables. What could possibly go wrong?

(Did I mention that this should be used for debugging purposes only?)

Edit: Note that if there is no function call right before then it must be a yielding Roblox function that is causing it. Also note that for this assumption to be true, you must require and call the module at the first line of every script you use. Like so: require(script.Parent.Sandbox)()

2 Likes

Side point: Please note that coroutine.resume(coroutine.create(function() ... end)) does NOT behave the same as coroutine.wrap(function() ... end)().

coroutine.resume will run it in “protected mode”, thus errors will not show up but will be returned, whereas coroutine.wrap does not run in protected mode. Robloxdev site documents this properly actually, but it’s commonly overlooked.

This doesn’t necessarily relate to the original problem, but thought it was worth mentioning just in case

4 Likes

The only error was the error from the title. I didn’t have a stack trace, server time, or anything to go along with it. Additionally, It very rarely happens(it has only happened one time) so I have no idea. The error doesn’t even make anything stop working, so I guess i will just ignore it.

1 Like

it might be a coroutine within a coroutine that is erroring, hence the ghost error.

1 Like

Oh, okay, I’ll look out for those. Thanks everyone for replying, I guess I’ll use this for a solution.

1 Like