diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2020-12-30 11:20:22 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2020-12-30 11:20:22 -0300 |
commit | ce101dcaf73ff6d610593230d41b63c163a91519 (patch) | |
tree | 6417f02cb96257a835fa908bfea15c2557a41413 | |
parent | 553b37ce4ff758d8cf80d48a21287526c92221c6 (diff) | |
download | lua-ce101dcaf73ff6d610593230d41b63c163a91519.tar.gz lua-ce101dcaf73ff6d610593230d41b63c163a91519.tar.bz2 lua-ce101dcaf73ff6d610593230d41b63c163a91519.zip |
Handles '__close' errors in coroutines in "coroutine style"
Errors in '__close' metamethods in coroutines are handled by the same
logic that handles other errors, through 'recover'.
-rw-r--r-- | ldo.c | 66 | ||||
-rw-r--r-- | testes/coroutine.lua | 41 |
2 files changed, 85 insertions, 22 deletions
@@ -103,7 +103,7 @@ void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) { | |||
103 | break; | 103 | break; |
104 | } | 104 | } |
105 | default: { | 105 | default: { |
106 | lua_assert(errcode >= LUA_ERRRUN); /* real error */ | 106 | lua_assert(errorstatus(errcode)); /* real error */ |
107 | setobjs2s(L, oldtop, L->top - 1); /* error message on current top */ | 107 | setobjs2s(L, oldtop, L->top - 1); /* error message on current top */ |
108 | break; | 108 | break; |
109 | } | 109 | } |
@@ -593,15 +593,11 @@ static void finishCcall (lua_State *L, int status) { | |||
593 | /* | 593 | /* |
594 | ** Executes "full continuation" (everything in the stack) of a | 594 | ** Executes "full continuation" (everything in the stack) of a |
595 | ** previously interrupted coroutine until the stack is empty (or another | 595 | ** previously interrupted coroutine until the stack is empty (or another |
596 | ** interruption long-jumps out of the loop). If the coroutine is | 596 | ** interruption long-jumps out of the loop). |
597 | ** recovering from an error, 'ud' points to the error status, which must | ||
598 | ** be passed to the first continuation function (otherwise the default | ||
599 | ** status is LUA_YIELD). | ||
600 | */ | 597 | */ |
601 | static void unroll (lua_State *L, void *ud) { | 598 | static void unroll (lua_State *L, void *ud) { |
602 | CallInfo *ci; | 599 | CallInfo *ci; |
603 | if (ud != NULL) /* error status? */ | 600 | UNUSED(ud); |
604 | finishCcall(L, *(int *)ud); /* finish 'lua_pcallk' callee */ | ||
605 | while ((ci = L->ci) != &L->base_ci) { /* something in the stack */ | 601 | while ((ci = L->ci) != &L->base_ci) { /* something in the stack */ |
606 | if (!isLua(ci)) /* C function? */ | 602 | if (!isLua(ci)) /* C function? */ |
607 | finishCcall(L, LUA_YIELD); /* complete its execution */ | 603 | finishCcall(L, LUA_YIELD); /* complete its execution */ |
@@ -628,21 +624,36 @@ static CallInfo *findpcall (lua_State *L) { | |||
628 | 624 | ||
629 | 625 | ||
630 | /* | 626 | /* |
631 | ** Recovers from an error in a coroutine. Finds a recover point (if | 627 | ** Auxiliary structure to call 'recover' in protected mode. |
632 | ** there is one) and completes the execution of the interrupted | ||
633 | ** 'luaD_pcall'. If there is no recover point, returns zero. | ||
634 | */ | 628 | */ |
635 | static int recover (lua_State *L, int status) { | 629 | struct RecoverS { |
636 | CallInfo *ci = findpcall(L); | 630 | int status; |
637 | if (ci == NULL) return 0; /* no recovery point */ | 631 | CallInfo *ci; |
632 | }; | ||
633 | |||
634 | |||
635 | /* | ||
636 | ** Recovers from an error in a coroutine: completes the execution of the | ||
637 | ** interrupted 'luaD_pcall', completes the interrupted C function which | ||
638 | ** called 'lua_pcallk', and continues running the coroutine. If there is | ||
639 | ** an error in 'luaF_close', this function will be called again and the | ||
640 | ** coroutine will continue from where it left. | ||
641 | */ | ||
642 | static void recover (lua_State *L, void *ud) { | ||
643 | struct RecoverS *r = cast(struct RecoverS *, ud); | ||
644 | int status = r->status; | ||
645 | CallInfo *ci = r->ci; /* recover point */ | ||
646 | StkId func = restorestack(L, ci->u2.funcidx); | ||
638 | /* "finish" luaD_pcall */ | 647 | /* "finish" luaD_pcall */ |
639 | L->ci = ci; | 648 | L->ci = ci; |
640 | L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */ | 649 | L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */ |
641 | status = luaD_closeprotected(L, ci->u2.funcidx, status); | 650 | luaF_close(L, func, status); /* may change the stack */ |
642 | luaD_seterrorobj(L, status, restorestack(L, ci->u2.funcidx)); | 651 | func = restorestack(L, ci->u2.funcidx); |
652 | luaD_seterrorobj(L, status, func); | ||
643 | luaD_shrinkstack(L); /* restore stack size in case of overflow */ | 653 | luaD_shrinkstack(L); /* restore stack size in case of overflow */ |
644 | L->errfunc = ci->u.c.old_errfunc; | 654 | L->errfunc = ci->u.c.old_errfunc; |
645 | return 1; /* continue running the coroutine */ | 655 | finishCcall(L, status); /* finish 'lua_pcallk' callee */ |
656 | unroll(L, NULL); /* continue running the coroutine */ | ||
646 | } | 657 | } |
647 | 658 | ||
648 | 659 | ||
@@ -692,6 +703,24 @@ static void resume (lua_State *L, void *ud) { | |||
692 | } | 703 | } |
693 | } | 704 | } |
694 | 705 | ||
706 | |||
707 | /* | ||
708 | ** Calls 'recover' in protected mode, repeating while there are | ||
709 | ** recoverable errors, that is, errors inside a protected call. (Any | ||
710 | ** error interrupts 'recover', and this loop protects it again so it | ||
711 | ** can continue.) Stops with a normal end (status == LUA_OK), an yield | ||
712 | ** (status == LUA_YIELD), or an unprotected error ('findpcall' doesn't | ||
713 | ** find a recover point). | ||
714 | */ | ||
715 | static int p_recover (lua_State *L, int status) { | ||
716 | struct RecoverS r; | ||
717 | r.status = status; | ||
718 | while (errorstatus(status) && (r.ci = findpcall(L)) != NULL) | ||
719 | r.status = luaD_rawrunprotected(L, recover, &r); | ||
720 | return r.status; | ||
721 | } | ||
722 | |||
723 | |||
695 | LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs, | 724 | LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs, |
696 | int *nresults) { | 725 | int *nresults) { |
697 | int status; | 726 | int status; |
@@ -709,10 +738,7 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs, | |||
709 | api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); | 738 | api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); |
710 | status = luaD_rawrunprotected(L, resume, &nargs); | 739 | status = luaD_rawrunprotected(L, resume, &nargs); |
711 | /* continue running after recoverable errors */ | 740 | /* continue running after recoverable errors */ |
712 | while (errorstatus(status) && recover(L, status)) { | 741 | status = p_recover(L, status); |
713 | /* unroll continuation */ | ||
714 | status = luaD_rawrunprotected(L, unroll, &status); | ||
715 | } | ||
716 | if (likely(!errorstatus(status))) | 742 | if (likely(!errorstatus(status))) |
717 | lua_assert(status == L->status); /* normal end or yield */ | 743 | lua_assert(status == L->status); /* normal end or yield */ |
718 | else { /* unrecoverable error */ | 744 | else { /* unrecoverable error */ |
diff --git a/testes/coroutine.lua b/testes/coroutine.lua index 0a970e98..fbeabd07 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua | |||
@@ -123,7 +123,7 @@ assert(#a == 22 and a[#a] == 79) | |||
123 | x, a = nil | 123 | x, a = nil |
124 | 124 | ||
125 | 125 | ||
126 | -- coroutine closing | 126 | print("to-be-closed variables in coroutines") |
127 | 127 | ||
128 | local function func2close (f) | 128 | local function func2close (f) |
129 | return setmetatable({}, {__close = f}) | 129 | return setmetatable({}, {__close = f}) |
@@ -189,7 +189,6 @@ do | |||
189 | local st, msg = coroutine.close(co) | 189 | local st, msg = coroutine.close(co) |
190 | assert(st == false and coroutine.status(co) == "dead" and msg == 200) | 190 | assert(st == false and coroutine.status(co) == "dead" and msg == 200) |
191 | assert(x == 200) | 191 | assert(x == 200) |
192 | |||
193 | end | 192 | end |
194 | 193 | ||
195 | do | 194 | do |
@@ -207,6 +206,44 @@ do | |||
207 | local st1, st2, err = coroutine.resume(co) | 206 | local st1, st2, err = coroutine.resume(co) |
208 | assert(st1 and not st2 and err == 43) | 207 | assert(st1 and not st2 and err == 43) |
209 | assert(X == 43 and Y.name == "pcall") | 208 | assert(X == 43 and Y.name == "pcall") |
209 | |||
210 | -- recovering from errors in __close metamethods | ||
211 | local track = {} | ||
212 | |||
213 | local function h (o) | ||
214 | local hv <close> = o | ||
215 | return 1 | ||
216 | end | ||
217 | |||
218 | local function foo () | ||
219 | local x <close> = func2close(function(_,msg) | ||
220 | track[#track + 1] = msg or false | ||
221 | error(20) | ||
222 | end) | ||
223 | local y <close> = func2close(function(_,msg) | ||
224 | track[#track + 1] = msg or false | ||
225 | return 1000 | ||
226 | end) | ||
227 | local z <close> = func2close(function(_,msg) | ||
228 | track[#track + 1] = msg or false | ||
229 | error(10) | ||
230 | end) | ||
231 | coroutine.yield(1) | ||
232 | h(func2close(function(_,msg) | ||
233 | track[#track + 1] = msg or false | ||
234 | error(2) | ||
235 | end)) | ||
236 | end | ||
237 | |||
238 | local co = coroutine.create(pcall) | ||
239 | |||
240 | local st, res = coroutine.resume(co, foo) -- call 'foo' protected | ||
241 | assert(st and res == 1) -- yield 1 | ||
242 | local st, res1, res2 = coroutine.resume(co) -- continue | ||
243 | assert(coroutine.status(co) == "dead") | ||
244 | assert(st and not res1 and res2 == 20) -- last error (20) | ||
245 | assert(track[1] == false and track[2] == 2 and track[3] == 10 and | ||
246 | track[4] == 10) | ||
210 | end | 247 | end |
211 | 248 | ||
212 | 249 | ||