[New-bugs-announce] [issue1441] Cycles through ob_type aren't freed

Adam Olsen report at bugs.python.org
Wed Nov 14 04:01:32 CET 2007


New submission from Adam Olsen:

If I create a subclass of 'type' that's also an instance of 'type', then
I change __class__ to point to itself, at which point it cannot be freed
(as the type object is needed to delete the instance.)

I believe this can be solved by resetting __class__ to a known-safe
value.  Presumably this should be a hidden subclass of type, stored in a
C global, and used specifically for this purpose.  type_clear can do the
reset (checking that the passed in type is a heap type, perhaps with a
heap type metaclass); I'm hoping __del__ and weakref callbacks are not
an issue at this point, but I'll defer to the experts for verification.

This log using gdb shows that type_dealloc is called for a normal type
(BoringType), but not for the self-cyclic one (ImmortalType). 
ImmortalType shows up in every collection, never actually getting collected.

(I'm assuming Python doesn't bother to delete heap types during
shutdown, which is why type_dealloc isn't called more.)

**********

rhamph at factor:~/src/python-p3yk/build-debug$ gdb ./python 
GNU gdb 6.6.90.20070912-debian
Copyright (C) 2007 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
<http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) set height 100000
(gdb) break type_dealloc
Breakpoint 1 at 0x80af057: file ../Objects/typeobject.c, line 2146.
(gdb) commands
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>silent
>printf "*** type_dealloc %p: %s\n", type, type->tp_name
>cont
>end
(gdb) break typeobject.c:2010
Breakpoint 2 at 0x80aec35: file ../Objects/typeobject.c, line 2010.
(gdb) commands
Type commands for when breakpoint 2 is hit, one per line.
End with a line saying just "end".
>silent
>printf "*** type_new %p: %s\n", type, type->tp_name
>cont
>end
(gdb) run
Starting program: /home/rhamph/src/python-p3yk/build-debug/python 
Failed to read a valid object file image from memory.
[Thread debugging using libthread_db enabled]
[New Thread 0xb7e156b0 (LWP 25496)]
[Switching to Thread 0xb7e156b0 (LWP 25496)]
*** type_new 0x81c80ac: ZipImportError
*** type_new 0x81e9934: abstractproperty
*** type_new 0x81ea484: _Abstract
*** type_new 0x81eab04: ABCMeta
*** type_new 0x81eb6b4: Hashable
*** type_new 0x81ecb7c: Iterable
*** type_new 0x81ed9a4: Iterator
*** type_new 0x81ede84: Sized
*** type_new 0x81ee364: Container
*** type_new 0x822f2fc: Callable
*** type_new 0x822f974: Set
*** type_new 0x823094c: MutableSet
*** type_new 0x8230fec: Mapping
*** type_new 0x823135c: MappingView
*** type_new 0x823183c: KeysView
*** type_new 0x8231eb4: ItemsView
*** type_new 0x823252c: ValuesView
*** type_new 0x8232ba4: MutableMapping
*** type_new 0x82330ac: Sequence
*** type_new 0x8233fa4: MutableSequence
*** type_new 0x81e61ac: _Environ
*** type_new 0x823657c: _wrap_close
*** type_new 0x81d41a4: _Printer
*** type_new 0x81dab84: _Helper
*** type_new 0x81d12a4: error
*** type_new 0x82ad5b4: Pattern
*** type_new 0x82adc2c: SubPattern
*** type_new 0x82ae134: Tokenizer
*** type_new 0x82afb04: Scanner
*** type_new 0x8249f34: _multimap
*** type_new 0x824892c: _TemplateMetaclass
*** type_new 0x82b0634: Template
*** type_new 0x82b34ac: Formatter
*** type_new 0x82b000c: DistutilsError
*** type_new 0x82b40c4: DistutilsModuleError
*** type_new 0x82b440c: DistutilsClassError
*** type_new 0x82b4754: DistutilsGetoptError
*** type_new 0x82b4a9c: DistutilsArgError
*** type_new 0x82b4de4: DistutilsFileError
*** type_new 0x82b512c: DistutilsOptionError
*** type_new 0x82b57d4: DistutilsSetupError
*** type_new 0x82b5b1c: DistutilsPlatformError
*** type_new 0x82b5e64: DistutilsExecError
*** type_new 0x82b61ac: DistutilsInternalError
*** type_new 0x82b64f4: DistutilsTemplateError
*** type_new 0x82b683c: CCompilerError
*** type_new 0x82b6b84: PreprocessError
*** type_new 0x82b6ecc: CompileError
*** type_new 0x82b7214: LibError
*** type_new 0x82b755c: LinkError
*** type_new 0x82b7d4c: UnknownFileError
*** type_new 0x82b9b6c: Log
*** type_new 0x82ba994: Quitter
*** type_new 0x82bcdbc: CodecInfo
*** type_new 0x82bd104: Codec
*** type_new 0x82bdd94: IncrementalEncoder
*** type_new 0x82be224: BufferedIncrementalEncoder
*** type_new 0x82be72c: IncrementalDecoder
*** type_new 0x82bebbc: BufferedIncrementalDecoder
*** type_new 0x82bf0c4: StreamWriter
*** type_new 0x82bf5cc: StreamReader
*** type_new 0x82bfad4: StreamReaderWriter
*** type_new 0x82c022c: StreamRecoder
*** type_new 0x82c221c: CodecRegistryError
*** type_new 0x82c5414: _OptionError
*** type_new 0x82c23f4: BlockingIOError
*** type_new 0x82c25cc: UnsupportedOperation
*** type_new 0x82c2f3c: IOBase
*** type_new 0x82c2924: RawIOBase
*** type_new 0x82c2d3c: FileIO
*** type_new 0x8316844: BufferedIOBase
*** type_new 0x831733c: _BufferedIOMixin
*** type_new 0x8317ddc: BytesIO
*** type_new 0x83182e4: BufferedReader
*** type_new 0x83187ec: BufferedWriter
*** type_new 0x8318e74: BufferedRWPair
*** type_new 0x831966c: BufferedRandom
*** type_new 0x8319b74: TextIOBase
*** type_new 0x831a694: TextIOWrapper
*** type_new 0x831a064: StringIO
*** type_new 0x831a3d4: Codec
*** type_new 0x8317034: IncrementalEncoder
*** type_new 0x82bb32c: IncrementalDecoder
*** type_new 0x82c8ebc: StreamWriter
*** type_new 0x831ab84: StreamReader
*** type_new 0x831ad5c: StreamConverter
*** type_new 0x82c9094: IncrementalEncoder
*** type_new 0x831af34: IncrementalDecoder
*** type_new 0x831b10c: StreamWriter
*** type_new 0x831b2e4: StreamReader
*** type_new 0x831b4bc: open
Python 3.0a1 (py3k:57858M, Nov 13 2007, 17:35:03) 
[GCC 4.2.2 (Debian 4.2.2-1)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import gc
[36431 refs]
>>> gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_COLLECTABLE |
gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_OBJECTS)
[36431 refs]
>>> # Our metaclass must start out as a heaptype if we want to change
__class__
... class BoringType(type): pass
... 
*** type_new 0x832b42c: BoringType
[36453 refs]
>>> class ImmortalType(type, metaclass=BoringType): pass
... 
*** type_new 0x832b604: ImmortalType
[36473 refs]
>>> ImmortalType.__class__ = ImmortalType
[36473 refs]
>>> gc.collect()
gc: collecting generation 2...
gc: objects in each generation: 152 4735 0
gc: done.
0
[36480 refs]
>>> del BoringType
[36478 refs]
>>> gc.collect()
gc: collecting generation 2...
gc: objects in each generation: 4 0 4883
gc: collectable <type 0x832b42c>
gc: 0.0095s elapsed.
gc: collectable <dict 0x830ac14>
gc: 1195008581.8807s elapsed.
gc: collectable <tuple 0x830c934>
gc: 0.0100s elapsed.
gc: collectable <tuple 0x828f06c>
gc: 1195008581.8812s elapsed.
*** type_dealloc 0x832b42c: BoringType
gc: done, 4 unreachable, 0 uncollectable.
4
[36462 refs]
>>> del ImmortalType
[36460 refs]
>>> gc.collect()
gc: collecting generation 2...
gc: objects in each generation: 4 0 4879
gc: collectable <ImmortalType 0x832b604>
gc: 0.0251s elapsed.
gc: collectable <dict 0x830acb4>
gc: 1195008581.8980s elapsed.
gc: collectable <tuple 0x830ca34>
gc: 0.0255s elapsed.
gc: collectable <tuple 0x828cd8c>
gc: 1195008581.8984s elapsed.
gc: done, 4 unreachable, 0 uncollectable.
4
[36452 refs]
>>> 
[36452 refs]
gc: collecting generation 2...
gc: objects in each generation: 0 0 4878
gc: collectable <ImmortalType 0x832b604>
gc: 0.0080s elapsed.
gc: collectable <dict 0x830acb4>
gc: 1195008585.4450s elapsed.
gc: collectable <tuple 0x828cd8c>
gc: 0.0124s elapsed.
gc: done, 3 unreachable, 0 uncollectable.

Program exited normally.
(gdb) quit
rhamph at factor:~/src/python-p3yk/build-debug$ 

**********

Finally, this is what I pasted into gdb to produce that log.  Note that
I'm using a somewhat old checkout (r57858), so line numbers may need to
be adjusted.  (The second break point is intended to be at the end of
type_new.)

**********

set height 100000
break type_dealloc
commands
silent
printf "*** type_dealloc %p: %s\n", type, type->tp_name
cont
end
break typeobject.c:2010
commands
silent
printf "*** type_new %p: %s\n", type, type->tp_name
cont
end
run
import gc
gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_COLLECTABLE |
gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_OBJECTS)
# Our metaclass must start out as a heaptype if we want to change __class__
class BoringType(type): pass

class ImmortalType(type, metaclass=BoringType): pass

ImmortalType.__class__ = ImmortalType
gc.collect()
del BoringType
gc.collect()
del ImmortalType
gc.collect()

----------
components: Interpreter Core
messages: 57479
nosy: rhamphoryncus
severity: normal
status: open
title: Cycles through ob_type aren't freed

__________________________________
Tracker <report at bugs.python.org>
<http://bugs.python.org/issue1441>
__________________________________


More information about the New-bugs-announce mailing list