mirror of
https://github.com/lua/lua.git
synced 2025-02-04 06:13:04 +08:00
e2cc179454
The generational mode also uses the parameters for the incremental mode in its major collections, so it should be easy to change those parameters without having to change the GC mode.
181 lines
5.4 KiB
Lua
181 lines
5.4 KiB
Lua
-- $Id: testes/gengc.lua $
|
|
-- See Copyright Notice in file all.lua
|
|
|
|
print('testing generational garbage collection')
|
|
|
|
local debug = require"debug"
|
|
|
|
assert(collectgarbage("isrunning"))
|
|
|
|
collectgarbage()
|
|
|
|
local oldmode = collectgarbage("generational")
|
|
|
|
|
|
-- ensure that table barrier evolves correctly
|
|
do
|
|
local U = {}
|
|
-- full collection makes 'U' old
|
|
collectgarbage()
|
|
assert(not T or T.gcage(U) == "old")
|
|
|
|
-- U refers to a new table, so it becomes 'touched1'
|
|
U[1] = {x = {234}}
|
|
assert(not T or (T.gcage(U) == "touched1" and T.gcage(U[1]) == "new"))
|
|
|
|
-- both U and the table survive one more collection
|
|
collectgarbage("step", 0)
|
|
assert(not T or (T.gcage(U) == "touched2" and T.gcage(U[1]) == "survival"))
|
|
|
|
-- both U and the table survive yet another collection
|
|
-- now everything is old
|
|
collectgarbage("step", 0)
|
|
assert(not T or (T.gcage(U) == "old" and T.gcage(U[1]) == "old1"))
|
|
|
|
-- data was not corrupted
|
|
assert(U[1].x[1] == 234)
|
|
end
|
|
|
|
|
|
do
|
|
-- ensure that 'firstold1' is corrected when object is removed from
|
|
-- the 'allgc' list
|
|
local function foo () end
|
|
local old = {10}
|
|
collectgarbage() -- make 'old' old
|
|
assert(not T or T.gcage(old) == "old")
|
|
setmetatable(old, {}) -- new table becomes OLD0 (barrier)
|
|
assert(not T or T.gcage(getmetatable(old)) == "old0")
|
|
collectgarbage("step", 0) -- new table becomes OLD1 and firstold1
|
|
assert(not T or T.gcage(getmetatable(old)) == "old1")
|
|
setmetatable(getmetatable(old), {__gc = foo}) -- get it out of allgc list
|
|
collectgarbage("step", 0) -- should not seg. fault
|
|
end
|
|
|
|
|
|
do -- bug in 5.4.0
|
|
-- When an object aged OLD1 is finalized, it is moved from the list
|
|
-- 'finobj' to the *beginning* of the list 'allgc', but that part of the
|
|
-- list was not being visited by 'markold'.
|
|
local A = {}
|
|
A[1] = false -- old anchor for object
|
|
|
|
-- obj finalizer
|
|
local function gcf (obj)
|
|
A[1] = obj -- anchor object
|
|
assert(not T or T.gcage(obj) == "old1")
|
|
obj = nil -- remove it from the stack
|
|
collectgarbage("step", 0) -- do a young collection
|
|
print(getmetatable(A[1]).x) -- metatable was collected
|
|
end
|
|
|
|
collectgarbage() -- make A old
|
|
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 = "+"}) -- 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
|
|
|
|
|
|
do -- bug introduced in commit 9cf3299fa
|
|
local t = setmetatable({}, {__mode = "kv"}) -- all-weak table
|
|
collectgarbage() -- full collection
|
|
assert(not T or T.gcage(t) == "old")
|
|
t[1] = {10}
|
|
assert(not T or (T.gcage(t) == "touched1" and T.gccolor(t) == "gray"))
|
|
collectgarbage("step", 0) -- minor collection
|
|
assert(not T or (T.gcage(t) == "touched2" and T.gccolor(t) == "black"))
|
|
collectgarbage("step", 0) -- minor collection
|
|
assert(not T or T.gcage(t) == "old") -- t should be black, but it was gray
|
|
t[1] = {10} -- no barrier here, so t was still old
|
|
collectgarbage("step", 0) -- minor collection
|
|
-- t, being old, is ignored by the collection, so it is not cleared
|
|
assert(t[1] == nil) -- fails with the bug
|
|
end
|
|
|
|
|
|
if T == nil then
|
|
(Message or print)('\n >>> testC not active: \z
|
|
skipping some generational tests <<<\n')
|
|
print 'OK'
|
|
return
|
|
end
|
|
|
|
|
|
-- ensure that userdata barrier evolves correctly
|
|
do
|
|
local U = T.newuserdata(0, 1)
|
|
-- full collection makes 'U' old
|
|
collectgarbage()
|
|
assert(T.gcage(U) == "old")
|
|
|
|
-- U refers to a new table, so it becomes 'touched1'
|
|
debug.setuservalue(U, {x = {234}})
|
|
assert(T.gcage(U) == "touched1" and
|
|
T.gcage(debug.getuservalue(U)) == "new")
|
|
|
|
-- both U and the table survive one more collection
|
|
collectgarbage("step", 0)
|
|
assert(T.gcage(U) == "touched2" and
|
|
T.gcage(debug.getuservalue(U)) == "survival")
|
|
|
|
-- both U and the table survive yet another collection
|
|
-- now everything is old
|
|
collectgarbage("step", 0)
|
|
assert(T.gcage(U) == "old" and
|
|
T.gcage(debug.getuservalue(U)) == "old1")
|
|
|
|
-- data was not corrupted
|
|
assert(debug.getuservalue(U).x[1] == 234)
|
|
end
|
|
|
|
-- just to make sure
|
|
assert(collectgarbage'isrunning')
|
|
|
|
|
|
do print"testing stop-the-world collection"
|
|
local step = collectgarbage("setparam", "stepsize", 0);
|
|
collectgarbage("incremental")
|
|
|
|
-- each step does a complete cycle
|
|
assert(collectgarbage("step"))
|
|
assert(collectgarbage("step"))
|
|
|
|
-- back to default value
|
|
collectgarbage("setparam", "stepsize", step);
|
|
end
|
|
|
|
collectgarbage(oldmode)
|
|
|
|
print('OK')
|
|
|