2
0
Эх сурвалжийг харах

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.
Roberto Ierusalimschy 6 жил өмнө
parent
commit
b4d5dff8ec
5 өөрчлөгдсөн 56 нэмэгдсэн , 28 устгасан
  1. 2 5
      lcorolib.c
  2. 6 3
      lfunc.c
  3. 15 6
      manual/manual.of
  4. 9 1
      testes/coroutine.lua
  5. 24 13
      testes/locals.lua

+ 2 - 5
lcorolib.c

@@ -75,11 +75,8 @@ static int luaB_auxwrap (lua_State *L) {
   int r = auxresume(L, co, lua_gettop(L));
   int r = auxresume(L, co, lua_gettop(L));
   if (r < 0) {
   if (r < 0) {
     int stat = lua_status(co);
     int stat = lua_status(co);
-    if (stat != LUA_OK && stat != LUA_YIELD) {
-      stat = lua_resetthread(co);  /* close variables in case of errors */
-      if (stat != LUA_OK)  /* error closing variables? */
-        lua_xmove(co, L, 1);  /* get new error object */
-    }
+    if (stat != LUA_OK && stat != LUA_YIELD)
+      lua_resetthread(co);  /* close variables in case of errors */
     if (lua_type(L, -1) == LUA_TSTRING) {  /* error object is a string? */
     if (lua_type(L, -1) == LUA_TSTRING) {  /* error object is a string? */
       luaL_where(L, 1);  /* add extra info, if available */
       luaL_where(L, 1);  /* add extra info, if available */
       lua_insert(L, -2);
       lua_insert(L, -2);

+ 6 - 3
lfunc.c

@@ -144,13 +144,16 @@ static int callclosemth (lua_State *L, TValue *uv, StkId level, int status) {
       luaG_runerror(L, "attempt to close non-closable variable '%s'", vname);
       luaG_runerror(L, "attempt to close non-closable variable '%s'", vname);
     }
     }
   }
   }
-  else {  /* there was an error */
+  else {  /* must close the object in protected mode */
+    ptrdiff_t oldtop = savestack(L, level + 1);
     /* save error message and set stack top to 'level + 1' */
     /* save error message and set stack top to 'level + 1' */
     luaD_seterrorobj(L, status, level);
     luaD_seterrorobj(L, status, level);
     if (prepclosingmethod(L, uv, s2v(level))) {  /* something to call? */
     if (prepclosingmethod(L, uv, s2v(level))) {  /* something to call? */
-      int newstatus = luaD_pcall(L, callclose, NULL, savestack(L, level), 0);
-      if (newstatus != LUA_OK)  /* another error when closing? */
+      int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0);
+      if (newstatus != LUA_OK && status == CLOSEPROTECT)  /* first error? */
         status = newstatus;  /* this will be the new error */
         status = newstatus;  /* this will be the new error */
+      else  /* leave original error (or nil) on top */
+        L->top = restorestack(L, oldtop);
     }
     }
     /* else no metamethod; ignore this case and keep original error */
     /* else no metamethod; ignore this case and keep original error */
   }
   }

+ 15 - 6
manual/manual.of

@@ -1541,11 +1541,17 @@ if there was no error, the second argument is @nil.
 
 
 If several to-be-closed variables go out of scope at the same event,
 If several to-be-closed variables go out of scope at the same event,
 they are closed in the reverse order that they were declared.
 they are closed in the reverse order that they were declared.
+
 If there is any error while running a closing method,
 If there is any error while running a closing method,
 that error is handled like an error in the regular code
 that error is handled like an error in the regular code
 where the variable was defined;
 where the variable was defined;
 in particular,
 in particular,
 the other pending closing methods will still be called.
 the other pending closing methods will still be called.
+After an error,
+other errors in closing methods
+interrupt the respective method,
+but are otherwise ignored;
+the error reported is the original one.
 
 
 If a coroutine yields inside a block and is never resumed again,
 If a coroutine yields inside a block and is never resumed again,
 the variables visible at that block will never go out of scope,
 the variables visible at that block will never go out of scope,
@@ -1553,11 +1559,12 @@ and therefore they will never be closed.
 Similarly, if a coroutine ends with an error,
 Similarly, if a coroutine ends with an error,
 it does not unwind its stack,
 it does not unwind its stack,
 so it does not close any variable.
 so it does not close any variable.
-You should either use finalizers
-or call @Lid{coroutine.close} to close the variables in these cases.
-However, note that if the coroutine was created
+In both cases,
+you should either use finalizers
+or call @Lid{coroutine.close} to close the variables.
+However, if the coroutine was created
 through @Lid{coroutine.wrap},
 through @Lid{coroutine.wrap},
-then its corresponding function will close all variables
+then its corresponding function will close the coroutine
 in case of errors.
 in case of errors.
 
 
 }
 }
@@ -3932,7 +3939,7 @@ Returns a status code:
 @Lid{LUA_OK} for no errors in closing methods,
 @Lid{LUA_OK} for no errors in closing methods,
 or an error status otherwise.
 or an error status otherwise.
 In case of error,
 In case of error,
-leave the error object on the stack,
+leaves the error object on the top of the stack,
 
 
 }
 }
 
 
@@ -6355,6 +6362,7 @@ Closes coroutine @id{co},
 that is,
 that is,
 closes all its pending to-be-closed variables
 closes all its pending to-be-closed variables
 and puts the coroutine in a dead state.
 and puts the coroutine in a dead state.
+The given coroutine must be dead or suspended.
 In case of error closing some variable,
 In case of error closing some variable,
 returns @false plus the error object;
 returns @false plus the error object;
 otherwise returns @true.
 otherwise returns @true.
@@ -6412,7 +6420,8 @@ true when the running coroutine is the main one.
 
 
 Returns the status of the coroutine @id{co}, as a string:
 Returns the status of the coroutine @id{co}, as a string:
 @T{"running"},
 @T{"running"},
-if the coroutine is running (that is, it called @id{status});
+if the coroutine is running
+(that is, it is the one that called @id{status});
 @T{"suspended"}, if the coroutine is suspended in a call to @id{yield},
 @T{"suspended"}, if the coroutine is suspended in a call to @id{yield},
 or if it has not started running yet;
 or if it has not started running yet;
 @T{"normal"} if the coroutine is active but not running
 @T{"normal"} if the coroutine is active but not running

+ 9 - 1
testes/coroutine.lua

@@ -163,15 +163,23 @@ do
   assert(not X and coroutine.status(co) == "dead")
   assert(not X and coroutine.status(co) == "dead")
 
 
   -- error closing a coroutine
   -- error closing a coroutine
+  local x = 0
   co = coroutine.create(function()
   co = coroutine.create(function()
+    local <toclose> y = func2close(function (self,err)
+      if (err ~= 111) then os.exit(false) end   -- should not happen
+      x = 200
+      error(200)
+    end)
     local <toclose> x = func2close(function (self, err)
     local <toclose> x = func2close(function (self, err)
       assert(err == nil); error(111)
       assert(err == nil); error(111)
     end)
     end)
     coroutine.yield()
     coroutine.yield()
   end)
   end)
   coroutine.resume(co)
   coroutine.resume(co)
+  assert(x == 0)
   local st, msg = coroutine.close(co)
   local st, msg = coroutine.close(co)
-  assert(not st and coroutine.status(co) == "dead" and msg == 111)
+  assert(st == false and coroutine.status(co) == "dead" and msg == 111)
+  assert(x == 200)
 
 
 end
 end
 
 

+ 24 - 13
testes/locals.lua

@@ -267,14 +267,14 @@ do   -- errors in __close
     if err then error(4) end
     if err then error(4) end
   end
   end
   local stat, msg = pcall(foo, false)
   local stat, msg = pcall(foo, false)
-  assert(msg == 1)
-  assert(log[1] == 10 and log[2] == 3 and log[3] == 2 and log[4] == 2
+  assert(msg == 3)
+  assert(log[1] == 10 and log[2] == 3 and log[3] == 3 and log[4] == 3
          and #log == 4)
          and #log == 4)
 
 
   log = {}
   log = {}
   local stat, msg = pcall(foo, true)
   local stat, msg = pcall(foo, true)
-  assert(msg == 1)
-  assert(log[1] == 4 and log[2] == 3 and log[3] == 2 and log[4] == 2
+  assert(msg == 4)
+  assert(log[1] == 4 and log[2] == 4 and log[3] == 4 and log[4] == 4
          and #log == 4)
          and #log == 4)
 
 
   -- error in toclose in vararg function
   -- error in toclose in vararg function
@@ -317,7 +317,7 @@ if rawget(_G, "T") then
     local <toclose> x = setmetatable({}, {__close = function ()
     local <toclose> x = setmetatable({}, {__close = function ()
       T.alloccount(0); local x = {}   -- force a memory error
       T.alloccount(0); local x = {}   -- force a memory error
     end})
     end})
-    error("a")   -- common error inside the function's body
+    error(1000)   -- common error inside the function's body
   end
   end
 
 
   stack(5)    -- ensure a minimal number of CI structures
   stack(5)    -- ensure a minimal number of CI structures
@@ -325,7 +325,7 @@ if rawget(_G, "T") then
   -- despite memory error, 'y' will be executed and
   -- despite memory error, 'y' will be executed and
   -- memory limit will be lifted
   -- memory limit will be lifted
   local _, msg = pcall(foo)
   local _, msg = pcall(foo)
-  assert(msg == "not enough memory")
+  assert(msg == 1000)
 
 
   local close = func2close(function (self, msg)
   local close = func2close(function (self, msg)
     T.alloccount()
     T.alloccount()
@@ -368,8 +368,7 @@ if rawget(_G, "T") then
   end
   end
 
 
   local _, msg = pcall(test)
   local _, msg = pcall(test)
-  assert(msg == 1000)
-
+  assert(msg == "not enough memory")   -- reported error is the first one
 
 
   do    -- testing 'toclose' in C string buffer
   do    -- testing 'toclose' in C string buffer
     collectgarbage()
     collectgarbage()
@@ -453,15 +452,27 @@ end
 
 
 do
 do
   -- error in a wrapped coroutine raising errors when closing a variable
   -- error in a wrapped coroutine raising errors when closing a variable
-  local x = false
+  local x = 0
   local co = coroutine.wrap(function ()
   local co = coroutine.wrap(function ()
-    local <toclose> xv = func2close(function () error("XXX") end)
+    local <toclose> xx = func2close(function () x = x + 1; error("YYY") end)
+    local <toclose> xv = func2close(function () x = x + 1; error("XXX") end)
       coroutine.yield(100)
       coroutine.yield(100)
       error(200)
       error(200)
   end)
   end)
-  assert(co() == 100)
-  local st, msg = pcall(co)
-  -- should get last error raised
+  assert(co() == 100); assert(x == 0)
+  local st, msg = pcall(co); assert(x == 2)
+  assert(not st and msg == 200)   -- should get first error raised
+
+  x = 0
+  co = coroutine.wrap(function ()
+    local <toclose> xx = func2close(function () x = x + 1; error("YYY") end)
+    local <toclose> xv = func2close(function () x = x + 1; error("XXX") end)
+      coroutine.yield(100)
+      return 200
+  end)
+  assert(co() == 100); assert(x == 0)
+  local st, msg = pcall(co); assert(x == 2)
+  -- should get first error raised
   assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
   assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
 end
 end