mirror of
https://github.com/lua/lua
synced 2024-11-22 21:01:26 +03:00
f7ce7e5faa
This commit fixes a bug introduced in commit 9cf3299fa
. TOUCHED2
objects are always black while the mutator runs, but they can become
temporarily gray inside a minor collection (e.g., if the object is a
weak table).
173 lines
5.1 KiB
Lua
173 lines
5.1 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')
|
|
|
|
|
|
|
|
-- just to make sure
|
|
assert(collectgarbage'isrunning')
|
|
|
|
collectgarbage(oldmode)
|
|
|
|
print('OK')
|
|
|