Fixed bug: barriers cannot be active during sweep
Barriers cannot be active during sweep, even in generational mode.
(Although gen. mode is not incremental, it can hit a barrier when
deleting a thread and closing its upvalues.) The colors of objects are
being changed during sweep and, therefore, cannot be trusted.
diff --git a/lgc.c b/lgc.c
index f7fd7a5..64d1334 100644
--- a/lgc.c
+++ b/lgc.c
@@ -181,14 +181,17 @@
/*
-** barrier that moves collector forward, that is, mark the white object
-** 'v' being pointed by the black object 'o'. (If in sweep phase, clear
-** the black object to white [sweep it] to avoid other barrier calls for
-** this same object.) In the generational mode, 'v' must also become
-** old, if 'o' is old; however, it cannot be changed directly to OLD,
-** because it may still point to non-old objects. So, it is marked as
-** OLD0. In the next cycle it will become OLD1, and in the next it
-** will finally become OLD (regular old).
+** Barrier that moves collector forward, that is, marks the white object
+** 'v' being pointed by the black object 'o'. In the generational
+** mode, 'v' must also become old, if 'o' is old; however, it cannot
+** be changed directly to OLD, because it may still point to non-old
+** objects. So, it is marked as OLD0. In the next cycle it will become
+** OLD1, and in the next it will finally become OLD (regular old). By
+** then, any object it points to will also be old. If called in the
+** incremental sweep phase, it clears the black object to white (sweep
+** it) to avoid other barrier calls for this same object. (That cannot
+** be done is generational mode, as its sweep does not distinguish
+** whites from deads.)
*/
void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) {
global_State *g = G(L);
@@ -202,7 +205,8 @@
}
else { /* sweep phase */
lua_assert(issweepphase(g));
- makewhite(g, o); /* mark main obj. as white to avoid other barriers */
+ if (g->gckind == KGC_INC) /* incremental mode? */
+ makewhite(g, o); /* mark 'o' as white to avoid other barriers */
}
}
@@ -324,10 +328,15 @@
/*
-** Mark all values stored in marked open upvalues from non-marked threads.
-** (Values from marked threads were already marked when traversing the
-** thread.) Remove from the list threads that no longer have upvalues and
-** not-marked threads.
+** For each non-marked thread, simulates a barrier between each open
+** upvalue and its value. (If the thread is collected, the value will be
+** assigned to the upvalue, but then it can be too late for the barrier
+** to act. The "barrier" does not need to check colors: A non-marked
+** thread must be young; upvalues cannot be older than their threads; so
+** any visited upvalue must be young too.) Also removes the thread from
+** the list, as it was already visited. Removes also threads with no
+** upvalues, as they have nothing to be checked. (If the thread gets an
+** upvalue later, it will be linked in the list again.)
*/
static int remarkupvals (global_State *g) {
lua_State *thread;
@@ -340,9 +349,11 @@
p = &thread->twups; /* keep marked thread with upvalues in the list */
else { /* thread is not marked or without upvalues */
UpVal *uv;
+ lua_assert(!isold(thread) || thread->openupval == NULL);
*p = thread->twups; /* remove thread from the list */
thread->twups = thread; /* mark that it is out of list */
for (uv = thread->openupval; uv != NULL; uv = uv->u.open.next) {
+ lua_assert(getage(uv) <= getage(thread));
work++;
if (!iswhite(uv)) /* upvalue already visited? */
markvalue(g, uv->v); /* mark its value */
@@ -995,6 +1006,9 @@
** during the sweep. So, any white object must be dead.) For
** non-dead objects, advance their ages and clear the color of
** new objects. (Old objects keep their colors.)
+** The ages of G_TOUCHED1 and G_TOUCHED2 objects will advance
+** in 'correctgraylist'. (That function will also remove objects
+** turned white here from any gray list.)
*/
static GCObject **sweepgen (lua_State *L, global_State *g, GCObject **p,
GCObject *limit) {
@@ -1055,16 +1069,16 @@
lua_assert(isgray(curr));
gray2black(curr); /* make it black, for next barrier */
changeage(curr, G_TOUCHED1, G_TOUCHED2);
- p = next; /* go to next element */
+ p = next; /* keep it in the list and go to next element */
}
- else { /* not touched in this cycle */
+ else { /* everything else is removed */
+ /* white objects are simply removed */
if (!iswhite(curr)) { /* not white? */
lua_assert(isold(curr));
if (getage(curr) == G_TOUCHED2) /* advance from G_TOUCHED2... */
changeage(curr, G_TOUCHED2, G_OLD); /* ... to G_OLD */
gray2black(curr); /* make it black */
}
- /* else, object is white: just remove it from this list */
*p = *next; /* remove 'curr' from gray list */
}
break;
@@ -1143,6 +1157,7 @@
atomic(L);
/* sweep nursery and get a pointer to its last live element */
+ g->gcstate = GCSswpallgc;
psurvival = sweepgen(L, g, &g->allgc, g->survival);
/* sweep 'survival' and 'old' */
sweepgen(L, g, psurvival, g->reallyold);
@@ -1166,6 +1181,7 @@
static void atomic2gen (lua_State *L, global_State *g) {
/* sweep all elements making them old */
+ g->gcstate = GCSswpallgc;
sweep2old(L, &g->allgc);
/* everything alive now is old */
g->reallyold = g->old = g->survival = g->allgc;
diff --git a/testes/gengc.lua b/testes/gengc.lua
index 4e80dd7..7a7dabd 100644
--- a/testes/gengc.lua
+++ b/testes/gengc.lua
@@ -57,13 +57,39 @@
local obj = {} -- create a new object
collectgarbage("step", 0) -- make it a survival
assert(not T or T.gcage(obj) == "survival")
- setmetatable(obj, {__gc = gcf, x = "ok"}) -- create its metatable
+ setmetatable(obj, {__gc = gcf, x = "+"}) -- create its metatable
assert(not T or T.gcage(getmetatable(obj)) == "new")
obj = nil -- clear object
collectgarbage("step", 0) -- will call obj's finalizer
end
+do -- another bug in 5.4.0
+ local old = {10}
+ collectgarbage() -- make 'old' old
+ local co = coroutine.create(
+ function ()
+ local x = nil
+ local f = function ()
+ return x[1]
+ end
+ x = coroutine.yield(f)
+ coroutine.yield()
+ end
+ )
+ local _, f = coroutine.resume(co) -- create closure over 'x' in coroutine
+ collectgarbage("step", 0) -- make upvalue a survival
+ old[1] = {"hello"} -- 'old' go to grayagain as 'touched1'
+ coroutine.resume(co, {123}) -- its value will be new
+ co = nil
+ collectgarbage("step", 0) -- hit the barrier
+ assert(f() == 123 and old[1][1] == "hello")
+ collectgarbage("step", 0) -- run the collector once more
+ -- make sure old[1] was not collected
+ assert(f() == 123 and old[1][1] == "hello")
+end
+
+
if T == nil then
(Message or print)('\n >>> testC not active: \z
skipping some generational tests <<<\n')