lua

A copy of the Lua development repository
Log | Files | Refs | README

gc.lua (17737B)


      1 -- $Id: testes/gc.lua $
      2 -- See Copyright Notice in file all.lua
      3 
      4 print('testing incremental garbage collection')
      5 
      6 local debug = require"debug"
      7 
      8 assert(collectgarbage("isrunning"))
      9 
     10 collectgarbage()
     11 
     12 local oldmode = collectgarbage("incremental")
     13 
     14 -- changing modes should return previous mode
     15 assert(collectgarbage("generational") == "incremental")
     16 assert(collectgarbage("generational") == "generational")
     17 assert(collectgarbage("incremental") == "generational")
     18 assert(collectgarbage("incremental") == "incremental")
     19 
     20 
     21 local function nop () end
     22 
     23 local function gcinfo ()
     24   return collectgarbage"count" * 1024
     25 end
     26 
     27 
     28 -- test weird parameters to 'collectgarbage'
     29 do
     30   collectgarbage("incremental")
     31   local opause = collectgarbage("param", "pause", 100)
     32   local ostepmul = collectgarbage("param", "stepmul", 100)
     33   assert(collectgarbage("param", "pause") == 100)
     34   assert(collectgarbage("param", "stepmul") == 100)
     35   local t = {0, 2, 10, 90, 500, 5000, 30000, 0x7ffffffe}
     36   for i = 1, #t do
     37     collectgarbage("param", "pause", t[i])
     38     for j = 1, #t do
     39       collectgarbage("param", "stepmul", t[j])
     40       collectgarbage("step", t[j])
     41     end
     42   end
     43   -- restore original parameters
     44   collectgarbage("param", "pause", opause)
     45   collectgarbage("param", "stepmul", ostepmul)
     46   collectgarbage()
     47 end
     48 
     49 
     50 --
     51 -- test the "size" of basic GC steps (whatever they mean...)
     52 --
     53 do  print("steps")
     54 
     55   local function dosteps (siz)
     56     collectgarbage()
     57     local a = {}
     58     for i=1,100 do a[i] = {{}}; local b = {} end
     59     local x = gcinfo()
     60     local i = 0
     61     repeat   -- do steps until it completes a collection cycle
     62       i = i+1
     63     until collectgarbage("step", siz)
     64     assert(gcinfo() < x)
     65     return i    -- number of steps
     66   end
     67 
     68 
     69   if not _port then
     70     collectgarbage"stop"
     71     assert(dosteps(10) < dosteps(2))
     72     collectgarbage"restart"
     73   end
     74 
     75 end
     76 
     77 
     78 _G["while"] = 234
     79 
     80 
     81 --
     82 -- tests for GC activation when creating different kinds of objects
     83 --
     84 local function GC1 ()
     85   local u
     86   local b     -- (above 'u' it in the stack)
     87   local finish = false
     88   u = setmetatable({}, {__gc = function () finish = true end})
     89   b = {34}
     90   repeat u = {} until finish
     91   assert(b[1] == 34)   -- 'u' was collected, but 'b' was not
     92 
     93   finish = false; local i = 1
     94   u = setmetatable({}, {__gc = function () finish = true end})
     95   repeat i = i + 1; u = tostring(i) .. tostring(i) until finish
     96   assert(b[1] == 34)   -- 'u' was collected, but 'b' was not
     97 
     98   finish = false
     99   u = setmetatable({}, {__gc = function () finish = true end})
    100   repeat local i; u = function () return i end until finish
    101   assert(b[1] == 34)   -- 'u' was collected, but 'b' was not
    102 end
    103 
    104 local function GC2 ()
    105   local u
    106   local finish = false
    107   u = {setmetatable({}, {__gc = function () finish = true end})}
    108   local b = {34}
    109   repeat u = {{}} until finish
    110   assert(b[1] == 34)   -- 'u' was collected, but 'b' was not
    111 
    112   finish = false; local i = 1
    113   u = {setmetatable({}, {__gc = function () finish = true end})}
    114   repeat i = i + 1; u = {tostring(i) .. tostring(i)} until finish
    115   assert(b[1] == 34)   -- 'u' was collected, but 'b' was not
    116 
    117   finish = false
    118   u = {setmetatable({}, {__gc = function () finish = true end})}
    119   repeat local i; u = {function () return i end} until finish
    120   assert(b[1] == 34)   -- 'u' was collected, but 'b' was not
    121 end
    122 
    123 local function GC()  GC1(); GC2() end
    124 
    125 
    126 do
    127   print("creating many objects")
    128 
    129   local limit = 5000
    130 
    131   for i = 1, limit do
    132     local a = {}; a = nil
    133   end
    134 
    135   local a = "a"
    136 
    137   for i = 1, limit do
    138     a = i .. "b";
    139     a = string.gsub(a, '(%d%d*)', "%1 %1")
    140     a = "a"
    141   end
    142 
    143 
    144 
    145   a = {}
    146 
    147   function a:test ()
    148     for i = 1, limit do
    149       load(string.format("function temp(a) return 'a%d' end", i), "")()
    150       assert(temp() == string.format('a%d', i))
    151     end
    152   end
    153 
    154   a:test()
    155   _G.temp = nil
    156 end
    157 
    158 
    159 -- collection of functions without locals, globals, etc.
    160 do local f = function () end end
    161 
    162 
    163 print("functions with errors")
    164 local prog = [[
    165 do
    166   a = 10;
    167   function foo(x,y)
    168     a = sin(a+0.456-0.23e-12);
    169     return function (z) return sin(%x+z) end
    170   end
    171   local x = function (w) a=a+w; end
    172 end
    173 ]]
    174 do
    175   local step = 1
    176   if _soft then step = 13 end
    177   for i=1, string.len(prog), step do
    178     for j=i, string.len(prog), step do
    179       pcall(load(string.sub(prog, i, j), ""))
    180     end
    181   end
    182 end
    183 rawset(_G, "a", nil)
    184 _G.x = nil
    185 
    186 do
    187   foo = nil
    188   print('long strings')
    189   local x = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
    190   assert(string.len(x)==80)
    191   local s = ''
    192   local k = math.min(300, (math.maxinteger // 80) // 2)
    193   for n = 1, k do s = s..x; local j=tostring(n)  end
    194   assert(string.len(s) == k*80)
    195   s = string.sub(s, 1, 10000)
    196   local s, i = string.gsub(s, '(%d%d%d%d)', '')
    197   assert(i==10000 // 4)
    198 
    199   assert(_G["while"] == 234)
    200   _G["while"] = nil
    201 end
    202 
    203 
    204 if not _port then
    205   -- test the pace of the collector
    206   collectgarbage(); collectgarbage()
    207   local x = gcinfo()
    208   collectgarbage"stop"
    209   repeat
    210     local a = {}
    211   until gcinfo() > 3 * x
    212   collectgarbage"restart"
    213   assert(collectgarbage("isrunning"))
    214   repeat
    215     local a = {}
    216   until gcinfo() <= x * 2
    217 end
    218 
    219 
    220 print("clearing tables")
    221 local lim = 15
    222 local a = {}
    223 -- fill a with `collectable' indices
    224 for i=1,lim do a[{}] = i end
    225 b = {}
    226 for k,v in pairs(a) do b[k]=v end
    227 -- remove all indices and collect them
    228 for n in pairs(b) do
    229   a[n] = undef
    230   assert(type(n) == 'table' and next(n) == nil)
    231   collectgarbage()
    232 end
    233 b = nil
    234 collectgarbage()
    235 for n in pairs(a) do error'cannot be here' end
    236 for i=1,lim do a[i] = i end
    237 for i=1,lim do assert(a[i] == i) end
    238 
    239 
    240 print('weak tables')
    241 a = {}; setmetatable(a, {__mode = 'k'});
    242 -- fill a with some `collectable' indices
    243 for i=1,lim do a[{}] = i end
    244 -- and some non-collectable ones
    245 for i=1,lim do a[i] = i end
    246 for i=1,lim do local s=string.rep('@', i); a[s] = s..'#' end
    247 collectgarbage()
    248 local i = 0
    249 for k,v in pairs(a) do assert(k==v or k..'#'==v); i=i+1 end
    250 assert(i == 2*lim)
    251 
    252 a = {}; setmetatable(a, {__mode = 'v'});
    253 a[1] = string.rep('b', 21)
    254 collectgarbage()
    255 assert(a[1])   -- strings are *values*
    256 a[1] = undef
    257 -- fill a with some `collectable' values (in both parts of the table)
    258 for i=1,lim do a[i] = {} end
    259 for i=1,lim do a[i..'x'] = {} end
    260 -- and some non-collectable ones
    261 for i=1,lim do local t={}; a[t]=t end
    262 for i=1,lim do a[i+lim]=i..'x' end
    263 collectgarbage()
    264 local i = 0
    265 for k,v in pairs(a) do assert(k==v or k-lim..'x' == v); i=i+1 end
    266 assert(i == 2*lim)
    267 
    268 a = {}; setmetatable(a, {__mode = 'kv'});
    269 local x, y, z = {}, {}, {}
    270 -- keep only some items
    271 a[1], a[2], a[3] = x, y, z
    272 a[string.rep('$', 11)] = string.rep('$', 11)
    273 -- fill a with some `collectable' values
    274 for i=4,lim do a[i] = {} end
    275 for i=1,lim do a[{}] = i end
    276 for i=1,lim do local t={}; a[t]=t end
    277 collectgarbage()
    278 assert(next(a) ~= nil)
    279 local i = 0
    280 for k,v in pairs(a) do
    281   assert((k == 1 and v == x) or
    282          (k == 2 and v == y) or
    283          (k == 3 and v == z) or k==v);
    284   i = i+1
    285 end
    286 assert(i == 4)
    287 x,y,z=nil
    288 collectgarbage()
    289 assert(next(a) == string.rep('$', 11))
    290 
    291 
    292 -- 'bug' in 5.1
    293 a = {}
    294 local t = {x = 10}
    295 local C = setmetatable({key = t}, {__mode = 'v'})
    296 local C1 = setmetatable({[t] = 1}, {__mode = 'k'})
    297 a.x = t  -- this should not prevent 't' from being removed from
    298          -- weak table 'C' by the time 'a' is finalized
    299 
    300 setmetatable(a, {__gc = function (u)
    301                           assert(C.key == nil)
    302                           assert(type(next(C1)) == 'table')
    303                           end})
    304 
    305 a, t = nil
    306 collectgarbage()
    307 collectgarbage()
    308 assert(next(C) == nil and next(C1) == nil)
    309 C, C1 = nil
    310 
    311 
    312 -- ephemerons
    313 local mt = {__mode = 'k'}
    314 a = {{10},{20},{30},{40}}; setmetatable(a, mt)
    315 x = nil
    316 for i = 1, 100 do local n = {}; a[n] = {k = {x}}; x = n end
    317 GC()
    318 local n = x
    319 local i = 0
    320 while n do n = a[n].k[1]; i = i + 1 end
    321 assert(i == 100)
    322 x = nil
    323 GC()
    324 for i = 1, 4 do assert(a[i][1] == i * 10); a[i] = undef end
    325 assert(next(a) == nil)
    326 
    327 local K = {}
    328 a[K] = {}
    329 for i=1,10 do a[K][i] = {}; a[a[K][i]] = setmetatable({}, mt) end
    330 x = nil
    331 local k = 1
    332 for j = 1,100 do
    333   local n = {}; local nk = k%10 + 1
    334   a[a[K][nk]][n] = {x, k = k}; x = n; k = nk
    335 end
    336 GC()
    337 local n = x
    338 local i = 0
    339 while n do local t = a[a[K][k]][n]; n = t[1]; k = t.k; i = i + 1 end
    340 assert(i == 100)
    341 K = nil
    342 GC()
    343 -- assert(next(a) == nil)
    344 
    345 
    346 -- testing errors during GC
    347 if T then
    348   collectgarbage("stop")   -- stop collection
    349   local u = {}
    350   local s = {}; setmetatable(s, {__mode = 'k'})
    351   setmetatable(u, {__gc = function (o)
    352     local i = s[o]
    353     s[i] = true
    354     assert(not s[i - 1])   -- check proper finalization order
    355     if i == 8 then error("@expected@") end   -- error during GC
    356   end})
    357 
    358   for i = 6, 10 do
    359     local n = setmetatable({}, getmetatable(u))
    360     s[n] = i
    361   end
    362 
    363   warn("@on"); warn("@store")
    364   collectgarbage()
    365   assert(string.find(_WARN, "error in __gc"))
    366   assert(string.match(_WARN, "@(.-)@") == "expected"); _WARN = false
    367   for i = 8, 10 do assert(s[i]) end
    368 
    369   for i = 1, 5 do
    370     local n = setmetatable({}, getmetatable(u))
    371     s[n] = i
    372   end
    373 
    374   collectgarbage()
    375   for i = 1, 10 do assert(s[i]) end
    376 
    377   getmetatable(u).__gc = nil
    378   warn("@normal")
    379 
    380 end
    381 print '+'
    382 
    383 
    384 -- testing userdata
    385 if T==nil then
    386   (Message or print)('\n >>> testC not active: skipping userdata GC tests <<<\n')
    387 
    388 else
    389 
    390   local function newproxy(u)
    391     return debug.setmetatable(T.newuserdata(0), debug.getmetatable(u))
    392   end
    393 
    394   collectgarbage("stop")   -- stop collection
    395   local u = newproxy(nil)
    396   debug.setmetatable(u, {__gc = true})
    397   local s = 0
    398   local a = {[u] = 0}; setmetatable(a, {__mode = 'vk'})
    399   for i=1,10 do a[newproxy(u)] = i end
    400   for k in pairs(a) do assert(getmetatable(k) == getmetatable(u)) end
    401   local a1 = {}; for k,v in pairs(a) do a1[k] = v end
    402   for k,v in pairs(a1) do a[v] = k end
    403   for i =1,10 do assert(a[i]) end
    404   getmetatable(u).a = a1
    405   getmetatable(u).u = u
    406   do
    407     local u = u
    408     getmetatable(u).__gc = function (o)
    409       assert(a[o] == 10-s)
    410       assert(a[10-s] == undef) -- udata already removed from weak table
    411       assert(getmetatable(o) == getmetatable(u))
    412     assert(getmetatable(o).a[o] == 10-s)
    413       s=s+1
    414     end
    415   end
    416   a1, u = nil
    417   assert(next(a) ~= nil)
    418   collectgarbage()
    419   assert(s==11)
    420   collectgarbage()
    421   assert(next(a) == nil)  -- finalized keys are removed in two cycles
    422 end
    423 
    424 
    425 -- __gc x weak tables
    426 local u = setmetatable({}, {__gc = true})
    427 -- __gc metamethod should be collected before running
    428 setmetatable(getmetatable(u), {__mode = "v"})
    429 getmetatable(u).__gc = function (o) os.exit(1) end  -- cannot happen
    430 u = nil
    431 collectgarbage()
    432 
    433 local u = setmetatable({}, {__gc = true})
    434 local m = getmetatable(u)
    435 m.x = {[{0}] = 1; [0] = {1}}; setmetatable(m.x, {__mode = "kv"});
    436 m.__gc = function (o)
    437   assert(next(getmetatable(o).x) == nil)
    438   m = 10
    439 end
    440 u, m = nil
    441 collectgarbage()
    442 assert(m==10)
    443 
    444 do   -- tests for string keys in weak tables
    445   collectgarbage(); collectgarbage()
    446   local m = collectgarbage("count")         -- current memory
    447   local a = setmetatable({}, {__mode = "kv"})
    448   a[string.rep("a", 2^22)] = 25   -- long string key -> number value
    449   a[string.rep("b", 2^22)] = {}   -- long string key -> colectable value
    450   a[{}] = 14                     -- colectable key
    451   collectgarbage()
    452   local k, v = next(a)   -- string key with number value preserved
    453   assert(k == string.rep("a", 2^22) and v == 25)
    454   assert(next(a, k) == nil)  -- everything else cleared
    455   assert(a[string.rep("b", 2^22)] == undef)
    456   a[k] = undef        -- erase this last entry
    457   k = nil
    458   collectgarbage()
    459   assert(next(a) == nil)
    460   -- make sure will not try to compare with dead key
    461   assert(a[string.rep("b", 100)] == undef)
    462   assert(collectgarbage("count") <= m + 1)   -- eveything collected
    463 end
    464 
    465 
    466 -- errors during collection
    467 if T then
    468   warn("@store")
    469   u = setmetatable({}, {__gc = function () error "@expected error" end})
    470   u = nil
    471   collectgarbage()
    472   assert(string.find(_WARN, "@expected error")); _WARN = false
    473   warn("@normal")
    474 end
    475 
    476 
    477 if not _soft then
    478   print("long list")
    479   local a = {}
    480   for i = 1,200000 do
    481     a = {next = a}
    482   end
    483   a = nil
    484   collectgarbage()
    485 end
    486 
    487 -- create many threads with self-references and open upvalues
    488 print("self-referenced threads")
    489 local thread_id = 0
    490 local threads = {}
    491 
    492 local function fn (thread)
    493     local x = {}
    494     threads[thread_id] = function()
    495                              thread = x
    496                          end
    497     coroutine.yield()
    498 end
    499 
    500 while thread_id < 1000 do
    501     local thread = coroutine.create(fn)
    502     coroutine.resume(thread, thread)
    503     thread_id = thread_id + 1
    504 end
    505 
    506 
    507 -- Create a closure (function inside 'f') with an upvalue ('param') that
    508 -- points (through a table) to the closure itself and to the thread
    509 -- ('co' and the initial value of 'param') where closure is running.
    510 -- Then, assert that table (and therefore everything else) will be
    511 -- collected.
    512 do
    513   local collected = false   -- to detect collection
    514   collectgarbage(); collectgarbage("stop")
    515   do
    516     local function f (param)
    517       ;(function ()
    518         assert(type(f) == 'function' and type(param) == 'thread')
    519         param = {param, f}
    520         setmetatable(param, {__gc = function () collected = true end})
    521         coroutine.yield(100)
    522       end)()
    523     end
    524     local co = coroutine.create(f)
    525     assert(coroutine.resume(co, co))
    526   end
    527   -- Now, thread and closure are not reacheable any more.
    528   collectgarbage()
    529   assert(collected)
    530   collectgarbage("restart")
    531 end
    532 
    533 
    534 do
    535   collectgarbage()
    536   collectgarbage"stop"
    537   collectgarbage("step")   -- steps should not unblock the collector
    538   local x = gcinfo()
    539   repeat
    540     for i=1,1000 do _ENV.a = {} end   -- no collection during the loop
    541   until gcinfo() > 2 * x
    542   collectgarbage"restart"
    543   _ENV.a = nil
    544 end
    545 
    546 
    547 if T then   -- tests for weird cases collecting upvalues
    548 
    549   local function foo ()
    550     local a = {x = 20}
    551     coroutine.yield(function () return a.x end)  -- will run collector
    552     assert(a.x == 20)   -- 'a' is 'ok'
    553     a = {x = 30}   -- create a new object
    554     assert(T.gccolor(a) == "white")   -- of course it is new...
    555     coroutine.yield(100)   -- 'a' is still local to this thread
    556   end
    557 
    558   local t = setmetatable({}, {__mode = "kv"})
    559   collectgarbage(); collectgarbage('stop')
    560   -- create coroutine in a weak table, so it will never be marked
    561   t.co = coroutine.wrap(foo)
    562   local f = t.co()   -- create function to access local 'a'
    563   T.gcstate("enteratomic")   -- ensure all objects are traversed
    564   assert(T.gcstate() == "enteratomic")
    565   assert(t.co() == 100)   -- resume coroutine, creating new table for 'a'
    566   assert(T.gccolor(t.co) == "white")  -- thread was not traversed
    567   T.gcstate("pause")   -- collect thread, but should mark 'a' before that
    568   assert(t.co == nil and f() == 30)   -- ensure correct access to 'a'
    569 
    570   collectgarbage("restart")
    571 
    572   -- test barrier in sweep phase (backing userdata to gray)
    573   local u = T.newuserdata(0, 1)   -- create a userdata
    574   collectgarbage()
    575   collectgarbage"stop"
    576   local a = {}     -- avoid 'u' as first element in 'allgc'
    577   T.gcstate"enteratomic"
    578   T.gcstate"sweepallgc"
    579   local x = {}
    580   assert(T.gccolor(u) == "black")   -- userdata is "old" (black)
    581   assert(T.gccolor(x) == "white")   -- table is "new" (white)
    582   debug.setuservalue(u, x)          -- trigger barrier
    583   assert(T.gccolor(u) == "gray")   -- userdata changed back to gray
    584   collectgarbage"restart"
    585 
    586   print"+"
    587 end
    588 
    589 
    590 if T then
    591   local debug = require "debug"
    592   collectgarbage("stop")
    593   local x = T.newuserdata(0)
    594   local y = T.newuserdata(0)
    595   debug.setmetatable(y, {__gc = nop})   -- bless the new udata before...
    596   debug.setmetatable(x, {__gc = nop})   -- ...the old one
    597   assert(T.gccolor(y) == "white")
    598   T.checkmemory()
    599   collectgarbage("restart")
    600 end
    601 
    602 
    603 if T then
    604   print("emergency collections")
    605   collectgarbage()
    606   collectgarbage()
    607   T.totalmem(T.totalmem() + 200)
    608   for i=1,200 do local a = {} end
    609   T.totalmem(0)
    610   collectgarbage()
    611   local t = T.totalmem("table")
    612   local a = {{}, {}, {}}   -- create 4 new tables
    613   assert(T.totalmem("table") == t + 4)
    614   t = T.totalmem("function")
    615   a = function () end   -- create 1 new closure
    616   assert(T.totalmem("function") == t + 1)
    617   t = T.totalmem("thread")
    618   a = coroutine.create(function () end)   -- create 1 new coroutine
    619   assert(T.totalmem("thread") == t + 1)
    620 end
    621 
    622 
    623 -- create an object to be collected when state is closed
    624 do
    625   local setmetatable,assert,type,print,getmetatable =
    626         setmetatable,assert,type,print,getmetatable
    627   local tt = {}
    628   tt.__gc = function (o)
    629     assert(getmetatable(o) == tt)
    630     -- create new objects during GC
    631     local a = 'xuxu'..(10+3)..'joao', {}
    632     ___Glob = o  -- ressurrect object!
    633     setmetatable({}, tt)  -- creates a new one with same metatable
    634     print(">>> closing state " .. "<<<\n")
    635   end
    636   local u = setmetatable({}, tt)
    637   ___Glob = {u}   -- avoid object being collected before program end
    638 end
    639 
    640 -- create several objects to raise errors when collected while closing state
    641 if T then
    642   local error, assert, find, warn = error, assert, string.find, warn
    643   local n = 0
    644   local lastmsg
    645   local mt = {__gc = function (o)
    646     n = n + 1
    647     assert(n == o[1])
    648     if n == 1 then
    649       _WARN = false
    650     elseif n == 2 then
    651       assert(find(_WARN, "@expected warning"))
    652       lastmsg = _WARN    -- get message from previous error (first 'o')
    653     else
    654       assert(lastmsg == _WARN)  -- subsequent error messages are equal
    655     end
    656     warn("@store"); _WARN = false
    657     error"@expected warning"
    658   end}
    659   for i = 10, 1, -1 do
    660     -- create object and preserve it until the end
    661     table.insert(___Glob, setmetatable({i}, mt))
    662   end
    663 end
    664 
    665 -- just to make sure
    666 assert(collectgarbage'isrunning')
    667 
    668 do    -- check that the collector is not reentrant in incremental mode
    669   local res = true
    670   setmetatable({}, {__gc = function ()
    671     res = collectgarbage()
    672   end})
    673   collectgarbage()
    674   assert(not res)
    675 end
    676 
    677 
    678 collectgarbage(oldmode)
    679 
    680 print('OK')