mirror of
https://github.com/lua/lua.git
synced 2026-01-26 15:39:12 +00:00
Before calling a finalizer, Lua not only checks stack limits, but actually ensures that a minimum number of slots are already allocated for the call. (If it cannot ensure that, it postpones the finalizer.) That avoids finalizers not running due to memory errors that the programmer cannot control.
753 lines
19 KiB
Lua
753 lines
19 KiB
Lua
-- $Id: testes/gc.lua $
|
|
-- See Copyright Notice in file lua.h
|
|
|
|
print('testing incremental garbage collection')
|
|
|
|
local debug = require"debug"
|
|
|
|
assert(collectgarbage("isrunning"))
|
|
|
|
collectgarbage()
|
|
|
|
local oldmode = collectgarbage("incremental")
|
|
|
|
-- changing modes should return previous mode
|
|
assert(collectgarbage("generational") == "incremental")
|
|
assert(collectgarbage("generational") == "generational")
|
|
assert(collectgarbage("incremental") == "generational")
|
|
assert(collectgarbage("incremental") == "incremental")
|
|
|
|
|
|
local function nop () end
|
|
|
|
local function gcinfo ()
|
|
return collectgarbage"count" * 1024
|
|
end
|
|
|
|
|
|
-- test weird parameters to 'collectgarbage'
|
|
do
|
|
collectgarbage("incremental")
|
|
local opause = collectgarbage("param", "pause", 100)
|
|
local ostepmul = collectgarbage("param", "stepmul", 100)
|
|
assert(collectgarbage("param", "pause") == 100)
|
|
assert(collectgarbage("param", "stepmul") == 100)
|
|
local t = {0, 2, 10, 90, 500, 5000, 30000, 0x7ffffffe}
|
|
for i = 1, #t do
|
|
collectgarbage("param", "pause", t[i])
|
|
for j = 1, #t do
|
|
collectgarbage("param", "stepmul", t[j])
|
|
collectgarbage("step", t[j])
|
|
end
|
|
end
|
|
-- restore original parameters
|
|
collectgarbage("param", "pause", opause)
|
|
collectgarbage("param", "stepmul", ostepmul)
|
|
collectgarbage()
|
|
end
|
|
|
|
|
|
--
|
|
-- test the "size" of basic GC steps (whatever they mean...)
|
|
--
|
|
do print("steps")
|
|
|
|
local function dosteps (siz)
|
|
collectgarbage()
|
|
local a = {}
|
|
for i=1,100 do a[i] = {{}}; local b = {} end
|
|
local x = gcinfo()
|
|
local i = 0
|
|
repeat -- do steps until it completes a collection cycle
|
|
i = i+1
|
|
until collectgarbage("step", siz)
|
|
assert(gcinfo() < x)
|
|
return i -- number of steps
|
|
end
|
|
|
|
|
|
if not _port then
|
|
collectgarbage"stop"
|
|
assert(dosteps(10) < dosteps(2))
|
|
collectgarbage"restart"
|
|
end
|
|
|
|
end
|
|
|
|
|
|
_G["while"] = 234
|
|
|
|
|
|
--
|
|
-- tests for GC activation when creating different kinds of objects
|
|
--
|
|
local function GC1 ()
|
|
local u
|
|
local b -- (above 'u' it in the stack)
|
|
local finish = false
|
|
u = setmetatable({}, {__gc = function () finish = true end})
|
|
b = {34}
|
|
repeat u = {} until finish
|
|
assert(b[1] == 34) -- 'u' was collected, but 'b' was not
|
|
|
|
finish = false; local i = 1
|
|
u = setmetatable({}, {__gc = function () finish = true end})
|
|
repeat i = i + 1; u = tostring(i) .. tostring(i) until finish
|
|
assert(b[1] == 34) -- 'u' was collected, but 'b' was not
|
|
|
|
finish = false
|
|
u = setmetatable({}, {__gc = function () finish = true end})
|
|
repeat local i; u = function () return i end until finish
|
|
assert(b[1] == 34) -- 'u' was collected, but 'b' was not
|
|
end
|
|
|
|
local function GC2 ()
|
|
local u
|
|
local finish = false
|
|
u = {setmetatable({}, {__gc = function () finish = true end})}
|
|
local b = {34}
|
|
repeat u = {{}} until finish
|
|
assert(b[1] == 34) -- 'u' was collected, but 'b' was not
|
|
|
|
finish = false; local i = 1
|
|
u = {setmetatable({}, {__gc = function () finish = true end})}
|
|
repeat i = i + 1; u = {tostring(i) .. tostring(i)} until finish
|
|
assert(b[1] == 34) -- 'u' was collected, but 'b' was not
|
|
|
|
finish = false
|
|
u = {setmetatable({}, {__gc = function () finish = true end})}
|
|
repeat local i; u = {function () return i end} until finish
|
|
assert(b[1] == 34) -- 'u' was collected, but 'b' was not
|
|
end
|
|
|
|
local function GC() GC1(); GC2() end
|
|
|
|
|
|
do
|
|
print("creating many objects")
|
|
|
|
local limit = 5000
|
|
|
|
for i = 1, limit do
|
|
local a = {}; a = nil
|
|
end
|
|
|
|
local a = "a"
|
|
|
|
for i = 1, limit do
|
|
a = i .. "b";
|
|
a = string.gsub(a, '(%d%d*)', "%1 %1")
|
|
a = "a"
|
|
end
|
|
|
|
|
|
|
|
a = {}
|
|
|
|
function a:test ()
|
|
for i = 1, limit do
|
|
load(string.format("function temp(a) return 'a%d' end", i), "")()
|
|
assert(temp() == string.format('a%d', i))
|
|
end
|
|
end
|
|
|
|
a:test()
|
|
_G.temp = nil
|
|
end
|
|
|
|
|
|
-- collection of functions without locals, globals, etc.
|
|
do local f = function () end end
|
|
|
|
|
|
print("functions with errors")
|
|
local prog = [[
|
|
do
|
|
a = 10;
|
|
function foo(x,y)
|
|
a = sin(a+0.456-0.23e-12);
|
|
return function (z) return sin(%x+z) end
|
|
end
|
|
local x = function (w) a=a+w; end
|
|
end
|
|
]]
|
|
do
|
|
local step = 1
|
|
if _soft then step = 13 end
|
|
for i=1, string.len(prog), step do
|
|
for j=i, string.len(prog), step do
|
|
pcall(load(string.sub(prog, i, j), ""))
|
|
end
|
|
end
|
|
end
|
|
rawset(_G, "a", nil)
|
|
_G.x = nil
|
|
|
|
do
|
|
foo = nil
|
|
print('long strings')
|
|
local x = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
|
assert(string.len(x)==80)
|
|
local s = ''
|
|
local k = math.min(300, (math.maxinteger // 80) // 2)
|
|
for n = 1, k do s = s..x; local j=tostring(n) end
|
|
assert(string.len(s) == k*80)
|
|
s = string.sub(s, 1, 10000)
|
|
local s, i = string.gsub(s, '(%d%d%d%d)', '')
|
|
assert(i==10000 // 4)
|
|
|
|
assert(_G["while"] == 234)
|
|
_G["while"] = nil
|
|
end
|
|
|
|
|
|
if not _port then
|
|
-- test the pace of the collector
|
|
collectgarbage(); collectgarbage()
|
|
local x = gcinfo()
|
|
collectgarbage"stop"
|
|
repeat
|
|
local a = {}
|
|
until gcinfo() > 3 * x
|
|
collectgarbage"restart"
|
|
assert(collectgarbage("isrunning"))
|
|
repeat
|
|
local a = {}
|
|
until gcinfo() <= x * 2
|
|
end
|
|
|
|
|
|
print("clearing tables")
|
|
local lim = 15
|
|
local a = {}
|
|
-- fill a with `collectable' indices
|
|
for i=1,lim do a[{}] = i end
|
|
b = {}
|
|
for k,v in pairs(a) do b[k]=v end
|
|
-- remove all indices and collect them
|
|
for n in pairs(b) do
|
|
a[n] = undef
|
|
assert(type(n) == 'table' and next(n) == nil)
|
|
collectgarbage()
|
|
end
|
|
b = nil
|
|
collectgarbage()
|
|
for n in pairs(a) do error'cannot be here' end
|
|
for i=1,lim do a[i] = i end
|
|
for i=1,lim do assert(a[i] == i) end
|
|
|
|
|
|
print('weak tables')
|
|
a = {}; setmetatable(a, {__mode = 'k'});
|
|
-- fill a with some `collectable' indices
|
|
for i=1,lim do a[{}] = i end
|
|
-- and some non-collectable ones
|
|
for i=1,lim do a[i] = i end
|
|
for i=1,lim do local s=string.rep('@', i); a[s] = s..'#' end
|
|
collectgarbage()
|
|
local i = 0
|
|
for k,v in pairs(a) do assert(k==v or k..'#'==v); i=i+1 end
|
|
assert(i == 2*lim)
|
|
|
|
a = {}; setmetatable(a, {__mode = 'v'});
|
|
a[1] = string.rep('b', 21)
|
|
collectgarbage()
|
|
assert(a[1]) -- strings are *values*
|
|
a[1] = undef
|
|
-- fill a with some `collectable' values (in both parts of the table)
|
|
for i=1,lim do a[i] = {} end
|
|
for i=1,lim do a[i..'x'] = {} end
|
|
-- and some non-collectable ones
|
|
for i=1,lim do local t={}; a[t]=t end
|
|
for i=1,lim do a[i+lim]=i..'x' end
|
|
collectgarbage()
|
|
local i = 0
|
|
for k,v in pairs(a) do assert(k==v or k-lim..'x' == v); i=i+1 end
|
|
assert(i == 2*lim)
|
|
|
|
a = {}; setmetatable(a, {__mode = 'kv'});
|
|
local x, y, z = {}, {}, {}
|
|
-- keep only some items
|
|
a[1], a[2], a[3] = x, y, z
|
|
a[string.rep('$', 11)] = string.rep('$', 11)
|
|
-- fill a with some `collectable' values
|
|
for i=4,lim do a[i] = {} end
|
|
for i=1,lim do a[{}] = i end
|
|
for i=1,lim do local t={}; a[t]=t end
|
|
collectgarbage()
|
|
assert(next(a) ~= nil)
|
|
local i = 0
|
|
for k,v in pairs(a) do
|
|
assert((k == 1 and v == x) or
|
|
(k == 2 and v == y) or
|
|
(k == 3 and v == z) or k==v);
|
|
i = i+1
|
|
end
|
|
assert(i == 4)
|
|
x,y,z=nil
|
|
collectgarbage()
|
|
assert(next(a) == string.rep('$', 11))
|
|
|
|
do -- invalid mode
|
|
local a = setmetatable({}, {__mode = 34})
|
|
collectgarbage()
|
|
end
|
|
|
|
|
|
if T then -- bug since 5.3: all-weak tables are not being revisited
|
|
T.gcstate("propagate")
|
|
local t = setmetatable({}, {__mode = "kv"})
|
|
T.gcstate("enteratomic") -- 't' was visited
|
|
setmetatable(t, {__mode = "kv"})
|
|
T.gcstate("pause") -- its new metatable is not being visited
|
|
assert(getmetatable(t).__mode == "kv")
|
|
end
|
|
|
|
|
|
-- 'bug' in 5.1
|
|
a = {}
|
|
local t = {x = 10}
|
|
local C = setmetatable({key = t}, {__mode = 'v'})
|
|
local C1 = setmetatable({[t] = 1}, {__mode = 'k'})
|
|
a.x = t -- this should not prevent 't' from being removed from
|
|
-- weak table 'C' by the time 'a' is finalized
|
|
|
|
setmetatable(a, {__gc = function (u)
|
|
assert(C.key == nil)
|
|
assert(type(next(C1)) == 'table')
|
|
end})
|
|
|
|
a, t = nil
|
|
collectgarbage()
|
|
collectgarbage()
|
|
assert(next(C) == nil and next(C1) == nil)
|
|
C, C1 = nil
|
|
|
|
|
|
-- ephemerons
|
|
local mt = {__mode = 'k'}
|
|
a = {{10},{20},{30},{40}}; setmetatable(a, mt)
|
|
x = nil
|
|
for i = 1, 100 do local n = {}; a[n] = {k = {x}}; x = n end
|
|
GC()
|
|
local n = x
|
|
local i = 0
|
|
while n do n = a[n].k[1]; i = i + 1 end
|
|
assert(i == 100)
|
|
x = nil
|
|
GC()
|
|
for i = 1, 4 do assert(a[i][1] == i * 10); a[i] = undef end
|
|
assert(next(a) == nil)
|
|
|
|
local K = {}
|
|
a[K] = {}
|
|
for i=1,10 do a[K][i] = {}; a[a[K][i]] = setmetatable({}, mt) end
|
|
x = nil
|
|
local k = 1
|
|
for j = 1,100 do
|
|
local n = {}; local nk = k%10 + 1
|
|
a[a[K][nk]][n] = {x, k = k}; x = n; k = nk
|
|
end
|
|
GC()
|
|
local n = x
|
|
local i = 0
|
|
while n do local t = a[a[K][k]][n]; n = t[1]; k = t.k; i = i + 1 end
|
|
assert(i == 100)
|
|
K = nil
|
|
GC()
|
|
-- assert(next(a) == nil)
|
|
|
|
|
|
-- testing errors during GC
|
|
if T then
|
|
collectgarbage("stop") -- stop collection
|
|
local u = {}
|
|
local s = {}; setmetatable(s, {__mode = 'k'})
|
|
setmetatable(u, {__gc = function (o)
|
|
local i = s[o]
|
|
s[i] = true
|
|
assert(not s[i - 1]) -- check proper finalization order
|
|
if i == 8 then error("@expected@") end -- error during GC
|
|
end})
|
|
|
|
for i = 6, 10 do
|
|
local n = setmetatable({}, getmetatable(u))
|
|
s[n] = i
|
|
end
|
|
|
|
warn("@on"); warn("@store")
|
|
collectgarbage()
|
|
assert(string.find(_WARN, "error in __gc"))
|
|
assert(string.match(_WARN, "@(.-)@") == "expected"); _WARN = false
|
|
for i = 8, 10 do assert(s[i]) end
|
|
|
|
for i = 1, 5 do
|
|
local n = setmetatable({}, getmetatable(u))
|
|
s[n] = i
|
|
end
|
|
|
|
collectgarbage()
|
|
for i = 1, 10 do assert(s[i]) end
|
|
|
|
getmetatable(u).__gc = nil
|
|
warn("@normal")
|
|
|
|
end
|
|
print '+'
|
|
|
|
|
|
-- testing userdata
|
|
if T==nil then
|
|
(Message or print)('\n >>> testC not active: skipping userdata GC tests <<<\n')
|
|
|
|
else
|
|
|
|
local function newproxy(u)
|
|
return debug.setmetatable(T.newuserdata(0), debug.getmetatable(u))
|
|
end
|
|
|
|
collectgarbage("stop") -- stop collection
|
|
local u = newproxy(nil)
|
|
debug.setmetatable(u, {__gc = true})
|
|
local s = 0
|
|
local a = {[u] = 0}; setmetatable(a, {__mode = 'vk'})
|
|
for i=1,10 do a[newproxy(u)] = i end
|
|
for k in pairs(a) do assert(getmetatable(k) == getmetatable(u)) end
|
|
local a1 = {}; for k,v in pairs(a) do a1[k] = v end
|
|
for k,v in pairs(a1) do a[v] = k end
|
|
for i =1,10 do assert(a[i]) end
|
|
getmetatable(u).a = a1
|
|
getmetatable(u).u = u
|
|
do
|
|
local u = u
|
|
getmetatable(u).__gc = function (o)
|
|
assert(a[o] == 10-s)
|
|
assert(a[10-s] == undef) -- udata already removed from weak table
|
|
assert(getmetatable(o) == getmetatable(u))
|
|
assert(getmetatable(o).a[o] == 10-s)
|
|
s=s+1
|
|
end
|
|
end
|
|
a1, u = nil
|
|
assert(next(a) ~= nil)
|
|
collectgarbage()
|
|
assert(s==11)
|
|
collectgarbage()
|
|
assert(next(a) == nil) -- finalized keys are removed in two cycles
|
|
end
|
|
|
|
|
|
-- __gc x weak tables
|
|
local u = setmetatable({}, {__gc = true})
|
|
-- __gc metamethod should be collected before running
|
|
setmetatable(getmetatable(u), {__mode = "v"})
|
|
getmetatable(u).__gc = function (o) os.exit(1) end -- cannot happen
|
|
u = nil
|
|
collectgarbage()
|
|
|
|
local u = setmetatable({}, {__gc = true})
|
|
local m = getmetatable(u)
|
|
m.x = {[{0}] = 1; [0] = {1}}; setmetatable(m.x, {__mode = "kv"});
|
|
m.__gc = function (o)
|
|
assert(next(getmetatable(o).x) == nil)
|
|
m = 10
|
|
end
|
|
u, m = nil
|
|
collectgarbage()
|
|
assert(m==10)
|
|
|
|
do -- tests for string keys in weak tables
|
|
collectgarbage(); collectgarbage()
|
|
local m = collectgarbage("count") -- current memory
|
|
local a = setmetatable({}, {__mode = "kv"})
|
|
a[string.rep("a", 2^22)] = 25 -- long string key -> number value
|
|
a[string.rep("b", 2^22)] = {} -- long string key -> collectable value
|
|
a[{}] = 14 -- collectable key
|
|
collectgarbage()
|
|
local k, v = next(a) -- string key with number value preserved
|
|
assert(k == string.rep("a", 2^22) and v == 25)
|
|
assert(next(a, k) == nil) -- everything else cleared
|
|
assert(a[string.rep("b", 2^22)] == undef)
|
|
a[k] = undef -- erase this last entry
|
|
k = nil
|
|
collectgarbage()
|
|
assert(next(a) == nil)
|
|
-- make sure will not try to compare with dead key
|
|
assert(a[string.rep("b", 100)] == undef)
|
|
assert(collectgarbage("count") <= m + 1) -- everything collected
|
|
end
|
|
|
|
|
|
-- errors during collection
|
|
if T then
|
|
warn("@store")
|
|
u = setmetatable({}, {__gc = function () error "@expected error" end})
|
|
u = nil
|
|
collectgarbage()
|
|
assert(string.find(_WARN, "@expected error")); _WARN = false
|
|
warn("@normal")
|
|
end
|
|
|
|
|
|
if not _soft then
|
|
print("long list")
|
|
local a = {}
|
|
for i = 1,200000 do
|
|
a = {next = a}
|
|
end
|
|
a = nil
|
|
collectgarbage()
|
|
end
|
|
|
|
-- create many threads with self-references and open upvalues
|
|
print("self-referenced threads")
|
|
local thread_id = 0
|
|
local threads = {}
|
|
|
|
local function fn (thread)
|
|
local x = {}
|
|
threads[thread_id] = function()
|
|
thread = x
|
|
end
|
|
coroutine.yield()
|
|
end
|
|
|
|
while thread_id < 1000 do
|
|
local thread = coroutine.create(fn)
|
|
coroutine.resume(thread, thread)
|
|
thread_id = thread_id + 1
|
|
end
|
|
|
|
|
|
-- Create a closure (function inside 'f') with an upvalue ('param') that
|
|
-- points (through a table) to the closure itself and to the thread
|
|
-- ('co' and the initial value of 'param') where closure is running.
|
|
-- Then, assert that table (and therefore everything else) will be
|
|
-- collected.
|
|
do
|
|
local collected = false -- to detect collection
|
|
collectgarbage(); collectgarbage("stop")
|
|
do
|
|
local function f (param)
|
|
;(function ()
|
|
assert(type(f) == 'function' and type(param) == 'thread')
|
|
param = {param, f}
|
|
setmetatable(param, {__gc = function () collected = true end})
|
|
coroutine.yield(100)
|
|
end)()
|
|
end
|
|
local co = coroutine.create(f)
|
|
assert(coroutine.resume(co, co))
|
|
end
|
|
-- Now, thread and closure are not reachable any more.
|
|
collectgarbage()
|
|
assert(collected)
|
|
collectgarbage("restart")
|
|
end
|
|
|
|
|
|
do
|
|
collectgarbage()
|
|
collectgarbage"stop"
|
|
collectgarbage("step") -- steps should not unblock the collector
|
|
local x = gcinfo()
|
|
repeat
|
|
for i=1,1000 do _ENV.a = {} end -- no collection during the loop
|
|
until gcinfo() > 2 * x
|
|
collectgarbage"restart"
|
|
_ENV.a = nil
|
|
end
|
|
|
|
|
|
if T then -- tests for weird cases collecting upvalues
|
|
|
|
local function foo ()
|
|
local a = {x = 20}
|
|
coroutine.yield(function () return a.x end) -- will run collector
|
|
assert(a.x == 20) -- 'a' is 'ok'
|
|
a = {x = 30} -- create a new object
|
|
assert(T.gccolor(a) == "white") -- of course it is new...
|
|
coroutine.yield(100) -- 'a' is still local to this thread
|
|
end
|
|
|
|
local t = setmetatable({}, {__mode = "kv"})
|
|
collectgarbage(); collectgarbage('stop')
|
|
-- create coroutine in a weak table, so it will never be marked
|
|
t.co = coroutine.wrap(foo)
|
|
local f = t.co() -- create function to access local 'a'
|
|
T.gcstate("enteratomic") -- ensure all objects are traversed
|
|
assert(T.gcstate() == "enteratomic")
|
|
assert(t.co() == 100) -- resume coroutine, creating new table for 'a'
|
|
assert(T.gccolor(t.co) == "white") -- thread was not traversed
|
|
T.gcstate("pause") -- collect thread, but should mark 'a' before that
|
|
assert(t.co == nil and f() == 30) -- ensure correct access to 'a'
|
|
|
|
collectgarbage("restart")
|
|
|
|
-- test barrier in sweep phase (backing userdata to gray)
|
|
local u = T.newuserdata(0, 1) -- create a userdata
|
|
collectgarbage()
|
|
collectgarbage"stop"
|
|
local a = {} -- avoid 'u' as first element in 'allgc'
|
|
T.gcstate"enteratomic"
|
|
T.gcstate"sweepallgc"
|
|
local x = {}
|
|
assert(T.gccolor(u) == "black") -- userdata is "old" (black)
|
|
assert(T.gccolor(x) == "white") -- table is "new" (white)
|
|
debug.setuservalue(u, x) -- trigger barrier
|
|
assert(T.gccolor(u) == "gray") -- userdata changed back to gray
|
|
collectgarbage"restart"
|
|
|
|
print"+"
|
|
end
|
|
|
|
|
|
if T then
|
|
local debug = require "debug"
|
|
collectgarbage("stop")
|
|
local x = T.newuserdata(0)
|
|
local y = T.newuserdata(0)
|
|
debug.setmetatable(y, {__gc = nop}) -- bless the new udata before...
|
|
debug.setmetatable(x, {__gc = nop}) -- ...the old one
|
|
assert(T.gccolor(y) == "white")
|
|
T.checkmemory()
|
|
collectgarbage("restart")
|
|
end
|
|
|
|
|
|
if T then
|
|
collectgarbage("stop")
|
|
T.gcstate("pause")
|
|
local sup = {x = 0}
|
|
local a = setmetatable({}, {__newindex = sup})
|
|
T.gcstate("enteratomic")
|
|
assert(T.gccolor(sup) == "black")
|
|
a.x = {} -- should not break the invariant
|
|
assert(not (T.gccolor(sup) == "black" and T.gccolor(sup.x) == "white"))
|
|
T.gcstate("pause") -- complete the GC cycle
|
|
sup.x.y = 10
|
|
collectgarbage("restart")
|
|
end
|
|
|
|
|
|
if T then
|
|
print("emergency collections")
|
|
collectgarbage()
|
|
collectgarbage()
|
|
T.totalmem(T.totalmem() + 200)
|
|
for i=1,200 do local a = {} end
|
|
T.totalmem(0)
|
|
collectgarbage()
|
|
local t = T.totalmem("table")
|
|
local a = {{}, {}, {}} -- create 4 new tables
|
|
assert(T.totalmem("table") == t + 4)
|
|
t = T.totalmem("function")
|
|
a = function () end -- create 1 new closure
|
|
assert(T.totalmem("function") == t + 1)
|
|
t = T.totalmem("thread")
|
|
a = coroutine.create(function () end) -- create 1 new coroutine
|
|
assert(T.totalmem("thread") == t + 1)
|
|
end
|
|
|
|
|
|
-- create an object to be collected when state is closed
|
|
do
|
|
local setmetatable,assert,type,print,getmetatable =
|
|
setmetatable,assert,type,print,getmetatable
|
|
local tt = {}
|
|
tt.__gc = function (o)
|
|
assert(getmetatable(o) == tt)
|
|
-- create new objects during GC
|
|
local a = 'xuxu'..(10+3)..'joao', {}
|
|
___Glob = o -- resurrect object!
|
|
setmetatable({}, tt) -- creates a new one with same metatable
|
|
print(">>> closing state " .. "<<<\n")
|
|
end
|
|
local u = setmetatable({}, tt)
|
|
___Glob = {u} -- avoid object being collected before program end
|
|
end
|
|
|
|
-- create several objects to raise errors when collected while closing state
|
|
if T then
|
|
local error, assert, find, warn = error, assert, string.find, warn
|
|
local n = 0
|
|
local lastmsg
|
|
local mt = {__gc = function (o)
|
|
n = n + 1
|
|
assert(n == o[1])
|
|
if n == 1 then
|
|
_WARN = false
|
|
elseif n == 2 then
|
|
assert(find(_WARN, "@expected warning"))
|
|
lastmsg = _WARN -- get message from previous error (first 'o')
|
|
else
|
|
assert(lastmsg == _WARN) -- subsequent error messages are equal
|
|
end
|
|
warn("@store"); _WARN = false
|
|
error"@expected warning"
|
|
end}
|
|
for i = 10, 1, -1 do
|
|
-- create object and preserve it until the end
|
|
table.insert(___Glob, setmetatable({i}, mt))
|
|
end
|
|
end
|
|
|
|
-- just to make sure
|
|
assert(collectgarbage'isrunning')
|
|
|
|
do -- check that the collector is not reentrant in incremental mode
|
|
local res = true
|
|
setmetatable({}, {__gc = function ()
|
|
res = collectgarbage()
|
|
end})
|
|
collectgarbage()
|
|
assert(not res)
|
|
end
|
|
|
|
|
|
collectgarbage(oldmode)
|
|
|
|
|
|
if T then
|
|
print("testing stack issues when calling finalizers")
|
|
|
|
local X
|
|
local obj
|
|
|
|
local function initobj ()
|
|
X = false
|
|
obj = setmetatable({}, {__gc = function () X = true end})
|
|
end
|
|
|
|
local function loop (n)
|
|
if n > 0 then loop(n - 1) end
|
|
end
|
|
|
|
-- should not try to call finalizer without a CallInfo available
|
|
initobj()
|
|
loop(20) -- ensure stack space
|
|
T.resetCI() -- remove extra CallInfos
|
|
T.alloccount(0) -- cannot allocate more CallInfos
|
|
obj = nil
|
|
collectgarbage() -- will not call finalizer
|
|
T.alloccount()
|
|
assert(X == false)
|
|
collectgarbage() -- now will call finalizer (it was still pending)
|
|
assert(X == true)
|
|
|
|
-- should not try to call finalizer without stack space available
|
|
initobj()
|
|
loop(5) -- ensure enough CallInfos
|
|
T.reallocstack(0) -- remove extra stack slots
|
|
T.alloccount(0) -- cannot reallocate stack
|
|
obj = nil
|
|
collectgarbage() -- will not call finalizer
|
|
T.alloccount()
|
|
assert(X == false)
|
|
collectgarbage() -- now will call finalizer (it was still pending)
|
|
assert(X == true)
|
|
end
|
|
|
|
|
|
print('OK')
|