]>
Commit | Line | Data |
---|---|---|
4710c53d | 1 | import unittest\r |
2 | from test.test_support import verbose, run_unittest\r | |
3 | import sys\r | |
4 | import gc\r | |
5 | import weakref\r | |
6 | \r | |
7 | ### Support code\r | |
8 | ###############################################################################\r | |
9 | \r | |
10 | # Bug 1055820 has several tests of longstanding bugs involving weakrefs and\r | |
11 | # cyclic gc.\r | |
12 | \r | |
13 | # An instance of C1055820 has a self-loop, so becomes cyclic trash when\r | |
14 | # unreachable.\r | |
15 | class C1055820(object):\r | |
16 | def __init__(self, i):\r | |
17 | self.i = i\r | |
18 | self.loop = self\r | |
19 | \r | |
20 | class GC_Detector(object):\r | |
21 | # Create an instance I. Then gc hasn't happened again so long as\r | |
22 | # I.gc_happened is false.\r | |
23 | \r | |
24 | def __init__(self):\r | |
25 | self.gc_happened = False\r | |
26 | \r | |
27 | def it_happened(ignored):\r | |
28 | self.gc_happened = True\r | |
29 | \r | |
30 | # Create a piece of cyclic trash that triggers it_happened when\r | |
31 | # gc collects it.\r | |
32 | self.wr = weakref.ref(C1055820(666), it_happened)\r | |
33 | \r | |
34 | \r | |
35 | ### Tests\r | |
36 | ###############################################################################\r | |
37 | \r | |
38 | class GCTests(unittest.TestCase):\r | |
39 | def test_list(self):\r | |
40 | l = []\r | |
41 | l.append(l)\r | |
42 | gc.collect()\r | |
43 | del l\r | |
44 | self.assertEqual(gc.collect(), 1)\r | |
45 | \r | |
46 | def test_dict(self):\r | |
47 | d = {}\r | |
48 | d[1] = d\r | |
49 | gc.collect()\r | |
50 | del d\r | |
51 | self.assertEqual(gc.collect(), 1)\r | |
52 | \r | |
53 | def test_tuple(self):\r | |
54 | # since tuples are immutable we close the loop with a list\r | |
55 | l = []\r | |
56 | t = (l,)\r | |
57 | l.append(t)\r | |
58 | gc.collect()\r | |
59 | del t\r | |
60 | del l\r | |
61 | self.assertEqual(gc.collect(), 2)\r | |
62 | \r | |
63 | def test_class(self):\r | |
64 | class A:\r | |
65 | pass\r | |
66 | A.a = A\r | |
67 | gc.collect()\r | |
68 | del A\r | |
69 | self.assertNotEqual(gc.collect(), 0)\r | |
70 | \r | |
71 | def test_newstyleclass(self):\r | |
72 | class A(object):\r | |
73 | pass\r | |
74 | gc.collect()\r | |
75 | del A\r | |
76 | self.assertNotEqual(gc.collect(), 0)\r | |
77 | \r | |
78 | def test_instance(self):\r | |
79 | class A:\r | |
80 | pass\r | |
81 | a = A()\r | |
82 | a.a = a\r | |
83 | gc.collect()\r | |
84 | del a\r | |
85 | self.assertNotEqual(gc.collect(), 0)\r | |
86 | \r | |
87 | def test_newinstance(self):\r | |
88 | class A(object):\r | |
89 | pass\r | |
90 | a = A()\r | |
91 | a.a = a\r | |
92 | gc.collect()\r | |
93 | del a\r | |
94 | self.assertNotEqual(gc.collect(), 0)\r | |
95 | class B(list):\r | |
96 | pass\r | |
97 | class C(B, A):\r | |
98 | pass\r | |
99 | a = C()\r | |
100 | a.a = a\r | |
101 | gc.collect()\r | |
102 | del a\r | |
103 | self.assertNotEqual(gc.collect(), 0)\r | |
104 | del B, C\r | |
105 | self.assertNotEqual(gc.collect(), 0)\r | |
106 | A.a = A()\r | |
107 | del A\r | |
108 | self.assertNotEqual(gc.collect(), 0)\r | |
109 | self.assertEqual(gc.collect(), 0)\r | |
110 | \r | |
111 | def test_method(self):\r | |
112 | # Tricky: self.__init__ is a bound method, it references the instance.\r | |
113 | class A:\r | |
114 | def __init__(self):\r | |
115 | self.init = self.__init__\r | |
116 | a = A()\r | |
117 | gc.collect()\r | |
118 | del a\r | |
119 | self.assertNotEqual(gc.collect(), 0)\r | |
120 | \r | |
121 | def test_finalizer(self):\r | |
122 | # A() is uncollectable if it is part of a cycle, make sure it shows up\r | |
123 | # in gc.garbage.\r | |
124 | class A:\r | |
125 | def __del__(self): pass\r | |
126 | class B:\r | |
127 | pass\r | |
128 | a = A()\r | |
129 | a.a = a\r | |
130 | id_a = id(a)\r | |
131 | b = B()\r | |
132 | b.b = b\r | |
133 | gc.collect()\r | |
134 | del a\r | |
135 | del b\r | |
136 | self.assertNotEqual(gc.collect(), 0)\r | |
137 | for obj in gc.garbage:\r | |
138 | if id(obj) == id_a:\r | |
139 | del obj.a\r | |
140 | break\r | |
141 | else:\r | |
142 | self.fail("didn't find obj in garbage (finalizer)")\r | |
143 | gc.garbage.remove(obj)\r | |
144 | \r | |
145 | def test_finalizer_newclass(self):\r | |
146 | # A() is uncollectable if it is part of a cycle, make sure it shows up\r | |
147 | # in gc.garbage.\r | |
148 | class A(object):\r | |
149 | def __del__(self): pass\r | |
150 | class B(object):\r | |
151 | pass\r | |
152 | a = A()\r | |
153 | a.a = a\r | |
154 | id_a = id(a)\r | |
155 | b = B()\r | |
156 | b.b = b\r | |
157 | gc.collect()\r | |
158 | del a\r | |
159 | del b\r | |
160 | self.assertNotEqual(gc.collect(), 0)\r | |
161 | for obj in gc.garbage:\r | |
162 | if id(obj) == id_a:\r | |
163 | del obj.a\r | |
164 | break\r | |
165 | else:\r | |
166 | self.fail("didn't find obj in garbage (finalizer)")\r | |
167 | gc.garbage.remove(obj)\r | |
168 | \r | |
169 | def test_function(self):\r | |
170 | # Tricky: f -> d -> f, code should call d.clear() after the exec to\r | |
171 | # break the cycle.\r | |
172 | d = {}\r | |
173 | exec("def f(): pass\n") in d\r | |
174 | gc.collect()\r | |
175 | del d\r | |
176 | self.assertEqual(gc.collect(), 2)\r | |
177 | \r | |
178 | def test_frame(self):\r | |
179 | def f():\r | |
180 | frame = sys._getframe()\r | |
181 | gc.collect()\r | |
182 | f()\r | |
183 | self.assertEqual(gc.collect(), 1)\r | |
184 | \r | |
185 | def test_saveall(self):\r | |
186 | # Verify that cyclic garbage like lists show up in gc.garbage if the\r | |
187 | # SAVEALL option is enabled.\r | |
188 | \r | |
189 | # First make sure we don't save away other stuff that just happens to\r | |
190 | # be waiting for collection.\r | |
191 | gc.collect()\r | |
192 | # if this fails, someone else created immortal trash\r | |
193 | self.assertEqual(gc.garbage, [])\r | |
194 | \r | |
195 | L = []\r | |
196 | L.append(L)\r | |
197 | id_L = id(L)\r | |
198 | \r | |
199 | debug = gc.get_debug()\r | |
200 | gc.set_debug(debug | gc.DEBUG_SAVEALL)\r | |
201 | del L\r | |
202 | gc.collect()\r | |
203 | gc.set_debug(debug)\r | |
204 | \r | |
205 | self.assertEqual(len(gc.garbage), 1)\r | |
206 | obj = gc.garbage.pop()\r | |
207 | self.assertEqual(id(obj), id_L)\r | |
208 | \r | |
209 | def test_del(self):\r | |
210 | # __del__ methods can trigger collection, make this to happen\r | |
211 | thresholds = gc.get_threshold()\r | |
212 | gc.enable()\r | |
213 | gc.set_threshold(1)\r | |
214 | \r | |
215 | class A:\r | |
216 | def __del__(self):\r | |
217 | dir(self)\r | |
218 | a = A()\r | |
219 | del a\r | |
220 | \r | |
221 | gc.disable()\r | |
222 | gc.set_threshold(*thresholds)\r | |
223 | \r | |
224 | def test_del_newclass(self):\r | |
225 | # __del__ methods can trigger collection, make this to happen\r | |
226 | thresholds = gc.get_threshold()\r | |
227 | gc.enable()\r | |
228 | gc.set_threshold(1)\r | |
229 | \r | |
230 | class A(object):\r | |
231 | def __del__(self):\r | |
232 | dir(self)\r | |
233 | a = A()\r | |
234 | del a\r | |
235 | \r | |
236 | gc.disable()\r | |
237 | gc.set_threshold(*thresholds)\r | |
238 | \r | |
239 | # The following two tests are fragile:\r | |
240 | # They precisely count the number of allocations,\r | |
241 | # which is highly implementation-dependent.\r | |
242 | # For example:\r | |
243 | # - disposed tuples are not freed, but reused\r | |
244 | # - the call to assertEqual somehow avoids building its args tuple\r | |
245 | def test_get_count(self):\r | |
246 | # Avoid future allocation of method object\r | |
247 | assertEqual = self._baseAssertEqual\r | |
248 | gc.collect()\r | |
249 | assertEqual(gc.get_count(), (0, 0, 0))\r | |
250 | a = dict()\r | |
251 | # since gc.collect(), we created two objects:\r | |
252 | # the dict, and the tuple returned by get_count()\r | |
253 | assertEqual(gc.get_count(), (2, 0, 0))\r | |
254 | \r | |
255 | def test_collect_generations(self):\r | |
256 | # Avoid future allocation of method object\r | |
257 | assertEqual = self.assertEqual\r | |
258 | gc.collect()\r | |
259 | a = dict()\r | |
260 | gc.collect(0)\r | |
261 | assertEqual(gc.get_count(), (0, 1, 0))\r | |
262 | gc.collect(1)\r | |
263 | assertEqual(gc.get_count(), (0, 0, 1))\r | |
264 | gc.collect(2)\r | |
265 | assertEqual(gc.get_count(), (0, 0, 0))\r | |
266 | \r | |
267 | def test_trashcan(self):\r | |
268 | class Ouch:\r | |
269 | n = 0\r | |
270 | def __del__(self):\r | |
271 | Ouch.n = Ouch.n + 1\r | |
272 | if Ouch.n % 17 == 0:\r | |
273 | gc.collect()\r | |
274 | \r | |
275 | # "trashcan" is a hack to prevent stack overflow when deallocating\r | |
276 | # very deeply nested tuples etc. It works in part by abusing the\r | |
277 | # type pointer and refcount fields, and that can yield horrible\r | |
278 | # problems when gc tries to traverse the structures.\r | |
279 | # If this test fails (as it does in 2.0, 2.1 and 2.2), it will\r | |
280 | # most likely die via segfault.\r | |
281 | \r | |
282 | # Note: In 2.3 the possibility for compiling without cyclic gc was\r | |
283 | # removed, and that in turn allows the trashcan mechanism to work\r | |
284 | # via much simpler means (e.g., it never abuses the type pointer or\r | |
285 | # refcount fields anymore). Since it's much less likely to cause a\r | |
286 | # problem now, the various constants in this expensive (we force a lot\r | |
287 | # of full collections) test are cut back from the 2.2 version.\r | |
288 | gc.enable()\r | |
289 | N = 150\r | |
290 | for count in range(2):\r | |
291 | t = []\r | |
292 | for i in range(N):\r | |
293 | t = [t, Ouch()]\r | |
294 | u = []\r | |
295 | for i in range(N):\r | |
296 | u = [u, Ouch()]\r | |
297 | v = {}\r | |
298 | for i in range(N):\r | |
299 | v = {1: v, 2: Ouch()}\r | |
300 | gc.disable()\r | |
301 | \r | |
302 | def test_boom(self):\r | |
303 | class Boom:\r | |
304 | def __getattr__(self, someattribute):\r | |
305 | del self.attr\r | |
306 | raise AttributeError\r | |
307 | \r | |
308 | a = Boom()\r | |
309 | b = Boom()\r | |
310 | a.attr = b\r | |
311 | b.attr = a\r | |
312 | \r | |
313 | gc.collect()\r | |
314 | garbagelen = len(gc.garbage)\r | |
315 | del a, b\r | |
316 | # a<->b are in a trash cycle now. Collection will invoke\r | |
317 | # Boom.__getattr__ (to see whether a and b have __del__ methods), and\r | |
318 | # __getattr__ deletes the internal "attr" attributes as a side effect.\r | |
319 | # That causes the trash cycle to get reclaimed via refcounts falling to\r | |
320 | # 0, thus mutating the trash graph as a side effect of merely asking\r | |
321 | # whether __del__ exists. This used to (before 2.3b1) crash Python.\r | |
322 | # Now __getattr__ isn't called.\r | |
323 | self.assertEqual(gc.collect(), 4)\r | |
324 | self.assertEqual(len(gc.garbage), garbagelen)\r | |
325 | \r | |
326 | def test_boom2(self):\r | |
327 | class Boom2:\r | |
328 | def __init__(self):\r | |
329 | self.x = 0\r | |
330 | \r | |
331 | def __getattr__(self, someattribute):\r | |
332 | self.x += 1\r | |
333 | if self.x > 1:\r | |
334 | del self.attr\r | |
335 | raise AttributeError\r | |
336 | \r | |
337 | a = Boom2()\r | |
338 | b = Boom2()\r | |
339 | a.attr = b\r | |
340 | b.attr = a\r | |
341 | \r | |
342 | gc.collect()\r | |
343 | garbagelen = len(gc.garbage)\r | |
344 | del a, b\r | |
345 | # Much like test_boom(), except that __getattr__ doesn't break the\r | |
346 | # cycle until the second time gc checks for __del__. As of 2.3b1,\r | |
347 | # there isn't a second time, so this simply cleans up the trash cycle.\r | |
348 | # We expect a, b, a.__dict__ and b.__dict__ (4 objects) to get\r | |
349 | # reclaimed this way.\r | |
350 | self.assertEqual(gc.collect(), 4)\r | |
351 | self.assertEqual(len(gc.garbage), garbagelen)\r | |
352 | \r | |
353 | def test_boom_new(self):\r | |
354 | # boom__new and boom2_new are exactly like boom and boom2, except use\r | |
355 | # new-style classes.\r | |
356 | \r | |
357 | class Boom_New(object):\r | |
358 | def __getattr__(self, someattribute):\r | |
359 | del self.attr\r | |
360 | raise AttributeError\r | |
361 | \r | |
362 | a = Boom_New()\r | |
363 | b = Boom_New()\r | |
364 | a.attr = b\r | |
365 | b.attr = a\r | |
366 | \r | |
367 | gc.collect()\r | |
368 | garbagelen = len(gc.garbage)\r | |
369 | del a, b\r | |
370 | self.assertEqual(gc.collect(), 4)\r | |
371 | self.assertEqual(len(gc.garbage), garbagelen)\r | |
372 | \r | |
373 | def test_boom2_new(self):\r | |
374 | class Boom2_New(object):\r | |
375 | def __init__(self):\r | |
376 | self.x = 0\r | |
377 | \r | |
378 | def __getattr__(self, someattribute):\r | |
379 | self.x += 1\r | |
380 | if self.x > 1:\r | |
381 | del self.attr\r | |
382 | raise AttributeError\r | |
383 | \r | |
384 | a = Boom2_New()\r | |
385 | b = Boom2_New()\r | |
386 | a.attr = b\r | |
387 | b.attr = a\r | |
388 | \r | |
389 | gc.collect()\r | |
390 | garbagelen = len(gc.garbage)\r | |
391 | del a, b\r | |
392 | self.assertEqual(gc.collect(), 4)\r | |
393 | self.assertEqual(len(gc.garbage), garbagelen)\r | |
394 | \r | |
395 | def test_get_referents(self):\r | |
396 | alist = [1, 3, 5]\r | |
397 | got = gc.get_referents(alist)\r | |
398 | got.sort()\r | |
399 | self.assertEqual(got, alist)\r | |
400 | \r | |
401 | atuple = tuple(alist)\r | |
402 | got = gc.get_referents(atuple)\r | |
403 | got.sort()\r | |
404 | self.assertEqual(got, alist)\r | |
405 | \r | |
406 | adict = {1: 3, 5: 7}\r | |
407 | expected = [1, 3, 5, 7]\r | |
408 | got = gc.get_referents(adict)\r | |
409 | got.sort()\r | |
410 | self.assertEqual(got, expected)\r | |
411 | \r | |
412 | got = gc.get_referents([1, 2], {3: 4}, (0, 0, 0))\r | |
413 | got.sort()\r | |
414 | self.assertEqual(got, [0, 0] + range(5))\r | |
415 | \r | |
416 | self.assertEqual(gc.get_referents(1, 'a', 4j), [])\r | |
417 | \r | |
418 | def test_is_tracked(self):\r | |
419 | # Atomic built-in types are not tracked, user-defined objects and\r | |
420 | # mutable containers are.\r | |
421 | # NOTE: types with special optimizations (e.g. tuple) have tests\r | |
422 | # in their own test files instead.\r | |
423 | self.assertFalse(gc.is_tracked(None))\r | |
424 | self.assertFalse(gc.is_tracked(1))\r | |
425 | self.assertFalse(gc.is_tracked(1.0))\r | |
426 | self.assertFalse(gc.is_tracked(1.0 + 5.0j))\r | |
427 | self.assertFalse(gc.is_tracked(True))\r | |
428 | self.assertFalse(gc.is_tracked(False))\r | |
429 | self.assertFalse(gc.is_tracked("a"))\r | |
430 | self.assertFalse(gc.is_tracked(u"a"))\r | |
431 | self.assertFalse(gc.is_tracked(bytearray("a")))\r | |
432 | self.assertFalse(gc.is_tracked(type))\r | |
433 | self.assertFalse(gc.is_tracked(int))\r | |
434 | self.assertFalse(gc.is_tracked(object))\r | |
435 | self.assertFalse(gc.is_tracked(object()))\r | |
436 | \r | |
437 | class OldStyle:\r | |
438 | pass\r | |
439 | class NewStyle(object):\r | |
440 | pass\r | |
441 | self.assertTrue(gc.is_tracked(gc))\r | |
442 | self.assertTrue(gc.is_tracked(OldStyle))\r | |
443 | self.assertTrue(gc.is_tracked(OldStyle()))\r | |
444 | self.assertTrue(gc.is_tracked(NewStyle))\r | |
445 | self.assertTrue(gc.is_tracked(NewStyle()))\r | |
446 | self.assertTrue(gc.is_tracked([]))\r | |
447 | self.assertTrue(gc.is_tracked(set()))\r | |
448 | \r | |
449 | def test_bug1055820b(self):\r | |
450 | # Corresponds to temp2b.py in the bug report.\r | |
451 | \r | |
452 | ouch = []\r | |
453 | def callback(ignored):\r | |
454 | ouch[:] = [wr() for wr in WRs]\r | |
455 | \r | |
456 | Cs = [C1055820(i) for i in range(2)]\r | |
457 | WRs = [weakref.ref(c, callback) for c in Cs]\r | |
458 | c = None\r | |
459 | \r | |
460 | gc.collect()\r | |
461 | self.assertEqual(len(ouch), 0)\r | |
462 | # Make the two instances trash, and collect again. The bug was that\r | |
463 | # the callback materialized a strong reference to an instance, but gc\r | |
464 | # cleared the instance's dict anyway.\r | |
465 | Cs = None\r | |
466 | gc.collect()\r | |
467 | self.assertEqual(len(ouch), 2) # else the callbacks didn't run\r | |
468 | for x in ouch:\r | |
469 | # If the callback resurrected one of these guys, the instance\r | |
470 | # would be damaged, with an empty __dict__.\r | |
471 | self.assertEqual(x, None)\r | |
472 | \r | |
473 | class GCTogglingTests(unittest.TestCase):\r | |
474 | def setUp(self):\r | |
475 | gc.enable()\r | |
476 | \r | |
477 | def tearDown(self):\r | |
478 | gc.disable()\r | |
479 | \r | |
480 | def test_bug1055820c(self):\r | |
481 | # Corresponds to temp2c.py in the bug report. This is pretty\r | |
482 | # elaborate.\r | |
483 | \r | |
484 | c0 = C1055820(0)\r | |
485 | # Move c0 into generation 2.\r | |
486 | gc.collect()\r | |
487 | \r | |
488 | c1 = C1055820(1)\r | |
489 | c1.keep_c0_alive = c0\r | |
490 | del c0.loop # now only c1 keeps c0 alive\r | |
491 | \r | |
492 | c2 = C1055820(2)\r | |
493 | c2wr = weakref.ref(c2) # no callback!\r | |
494 | \r | |
495 | ouch = []\r | |
496 | def callback(ignored):\r | |
497 | ouch[:] = [c2wr()]\r | |
498 | \r | |
499 | # The callback gets associated with a wr on an object in generation 2.\r | |
500 | c0wr = weakref.ref(c0, callback)\r | |
501 | \r | |
502 | c0 = c1 = c2 = None\r | |
503 | \r | |
504 | # What we've set up: c0, c1, and c2 are all trash now. c0 is in\r | |
505 | # generation 2. The only thing keeping it alive is that c1 points to\r | |
506 | # it. c1 and c2 are in generation 0, and are in self-loops. There's a\r | |
507 | # global weakref to c2 (c2wr), but that weakref has no callback.\r | |
508 | # There's also a global weakref to c0 (c0wr), and that does have a\r | |
509 | # callback, and that callback references c2 via c2wr().\r | |
510 | #\r | |
511 | # c0 has a wr with callback, which references c2wr\r | |
512 | # ^\r | |
513 | # |\r | |
514 | # | Generation 2 above dots\r | |
515 | #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . .\r | |
516 | # | Generation 0 below dots\r | |
517 | # |\r | |
518 | # |\r | |
519 | # ^->c1 ^->c2 has a wr but no callback\r | |
520 | # | | | |\r | |
521 | # <--v <--v\r | |
522 | #\r | |
523 | # So this is the nightmare: when generation 0 gets collected, we see\r | |
524 | # that c2 has a callback-free weakref, and c1 doesn't even have a\r | |
525 | # weakref. Collecting generation 0 doesn't see c0 at all, and c0 is\r | |
526 | # the only object that has a weakref with a callback. gc clears c1\r | |
527 | # and c2. Clearing c1 has the side effect of dropping the refcount on\r | |
528 | # c0 to 0, so c0 goes away (despite that it's in an older generation)\r | |
529 | # and c0's wr callback triggers. That in turn materializes a reference\r | |
530 | # to c2 via c2wr(), but c2 gets cleared anyway by gc.\r | |
531 | \r | |
532 | # We want to let gc happen "naturally", to preserve the distinction\r | |
533 | # between generations.\r | |
534 | junk = []\r | |
535 | i = 0\r | |
536 | detector = GC_Detector()\r | |
537 | while not detector.gc_happened:\r | |
538 | i += 1\r | |
539 | if i > 10000:\r | |
540 | self.fail("gc didn't happen after 10000 iterations")\r | |
541 | self.assertEqual(len(ouch), 0)\r | |
542 | junk.append([]) # this will eventually trigger gc\r | |
543 | \r | |
544 | self.assertEqual(len(ouch), 1) # else the callback wasn't invoked\r | |
545 | for x in ouch:\r | |
546 | # If the callback resurrected c2, the instance would be damaged,\r | |
547 | # with an empty __dict__.\r | |
548 | self.assertEqual(x, None)\r | |
549 | \r | |
550 | def test_bug1055820d(self):\r | |
551 | # Corresponds to temp2d.py in the bug report. This is very much like\r | |
552 | # test_bug1055820c, but uses a __del__ method instead of a weakref\r | |
553 | # callback to sneak in a resurrection of cyclic trash.\r | |
554 | \r | |
555 | ouch = []\r | |
556 | class D(C1055820):\r | |
557 | def __del__(self):\r | |
558 | ouch[:] = [c2wr()]\r | |
559 | \r | |
560 | d0 = D(0)\r | |
561 | # Move all the above into generation 2.\r | |
562 | gc.collect()\r | |
563 | \r | |
564 | c1 = C1055820(1)\r | |
565 | c1.keep_d0_alive = d0\r | |
566 | del d0.loop # now only c1 keeps d0 alive\r | |
567 | \r | |
568 | c2 = C1055820(2)\r | |
569 | c2wr = weakref.ref(c2) # no callback!\r | |
570 | \r | |
571 | d0 = c1 = c2 = None\r | |
572 | \r | |
573 | # What we've set up: d0, c1, and c2 are all trash now. d0 is in\r | |
574 | # generation 2. The only thing keeping it alive is that c1 points to\r | |
575 | # it. c1 and c2 are in generation 0, and are in self-loops. There's\r | |
576 | # a global weakref to c2 (c2wr), but that weakref has no callback.\r | |
577 | # There are no other weakrefs.\r | |
578 | #\r | |
579 | # d0 has a __del__ method that references c2wr\r | |
580 | # ^\r | |
581 | # |\r | |
582 | # | Generation 2 above dots\r | |
583 | #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . .\r | |
584 | # | Generation 0 below dots\r | |
585 | # |\r | |
586 | # |\r | |
587 | # ^->c1 ^->c2 has a wr but no callback\r | |
588 | # | | | |\r | |
589 | # <--v <--v\r | |
590 | #\r | |
591 | # So this is the nightmare: when generation 0 gets collected, we see\r | |
592 | # that c2 has a callback-free weakref, and c1 doesn't even have a\r | |
593 | # weakref. Collecting generation 0 doesn't see d0 at all. gc clears\r | |
594 | # c1 and c2. Clearing c1 has the side effect of dropping the refcount\r | |
595 | # on d0 to 0, so d0 goes away (despite that it's in an older\r | |
596 | # generation) and d0's __del__ triggers. That in turn materializes\r | |
597 | # a reference to c2 via c2wr(), but c2 gets cleared anyway by gc.\r | |
598 | \r | |
599 | # We want to let gc happen "naturally", to preserve the distinction\r | |
600 | # between generations.\r | |
601 | detector = GC_Detector()\r | |
602 | junk = []\r | |
603 | i = 0\r | |
604 | while not detector.gc_happened:\r | |
605 | i += 1\r | |
606 | if i > 10000:\r | |
607 | self.fail("gc didn't happen after 10000 iterations")\r | |
608 | self.assertEqual(len(ouch), 0)\r | |
609 | junk.append([]) # this will eventually trigger gc\r | |
610 | \r | |
611 | self.assertEqual(len(ouch), 1) # else __del__ wasn't invoked\r | |
612 | for x in ouch:\r | |
613 | # If __del__ resurrected c2, the instance would be damaged, with an\r | |
614 | # empty __dict__.\r | |
615 | self.assertEqual(x, None)\r | |
616 | \r | |
617 | def test_main():\r | |
618 | enabled = gc.isenabled()\r | |
619 | gc.disable()\r | |
620 | assert not gc.isenabled()\r | |
621 | debug = gc.get_debug()\r | |
622 | gc.set_debug(debug & ~gc.DEBUG_LEAK) # this test is supposed to leak\r | |
623 | \r | |
624 | try:\r | |
625 | gc.collect() # Delete 2nd generation garbage\r | |
626 | run_unittest(GCTests, GCTogglingTests)\r | |
627 | finally:\r | |
628 | gc.set_debug(debug)\r | |
629 | # test gc.enable() even if GC is disabled by default\r | |
630 | if verbose:\r | |
631 | print "restoring automatic collection"\r | |
632 | # make sure to always test gc.enable()\r | |
633 | gc.enable()\r | |
634 | assert gc.isenabled()\r | |
635 | if not enabled:\r | |
636 | gc.disable()\r | |
637 | \r | |
638 | if __name__ == "__main__":\r | |
639 | test_main()\r |