1-- $Id: testes/gengc.lua $
  2-- See Copyright Notice in file all.lua
  3
  4print('testing generational garbage collection')
  5
  6local debug = require"debug"
  7
  8assert(collectgarbage("isrunning"))
  9
 10collectgarbage()
 11
 12local oldmode = collectgarbage("generational")
 13
 14
 15-- ensure that table barrier evolves correctly
 16do
 17  local U = {}
 18  -- full collection makes 'U' old
 19  collectgarbage()
 20  assert(not T or T.gcage(U) == "old")
 21
 22  -- U refers to a new table, so it becomes 'touched1'
 23  U[1] = {x = {234}}
 24  assert(not T or (T.gcage(U) == "touched1" and T.gcage(U[1]) == "new"))
 25
 26  -- both U and the table survive one more collection
 27  collectgarbage("step", 0)
 28  assert(not T or (T.gcage(U) == "touched2" and T.gcage(U[1]) == "survival"))
 29
 30  -- both U and the table survive yet another collection
 31  -- now everything is old
 32  collectgarbage("step", 0)
 33  assert(not T or (T.gcage(U) == "old" and T.gcage(U[1]) == "old1"))
 34
 35  -- data was not corrupted
 36  assert(U[1].x[1] == 234)
 37end
 38
 39
 40do
 41  -- ensure that 'firstold1' is corrected when object is removed from
 42  -- the 'allgc' list
 43  local function foo () end
 44  local old = {10}
 45  collectgarbage()    -- make 'old' old
 46  assert(not T or T.gcage(old) == "old")
 47  setmetatable(old, {})    -- new table becomes OLD0 (barrier)
 48  assert(not T or T.gcage(getmetatable(old)) == "old0")
 49  collectgarbage("step", 0)   -- new table becomes OLD1 and firstold1
 50  assert(not T or T.gcage(getmetatable(old)) == "old1")
 51  setmetatable(getmetatable(old), {__gc = foo})  -- get it out of allgc list
 52  collectgarbage("step", 0)   -- should not seg. fault
 53end
 54
 55
 56do   -- bug in 5.4.0
 57-- When an object aged OLD1 is finalized, it is moved from the list
 58-- 'finobj' to the *beginning* of the list 'allgc', but that part of the
 59-- list was not being visited by 'markold'.
 60  local A = {}
 61  A[1] = false     -- old anchor for object
 62
 63  -- obj finalizer
 64  local function gcf (obj)
 65    A[1] = obj     -- anchor object
 66    assert(not T or T.gcage(obj) == "old1")
 67    obj = nil      -- remove it from the stack
 68    collectgarbage("step", 0)   -- do a young collection
 69    print(getmetatable(A[1]).x)   -- metatable was collected
 70  end
 71
 72  collectgarbage()   -- make A old
 73  local obj = {}     -- create a new object
 74  collectgarbage("step", 0)   -- make it a survival
 75  assert(not T or T.gcage(obj) == "survival")
 76  setmetatable(obj, {__gc = gcf, x = "+"})   -- create its metatable
 77  assert(not T or T.gcage(getmetatable(obj)) == "new")
 78  obj = nil   -- clear object
 79  collectgarbage("step", 0)   -- will call obj's finalizer
 80end
 81
 82
 83do   -- another bug in 5.4.0
 84  local old = {10}
 85  collectgarbage()   -- make 'old' old
 86  local co = coroutine.create(
 87    function ()
 88      local x = nil
 89      local f = function ()
 90                  return x[1]
 91                end
 92      x = coroutine.yield(f)
 93      coroutine.yield()
 94    end
 95  )
 96  local _, f = coroutine.resume(co)   -- create closure over 'x' in coroutine
 97  collectgarbage("step", 0)   -- make upvalue a survival
 98  old[1] = {"hello"}    -- 'old' go to grayagain as 'touched1'
 99  coroutine.resume(co, {123})     -- its value will be new
100  co = nil
101  collectgarbage("step", 0)   -- hit the barrier
102  assert(f() == 123 and old[1][1] == "hello")
103  collectgarbage("step", 0)   -- run the collector once more
104  -- make sure old[1] was not collected
105  assert(f() == 123 and old[1][1] == "hello")
106end
107
108
109do   -- bug introduced in commit 9cf3299fa
110  local t = setmetatable({}, {__mode = "kv"})   -- all-weak table
111  collectgarbage()   -- full collection
112  assert(not T or T.gcage(t) == "old")
113  t[1] = {10}
114  assert(not T or (T.gcage(t) == "touched1" and T.gccolor(t) == "gray"))
115  collectgarbage("step", 0)   -- minor collection
116  assert(not T or (T.gcage(t) == "touched2" and T.gccolor(t) == "black"))
117  collectgarbage("step", 0)   -- minor collection
118  assert(not T or T.gcage(t) == "old")   -- t should be black, but it was gray
119  t[1] = {10}      -- no barrier here, so t was still old
120  collectgarbage("step", 0)   -- minor collection
121  -- t, being old, is ignored by the collection, so it is not cleared
122  assert(t[1] == nil)   -- fails with the bug
123end
124
125
126if T == nil then
127  (Message or print)('\n >>> testC not active: \z
128                             skipping some generational tests <<<\n')
129  print 'OK'
130  return
131end
132
133
134-- ensure that userdata barrier evolves correctly
135do
136  local U = T.newuserdata(0, 1)
137  -- full collection makes 'U' old
138  collectgarbage()
139  assert(T.gcage(U) == "old")
140
141  -- U refers to a new table, so it becomes 'touched1'
142  debug.setuservalue(U, {x = {234}})
143  assert(T.gcage(U) == "touched1" and
144         T.gcage(debug.getuservalue(U)) == "new")
145
146  -- both U and the table survive one more collection
147  collectgarbage("step", 0)
148  assert(T.gcage(U) == "touched2" and
149         T.gcage(debug.getuservalue(U)) == "survival")
150
151  -- both U and the table survive yet another collection
152  -- now everything is old
153  collectgarbage("step", 0)
154  assert(T.gcage(U) == "old" and
155         T.gcage(debug.getuservalue(U)) == "old1")
156
157  -- data was not corrupted
158  assert(debug.getuservalue(U).x[1] == 234)
159end
160
161-- just to make sure
162assert(collectgarbage'isrunning')
163
164
165
166-- just to make sure
167assert(collectgarbage'isrunning')
168
169collectgarbage(oldmode)
170
171print('OK')
172