Bug: GC is not reentrant
As the GC is not reentrant, finalizers should not be able to invoke it.
This commit is contained in:
17
lapi.c
17
lapi.c
@@ -1136,18 +1136,19 @@ LUA_API int lua_status (lua_State *L) {
|
|||||||
LUA_API int lua_gc (lua_State *L, int what, ...) {
|
LUA_API int lua_gc (lua_State *L, int what, ...) {
|
||||||
va_list argp;
|
va_list argp;
|
||||||
int res = 0;
|
int res = 0;
|
||||||
global_State *g;
|
global_State *g = G(L);
|
||||||
|
if (g->gcstp & GCSTPGC) /* internal stop? */
|
||||||
|
return -1; /* all options are invalid when stopped */
|
||||||
lua_lock(L);
|
lua_lock(L);
|
||||||
g = G(L);
|
|
||||||
va_start(argp, what);
|
va_start(argp, what);
|
||||||
switch (what) {
|
switch (what) {
|
||||||
case LUA_GCSTOP: {
|
case LUA_GCSTOP: {
|
||||||
g->gcrunning = 0;
|
g->gcstp = GCSTPUSR; /* stopeed by the user */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LUA_GCRESTART: {
|
case LUA_GCRESTART: {
|
||||||
luaE_setdebt(g, 0);
|
luaE_setdebt(g, 0);
|
||||||
g->gcrunning = 1;
|
g->gcstp = 0; /* (GCSTPGC must be already zero here) */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LUA_GCCOLLECT: {
|
case LUA_GCCOLLECT: {
|
||||||
@@ -1166,8 +1167,8 @@ LUA_API int lua_gc (lua_State *L, int what, ...) {
|
|||||||
case LUA_GCSTEP: {
|
case LUA_GCSTEP: {
|
||||||
int data = va_arg(argp, int);
|
int data = va_arg(argp, int);
|
||||||
l_mem debt = 1; /* =1 to signal that it did an actual step */
|
l_mem debt = 1; /* =1 to signal that it did an actual step */
|
||||||
lu_byte oldrunning = g->gcrunning;
|
lu_byte oldstp = g->gcstp;
|
||||||
g->gcrunning = 1; /* allow GC to run */
|
g->gcstp = 0; /* allow GC to run (GCSTPGC must be zero here) */
|
||||||
if (data == 0) {
|
if (data == 0) {
|
||||||
luaE_setdebt(g, 0); /* do a basic step */
|
luaE_setdebt(g, 0); /* do a basic step */
|
||||||
luaC_step(L);
|
luaC_step(L);
|
||||||
@@ -1177,7 +1178,7 @@ LUA_API int lua_gc (lua_State *L, int what, ...) {
|
|||||||
luaE_setdebt(g, debt);
|
luaE_setdebt(g, debt);
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
}
|
}
|
||||||
g->gcrunning = oldrunning; /* restore previous state */
|
g->gcstp = oldstp; /* restore previous state */
|
||||||
if (debt > 0 && g->gcstate == GCSpause) /* end of cycle? */
|
if (debt > 0 && g->gcstate == GCSpause) /* end of cycle? */
|
||||||
res = 1; /* signal it */
|
res = 1; /* signal it */
|
||||||
break;
|
break;
|
||||||
@@ -1195,7 +1196,7 @@ LUA_API int lua_gc (lua_State *L, int what, ...) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LUA_GCISRUNNING: {
|
case LUA_GCISRUNNING: {
|
||||||
res = g->gcrunning;
|
res = gcrunning(g);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LUA_GCGEN: {
|
case LUA_GCGEN: {
|
||||||
|
|||||||
15
lbaselib.c
15
lbaselib.c
@@ -182,12 +182,20 @@ static int luaB_rawset (lua_State *L) {
|
|||||||
|
|
||||||
|
|
||||||
static int pushmode (lua_State *L, int oldmode) {
|
static int pushmode (lua_State *L, int oldmode) {
|
||||||
|
if (oldmode == -1)
|
||||||
|
luaL_pushfail(L); /* invalid call to 'lua_gc' */
|
||||||
|
else
|
||||||
lua_pushstring(L, (oldmode == LUA_GCINC) ? "incremental"
|
lua_pushstring(L, (oldmode == LUA_GCINC) ? "incremental"
|
||||||
: "generational");
|
: "generational");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** check whether call to 'lua_gc' was valid (not inside a finalizer)
|
||||||
|
*/
|
||||||
|
#define checkvalres(res) { if (res == -1) break; }
|
||||||
|
|
||||||
static int luaB_collectgarbage (lua_State *L) {
|
static int luaB_collectgarbage (lua_State *L) {
|
||||||
static const char *const opts[] = {"stop", "restart", "collect",
|
static const char *const opts[] = {"stop", "restart", "collect",
|
||||||
"count", "step", "setpause", "setstepmul",
|
"count", "step", "setpause", "setstepmul",
|
||||||
@@ -200,12 +208,14 @@ static int luaB_collectgarbage (lua_State *L) {
|
|||||||
case LUA_GCCOUNT: {
|
case LUA_GCCOUNT: {
|
||||||
int k = lua_gc(L, o);
|
int k = lua_gc(L, o);
|
||||||
int b = lua_gc(L, LUA_GCCOUNTB);
|
int b = lua_gc(L, LUA_GCCOUNTB);
|
||||||
|
checkvalres(k);
|
||||||
lua_pushnumber(L, (lua_Number)k + ((lua_Number)b/1024));
|
lua_pushnumber(L, (lua_Number)k + ((lua_Number)b/1024));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
case LUA_GCSTEP: {
|
case LUA_GCSTEP: {
|
||||||
int step = (int)luaL_optinteger(L, 2, 0);
|
int step = (int)luaL_optinteger(L, 2, 0);
|
||||||
int res = lua_gc(L, o, step);
|
int res = lua_gc(L, o, step);
|
||||||
|
checkvalres(res);
|
||||||
lua_pushboolean(L, res);
|
lua_pushboolean(L, res);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -213,11 +223,13 @@ static int luaB_collectgarbage (lua_State *L) {
|
|||||||
case LUA_GCSETSTEPMUL: {
|
case LUA_GCSETSTEPMUL: {
|
||||||
int p = (int)luaL_optinteger(L, 2, 0);
|
int p = (int)luaL_optinteger(L, 2, 0);
|
||||||
int previous = lua_gc(L, o, p);
|
int previous = lua_gc(L, o, p);
|
||||||
|
checkvalres(previous);
|
||||||
lua_pushinteger(L, previous);
|
lua_pushinteger(L, previous);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
case LUA_GCISRUNNING: {
|
case LUA_GCISRUNNING: {
|
||||||
int res = lua_gc(L, o);
|
int res = lua_gc(L, o);
|
||||||
|
checkvalres(res);
|
||||||
lua_pushboolean(L, res);
|
lua_pushboolean(L, res);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -234,10 +246,13 @@ static int luaB_collectgarbage (lua_State *L) {
|
|||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
int res = lua_gc(L, o);
|
int res = lua_gc(L, o);
|
||||||
|
checkvalres(res);
|
||||||
lua_pushinteger(L, res);
|
lua_pushinteger(L, res);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
luaL_pushfail(L); /* invalid call (inside a finalizer) */
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
11
lgc.c
11
lgc.c
@@ -906,16 +906,16 @@ static void GCTM (lua_State *L) {
|
|||||||
if (!notm(tm)) { /* is there a finalizer? */
|
if (!notm(tm)) { /* is there a finalizer? */
|
||||||
int status;
|
int status;
|
||||||
lu_byte oldah = L->allowhook;
|
lu_byte oldah = L->allowhook;
|
||||||
int running = g->gcrunning;
|
int oldgcstp = g->gcstp;
|
||||||
|
g->gcstp = GCSTPGC; /* avoid GC steps */
|
||||||
L->allowhook = 0; /* stop debug hooks during GC metamethod */
|
L->allowhook = 0; /* stop debug hooks during GC metamethod */
|
||||||
g->gcrunning = 0; /* avoid GC steps */
|
|
||||||
setobj2s(L, L->top++, tm); /* push finalizer... */
|
setobj2s(L, L->top++, tm); /* push finalizer... */
|
||||||
setobj2s(L, L->top++, &v); /* ... and its argument */
|
setobj2s(L, L->top++, &v); /* ... and its argument */
|
||||||
L->ci->callstatus |= CIST_FIN; /* will run a finalizer */
|
L->ci->callstatus |= CIST_FIN; /* will run a finalizer */
|
||||||
status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top - 2), 0);
|
status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top - 2), 0);
|
||||||
L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */
|
L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */
|
||||||
L->allowhook = oldah; /* restore hooks */
|
L->allowhook = oldah; /* restore hooks */
|
||||||
g->gcrunning = running; /* restore state */
|
g->gcstp = oldgcstp; /* restore state */
|
||||||
if (l_unlikely(status != LUA_OK)) { /* error while running __gc? */
|
if (l_unlikely(status != LUA_OK)) { /* error while running __gc? */
|
||||||
luaE_warnerror(L, "__gc metamethod");
|
luaE_warnerror(L, "__gc metamethod");
|
||||||
L->top--; /* pops error object */
|
L->top--; /* pops error object */
|
||||||
@@ -1502,9 +1502,11 @@ static void deletelist (lua_State *L, GCObject *p, GCObject *limit) {
|
|||||||
*/
|
*/
|
||||||
void luaC_freeallobjects (lua_State *L) {
|
void luaC_freeallobjects (lua_State *L) {
|
||||||
global_State *g = G(L);
|
global_State *g = G(L);
|
||||||
|
g->gcstp = GCSTPGC;
|
||||||
luaC_changemode(L, KGC_INC);
|
luaC_changemode(L, KGC_INC);
|
||||||
separatetobefnz(g, 1); /* separate all objects with finalizers */
|
separatetobefnz(g, 1); /* separate all objects with finalizers */
|
||||||
lua_assert(g->finobj == NULL);
|
lua_assert(g->finobj == NULL);
|
||||||
|
g->gcstp = 0;
|
||||||
callallpendingfinalizers(L);
|
callallpendingfinalizers(L);
|
||||||
deletelist(L, g->allgc, obj2gco(g->mainthread));
|
deletelist(L, g->allgc, obj2gco(g->mainthread));
|
||||||
deletelist(L, g->finobj, NULL);
|
deletelist(L, g->finobj, NULL);
|
||||||
@@ -1647,6 +1649,7 @@ void luaC_runtilstate (lua_State *L, int statesmask) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Performs a basic incremental step. The debt and step size are
|
** Performs a basic incremental step. The debt and step size are
|
||||||
** converted from bytes to "units of work"; then the function loops
|
** converted from bytes to "units of work"; then the function loops
|
||||||
@@ -1678,7 +1681,7 @@ static void incstep (lua_State *L, global_State *g) {
|
|||||||
void luaC_step (lua_State *L) {
|
void luaC_step (lua_State *L) {
|
||||||
global_State *g = G(L);
|
global_State *g = G(L);
|
||||||
lua_assert(!g->gcemergency);
|
lua_assert(!g->gcemergency);
|
||||||
if (g->gcrunning) { /* running? */
|
if (gcrunning(g)) { /* running? */
|
||||||
if(isdecGCmodegen(g))
|
if(isdecGCmodegen(g))
|
||||||
genstep(L, g);
|
genstep(L, g);
|
||||||
else
|
else
|
||||||
|
|||||||
9
lgc.h
9
lgc.h
@@ -148,6 +148,15 @@
|
|||||||
*/
|
*/
|
||||||
#define isdecGCmodegen(g) (g->gckind == KGC_GEN || g->lastatomic != 0)
|
#define isdecGCmodegen(g) (g->gckind == KGC_GEN || g->lastatomic != 0)
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Control when GC is running:
|
||||||
|
*/
|
||||||
|
#define GCSTPUSR 1 /* bit true when GC stopped by user */
|
||||||
|
#define GCSTPGC 2 /* bit true when GC stopped by itself */
|
||||||
|
#define gcrunning(g) ((g)->gcstp == 0)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Does one step of collection when debt becomes positive. 'pre'/'pos'
|
** Does one step of collection when debt becomes positive. 'pre'/'pos'
|
||||||
** allows some adjustments to be done only when needed. macro
|
** allows some adjustments to be done only when needed. macro
|
||||||
|
|||||||
4
lstate.c
4
lstate.c
@@ -236,7 +236,7 @@ static void f_luaopen (lua_State *L, void *ud) {
|
|||||||
luaS_init(L);
|
luaS_init(L);
|
||||||
luaT_init(L);
|
luaT_init(L);
|
||||||
luaX_init(L);
|
luaX_init(L);
|
||||||
g->gcrunning = 1; /* allow gc */
|
g->gcstp = 0; /* allow gc */
|
||||||
setnilvalue(&g->nilvalue); /* now state is complete */
|
setnilvalue(&g->nilvalue); /* now state is complete */
|
||||||
luai_userstateopen(L);
|
luai_userstateopen(L);
|
||||||
}
|
}
|
||||||
@@ -373,7 +373,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
|
|||||||
g->ud_warn = NULL;
|
g->ud_warn = NULL;
|
||||||
g->mainthread = L;
|
g->mainthread = L;
|
||||||
g->seed = luai_makeseed(L);
|
g->seed = luai_makeseed(L);
|
||||||
g->gcrunning = 0; /* no GC while building state */
|
g->gcstp = GCSTPGC; /* no GC while building state */
|
||||||
g->strt.size = g->strt.nuse = 0;
|
g->strt.size = g->strt.nuse = 0;
|
||||||
g->strt.hash = NULL;
|
g->strt.hash = NULL;
|
||||||
setnilvalue(&g->l_registry);
|
setnilvalue(&g->l_registry);
|
||||||
|
|||||||
2
lstate.h
2
lstate.h
@@ -263,7 +263,7 @@ typedef struct global_State {
|
|||||||
lu_byte gcstopem; /* stops emergency collections */
|
lu_byte gcstopem; /* stops emergency collections */
|
||||||
lu_byte genminormul; /* control for minor generational collections */
|
lu_byte genminormul; /* control for minor generational collections */
|
||||||
lu_byte genmajormul; /* control for major generational collections */
|
lu_byte genmajormul; /* control for major generational collections */
|
||||||
lu_byte gcrunning; /* true if GC is running */
|
lu_byte gcstp; /* control whether GC is running */
|
||||||
lu_byte gcemergency; /* true if this is an emergency collection */
|
lu_byte gcemergency; /* true if this is an emergency collection */
|
||||||
lu_byte gcpause; /* size of pause between successive GCs */
|
lu_byte gcpause; /* size of pause between successive GCs */
|
||||||
lu_byte gcstepmul; /* GC "speed" */
|
lu_byte gcstepmul; /* GC "speed" */
|
||||||
|
|||||||
@@ -787,11 +787,8 @@ following the reverse order that they were marked.
|
|||||||
If any finalizer marks objects for collection during that phase,
|
If any finalizer marks objects for collection during that phase,
|
||||||
these marks have no effect.
|
these marks have no effect.
|
||||||
|
|
||||||
Finalizers cannot yield.
|
Finalizers cannot yield nor run the garbage collector.
|
||||||
Except for that, they can do anything,
|
Because they can run in unpredictable times,
|
||||||
such as raise errors, create new objects,
|
|
||||||
or even run the garbage collector.
|
|
||||||
However, because they can run in unpredictable times,
|
|
||||||
it is good practice to restrict each finalizer
|
it is good practice to restrict each finalizer
|
||||||
to the minimum necessary to properly release
|
to the minimum necessary to properly release
|
||||||
its associated resource.
|
its associated resource.
|
||||||
@@ -3276,6 +3273,8 @@ Returns the previous mode (@id{LUA_GCGEN} or @id{LUA_GCINC}).
|
|||||||
For more details about these options,
|
For more details about these options,
|
||||||
see @Lid{collectgarbage}.
|
see @Lid{collectgarbage}.
|
||||||
|
|
||||||
|
This function should not be called by a finalizer.
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@APIEntry{lua_Alloc lua_getallocf (lua_State *L, void **ud);|
|
@APIEntry{lua_Alloc lua_getallocf (lua_State *L, void **ud);|
|
||||||
@@ -6233,6 +6232,8 @@ A zero means to not change that value.
|
|||||||
See @See{GC} for more details about garbage collection
|
See @See{GC} for more details about garbage collection
|
||||||
and some of these options.
|
and some of these options.
|
||||||
|
|
||||||
|
This function should not be called by a finalizer.
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@LibEntry{dofile ([filename])|
|
@LibEntry{dofile ([filename])|
|
||||||
|
|||||||
@@ -804,15 +804,14 @@ F = function (x)
|
|||||||
d = nil
|
d = nil
|
||||||
assert(debug.getmetatable(x).__gc == F)
|
assert(debug.getmetatable(x).__gc == F)
|
||||||
assert(load("table.insert({}, {})"))() -- create more garbage
|
assert(load("table.insert({}, {})"))() -- create more garbage
|
||||||
collectgarbage() -- force a GC during GC
|
assert(not collectgarbage()) -- GC during GC (no op)
|
||||||
assert(debug.getmetatable(x).__gc == F) -- previous GC did not mess this?
|
|
||||||
local dummy = {} -- create more garbage during GC
|
local dummy = {} -- create more garbage during GC
|
||||||
if A ~= nil then
|
if A ~= nil then
|
||||||
assert(type(A) == "userdata")
|
assert(type(A) == "userdata")
|
||||||
assert(T.udataval(A) == B)
|
assert(T.udataval(A) == B)
|
||||||
debug.getmetatable(A) -- just access it
|
debug.getmetatable(A) -- just access it
|
||||||
end
|
end
|
||||||
A = x -- ressucita userdata
|
A = x -- ressurect userdata
|
||||||
B = udval
|
B = udval
|
||||||
return 1,2,3
|
return 1,2,3
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -676,11 +676,13 @@ end
|
|||||||
-- just to make sure
|
-- just to make sure
|
||||||
assert(collectgarbage'isrunning')
|
assert(collectgarbage'isrunning')
|
||||||
|
|
||||||
do -- check that the collector is reentrant in incremental mode
|
do -- check that the collector is not reentrant in incremental mode
|
||||||
|
local res = true
|
||||||
setmetatable({}, {__gc = function ()
|
setmetatable({}, {__gc = function ()
|
||||||
collectgarbage()
|
res = collectgarbage()
|
||||||
end})
|
end})
|
||||||
collectgarbage()
|
collectgarbage()
|
||||||
|
assert(not res)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user