-- $Id: testes/gc.lua $ -- See Copyright Notice in file all.lua 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 -- save original parameters local a = collectgarbage("setpause", 200) local b = collectgarbage("setstepmul", 200) local t = {0, 2, 10, 90, 500, 5000, 30000, 0x7ffffffe} for i = 1, #t do local p = t[i] for j = 1, #t do local m = t[j] collectgarbage("setpause", p) collectgarbage("setstepmul", m) collectgarbage("step", 0) collectgarbage("step", 10000) end end -- restore original parameters collectgarbage("setpause", a) collectgarbage("setstepmul", b) collectgarbage() 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() end -- collection of functions without locals, globals, etc. do local f = function () end end print("functions with errors") 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 foo = nil print('long strings') x = "01234567890123456789012345678901234567890123456789012345678901234567890123456789" assert(string.len(x)==80) s = '' k = math.min(300, (math.maxinteger // 80) // 2) for n = 1, k do s = s..x; j=tostring(n) end assert(string.len(s) == k*80) s = string.sub(s, 1, 10000) s, i = string.gsub(s, '(%d%d%d%d)', '') assert(i==10000 // 4) s = nil x = nil assert(_G["while"] == 234) -- -- test the "size" of basic GC steps (whatever they mean...) -- do print("steps") print("steps (2)") 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 collectgarbage"stop" if not _port then assert(dosteps(10) < dosteps(2)) end -- collector should do a full collection with so many steps assert(dosteps(20000) == 1) assert(collectgarbage("step", 20000) == true) assert(collectgarbage("step", 20000) == true) assert(not collectgarbage("isrunning")) collectgarbage"restart" assert(collectgarbage("isrunning")) 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") lim = 15 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)) -- '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 metamethod")) assert(string.match(_WARN, "@(.-)@") == "expected"); _WARN = nil 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 -> colectable value a[{}] = 14 -- colectable key assert(collectgarbage("count") > m + 2^13) -- 2^13 == 2 * 2^22 in KB collectgarbage() assert(collectgarbage("count") >= m + 2^12 and collectgarbage("count") < m + 2^13) -- one key was collected 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) -- eveything 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 = nil 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 reacheable any more. collectgarbage() assert(collected) collectgarbage("restart") end do collectgarbage() collectgarbage"stop" collectgarbage("step", 0) -- 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" 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("atomic") -- ensure all objects are traversed assert(T.gcstate() == "atomic") 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"atomic" 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 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 -- ressurect 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 = nil 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 = nil 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') collectgarbage(oldmode) print('OK')