From 389116d8abcc96db3cfe2f3cc25789c089fe12d6 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 9 May 2019 11:13:45 -0300 Subject: Coroutines do not unwind the stack in case of errors Back to how it was, a coroutine does not unwind its stack in case of errors (and therefore do not close its to-be-closed variables). This allows the stack to be examined after the error. The program can use 'coroutine.kill' to close the variables. The function created by 'coroutine.wrap', however, closes the coroutine's variables in case of errors, as it is impossible to examine the stack any way. --- testes/coroutine.lua | 8 ++++++-- testes/db.lua | 14 ++++++++++---- testes/locals.lua | 35 +++++++++++++++++++++++++++-------- 3 files changed, 43 insertions(+), 14 deletions(-) (limited to 'testes') diff --git a/testes/coroutine.lua b/testes/coroutine.lua index 35ff27fb..9dd501e7 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -346,9 +346,13 @@ do local st, res = coroutine.resume(B) assert(st == true and res == false) - A = coroutine.wrap(function() return pcall(A, 1) end) + local X = false + A = coroutine.wrap(function() + local *toclose _ = setmetatable({}, {__close = function () X = true end}) + return pcall(A, 1) + end) st, res = A() - assert(not st and string.find(res, "non%-suspended")) + assert(not st and string.find(res, "non%-suspended") and X == true) end diff --git a/testes/db.lua b/testes/db.lua index 95275fb4..3d94f776 100644 --- a/testes/db.lua +++ b/testes/db.lua @@ -734,18 +734,24 @@ a, b = coroutine.resume(co, 100) assert(a and b == 30) --- check traceback of suspended coroutines +-- check traceback of suspended (or dead with error) coroutines + +function f(i) + if i == 0 then error(i) + else coroutine.yield(); f(i-1) + end +end -function f(i) coroutine.yield(i == 0); f(i - 1) end co = coroutine.create(function (x) f(x) end) a, b = coroutine.resume(co, 3) t = {"'coroutine.yield'", "'f'", "in function <"} -repeat +while coroutine.status(co) == "suspended" do checktraceback(co, t) a, b = coroutine.resume(co) table.insert(t, 2, "'f'") -- one more recursive call to 'f' -until b +end +t[1] = "'error'" checktraceback(co, t) diff --git a/testes/locals.lua b/testes/locals.lua index de47ae31..814d1b16 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -417,12 +417,13 @@ if rawget(_G, "T") then end --- to-be-closed variables in coroutines +print "to-be-closed variables in coroutines" + do - -- an error in a coroutine closes variables + -- an error in a wrapped coroutine closes variables local x = false local y = false - local co = coroutine.create(function () + local co = coroutine.wrap(function () local *toclose xv = func2close(function () x = true end) do local *toclose yv = func2close(function () y = true end) @@ -432,14 +433,31 @@ do error(23) -- error does end) - local a, b = coroutine.resume(co) - assert(a and b == 100 and not x and not y) - a, b = coroutine.resume(co) - assert(a and b == 200 and not x and y) - a, b = coroutine.resume(co) + local b = co() + assert(b == 100 and not x and not y) + b = co() + assert(b == 200 and not x and y) + local a, b = pcall(co) assert(not a and b == 23 and x and y) end + +do + -- error in a wrapped coroutine raising errors when closing a variable + local x = false + local co = coroutine.wrap(function () + local *toclose xv = func2close(function () error("XXX") end) + coroutine.yield(100) + error(200) + end) + assert(co() == 100) + local st, msg = pcall(co) +print(msg) + -- should get last error raised + assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX")) +end + + -- a suspended coroutine should not close its variables when collected local co co = coroutine.wrap(function() @@ -449,6 +467,7 @@ co = coroutine.wrap(function() end) co() -- start coroutine assert(co == nil) -- eventually it will be collected +collectgarbage() -- to-be-closed variables in generic for loops -- cgit v1.2.3-55-g6feb