Log In  


hey @zep, I've found a nasty coroutine(?)/multival bug. I'm on Linux + picotron 0.1.0d.

tl;dr: sometimes select("#",tostr(i)) is 2, possibly triggered by calling coresume() with extra args.


I ran into this initially because add({},3,nil) is a runtime error now (it used to work in PICO-8, but now it throws bad argument #2 to 'add' (position out of bounds)). I had some code: add(list,quote(arg)) that was crashing as if quote() was returning a second value for some reason, even though the code for quote() definitely returned just one value. (surrounding it in parens (to only keep the first return value) fixed my bug: add(list,(quote(arg))))

Version A of the code is very short, and trips the assert inside spin maybe 50% of the time? sometimes many runs in a row don't trigger the assert, but sometimes many runs in a row all trigger the assert. (maybe that's just statistics tho). Version B is a bit more complex but always trips the assert instantly for me.


Version A: (short, inconsistent)

printh"---"

function _init()
	local bad,good = cocreate_spin()
	fn = bad
end
function _draw()
	cls()
	local _,prog = fn(0.70)
	?stat(1)
	?prog or "done"
end

function cocreate_spin()
	local coro = cocreate(spin)
	return function( cpu_limit)
		if costatus(coro)=="suspended" then
			--this one breaks sometimes
			return assert(coresume(coro,cpu_limit or 0.90))
		end
	end, function()
		if costatus(coro)=="suspended" then
			--this one always works
			return assert(coresume(coro))
		end
	end
end

local total = 1000000
function spin()
	for i=1,total do
		if i%10000==0 --[[and stat(1)>cpu_limit]] then
			yield(i/total)
		end
		local n = select("#",quote(i))
		if n!=1 then
			assert(false,tostr(n).." retvals?! "..i)
		end
	end
end

function quote(t)
	return tostr(t)
end

Version B: (longer, very consistent)

printh"---"

function _init()
	poke(0x5f36,0x80) -- wrap text
	window{
		title="import png",
		width=160,
		height=64,
	}
	job = job_importpng()
end
function _update()
	job:work(0.70)
end
function _draw()
	cls()
	print(stat(1))
	print(costatus(job.coro))
end

function job_importpng()
	local coro = cocreate(pq_many)
	assert(coresume(coro))
	local job = {
		coro = coro,
		progress = 0.00,
	}
	-- returns true iff job has more work to do
	function job:work( cpu_limit)
		if costatus(self.coro)=="suspended" then
			local _,dat = assert(coresume(self.coro,cpu_limit or 0.90))
			if dat then
				self.progress = dat
				return true -- more work to do
			end
		end
	end
	return job
end

function pq_many()
	for i=1,1000000 do
		if i&2047==0 then
			yield()
		end
		pq(i)
	end
end

-- quotes all args and prints to host console
-- usage:
--   pq("handles nils", many_vars, {tables=1, work=11, too=111})
function pq(arg)
	local s= {}
		local n = select("#",quote(arg))
		if n!=1 then
			local second = select(2,quote(arg))
			assert(false,tostr(n).." retvals?: "..quote(arg).." "..tostr(second))
		end
		add(s,(quote(arg)))
		-- add(s,quote(arg))
	printh(table.concat(s," "))
end

-- quote a single thing
-- like tostr() but for tables
-- don't call this directly; call pq or qq instead
function quote(t)
	if type(t)~="table" then return tostr(t) end
	local s={}
	for k,v in pairs(t) do
		add(s,tostr(k).."="..quote(v))
	end
	return "{"..table.concat(s,",").."}"
end


As noted in Version A, the two coresume-wrapping-functions inside cocreate_spin() act differently -- I've never been able to trip the assert with the "good" version. I've tried versions of this code with no coroutines and haven't been able to trip the assert.

idk what else to say, this bug seems baffling -- sometimes select("#",quote(i)) is 2, despite quote() being a wrapper for tostr()

1


1

Fascinating! I was able to simplify the reproduction quite a bit and have it fail every single time (sometimes it takes 100,000+ iterations, sometimes it only takes 650 🤷‍♂️):

coro = cocreate(function()
    for i=1,10000000 do
        -- ceil() could be any function as long as it's called within a co-routine
        -- and it should ALWAYS return only one value
        local rets = pack(ceil(i))
        if #rets > 1 then
             assert(false, "\n"..table.concat(rets, "\n"))
        end
    end
end)

-- second param to coresume _sometimes_ causes errors
-- remove second param to coresume, then it works
assert(coresume(coro, "uh-oh"))

It looks like any function called within a coroutine could occasionally return the parameter(s) passed to coresume (try adding more args to the coresume(...) call - they call get returned from ceil())!

Edit: Reproduced in 0.1.0d on MacOS M1.


The equivalent code in lua 5.4.6 on my host machine (MacOS 12.4 M1) doesn't exhibit any errors:

coro = coroutine.create(function()
  for i = 1, 10000000 do
    -- ceil() could be any function as long as it's called within a co-routine
    local rets = table.pack(math.ceil(i))
    if #rets > 1 then
      assert(false, "\n" .. table.concat(rets, "\n"))
    end
  end
end)

-- never errors
assert(coroutine.resume(coro, "uh-oh"))

great work shrinking this down! I forget if I said that in discord or not -- nice work, your repro code is nice and tiny

I just tried this in pico8, and it has a similar bug: https://www.lexaloffle.com/bbs/?tid=142550

I also tried your last snippet ("The equivalent code in lua 5.4.6...") on my machine (linux 6.6.30-2-MANJARO x86_64 / Lua 5.4.6) and it does not error, as expected



[Please log in to post a comment]