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