I wandered (more blundered ) into something with coroutines which threw me a little for a few minutes (a while ) so I thought I’d write a little about debugging. I couldn’t see something like this on here from google or the custom search so i thought I’d post it but apologies if i am repeating an existing post
So essentially the upshot of my (super fun…) time spent trying to figure out what was going on with my coroutine was: coroutines swallow error messages and won’t print them to the output unless you handle errors and print them to the output yourself
So if you have any potentially error prone code in a coroutine it’ll hopefully save you a few minutes by wrapping it in an xpcall, or pcall to get a stack trace (using debug.traceback()) or at least by knowing the point where the error occurred-ish by printing your own error message
I find debugging is not exactly an exact science and can sometimes be more guessing your way up the call stack until something jumps out at you and hits you in the face with a frying pan so essentially patience is required, you’ll get there eventually :). It can sometimes even require many iterations of commenting one line out, running your application, test and repeat until the culprit (or culprits) are found.
Here’s some examples of using xpcall and pcall to catch errors and get a stack trace when an error occurs
xpcall stack trace - probably quite a long call chain in a real use case.
local funcWithError = function() return nil_table[1] end
local r= coroutine.create(function() xpcall(funcWithError,function() print(debug.traceback()) end) end)
coroutine.resume(r)
-- instance output
stack traceback:
[string "ClassName = class()..."]:14: in function <[string "ClassName = class()..."]:14> -- first call /entry point here
[string "ClassName = class()..."]:13: in function <[string "ClassName = class()..."]:13> -- error occurred here
[C]: in function 'xpcall'
[string "ClassName = class()..."]:14: in function <[string "ClassName = class()..."]:14> -- error caught here
-- global output (from in setup())
stack traceback:
[string "-- ProjectName..."]:6: in function <[string "-- ProjectName..."]:6> -- first call / entry point here
[string "-- ProjectName..."]:5: in function <[string "-- ProjectName..."]:5> -- error occurred here
[C]: in function 'xpcall'
[string "-- ProjectName..."]:6: in function <[string "-- ProjectName..."]:6> -- error caught here
pcall stack trace - this is not as detailed as xpcall because it has destroyed the part of the stack (specifically the bit where the error occurred) when it returns true or false for if the call was successful.
In general using just pcall does not seem to give such a nice/detailed stack trace because the stack has already unwound when the function returns but it could be a good starting point for working backwards manually :).
However the callback passed to xpcall is different, this is called before the stack unwinds so this usually has more detail because the call to print(debug.traceback()) is made before xpcall returns and the stack where the error occurred is still available.
local funcWithError = function() return nil_table[1] end
local r= coroutine.create(function() if not pcall(funcWithError) then print(debug.traceback()) end end)
coroutine.resume(r)
-- instance output
stack traceback:
[string "ClassName = class()..."]:14: in function <[string "ClassName = class()..."]:14>
-- global output
stack traceback:
[string "-- ProjectName..."]:7: in function <[string "-- ProjectName..."]:7>
And finally (the clean version of) quoting myself scratching my head earlier… “Just what happened here did it work :s, I don’t know the output says nothing so I’m going with yes…”
Nope because the error was eaten up inside the coroutine. Although you can receive a status like pcall from the first return value. False for something went wrong True for all went fine :). A call to print(debug.traceback()) will probably return similar results to that of pcall when ok is false.
local funcWithError = function() return nil_table[1] end
local r= coroutine.create(function() funcWithError() end)
local ok = coroutine.resume(r)
-- nothing printed in output
Last but not least some tips for reading stack traces:
Follow your stack traces from the bottom up, the top is the starting point of current the code path, the bottom is where the error emerged (was caught) so following it from the bottom up should take you on a shorter route to the call where the error occurred
You will see some numbers in the stack traces these are line numbers corresponding to the path the code follows/flows through your source files.
Absolutely finally I must say this is by no means a definitive debugging guide, but I hope this will save some headaches when it comes to debugging coroutines or just debugging in general