diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-07-13 15:43:02 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-07-13 15:43:02 -0300 |
commit | 2e297d6ab37c1bb255b6984b91dd92d9080e02c9 (patch) | |
tree | d9031b8d7fbd2670efb6f9a7d24a7a08f24de844 | |
parent | fb18346dddcb0400d0396111c56a817a8d4bd8bd (diff) | |
download | lua-2e297d6ab37c1bb255b6984b91dd92d9080e02c9.tar.gz lua-2e297d6ab37c1bb255b6984b91dd92d9080e02c9.tar.bz2 lua-2e297d6ab37c1bb255b6984b91dd92d9080e02c9.zip |
Fixed bug in generational collection of userdata
During generational collection, a userdatum must become gray and
go to a gray list after being traversed (like tables), so that
'correctgraylist' can handle it to its next stage.
This commit also added minimum tests for the generational collector,
including one that would detect this bug.
-rw-r--r-- | lgc.c | 26 | ||||
-rwxr-xr-x | testes/all.lua | 4 | ||||
-rw-r--r-- | testes/gc.lua | 4 | ||||
-rw-r--r-- | testes/gengc.lua | 83 |
4 files changed, 102 insertions, 15 deletions
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | ** $Id$ | 2 | ** $Id: lgc.c $ |
3 | ** Garbage Collector | 3 | ** Garbage Collector |
4 | ** See Copyright Notice in lua.h | 4 | ** See Copyright Notice in lua.h |
5 | */ | 5 | */ |
@@ -212,7 +212,7 @@ void luaC_barrierback_ (lua_State *L, GCObject *o) { | |||
212 | lua_assert(g->gckind != KGC_GEN || (isold(o) && getage(o) != G_TOUCHED1)); | 212 | lua_assert(g->gckind != KGC_GEN || (isold(o) && getage(o) != G_TOUCHED1)); |
213 | if (getage(o) != G_TOUCHED2) /* not already in gray list? */ | 213 | if (getage(o) != G_TOUCHED2) /* not already in gray list? */ |
214 | linkobjgclist(o, g->grayagain); /* link it in 'grayagain' */ | 214 | linkobjgclist(o, g->grayagain); /* link it in 'grayagain' */ |
215 | black2gray(o); /* make table gray (again) */ | 215 | black2gray(o); /* make object gray (again) */ |
216 | setage(o, G_TOUCHED1); /* touched in current cycle */ | 216 | setage(o, G_TOUCHED1); /* touched in current cycle */ |
217 | } | 217 | } |
218 | 218 | ||
@@ -515,6 +515,19 @@ static lu_mem traversetable (global_State *g, Table *h) { | |||
515 | } | 515 | } |
516 | 516 | ||
517 | 517 | ||
518 | static int traverseudata (global_State *g, Udata *u) { | ||
519 | int i; | ||
520 | markobjectN(g, u->metatable); /* mark its metatable */ | ||
521 | for (i = 0; i < u->nuvalue; i++) | ||
522 | markvalue(g, &u->uv[i].uv); | ||
523 | if (g->gckind == KGC_GEN) { | ||
524 | linkgclist(u, g->grayagain); /* keep it in some gray list */ | ||
525 | black2gray(u); | ||
526 | } | ||
527 | return 1 + u->nuvalue; | ||
528 | } | ||
529 | |||
530 | |||
518 | /* | 531 | /* |
519 | ** Check the cache of a prototype, to keep invariants. If the | 532 | ** Check the cache of a prototype, to keep invariants. If the |
520 | ** cache is white, clear it. (A cache should not prevent the | 533 | ** cache is white, clear it. (A cache should not prevent the |
@@ -613,15 +626,6 @@ static int traversethread (global_State *g, lua_State *th) { | |||
613 | } | 626 | } |
614 | 627 | ||
615 | 628 | ||
616 | static int traverseudata (global_State *g, Udata *u) { | ||
617 | int i; | ||
618 | markobjectN(g, u->metatable); /* mark its metatable */ | ||
619 | for (i = 0; i < u->nuvalue; i++) | ||
620 | markvalue(g, &u->uv[i].uv); | ||
621 | return 1 + u->nuvalue; | ||
622 | } | ||
623 | |||
624 | |||
625 | /* | 629 | /* |
626 | ** traverse one gray object, turning it to black (except for threads, | 630 | ** traverse one gray object, turning it to black (except for threads, |
627 | ** which are always gray). | 631 | ** which are always gray). |
diff --git a/testes/all.lua b/testes/all.lua index cfe21603..37796254 100755 --- a/testes/all.lua +++ b/testes/all.lua | |||
@@ -1,5 +1,5 @@ | |||
1 | #!../lua | 1 | #!../lua |
2 | -- $Id: all.lua,v 1.100 2018/03/09 14:23:48 roberto Exp $ | 2 | -- $Id: all.lua $ |
3 | -- See Copyright Notice at the end of this file | 3 | -- See Copyright Notice at the end of this file |
4 | 4 | ||
5 | 5 | ||
@@ -162,7 +162,7 @@ olddofile('strings.lua') | |||
162 | olddofile('literals.lua') | 162 | olddofile('literals.lua') |
163 | dofile('tpack.lua') | 163 | dofile('tpack.lua') |
164 | assert(dofile('attrib.lua') == 27) | 164 | assert(dofile('attrib.lua') == 27) |
165 | 165 | dofile('gengc.lua') | |
166 | assert(dofile('locals.lua') == 5) | 166 | assert(dofile('locals.lua') == 5) |
167 | dofile('constructs.lua') | 167 | dofile('constructs.lua') |
168 | dofile('code.lua', true) | 168 | dofile('code.lua', true) |
diff --git a/testes/gc.lua b/testes/gc.lua index 9647cd54..05072efd 100644 --- a/testes/gc.lua +++ b/testes/gc.lua | |||
@@ -1,7 +1,7 @@ | |||
1 | -- $Id: gc.lua,v 1.82 2018/03/12 14:19:36 roberto Exp $ | 1 | -- $Id: gc.lua $ |
2 | -- See Copyright Notice in file all.lua | 2 | -- See Copyright Notice in file all.lua |
3 | 3 | ||
4 | print('testing garbage collection') | 4 | print('testing incremental garbage collection') |
5 | 5 | ||
6 | local debug = require"debug" | 6 | local debug = require"debug" |
7 | 7 | ||
diff --git a/testes/gengc.lua b/testes/gengc.lua new file mode 100644 index 00000000..36aed806 --- /dev/null +++ b/testes/gengc.lua | |||
@@ -0,0 +1,83 @@ | |||
1 | -- $Id: gengc.lua $ | ||
2 | -- See Copyright Notice in file all.lua | ||
3 | |||
4 | print('testing generational garbage collection') | ||
5 | |||
6 | local debug = require"debug" | ||
7 | |||
8 | assert(collectgarbage("isrunning")) | ||
9 | |||
10 | collectgarbage() | ||
11 | |||
12 | local oldmode = collectgarbage("generational") | ||
13 | |||
14 | |||
15 | -- ensure that table barrier evolves correctly | ||
16 | do | ||
17 | local U = {} | ||
18 | -- full collection makes 'U' old | ||
19 | collectgarbage() | ||
20 | assert(not T or T.gcage(U) == "old") | ||
21 | |||
22 | -- U refers to a new table, so it becomes 'touched1' | ||
23 | U[1] = {x = {234}} | ||
24 | assert(not T or (T.gcage(U) == "touched1" and T.gcage(U[1]) == "new")) | ||
25 | |||
26 | -- both U and the table survive one more collection | ||
27 | collectgarbage("step", 0) | ||
28 | assert(not T or (T.gcage(U) == "touched2" and T.gcage(U[1]) == "survival")) | ||
29 | |||
30 | -- both U and the table survive yet another collection | ||
31 | -- now everything is old | ||
32 | collectgarbage("step", 0) | ||
33 | assert(not T or (T.gcage(U) == "old" and T.gcage(U[1]) == "old1")) | ||
34 | |||
35 | -- data was not corrupted | ||
36 | assert(U[1].x[1] == 234) | ||
37 | end | ||
38 | |||
39 | |||
40 | if T == nil then | ||
41 | (Message or print)('\n >>> testC not active: \z | ||
42 | skipping some generational tests <<<\n') | ||
43 | print 'OK' | ||
44 | return | ||
45 | end | ||
46 | |||
47 | |||
48 | -- ensure that userdata barrier evolves correctly | ||
49 | do | ||
50 | local U = T.newuserdata(0, 1) | ||
51 | -- full collection makes 'U' old | ||
52 | collectgarbage() | ||
53 | assert(T.gcage(U) == "old") | ||
54 | |||
55 | -- U refers to a new table, so it becomes 'touched1' | ||
56 | debug.setuservalue(U, {x = {234}}) | ||
57 | assert(T.gcage(U) == "touched1" and | ||
58 | T.gcage(debug.getuservalue(U)) == "new") | ||
59 | |||
60 | -- both U and the table survive one more collection | ||
61 | collectgarbage("step", 0) | ||
62 | assert(T.gcage(U) == "touched2" and | ||
63 | T.gcage(debug.getuservalue(U)) == "survival") | ||
64 | |||
65 | -- both U and the table survive yet another collection | ||
66 | -- now everything is old | ||
67 | collectgarbage("step", 0) | ||
68 | assert(T.gcage(U) == "old" and | ||
69 | T.gcage(debug.getuservalue(U)) == "old1") | ||
70 | |||
71 | -- data was not corrupted | ||
72 | assert(debug.getuservalue(U).x[1] == 234) | ||
73 | end | ||
74 | |||
75 | |||
76 | |||
77 | -- just to make sure | ||
78 | assert(collectgarbage'isrunning') | ||
79 | |||
80 | collectgarbage(oldmode) | ||
81 | |||
82 | print('OK') | ||
83 | |||