diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2021-02-26 11:41:02 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2021-02-26 11:41:02 -0300 |
commit | 1537d6680bb66dc2484e11815bc2cd0e31ca39cc (patch) | |
tree | f035f9e28fa2f1a6fba62e1b25d32b8a4b824204 | |
parent | e0260eb2d4085723302d637dd8f3fca339d18817 (diff) | |
download | lua-1537d6680bb66dc2484e11815bc2cd0e31ca39cc.tar.gz lua-1537d6680bb66dc2484e11815bc2cd0e31ca39cc.tar.bz2 lua-1537d6680bb66dc2484e11815bc2cd0e31ca39cc.zip |
New control for reentrancy of emergency collections
Instead of assuming that shrinking a block may be an emergency
collection, use an explicit field ('gcstopem') to stop emergency
collections while GC is working.
-rw-r--r-- | lgc.c | 36 | ||||
-rw-r--r-- | lmem.c | 25 | ||||
-rw-r--r-- | lstate.c | 1 | ||||
-rw-r--r-- | lstate.h | 1 | ||||
-rw-r--r-- | testes/gc.lua | 8 |
5 files changed, 46 insertions, 25 deletions
@@ -1575,52 +1575,64 @@ static int sweepstep (lua_State *L, global_State *g, | |||
1575 | 1575 | ||
1576 | static lu_mem singlestep (lua_State *L) { | 1576 | static lu_mem singlestep (lua_State *L) { |
1577 | global_State *g = G(L); | 1577 | global_State *g = G(L); |
1578 | lu_mem work; | ||
1579 | lua_assert(!g->gcstopem); /* collector is not reentrant */ | ||
1580 | g->gcstopem = 1; /* no emergency collections while collecting */ | ||
1578 | switch (g->gcstate) { | 1581 | switch (g->gcstate) { |
1579 | case GCSpause: { | 1582 | case GCSpause: { |
1580 | restartcollection(g); | 1583 | restartcollection(g); |
1581 | g->gcstate = GCSpropagate; | 1584 | g->gcstate = GCSpropagate; |
1582 | return 1; | 1585 | work = 1; |
1586 | break; | ||
1583 | } | 1587 | } |
1584 | case GCSpropagate: { | 1588 | case GCSpropagate: { |
1585 | if (g->gray == NULL) { /* no more gray objects? */ | 1589 | if (g->gray == NULL) { /* no more gray objects? */ |
1586 | g->gcstate = GCSenteratomic; /* finish propagate phase */ | 1590 | g->gcstate = GCSenteratomic; /* finish propagate phase */ |
1587 | return 0; | 1591 | work = 0; |
1588 | } | 1592 | } |
1589 | else | 1593 | else |
1590 | return propagatemark(g); /* traverse one gray object */ | 1594 | work = propagatemark(g); /* traverse one gray object */ |
1595 | break; | ||
1591 | } | 1596 | } |
1592 | case GCSenteratomic: { | 1597 | case GCSenteratomic: { |
1593 | lu_mem work = atomic(L); /* work is what was traversed by 'atomic' */ | 1598 | work = atomic(L); /* work is what was traversed by 'atomic' */ |
1594 | entersweep(L); | 1599 | entersweep(L); |
1595 | g->GCestimate = gettotalbytes(g); /* first estimate */; | 1600 | g->GCestimate = gettotalbytes(g); /* first estimate */; |
1596 | return work; | 1601 | break; |
1597 | } | 1602 | } |
1598 | case GCSswpallgc: { /* sweep "regular" objects */ | 1603 | case GCSswpallgc: { /* sweep "regular" objects */ |
1599 | return sweepstep(L, g, GCSswpfinobj, &g->finobj); | 1604 | work = sweepstep(L, g, GCSswpfinobj, &g->finobj); |
1605 | break; | ||
1600 | } | 1606 | } |
1601 | case GCSswpfinobj: { /* sweep objects with finalizers */ | 1607 | case GCSswpfinobj: { /* sweep objects with finalizers */ |
1602 | return sweepstep(L, g, GCSswptobefnz, &g->tobefnz); | 1608 | work = sweepstep(L, g, GCSswptobefnz, &g->tobefnz); |
1609 | break; | ||
1603 | } | 1610 | } |
1604 | case GCSswptobefnz: { /* sweep objects to be finalized */ | 1611 | case GCSswptobefnz: { /* sweep objects to be finalized */ |
1605 | return sweepstep(L, g, GCSswpend, NULL); | 1612 | work = sweepstep(L, g, GCSswpend, NULL); |
1613 | break; | ||
1606 | } | 1614 | } |
1607 | case GCSswpend: { /* finish sweeps */ | 1615 | case GCSswpend: { /* finish sweeps */ |
1608 | checkSizes(L, g); | 1616 | checkSizes(L, g); |
1609 | g->gcstate = GCScallfin; | 1617 | g->gcstate = GCScallfin; |
1610 | return 0; | 1618 | work = 0; |
1619 | break; | ||
1611 | } | 1620 | } |
1612 | case GCScallfin: { /* call remaining finalizers */ | 1621 | case GCScallfin: { /* call remaining finalizers */ |
1613 | if (g->tobefnz && !g->gcemergency) { | 1622 | if (g->tobefnz && !g->gcemergency) { |
1614 | int n = runafewfinalizers(L, GCFINMAX); | 1623 | g->gcstopem = 0; /* ok collections during finalizers */ |
1615 | return n * GCFINALIZECOST; | 1624 | work = runafewfinalizers(L, GCFINMAX) * GCFINALIZECOST; |
1616 | } | 1625 | } |
1617 | else { /* emergency mode or no more finalizers */ | 1626 | else { /* emergency mode or no more finalizers */ |
1618 | g->gcstate = GCSpause; /* finish collection */ | 1627 | g->gcstate = GCSpause; /* finish collection */ |
1619 | return 0; | 1628 | work = 0; |
1620 | } | 1629 | } |
1630 | break; | ||
1621 | } | 1631 | } |
1622 | default: lua_assert(0); return 0; | 1632 | default: lua_assert(0); return 0; |
1623 | } | 1633 | } |
1634 | g->gcstopem = 0; | ||
1635 | return work; | ||
1624 | } | 1636 | } |
1625 | 1637 | ||
1626 | 1638 | ||
@@ -24,12 +24,12 @@ | |||
24 | 24 | ||
25 | #if defined(EMERGENCYGCTESTS) | 25 | #if defined(EMERGENCYGCTESTS) |
26 | /* | 26 | /* |
27 | ** First allocation will fail whenever not building initial state | 27 | ** First allocation will fail whenever not building initial state. |
28 | ** and not shrinking a block. (This fail will trigger 'tryagain' and | 28 | ** (This fail will trigger 'tryagain' and a full GC cycle at every |
29 | ** a full GC cycle at every allocation.) | 29 | ** allocation.) |
30 | */ | 30 | */ |
31 | static void *firsttry (global_State *g, void *block, size_t os, size_t ns) { | 31 | static void *firsttry (global_State *g, void *block, size_t os, size_t ns) { |
32 | if (completestate(g) && ns > os) | 32 | if (completestate(g) && ns > 0) /* frees never fail */ |
33 | return NULL; /* fail */ | 33 | return NULL; /* fail */ |
34 | else /* normal allocation */ | 34 | else /* normal allocation */ |
35 | return (*g->frealloc)(g->ud, block, os, ns); | 35 | return (*g->frealloc)(g->ud, block, os, ns); |
@@ -138,15 +138,17 @@ void luaM_free_ (lua_State *L, void *block, size_t osize) { | |||
138 | 138 | ||
139 | 139 | ||
140 | /* | 140 | /* |
141 | ** In case of allocation fail, this function will call the GC to try | 141 | ** In case of allocation fail, this function will do an emergency |
142 | ** to free some memory and then try the allocation again. | 142 | ** collection to free some memory and then try the allocation again. |
143 | ** (It should not be called when shrinking a block, because then the | 143 | ** The GC should not be called while state is not fully built, as the |
144 | ** interpreter may be in the middle of a collection step.) | 144 | ** collector is not yet fully initialized. Also, it should not be called |
145 | ** when 'gcstopem' is true, because then the interpreter is in the | ||
146 | ** middle of a collection step. | ||
145 | */ | 147 | */ |
146 | static void *tryagain (lua_State *L, void *block, | 148 | static void *tryagain (lua_State *L, void *block, |
147 | size_t osize, size_t nsize) { | 149 | size_t osize, size_t nsize) { |
148 | global_State *g = G(L); | 150 | global_State *g = G(L); |
149 | if (completestate(g)) { /* is state fully build? */ | 151 | if (completestate(g) && !g->gcstopem) { |
150 | luaC_fullgc(L, 1); /* try to free some memory... */ | 152 | luaC_fullgc(L, 1); /* try to free some memory... */ |
151 | return (*g->frealloc)(g->ud, block, osize, nsize); /* try again */ | 153 | return (*g->frealloc)(g->ud, block, osize, nsize); /* try again */ |
152 | } | 154 | } |
@@ -156,8 +158,6 @@ static void *tryagain (lua_State *L, void *block, | |||
156 | 158 | ||
157 | /* | 159 | /* |
158 | ** Generic allocation routine. | 160 | ** Generic allocation routine. |
159 | ** If allocation fails while shrinking a block, do not try again; the | ||
160 | ** GC shrinks some blocks and it is not reentrant. | ||
161 | */ | 161 | */ |
162 | void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { | 162 | void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { |
163 | void *newblock; | 163 | void *newblock; |
@@ -165,8 +165,7 @@ void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { | |||
165 | lua_assert((osize == 0) == (block == NULL)); | 165 | lua_assert((osize == 0) == (block == NULL)); |
166 | newblock = firsttry(g, block, osize, nsize); | 166 | newblock = firsttry(g, block, osize, nsize); |
167 | if (l_unlikely(newblock == NULL && nsize > 0)) { | 167 | if (l_unlikely(newblock == NULL && nsize > 0)) { |
168 | if (nsize > osize) /* not shrinking a block? */ | 168 | newblock = tryagain(L, block, osize, nsize); |
169 | newblock = tryagain(L, block, osize, nsize); | ||
170 | if (newblock == NULL) /* still no memory? */ | 169 | if (newblock == NULL) /* still no memory? */ |
171 | return NULL; /* do not update 'GCdebt' */ | 170 | return NULL; /* do not update 'GCdebt' */ |
172 | } | 171 | } |
@@ -379,6 +379,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { | |||
379 | g->panic = NULL; | 379 | g->panic = NULL; |
380 | g->gcstate = GCSpause; | 380 | g->gcstate = GCSpause; |
381 | g->gckind = KGC_INC; | 381 | g->gckind = KGC_INC; |
382 | g->gcstopem = 0; | ||
382 | g->gcemergency = 0; | 383 | g->gcemergency = 0; |
383 | g->finobj = g->tobefnz = g->fixedgc = NULL; | 384 | g->finobj = g->tobefnz = g->fixedgc = NULL; |
384 | g->firstold1 = g->survival = g->old1 = g->reallyold = NULL; | 385 | g->firstold1 = g->survival = g->old1 = g->reallyold = NULL; |
@@ -260,6 +260,7 @@ typedef struct global_State { | |||
260 | lu_byte currentwhite; | 260 | lu_byte currentwhite; |
261 | lu_byte gcstate; /* state of garbage collector */ | 261 | lu_byte gcstate; /* state of garbage collector */ |
262 | lu_byte gckind; /* kind of GC running */ | 262 | lu_byte gckind; /* kind of GC running */ |
263 | lu_byte gcstopem; /* stops emergency collections */ | ||
263 | lu_byte genminormul; /* control for minor generational collections */ | 264 | lu_byte genminormul; /* control for minor generational collections */ |
264 | lu_byte genmajormul; /* control for major generational collections */ | 265 | lu_byte genmajormul; /* control for major generational collections */ |
265 | lu_byte gcrunning; /* true if GC is running */ | 266 | lu_byte gcrunning; /* true if GC is running */ |
diff --git a/testes/gc.lua b/testes/gc.lua index 80850f92..2332c939 100644 --- a/testes/gc.lua +++ b/testes/gc.lua | |||
@@ -676,6 +676,14 @@ end | |||
676 | -- just to make sure | 676 | -- just to make sure |
677 | assert(collectgarbage'isrunning') | 677 | assert(collectgarbage'isrunning') |
678 | 678 | ||
679 | do -- check that the collector is reentrant in incremental mode | ||
680 | setmetatable({}, {__gc = function () | ||
681 | collectgarbage() | ||
682 | end}) | ||
683 | collectgarbage() | ||
684 | end | ||
685 | |||
686 | |||
679 | collectgarbage(oldmode) | 687 | collectgarbage(oldmode) |
680 | 688 | ||
681 | print('OK') | 689 | print('OK') |