Log In  


This one drove me nuts for a while today: it appears that if a runtime error occurs during a coroutine, coresume returns false and no runtime error is reported. This is indistinguishable from the coroutine finishing normally when using the return value of coresume for this information.

Example:

x=4
y=4
cor = nil

function anim()
  printh('x start='..x)
  for i=4,124,4 do
    x=i
    y=i
    yield()
  end
end

function _update()
  if btnp(5) then
    cor = cocreate(anim)
  end
  if cor and not coresume(cor) then
    cor = nil
  end
end

function _draw()
  cls()
  circfill(x, y, 4, 7)
end

This waits for a press of btn 5, then moves the circle across the screen once. Now introduce a runtime error inside anim(), such as by replacing "'x start='..x" with "'x start='..zzz" (attempt to concatenate a nil value). It runs, but just doesn't move the circle. Introduce a similar runtime error in _update() and you'll get the expected runtime error thrown all the way to the runtime environment, where it halts the program and prints an error message. Ideally it'd do the same from within a coroutine.

I haven't checked Lua 5.1's behavior in this regard. If there's a common way to handle it, I'd enjoy knowing about it.

P.S. I'm not sure what costatus(cor) is supposed to return. If I test it within this example's _update() it always returns true, even if the coroutine function has exited, which doesn't seem to match Lua's coroutine.status(cor). If there's a canonical definition I'd enjoy an explanation, otherwise this needs to be documented.

1


This has bitten me a couple of times. It's very confusing when things silently fail in an environment where that normally does not happen.


> Caution: As of Pico-8 v0.1.6, if a runtime error occurs inside a coroutine function, the function aborts, but instead of stopping the program and printing an error message, the coroutine dies and execution continues. This can make debugging coroutines difficult, as many common errors (such as incorrect nil values) manifest as runtime errors.

From http://pico-8.wikia.com/wiki/Cocreate


Sorry to resurrect this old thread, but this is a serious problem that needs fixing (in my opinion).

There's no error message and no way to differentiate between a nicely ended coroutine and a crashed one (costatus() returns "dead" for both).

It makes coroutine problems very hard to debug. Any chance of a fix zep?


I second @mrh, coroutines are great but if they can't be debugged properly then they cause more problems then they solve.


Here's a simple hack to test if there was a runtime error inside a coroutine.

Basically, it wraps cocreate and additionally returns an extended costatus function, which will return 'error' if your coroutine threw a runtime error.

Hope it helps someone!

function cowrap(func)
  local done = false
  local co = cocreate(function()
    func()
    done = true
  end)

  return co, function()
    -- In addition to 'running', 'suspended', and 'dead',
    -- you also have 'error' now.
    if costatus(co) == 'dead' and not done then
      return 'error'
    else
      return costatus(co)
    end
  end
end

co, status = cowrap(function()
  this_function_doesnt_exist()
end)

coresume(co)
print(status())

Hmm, I bet that could be rewritten as a drop-in replacement for cocreate, coresume, and costatus.


Yeah, not too bad. Anyone willing to code review?

Edit: disregard, see better info below.

_cocreate=cocreate
_costatus=costatus
_coresume=coresume

function cocreate(func)
  local co={}
  co.co=_cocreate(function()
    func()
    co.done=true
  end)
  return co
end
function costatus(co) 
  local st=_costatus(co.co)
  if not co.done and st=='dead' then
    st='error'
  end
  return st
end
function coresume(co) 
  return _coresume(co.co)
end

-- amazingly-detailed test suite -- 

co=cocreate(function() end)
print(costatus(co)) --suspended
coresume(co)
print(costatus(co)) --dead

co=cocreate(function()
  this_function_doesnt_exist()
end)
print(costatus(co)) --suspended
coresume(co)
print(costatus(co)) --error

Sorry to jump in here, but coresume does let you capture the failure error message from coroutine that crashed as the second return value from coresume when the first return value is false.

See here: https://www.lexaloffle.com/bbs/?pid=41760#p41760

This explains the weird error capturing, as well as the ability to do two-way communication between the current thread and the cororoutine using multiple arguments/return values.

--

Admittedly it is a little weird, but that's how it works in standard Lua. I guess the idea was that coroutines are made to run as protected lightweight threads, and then if one of the threads crashes, it doesn't bring down the full program. Their weird return value and calling behaviour matches another lua function, pcall, which does a "protected call" of a function and traps any errors. Since Lua doesn't have exceptions like other languages this is probably the best they could do.

It's unusual, because most PICO-8 functions are single-return only, but its behaviour pretty much matches the way it's done in Lua. Normal Lua uses a lot more multiple-return / variable-argument functions, so it mainly just feels out of place here. Especially since the manual doesn't make a mention of this usage really.

Anyway, working with what we have, using costatus is already too late to catch the error, because after the error is returned from the coresume, the coroutine is already dead.

However, coresume contains the information we want, we just need to capture multiple return values to trap the error message.

Using multiple assignment:

local status, err = coresume(co)
if not status then
  print(err)
end

or capturing into a table:

local t={coresume(co)}
if not t[1] then
  print(t[2])
end

Though yeah you could also wrap cocreate/costatus/coresume/etc to have a "done" flag, that gets checked, like above.

I wish assert worked like it did in Lua, because if it did

assert(1 == 2, "these are not the same")

then assert would print an optional error message taken from the second argument, and you could write:

assert(coresume(co))

Wow, that's great to know. Cheers! Loved to find out about the data passing back and forth with yield and coresume.

By the way, I use a workaround for asserting with text:

function msg(c,s)
 if(not c and s) print(s)
 return c
end

assert(msg(false,"duh!"))

It's a bit klunky, but it's better than nothing and almost as convenient. I wish I could roll it into assert(), but putting a wrapper on assert() obviously moves the assert location to a useless spot.

That being said... @zep, it'd be nice if the message worked natively.



[Please log in to post a comment]