Skip to content

Commit b4d5dff

Browse files
committed
Multiple errors in '__toclose' report the first one
When there are multiple errors when closing objects, the error reported by the protected call is the first one, for two reasons: First, other errors may be caused by this one; second, the first error is handled in the original execution context, and therefore has the full traceback.
1 parent 14edd36 commit b4d5dff

5 files changed

Lines changed: 56 additions & 28 deletions

File tree

lcorolib.c

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,8 @@ static int luaB_auxwrap (lua_State *L) {
7575
int r = auxresume(L, co, lua_gettop(L));
7676
if (r < 0) {
7777
int stat = lua_status(co);
78-
if (stat != LUA_OK && stat != LUA_YIELD) {
79-
stat = lua_resetthread(co); /* close variables in case of errors */
80-
if (stat != LUA_OK) /* error closing variables? */
81-
lua_xmove(co, L, 1); /* get new error object */
82-
}
78+
if (stat != LUA_OK && stat != LUA_YIELD)
79+
lua_resetthread(co); /* close variables in case of errors */
8380
if (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */
8481
luaL_where(L, 1); /* add extra info, if available */
8582
lua_insert(L, -2);

lfunc.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,16 @@ static int callclosemth (lua_State *L, TValue *uv, StkId level, int status) {
144144
luaG_runerror(L, "attempt to close non-closable variable '%s'", vname);
145145
}
146146
}
147-
else { /* there was an error */
147+
else { /* must close the object in protected mode */
148+
ptrdiff_t oldtop = savestack(L, level + 1);
148149
/* save error message and set stack top to 'level + 1' */
149150
luaD_seterrorobj(L, status, level);
150151
if (prepclosingmethod(L, uv, s2v(level))) { /* something to call? */
151-
int newstatus = luaD_pcall(L, callclose, NULL, savestack(L, level), 0);
152-
if (newstatus != LUA_OK) /* another error when closing? */
152+
int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0);
153+
if (newstatus != LUA_OK && status == CLOSEPROTECT) /* first error? */
153154
status = newstatus; /* this will be the new error */
155+
else /* leave original error (or nil) on top */
156+
L->top = restorestack(L, oldtop);
154157
}
155158
/* else no metamethod; ignore this case and keep original error */
156159
}

manual/manual.of

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,23 +1541,30 @@ if there was no error, the second argument is @nil.
15411541

15421542
If several to-be-closed variables go out of scope at the same event,
15431543
they are closed in the reverse order that they were declared.
1544+
15441545
If there is any error while running a closing method,
15451546
that error is handled like an error in the regular code
15461547
where the variable was defined;
15471548
in particular,
15481549
the other pending closing methods will still be called.
1550+
After an error,
1551+
other errors in closing methods
1552+
interrupt the respective method,
1553+
but are otherwise ignored;
1554+
the error reported is the original one.
15491555

15501556
If a coroutine yields inside a block and is never resumed again,
15511557
the variables visible at that block will never go out of scope,
15521558
and therefore they will never be closed.
15531559
Similarly, if a coroutine ends with an error,
15541560
it does not unwind its stack,
15551561
so it does not close any variable.
1556-
You should either use finalizers
1557-
or call @Lid{coroutine.close} to close the variables in these cases.
1558-
However, note that if the coroutine was created
1562+
In both cases,
1563+
you should either use finalizers
1564+
or call @Lid{coroutine.close} to close the variables.
1565+
However, if the coroutine was created
15591566
through @Lid{coroutine.wrap},
1560-
then its corresponding function will close all variables
1567+
then its corresponding function will close the coroutine
15611568
in case of errors.
15621569

15631570
}
@@ -3932,7 +3939,7 @@ Returns a status code:
39323939
@Lid{LUA_OK} for no errors in closing methods,
39333940
or an error status otherwise.
39343941
In case of error,
3935-
leave the error object on the stack,
3942+
leaves the error object on the top of the stack,
39363943

39373944
}
39383945

@@ -6355,6 +6362,7 @@ Closes coroutine @id{co},
63556362
that is,
63566363
closes all its pending to-be-closed variables
63576364
and puts the coroutine in a dead state.
6365+
The given coroutine must be dead or suspended.
63586366
In case of error closing some variable,
63596367
returns @false plus the error object;
63606368
otherwise returns @true.
@@ -6412,7 +6420,8 @@ true when the running coroutine is the main one.
64126420

64136421
Returns the status of the coroutine @id{co}, as a string:
64146422
@T{"running"},
6415-
if the coroutine is running (that is, it called @id{status});
6423+
if the coroutine is running
6424+
(that is, it is the one that called @id{status});
64166425
@T{"suspended"}, if the coroutine is suspended in a call to @id{yield},
64176426
or if it has not started running yet;
64186427
@T{"normal"} if the coroutine is active but not running

testes/coroutine.lua

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,23 @@ do
163163
assert(not X and coroutine.status(co) == "dead")
164164

165165
-- error closing a coroutine
166+
local x = 0
166167
co = coroutine.create(function()
168+
local <toclose> y = func2close(function (self,err)
169+
if (err ~= 111) then os.exit(false) end -- should not happen
170+
x = 200
171+
error(200)
172+
end)
167173
local <toclose> x = func2close(function (self, err)
168174
assert(err == nil); error(111)
169175
end)
170176
coroutine.yield()
171177
end)
172178
coroutine.resume(co)
179+
assert(x == 0)
173180
local st, msg = coroutine.close(co)
174-
assert(not st and coroutine.status(co) == "dead" and msg == 111)
181+
assert(st == false and coroutine.status(co) == "dead" and msg == 111)
182+
assert(x == 200)
175183

176184
end
177185

testes/locals.lua

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -267,14 +267,14 @@ do -- errors in __close
267267
if err then error(4) end
268268
end
269269
local stat, msg = pcall(foo, false)
270-
assert(msg == 1)
271-
assert(log[1] == 10 and log[2] == 3 and log[3] == 2 and log[4] == 2
270+
assert(msg == 3)
271+
assert(log[1] == 10 and log[2] == 3 and log[3] == 3 and log[4] == 3
272272
and #log == 4)
273273

274274
log = {}
275275
local stat, msg = pcall(foo, true)
276-
assert(msg == 1)
277-
assert(log[1] == 4 and log[2] == 3 and log[3] == 2 and log[4] == 2
276+
assert(msg == 4)
277+
assert(log[1] == 4 and log[2] == 4 and log[3] == 4 and log[4] == 4
278278
and #log == 4)
279279

280280
-- error in toclose in vararg function
@@ -317,15 +317,15 @@ if rawget(_G, "T") then
317317
local <toclose> x = setmetatable({}, {__close = function ()
318318
T.alloccount(0); local x = {} -- force a memory error
319319
end})
320-
error("a") -- common error inside the function's body
320+
error(1000) -- common error inside the function's body
321321
end
322322

323323
stack(5) -- ensure a minimal number of CI structures
324324

325325
-- despite memory error, 'y' will be executed and
326326
-- memory limit will be lifted
327327
local _, msg = pcall(foo)
328-
assert(msg == "not enough memory")
328+
assert(msg == 1000)
329329

330330
local close = func2close(function (self, msg)
331331
T.alloccount()
@@ -368,8 +368,7 @@ if rawget(_G, "T") then
368368
end
369369

370370
local _, msg = pcall(test)
371-
assert(msg == 1000)
372-
371+
assert(msg == "not enough memory") -- reported error is the first one
373372

374373
do -- testing 'toclose' in C string buffer
375374
collectgarbage()
@@ -453,15 +452,27 @@ end
453452

454453
do
455454
-- error in a wrapped coroutine raising errors when closing a variable
456-
local x = false
455+
local x = 0
457456
local co = coroutine.wrap(function ()
458-
local <toclose> xv = func2close(function () error("XXX") end)
457+
local <toclose> xx = func2close(function () x = x + 1; error("YYY") end)
458+
local <toclose> xv = func2close(function () x = x + 1; error("XXX") end)
459459
coroutine.yield(100)
460460
error(200)
461461
end)
462-
assert(co() == 100)
463-
local st, msg = pcall(co)
464-
-- should get last error raised
462+
assert(co() == 100); assert(x == 0)
463+
local st, msg = pcall(co); assert(x == 2)
464+
assert(not st and msg == 200) -- should get first error raised
465+
466+
x = 0
467+
co = coroutine.wrap(function ()
468+
local <toclose> xx = func2close(function () x = x + 1; error("YYY") end)
469+
local <toclose> xv = func2close(function () x = x + 1; error("XXX") end)
470+
coroutine.yield(100)
471+
return 200
472+
end)
473+
assert(co() == 100); assert(x == 0)
474+
local st, msg = pcall(co); assert(x == 2)
475+
-- should get first error raised
465476
assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
466477
end
467478

0 commit comments

Comments
 (0)