Author: Matti Picus <matti.pi...@gmail.com> Branch: cpyext-macros-cast Changeset: r84339:664e7d4392f4 Date: 2016-05-09 21:42 +0300 http://bitbucket.org/pypy/pypy/changeset/664e7d4392f4/
Log: merge default into branch diff too long, truncating to 2000 out of 3197 lines diff --git a/lib_pypy/cffi/api.py b/lib_pypy/cffi/api.py --- a/lib_pypy/cffi/api.py +++ b/lib_pypy/cffi/api.py @@ -397,20 +397,7 @@ data. Later, when this new cdata object is garbage-collected, 'destructor(old_cdata_object)' will be called. """ - try: - gcp = self._backend.gcp - except AttributeError: - pass - else: - return gcp(cdata, destructor) - # - with self._lock: - try: - gc_weakrefs = self.gc_weakrefs - except AttributeError: - from .gc_weakref import GcWeakrefs - gc_weakrefs = self.gc_weakrefs = GcWeakrefs(self) - return gc_weakrefs.build(cdata, destructor) + return self._backend.gcp(cdata, destructor) def _get_cached_btype(self, type): assert self._lock.acquire(False) is False diff --git a/lib_pypy/cffi/backend_ctypes.py b/lib_pypy/cffi/backend_ctypes.py --- a/lib_pypy/cffi/backend_ctypes.py +++ b/lib_pypy/cffi/backend_ctypes.py @@ -460,6 +460,11 @@ return x._value raise TypeError("character expected, got %s" % type(x).__name__) + def __nonzero__(self): + return ord(self._value) != 0 + else: + def __nonzero__(self): + return self._value != 0 if kind == 'float': @staticmethod @@ -993,6 +998,31 @@ assert onerror is None # XXX not implemented return BType(source, error) + def gcp(self, cdata, destructor): + BType = self.typeof(cdata) + + if destructor is None: + if not (hasattr(BType, '_gcp_type') and + BType._gcp_type is BType): + raise TypeError("Can remove destructor only on a object " + "previously returned by ffi.gc()") + cdata._destructor = None + return None + + try: + gcp_type = BType._gcp_type + except AttributeError: + class CTypesDataGcp(BType): + __slots__ = ['_orig', '_destructor'] + def __del__(self): + if self._destructor is not None: + self._destructor(self._orig) + gcp_type = BType._gcp_type = CTypesDataGcp + new_cdata = self.cast(gcp_type, cdata) + new_cdata._orig = cdata + new_cdata._destructor = destructor + return new_cdata + typeof = type def getcname(self, BType, replace_with): diff --git a/pypy/doc/discussion/finalizer-order.rst b/pypy/doc/discussion/finalizer-order.rst --- a/pypy/doc/discussion/finalizer-order.rst +++ b/pypy/doc/discussion/finalizer-order.rst @@ -33,26 +33,25 @@ it from a finalizer. A finalizer runs earlier, and in topological order; care must be taken that the object might still be reachable at this point if we're clever enough. A destructor on the other hand runs -last; nothing can be done with the object any more. +last; nothing can be done with the object any more, and the GC frees it +immediately. Destructors ----------- A destructor is an RPython ``__del__()`` method that is called directly -by the GC when there is no more reference to an object. Intended for -objects that just need to free a block of raw memory or close a file. +by the GC when it is about to free the memory. Intended for objects +that just need to free an extra block of raw memory. There are restrictions on the kind of code you can put in ``__del__()``, including all other functions called by it. These restrictions are -checked. In particular you cannot access fields containing GC objects; -and if you call an external C function, it must be a "safe" function -(e.g. not releasing the GIL; use ``releasegil=False`` in -``rffi.llexternal()``). +checked. In particular you cannot access fields containing GC objects. +Right now you can't call any external C function either. -If there are several objects with destructors that die during the same -GC cycle, they are called in a completely random order --- but that -should not matter because destructors cannot do much anyway. +Destructors are called precisely when the GC frees the memory of the +object. As long as the object exists (even in some finalizer queue or +anywhere), its destructor is not called. Register_finalizer @@ -95,10 +94,15 @@ To find the queued items, call ``fin.next_dead()`` repeatedly. It returns the next queued item, or ``None`` when the queue is empty. -It is allowed in theory to cumulate several different +In theory, it would kind of work if you cumulate several different ``FinalizerQueue`` instances for objects of the same class, and (always in theory) the same ``obj`` could be registered several times in the same queue, or in several queues. This is not tested though. +For now the untranslated emulation does not support registering the +same object several times. + +Note that the Boehm garbage collector, used in ``rpython -O0``, +completely ignores ``register_finalizer()``. Ordering of finalizers diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -79,3 +79,13 @@ It is a more flexible way to make RPython finalizers. .. branch: unpacking-cpython-shortcut + +.. branch: cleanups + +.. branch: cpyext-more-slots + +.. branch: use-gc-del-3 + +Use the new rgc.FinalizerQueue mechanism to clean up the handling of +``__del__`` methods. Fixes notably issue #2287. (All RPython +subclasses of W_Root need to use FinalizerQueue now.) diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -11,7 +11,7 @@ INT_MIN, INT_MAX, UINT_MAX, USHRT_MAX from pypy.interpreter.executioncontext import (ExecutionContext, ActionFlag, - UserDelAction) + make_finalizer_queue) from pypy.interpreter.error import OperationError, new_exception_class, oefmt from pypy.interpreter.argument import Arguments from pypy.interpreter.miscutils import ThreadLocals, make_weak_value_dictionary @@ -28,6 +28,7 @@ """This is the abstract root class of all wrapped objects that live in a 'normal' object space like StdObjSpace.""" __slots__ = ('__weakref__',) + _must_be_light_finalizer_ = True user_overridden_class = False def getdict(self, space): @@ -136,9 +137,8 @@ pass def clear_all_weakrefs(self): - """Call this at the beginning of interp-level __del__() methods - in subclasses. It ensures that weakrefs (if any) are cleared - before the object is further destroyed. + """Ensures that weakrefs (if any) are cleared now. This is + called by UserDelAction before the object is finalized further. """ lifeline = self.getweakref() if lifeline is not None: @@ -151,25 +151,37 @@ self.delweakref() lifeline.clear_all_weakrefs() - __already_enqueued_for_destruction = () + def _finalize_(self): + """The RPython-level finalizer. - def enqueue_for_destruction(self, space, callback, descrname): - """Put the object in the destructor queue of the space. - At a later, safe point in time, UserDelAction will call - callback(self). If that raises OperationError, prints it - to stderr with the descrname string. + By default, it is *not called*. See self.register_finalizer(). + Be ready to handle the case where the object is only half + initialized. Also, in some cases the object might still be + visible to app-level after _finalize_() is called (e.g. if + there is a __del__ that resurrects). + """ - Note that 'callback' will usually need to start with: - assert isinstance(self, W_SpecificClass) + def register_finalizer(self, space): + """Register a finalizer for this object, so that + self._finalize_() will be called. You must call this method at + most once. Be ready to handle in _finalize_() the case where + the object is half-initialized, even if you only call + self.register_finalizer() at the end of the initialization. + This is because there are cases where the finalizer is already + registered before: if the user makes an app-level subclass with + a __del__. (In that case only, self.register_finalizer() does + nothing, because the finalizer is already registered in + allocate_instance().) """ - # this function always resurect the object, so when - # running on top of CPython we must manually ensure that - # we enqueue it only once - if not we_are_translated(): - if callback in self.__already_enqueued_for_destruction: - return - self.__already_enqueued_for_destruction += (callback,) - space.user_del_action.register_callback(self, callback, descrname) + if self.user_overridden_class and self.getclass(space).hasuserdel: + # already registered by space.allocate_instance() + if not we_are_translated(): + assert space.finalizer_queue._already_registered(self) + else: + if not we_are_translated(): + # does not make sense if _finalize_ is not overridden + assert self._finalize_.im_func is not W_Root._finalize_.im_func + space.finalizer_queue.register_finalizer(self) # hooks that the mapdict implementations needs: def _get_mapdict_map(self): @@ -389,9 +401,9 @@ self.interned_strings = make_weak_value_dictionary(self, str, W_Root) self.actionflag = ActionFlag() # changed by the signal module self.check_signal_action = None # changed by the signal module - self.user_del_action = UserDelAction(self) + make_finalizer_queue(W_Root, self) self._code_of_sys_exc_info = None - + # can be overridden to a subclass self.initialize() @@ -1844,7 +1856,6 @@ ('get', 'get', 3, ['__get__']), ('set', 'set', 3, ['__set__']), ('delete', 'delete', 2, ['__delete__']), - ('userdel', 'del', 1, ['__del__']), ] ObjSpace.BuiltinModuleTable = [ diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -2,7 +2,7 @@ from pypy.interpreter.error import OperationError, get_cleared_operation_error from rpython.rlib.unroll import unrolling_iterable from rpython.rlib.objectmodel import specialize -from rpython.rlib import jit +from rpython.rlib import jit, rgc TICK_COUNTER_STEP = 100 @@ -141,6 +141,12 @@ actionflag.action_dispatcher(self, frame) # slow path bytecode_trace._always_inline_ = True + def _run_finalizers_now(self): + # Tests only: run the actions now, to ensure that the + # finalizable objects are really finalized. Used notably by + # pypy.tool.pytest.apptest. + self.space.actionflag.action_dispatcher(self, None) + def bytecode_only_trace(self, frame): """ Like bytecode_trace() but doesn't invoke any other events besides the @@ -515,75 +521,98 @@ """ -class UserDelCallback(object): - def __init__(self, w_obj, callback, descrname): - self.w_obj = w_obj - self.callback = callback - self.descrname = descrname - self.next = None - class UserDelAction(AsyncAction): """An action that invokes all pending app-level __del__() method. This is done as an action instead of immediately when the - interp-level __del__() is invoked, because the latter can occur more + WRootFinalizerQueue is triggered, because the latter can occur more or less anywhere in the middle of code that might not be happy with random app-level code mutating data structures under its feet. """ def __init__(self, space): AsyncAction.__init__(self, space) - self.dying_objects = None - self.dying_objects_last = None - self.finalizers_lock_count = 0 - self.enabled_at_app_level = True - - def register_callback(self, w_obj, callback, descrname): - cb = UserDelCallback(w_obj, callback, descrname) - if self.dying_objects_last is None: - self.dying_objects = cb - else: - self.dying_objects_last.next = cb - self.dying_objects_last = cb - self.fire() + self.finalizers_lock_count = 0 # see pypy/module/gc + self.enabled_at_app_level = True # see pypy/module/gc + self.pending_with_disabled_del = None def perform(self, executioncontext, frame): - if self.finalizers_lock_count > 0: - return self._run_finalizers() + @jit.dont_look_inside def _run_finalizers(self): - # Each call to perform() first grabs the self.dying_objects - # and replaces it with an empty list. We do this to try to - # avoid too deep recursions of the kind of __del__ being called - # while in the middle of another __del__ call. - pending = self.dying_objects - self.dying_objects = None - self.dying_objects_last = None + while True: + w_obj = self.space.finalizer_queue.next_dead() + if w_obj is None: + break + self._call_finalizer(w_obj) + + def gc_disabled(self, w_obj): + # If we're running in 'gc.disable()' mode, record w_obj in the + # "call me later" list and return True. In normal mode, return + # False. Use this function from some _finalize_() methods: + # if a _finalize_() method would call some user-defined + # app-level function, like a weakref callback, then first do + # 'if gc.disabled(self): return'. Another attempt at + # calling _finalize_() will be made after 'gc.enable()'. + # (The exact rule for when to use gc_disabled() or not is a bit + # vague, but most importantly this includes all user-level + # __del__().) + pdd = self.pending_with_disabled_del + if pdd is None: + return False + else: + pdd.append(w_obj) + return True + + def _call_finalizer(self, w_obj): + # Before calling the finalizers, clear the weakrefs, if any. + w_obj.clear_all_weakrefs() + + # Look up and call the app-level __del__, if any. space = self.space - while pending is not None: + if w_obj.typedef is None: + w_del = None # obscure case: for WeakrefLifeline + else: + w_del = space.lookup(w_obj, '__del__') + if w_del is not None: + if self.gc_disabled(w_obj): + return try: - pending.callback(pending.w_obj) - except OperationError as e: - e.write_unraisable(space, pending.descrname, pending.w_obj) - e.clear(space) # break up reference cycles - pending = pending.next - # - # Note: 'dying_objects' used to be just a regular list instead - # of a chained list. This was the cause of "leaks" if we have a - # program that constantly creates new objects with finalizers. - # Here is why: say 'dying_objects' is a long list, and there - # are n instances in it. Then we spend some time in this - # function, possibly triggering more GCs, but keeping the list - # of length n alive. Then the list is suddenly freed at the - # end, and we return to the user program. At this point the - # GC limit is still very high, because just before, there was - # a list of length n alive. Assume that the program continues - # to allocate a lot of instances with finalizers. The high GC - # limit means that it could allocate a lot of instances before - # reaching it --- possibly more than n. So the whole procedure - # repeats with higher and higher values of n. - # - # This does not occur in the current implementation because - # there is no list of length n: if n is large, then the GC - # will run several times while walking the list, but it will - # see lower and lower memory usage, with no lower bound of n. + space.get_and_call_function(w_del, w_obj) + except Exception as e: + report_error(space, e, "method __del__ of ", w_obj) + + # Call the RPython-level _finalize_() method. + try: + w_obj._finalize_() + except Exception as e: + report_error(space, e, "finalizer of ", w_obj) + + +def report_error(space, e, where, w_obj): + if isinstance(e, OperationError): + e.write_unraisable(space, where, w_obj) + e.clear(space) # break up reference cycles + else: + addrstring = w_obj.getaddrstring(space) + msg = ("RPython exception %s in %s<%s at 0x%s> ignored\n" % ( + str(e), where, space.type(w_obj).name, addrstring)) + space.call_method(space.sys.get('stderr'), 'write', + space.wrap(msg)) + + +def make_finalizer_queue(W_Root, space): + """Make a FinalizerQueue subclass which responds to GC finalizer + events by 'firing' the UserDelAction class above. It does not + directly fetches the objects to finalize at all; they stay in the + GC-managed queue, and will only be fetched by UserDelAction + (between bytecodes).""" + + class WRootFinalizerQueue(rgc.FinalizerQueue): + Class = W_Root + + def finalizer_trigger(self): + space.user_del_action.fire() + + space.user_del_action = UserDelAction(space) + space.finalizer_queue = WRootFinalizerQueue() diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py --- a/pypy/interpreter/generator.py +++ b/pypy/interpreter/generator.py @@ -1,6 +1,7 @@ from pypy.interpreter.baseobjspace import W_Root from pypy.interpreter.error import OperationError, oefmt from pypy.interpreter.pyopcode import LoopBlock +from pypy.interpreter.pycode import CO_YIELD_INSIDE_TRY from rpython.rlib import jit @@ -13,6 +14,8 @@ self.frame = frame # turned into None when frame_finished_execution self.pycode = frame.pycode self.running = False + if self.pycode.co_flags & CO_YIELD_INSIDE_TRY: + self.register_finalizer(self.space) def descr__repr__(self, space): if self.pycode is None: @@ -139,7 +142,6 @@ def descr_close(self): """x.close(arg) -> raise GeneratorExit inside generator.""" - assert isinstance(self, GeneratorIterator) space = self.space try: w_retval = self.throw(space.w_GeneratorExit, space.w_None, @@ -212,25 +214,21 @@ unpack_into = _create_unpack_into() unpack_into_w = _create_unpack_into() - -class GeneratorIteratorWithDel(GeneratorIterator): - - def __del__(self): - # Only bother enqueuing self to raise an exception if the frame is - # still not finished and finally or except blocks are present. - self.clear_all_weakrefs() + def _finalize_(self): + # This is only called if the CO_YIELD_INSIDE_TRY flag is set + # on the code object. If the frame is still not finished and + # finally or except blocks are present at the current + # position, then raise a GeneratorExit. Otherwise, there is + # no point. if self.frame is not None: block = self.frame.lastblock while block is not None: if not isinstance(block, LoopBlock): - self.enqueue_for_destruction(self.space, - GeneratorIterator.descr_close, - "interrupting generator of ") + self.descr_close() break block = block.previous - def get_printable_location_genentry(bytecode): return '%s <generator>' % (bytecode.get_repr(),) generatorentry_driver = jit.JitDriver(greens=['pycode'], diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py --- a/pypy/interpreter/pyframe.py +++ b/pypy/interpreter/pyframe.py @@ -241,12 +241,8 @@ def run(self): """Start this frame's execution.""" if self.getcode().co_flags & pycode.CO_GENERATOR: - if self.getcode().co_flags & pycode.CO_YIELD_INSIDE_TRY: - from pypy.interpreter.generator import GeneratorIteratorWithDel - return self.space.wrap(GeneratorIteratorWithDel(self)) - else: - from pypy.interpreter.generator import GeneratorIterator - return self.space.wrap(GeneratorIterator(self)) + from pypy.interpreter.generator import GeneratorIterator + return self.space.wrap(GeneratorIterator(self)) else: return self.execute_frame() diff --git a/pypy/interpreter/test/test_typedef.py b/pypy/interpreter/test/test_typedef.py --- a/pypy/interpreter/test/test_typedef.py +++ b/pypy/interpreter/test/test_typedef.py @@ -127,10 +127,7 @@ """ % (slots, methodname, checks[0], checks[1], checks[2], checks[3])) subclasses = {} - for key, subcls in typedef._subclass_cache.items(): - if key[0] is not space.config: - continue - cls = key[1] + for cls, subcls in typedef._unique_subclass_cache.items(): subclasses.setdefault(cls, {}) prevsubcls = subclasses[cls].setdefault(subcls.__name__, subcls) assert subcls is prevsubcls @@ -186,35 +183,20 @@ class W_Level1(W_Root): def __init__(self, space1): assert space1 is space - def __del__(self): + self.register_finalizer(space) + def _finalize_(self): space.call_method(w_seen, 'append', space.wrap(1)) - class W_Level2(W_Root): - def __init__(self, space1): - assert space1 is space - def __del__(self): - self.enqueue_for_destruction(space, W_Level2.destructormeth, - 'FOO ') - def destructormeth(self): - space.call_method(w_seen, 'append', space.wrap(2)) W_Level1.typedef = typedef.TypeDef( 'level1', __new__ = typedef.generic_new_descr(W_Level1)) - W_Level2.typedef = typedef.TypeDef( - 'level2', - __new__ = typedef.generic_new_descr(W_Level2)) # w_seen = space.newlist([]) W_Level1(space) gc.collect(); gc.collect() - assert space.unwrap(w_seen) == [1] - # - w_seen = space.newlist([]) - W_Level2(space) - gc.collect(); gc.collect() assert space.str_w(space.repr(w_seen)) == "[]" # not called yet ec = space.getexecutioncontext() self.space.user_del_action.perform(ec, None) - assert space.unwrap(w_seen) == [2] + assert space.unwrap(w_seen) == [1] # called by user_del_action # w_seen = space.newlist([]) self.space.appexec([self.space.gettypeobject(W_Level1.typedef)], @@ -236,29 +218,17 @@ A4() """) gc.collect(); gc.collect() - assert space.unwrap(w_seen) == [4, 1] + assert space.unwrap(w_seen) == [4, 1] # user __del__, and _finalize_ # w_seen = space.newlist([]) - self.space.appexec([self.space.gettypeobject(W_Level2.typedef)], + self.space.appexec([self.space.gettypeobject(W_Level1.typedef)], """(level2): class A5(level2): pass A5() """) gc.collect(); gc.collect() - assert space.unwrap(w_seen) == [2] - # - w_seen = space.newlist([]) - self.space.appexec([self.space.gettypeobject(W_Level2.typedef), - w_seen], - """(level2, seen): - class A6(level2): - def __del__(self): - seen.append(6) - A6() - """) - gc.collect(); gc.collect() - assert space.unwrap(w_seen) == [6, 2] + assert space.unwrap(w_seen) == [1] # _finalize_ only def test_multiple_inheritance(self): class W_A(W_Root): diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py --- a/pypy/interpreter/typedef.py +++ b/pypy/interpreter/typedef.py @@ -24,6 +24,8 @@ self.bases = bases self.heaptype = False self.hasdict = '__dict__' in rawdict + # no __del__: use an RPython _finalize_() method and register_finalizer + assert '__del__' not in rawdict self.weakrefable = '__weakref__' in rawdict self.doc = rawdict.pop('__doc__', None) for base in bases: @@ -103,26 +105,20 @@ # we need two subclasses of the app-level type, one to add mapdict, and then one # to add del to not slow down the GC. -def get_unique_interplevel_subclass(space, cls, needsdel=False): +def get_unique_interplevel_subclass(space, cls): "NOT_RPYTHON: initialization-time only" - if hasattr(cls, '__del__') and getattr(cls, "handle_del_manually", False): - needsdel = False assert cls.typedef.acceptable_as_base_class - key = space, cls, needsdel try: - return _subclass_cache[key] + return _unique_subclass_cache[cls] except KeyError: - # XXX can save a class if cls already has a __del__ - if needsdel: - cls = get_unique_interplevel_subclass(space, cls, False) - subcls = _getusercls(space, cls, needsdel) - assert key not in _subclass_cache - _subclass_cache[key] = subcls + subcls = _getusercls(cls) + assert cls not in _unique_subclass_cache + _unique_subclass_cache[cls] = subcls return subcls get_unique_interplevel_subclass._annspecialcase_ = "specialize:memo" -_subclass_cache = {} +_unique_subclass_cache = {} -def _getusercls(space, cls, wants_del, reallywantdict=False): +def _getusercls(cls, reallywantdict=False): from rpython.rlib import objectmodel from pypy.objspace.std.objectobject import W_ObjectObject from pypy.module.__builtin__.interp_classobj import W_InstanceObject @@ -132,11 +128,10 @@ typedef = cls.typedef name = cls.__name__ + "User" - mixins_needed = [] if cls is W_ObjectObject or cls is W_InstanceObject: - mixins_needed.append(_make_storage_mixin_size_n()) + base_mixin = _make_storage_mixin_size_n() else: - mixins_needed.append(MapdictStorageMixin) + base_mixin = MapdictStorageMixin copy_methods = [BaseUserClassMapdict] if reallywantdict or not typedef.hasdict: # the type has no dict, mapdict to provide the dict @@ -147,44 +142,12 @@ # support copy_methods.append(MapdictWeakrefSupport) name += "Weakrefable" - if wants_del: - # This subclass comes with an app-level __del__. To handle - # it, we make an RPython-level __del__ method. This - # RPython-level method is called directly by the GC and it - # cannot do random things (calling the app-level __del__ would - # be "random things"). So instead, we just call here - # enqueue_for_destruction(), and the app-level __del__ will be - # called later at a safe point (typically between bytecodes). - # If there is also an inherited RPython-level __del__, it is - # called afterwards---not immediately! This base - # RPython-level __del__ is supposed to run only when the - # object is not reachable any more. NOTE: it doesn't fully - # work: see issue #2287. - name += "Del" - parent_destructor = getattr(cls, '__del__', None) - def call_parent_del(self): - assert isinstance(self, subcls) - parent_destructor(self) - def call_applevel_del(self): - assert isinstance(self, subcls) - space.userdel(self) - class Proto(object): - def __del__(self): - self.clear_all_weakrefs() - self.enqueue_for_destruction(space, call_applevel_del, - 'method __del__ of ') - if parent_destructor is not None: - self.enqueue_for_destruction(space, call_parent_del, - 'internal destructor of ') - mixins_needed.append(Proto) class subcls(cls): user_overridden_class = True - for base in mixins_needed: - objectmodel.import_from_mixin(base) + objectmodel.import_from_mixin(base_mixin) for copycls in copy_methods: _copy_methods(copycls, subcls) - del subcls.base subcls.__name__ = name return subcls diff --git a/pypy/module/__builtin__/interp_classobj.py b/pypy/module/__builtin__/interp_classobj.py --- a/pypy/module/__builtin__/interp_classobj.py +++ b/pypy/module/__builtin__/interp_classobj.py @@ -44,13 +44,12 @@ self.bases_w = bases self.w_dict = w_dict + def has_user_del(self, space): + return self.lookup(space, '__del__') is not None + def instantiate(self, space): cache = space.fromcache(Cache) - if self.lookup(space, '__del__') is not None: - w_inst = cache.cls_with_del(space, self) - else: - w_inst = cache.cls_without_del(space, self) - return w_inst + return cache.InstanceObjectCls(space, self) def getdict(self, space): return self.w_dict @@ -132,9 +131,9 @@ self.setbases(space, w_value) return elif name == "__del__": - if self.lookup(space, name) is None: + if not self.has_user_del(space): msg = ("a __del__ method added to an existing class will " - "not be called") + "only be called on instances made from now on") space.warn(space.wrap(msg), space.w_RuntimeWarning) space.setitem(self.w_dict, w_attr, w_value) @@ -184,14 +183,11 @@ if hasattr(space, 'is_fake_objspace'): # hack: with the fake objspace, we don't want to see typedef's # _getusercls() at all - self.cls_without_del = W_InstanceObject - self.cls_with_del = W_InstanceObject + self.InstanceObjectCls = W_InstanceObject return - self.cls_without_del = _getusercls( - space, W_InstanceObject, False, reallywantdict=True) - self.cls_with_del = _getusercls( - space, W_InstanceObject, True, reallywantdict=True) + self.InstanceObjectCls = _getusercls( + W_InstanceObject, reallywantdict=True) def class_descr_call(space, w_self, __args__): @@ -297,12 +293,15 @@ class W_InstanceObject(W_Root): def __init__(self, space, w_class): # note that user_setup is overridden by the typedef.py machinery + self.space = space self.user_setup(space, space.gettypeobject(self.typedef)) assert isinstance(w_class, W_ClassObject) self.w_class = w_class + if w_class.has_user_del(space): + space.finalizer_queue.register_finalizer(self) def user_setup(self, space, w_subtype): - self.space = space + pass def set_oldstyle_class(self, space, w_class): if w_class is None or not isinstance(w_class, W_ClassObject): @@ -368,8 +367,7 @@ self.set_oldstyle_class(space, w_value) return if name == '__del__' and w_meth is None: - cache = space.fromcache(Cache) - if (not isinstance(self, cache.cls_with_del) + if (not self.w_class.has_user_del(space) and self.getdictvalue(space, '__del__') is None): msg = ("a __del__ method added to an instance with no " "__del__ in the class will not be called") @@ -646,13 +644,14 @@ raise oefmt(space.w_TypeError, "instance has no next() method") return space.call_function(w_func) - def descr_del(self, space): - # Note that this is called from executioncontext.UserDelAction - # via the space.userdel() method. + def _finalize_(self): + space = self.space w_func = self.getdictvalue(space, '__del__') if w_func is None: w_func = self.getattr_from_class(space, '__del__') if w_func is not None: + if self.space.user_del_action.gc_disabled(self): + return space.call_function(w_func) def descr_exit(self, space, w_type, w_value, w_tb): @@ -729,7 +728,6 @@ __pow__ = interp2app(W_InstanceObject.descr_pow), __rpow__ = interp2app(W_InstanceObject.descr_rpow), next = interp2app(W_InstanceObject.descr_next), - __del__ = interp2app(W_InstanceObject.descr_del), __exit__ = interp2app(W_InstanceObject.descr_exit), __dict__ = dict_descr, **rawdict diff --git a/pypy/module/_cffi_backend/allocator.py b/pypy/module/_cffi_backend/allocator.py --- a/pypy/module/_cffi_backend/allocator.py +++ b/pypy/module/_cffi_backend/allocator.py @@ -45,14 +45,11 @@ rffi.c_memset(rffi.cast(rffi.VOIDP, ptr), 0, rffi.cast(rffi.SIZE_T, datasize)) # - if self.w_free is None: - # use this class which does not have a __del__, but still - # keeps alive w_raw_cdata - res = cdataobj.W_CDataNewNonStdNoFree(space, ptr, ctype, length) - else: - res = cdataobj.W_CDataNewNonStdFree(space, ptr, ctype, length) + res = cdataobj.W_CDataNewNonStd(space, ptr, ctype, length) + res.w_raw_cdata = w_raw_cdata + if self.w_free is not None: res.w_free = self.w_free - res.w_raw_cdata = w_raw_cdata + res.register_finalizer(space) return res @unwrap_spec(w_init=WrappedDefault(None)) diff --git a/pypy/module/_cffi_backend/cdataobj.py b/pypy/module/_cffi_backend/cdataobj.py --- a/pypy/module/_cffi_backend/cdataobj.py +++ b/pypy/module/_cffi_backend/cdataobj.py @@ -71,7 +71,7 @@ def nonzero(self): with self as ptr: - nonzero = bool(ptr) + nonzero = self.ctype.nonzero(ptr) return self.space.wrap(nonzero) def int(self, space): @@ -365,8 +365,16 @@ return self.ctype.size def with_gc(self, w_destructor): + space = self.space + if space.is_none(w_destructor): + if isinstance(self, W_CDataGCP): + self.w_destructor = None + return space.w_None + raise oefmt(space.w_TypeError, + "Can remove destructor only on a object " + "previously returned by ffi.gc()") with self as ptr: - return W_CDataGCP(self.space, ptr, self.ctype, self, w_destructor) + return W_CDataGCP(space, ptr, self.ctype, self, w_destructor) def unpack(self, length): from pypy.module._cffi_backend.ctypeptr import W_CTypePtrOrArray @@ -441,22 +449,11 @@ lltype.free(self._ptr, flavor='raw') -class W_CDataNewNonStdNoFree(W_CDataNewOwning): - """Subclass using a non-standard allocator, no free()""" - _attrs_ = ['w_raw_cdata'] +class W_CDataNewNonStd(W_CDataNewOwning): + """Subclass using a non-standard allocator""" + _attrs_ = ['w_raw_cdata', 'w_free'] -class W_CDataNewNonStdFree(W_CDataNewNonStdNoFree): - """Subclass using a non-standard allocator, with a free()""" - _attrs_ = ['w_free'] - - def __del__(self): - self.clear_all_weakrefs() - self.enqueue_for_destruction(self.space, - W_CDataNewNonStdFree.call_destructor, - 'destructor of ') - - def call_destructor(self): - assert isinstance(self, W_CDataNewNonStdFree) + def _finalize_(self): self.space.call_function(self.w_free, self.w_raw_cdata) @@ -538,21 +535,19 @@ class W_CDataGCP(W_CData): """For ffi.gc().""" _attrs_ = ['w_original_cdata', 'w_destructor'] - _immutable_fields_ = ['w_original_cdata', 'w_destructor'] + _immutable_fields_ = ['w_original_cdata'] def __init__(self, space, cdata, ctype, w_original_cdata, w_destructor): W_CData.__init__(self, space, cdata, ctype) self.w_original_cdata = w_original_cdata self.w_destructor = w_destructor + self.register_finalizer(space) - def __del__(self): - self.clear_all_weakrefs() - self.enqueue_for_destruction(self.space, W_CDataGCP.call_destructor, - 'destructor of ') - - def call_destructor(self): - assert isinstance(self, W_CDataGCP) - self.space.call_function(self.w_destructor, self.w_original_cdata) + def _finalize_(self): + w_destructor = self.w_destructor + if w_destructor is not None: + self.w_destructor = None + self.space.call_function(w_destructor, self.w_original_cdata) W_CData.typedef = TypeDef( diff --git a/pypy/module/_cffi_backend/cdlopen.py b/pypy/module/_cffi_backend/cdlopen.py --- a/pypy/module/_cffi_backend/cdlopen.py +++ b/pypy/module/_cffi_backend/cdlopen.py @@ -25,10 +25,13 @@ raise wrap_dlopenerror(ffi.space, e, filename) W_LibObject.__init__(self, ffi, filename) self.libhandle = handle + self.register_finalizer(ffi.space) - def __del__(self): - if self.libhandle: - dlclose(self.libhandle) + def _finalize_(self): + h = self.libhandle + if h != rffi.cast(DLLHANDLE, 0): + self.libhandle = rffi.cast(DLLHANDLE, 0) + dlclose(h) def cdlopen_fetch(self, name): if not self.libhandle: diff --git a/pypy/module/_cffi_backend/ctypeobj.py b/pypy/module/_cffi_backend/ctypeobj.py --- a/pypy/module/_cffi_backend/ctypeobj.py +++ b/pypy/module/_cffi_backend/ctypeobj.py @@ -147,6 +147,9 @@ raise oefmt(space.w_TypeError, "cannot add a cdata '%s' and a number", self.name) + def nonzero(self, cdata): + return bool(cdata) + def insert_name(self, extra, extra_position): name = '%s%s%s' % (self.name[:self.name_position], extra, diff --git a/pypy/module/_cffi_backend/ctypeprim.py b/pypy/module/_cffi_backend/ctypeprim.py --- a/pypy/module/_cffi_backend/ctypeprim.py +++ b/pypy/module/_cffi_backend/ctypeprim.py @@ -93,6 +93,18 @@ return self.space.newlist_int(result) return W_CType.unpack_ptr(self, w_ctypeptr, ptr, length) + def nonzero(self, cdata): + if self.size <= rffi.sizeof(lltype.Signed): + value = misc.read_raw_long_data(cdata, self.size) + return value != 0 + else: + return self._nonzero_longlong(cdata) + + def _nonzero_longlong(self, cdata): + # in its own function: LONGLONG may make the whole function jit-opaque + value = misc.read_raw_signed_data(cdata, self.size) + return bool(value) + class W_CTypePrimitiveCharOrUniChar(W_CTypePrimitive): _attrs_ = [] @@ -435,6 +447,9 @@ return self.space.newlist_float(result) return W_CType.unpack_ptr(self, w_ctypeptr, ptr, length) + def nonzero(self, cdata): + return misc.is_nonnull_float(cdata, self.size) + class W_CTypePrimitiveLongDouble(W_CTypePrimitiveFloat): _attrs_ = [] @@ -501,3 +516,7 @@ rffi.LONGDOUBLE, rffi.LONGDOUBLEP) return True return W_CTypePrimitive.pack_list_of_items(self, cdata, w_ob) + + @jit.dont_look_inside + def nonzero(self, cdata): + return misc.is_nonnull_longdouble(cdata) diff --git a/pypy/module/_cffi_backend/libraryobj.py b/pypy/module/_cffi_backend/libraryobj.py --- a/pypy/module/_cffi_backend/libraryobj.py +++ b/pypy/module/_cffi_backend/libraryobj.py @@ -15,7 +15,6 @@ class W_Library(W_Root): _immutable_ = True - handle = rffi.cast(DLLHANDLE, 0) def __init__(self, space, filename, flags): self.space = space @@ -27,8 +26,9 @@ except DLOpenError as e: raise wrap_dlopenerror(space, e, filename) self.name = filename + self.register_finalizer(space) - def __del__(self): + def _finalize_(self): h = self.handle if h != rffi.cast(DLLHANDLE, 0): self.handle = rffi.cast(DLLHANDLE, 0) diff --git a/pypy/module/_cffi_backend/misc.py b/pypy/module/_cffi_backend/misc.py --- a/pypy/module/_cffi_backend/misc.py +++ b/pypy/module/_cffi_backend/misc.py @@ -256,7 +256,7 @@ def is_nonnull_longdouble(cdata): return _is_nonnull_longdouble(read_raw_longdouble_data(cdata)) def is_nonnull_float(cdata, size): - return read_raw_float_data(cdata, size) != 0.0 + return read_raw_float_data(cdata, size) != 0.0 # note: True if a NaN def object_as_bool(space, w_ob): # convert and cast a Python object to a boolean. Accept an integer diff --git a/pypy/module/_cffi_backend/test/_backend_test_c.py b/pypy/module/_cffi_backend/test/_backend_test_c.py --- a/pypy/module/_cffi_backend/test/_backend_test_c.py +++ b/pypy/module/_cffi_backend/test/_backend_test_c.py @@ -141,9 +141,13 @@ INF = 1E200 * 1E200 for name in ["float", "double"]: p = new_primitive_type(name) - assert bool(cast(p, 0)) + assert bool(cast(p, 0)) is False # since 1.7 + assert bool(cast(p, -0.0)) is False # since 1.7 + assert bool(cast(p, 1e-42)) is True + assert bool(cast(p, -1e-42)) is True assert bool(cast(p, INF)) assert bool(cast(p, -INF)) + assert bool(cast(p, float("nan"))) assert int(cast(p, -150)) == -150 assert int(cast(p, 61.91)) == 61 assert long(cast(p, 61.91)) == 61 @@ -202,7 +206,8 @@ def test_character_type(): p = new_primitive_type("char") - assert bool(cast(p, '\x00')) + assert bool(cast(p, 'A')) is True + assert bool(cast(p, '\x00')) is False # since 1.7 assert cast(p, '\x00') != cast(p, -17*256) assert int(cast(p, 'A')) == 65 assert long(cast(p, 'A')) == 65 @@ -2558,7 +2563,8 @@ BBoolP = new_pointer_type(BBool) assert int(cast(BBool, False)) == 0 assert int(cast(BBool, True)) == 1 - assert bool(cast(BBool, False)) is True # warning! + assert bool(cast(BBool, False)) is False # since 1.7 + assert bool(cast(BBool, True)) is True assert int(cast(BBool, 3)) == 1 assert int(cast(BBool, long(3))) == 1 assert int(cast(BBool, long(10)**4000)) == 1 diff --git a/pypy/module/_cffi_backend/test/test_ffi_obj.py b/pypy/module/_cffi_backend/test/test_ffi_obj.py --- a/pypy/module/_cffi_backend/test/test_ffi_obj.py +++ b/pypy/module/_cffi_backend/test/test_ffi_obj.py @@ -331,6 +331,25 @@ gc.collect() assert seen == [1] + def test_ffi_gc_disable(self): + import _cffi_backend as _cffi1_backend + ffi = _cffi1_backend.FFI() + p = ffi.new("int *", 123) + raises(TypeError, ffi.gc, p, None) + seen = [] + q1 = ffi.gc(p, lambda p: seen.append(1)) + q2 = ffi.gc(q1, lambda p: seen.append(2)) + import gc; gc.collect() + assert seen == [] + assert ffi.gc(q1, None) is None + del q1, q2 + for i in range(5): + if seen: + break + import gc + gc.collect() + assert seen == [2] + def test_ffi_new_allocator_1(self): import _cffi_backend as _cffi1_backend ffi = _cffi1_backend.FFI() diff --git a/pypy/module/_file/interp_file.py b/pypy/module/_file/interp_file.py --- a/pypy/module/_file/interp_file.py +++ b/pypy/module/_file/interp_file.py @@ -43,22 +43,18 @@ def __init__(self, space): self.space = space + self.register_finalizer(space) - def __del__(self): + def _finalize_(self): # assume that the file and stream objects are only visible in the - # thread that runs __del__, so no race condition should be possible - self.clear_all_weakrefs() + # thread that runs _finalize_, so no race condition should be + # possible and no locking is done here. if self.stream is not None: - self.enqueue_for_destruction(self.space, W_File.destructor, - 'close() method of ') - - def destructor(self): - assert isinstance(self, W_File) - try: - self.direct_close() - except StreamErrors as e: - operr = wrap_streamerror(self.space, e, self.w_name) - raise operr + try: + self.direct_close() + except StreamErrors as e: + operr = wrap_streamerror(self.space, e, self.w_name) + raise operr def fdopenstream(self, stream, fd, mode, w_name=None): self.fd = fd diff --git a/pypy/module/_hashlib/interp_hashlib.py b/pypy/module/_hashlib/interp_hashlib.py --- a/pypy/module/_hashlib/interp_hashlib.py +++ b/pypy/module/_hashlib/interp_hashlib.py @@ -76,11 +76,14 @@ except: lltype.free(ctx, flavor='raw') raise + self.register_finalizer(space) - def __del__(self): - if self.ctx: - ropenssl.EVP_MD_CTX_cleanup(self.ctx) - lltype.free(self.ctx, flavor='raw') + def _finalize_(self): + ctx = self.ctx + if ctx: + self.ctx = lltype.nullptr(ropenssl.EVP_MD_CTX.TO) + ropenssl.EVP_MD_CTX_cleanup(ctx) + lltype.free(ctx, flavor='raw') def digest_type_by_name(self, space): digest_type = ropenssl.EVP_get_digestbyname(self.name) diff --git a/pypy/module/_io/interp_bufferedio.py b/pypy/module/_io/interp_bufferedio.py --- a/pypy/module/_io/interp_bufferedio.py +++ b/pypy/module/_io/interp_bufferedio.py @@ -952,9 +952,15 @@ self.w_writer = None raise - def __del__(self): - self.clear_all_weakrefs() + def _finalize_(self): # Don't call the base __del__: do not close the files! + # Usually the _finalize_() method is not called at all because + # we set 'needs_to_finalize = False' in this class, so + # W_IOBase.__init__() won't call register_finalizer(). + # However, this method might still be called: if the user + # makes an app-level subclass and adds a custom __del__. + pass + needs_to_finalize = False # forward to reader for method in ['read', 'peek', 'read1', 'readinto', 'readable']: diff --git a/pypy/module/_io/interp_iobase.py b/pypy/module/_io/interp_iobase.py --- a/pypy/module/_io/interp_iobase.py +++ b/pypy/module/_io/interp_iobase.py @@ -59,6 +59,8 @@ self.__IOBase_closed = False if add_to_autoflusher: get_autoflusher(space).add(self) + if self.needs_to_finalize: + self.register_finalizer(space) def getdict(self, space): return self.w_dict @@ -71,13 +73,7 @@ return True return False - def __del__(self): - self.clear_all_weakrefs() - self.enqueue_for_destruction(self.space, W_IOBase.destructor, - 'internal __del__ of ') - - def destructor(self): - assert isinstance(self, W_IOBase) + def _finalize_(self): space = self.space w_closed = space.findattr(self, space.wrap('closed')) try: @@ -90,6 +86,7 @@ # equally as bad, and potentially more frequent (because of # shutdown issues). pass + needs_to_finalize = True def _CLOSED(self): # Use this macro whenever you want to check the internal `closed` diff --git a/pypy/module/_multibytecodec/app_multibytecodec.py b/pypy/module/_multibytecodec/app_multibytecodec.py --- a/pypy/module/_multibytecodec/app_multibytecodec.py +++ b/pypy/module/_multibytecodec/app_multibytecodec.py @@ -44,8 +44,10 @@ self, data)) def reset(self): - self.stream.write(MultibyteIncrementalEncoder.encode( - self, '', final=True)) + data = MultibyteIncrementalEncoder.encode( + self, '', final=True) + if len(data) > 0: + self.stream.write(data) MultibyteIncrementalEncoder.reset(self) def writelines(self, lines): diff --git a/pypy/module/_multibytecodec/interp_incremental.py b/pypy/module/_multibytecodec/interp_incremental.py --- a/pypy/module/_multibytecodec/interp_incremental.py +++ b/pypy/module/_multibytecodec/interp_incremental.py @@ -20,8 +20,9 @@ self.codec = codec.codec self.name = codec.name self._initialize() + self.register_finalizer(space) - def __del__(self): + def _finalize_(self): self._free() def reset_w(self): diff --git a/pypy/module/_multibytecodec/test/test_app_stream.py b/pypy/module/_multibytecodec/test/test_app_stream.py --- a/pypy/module/_multibytecodec/test/test_app_stream.py +++ b/pypy/module/_multibytecodec/test/test_app_stream.py @@ -90,3 +90,15 @@ w.write(u'\u304b') w.write(u'\u309a') assert w.stream.output == ['\x83m', '', '\x82\xf5'] + + def test_writer_seek_no_empty_write(self): + # issue #2293: codecs.py will sometimes issue a reset() + # on a StreamWriter attached to a file that is not opened + # for writing at all. We must not emit a "write('')"! + class FakeFile: + def write(self, data): + raise IOError("can't write!") + # + w = self.ShiftJisx0213StreamWriter(FakeFile()) + w.reset() + # assert did not crash diff --git a/pypy/module/_multiprocessing/interp_connection.py b/pypy/module/_multiprocessing/interp_connection.py --- a/pypy/module/_multiprocessing/interp_connection.py +++ b/pypy/module/_multiprocessing/interp_connection.py @@ -40,14 +40,17 @@ BUFFER_SIZE = 1024 buffer = lltype.nullptr(rffi.CCHARP.TO) - def __init__(self, flags): + def __init__(self, space, flags): self.flags = flags self.buffer = lltype.malloc(rffi.CCHARP.TO, self.BUFFER_SIZE, flavor='raw') + self.register_finalizer(space) - def __del__(self): - if self.buffer: - lltype.free(self.buffer, flavor='raw') + def _finalize_(self): + buf = self.buffer + if buf: + self.buffer = lltype.nullptr(rffi.CCHARP.TO) + lltype.free(buf, flavor='raw') try: self.do_close() except OSError: @@ -242,7 +245,7 @@ def __init__(self, space, fd, flags): if fd == self.INVALID_HANDLE_VALUE or fd < 0: raise oefmt(space.w_IOError, "invalid handle %d", fd) - W_BaseConnection.__init__(self, flags) + W_BaseConnection.__init__(self, space, flags) self.fd = fd @unwrap_spec(fd=int, readable=bool, writable=bool) @@ -363,8 +366,8 @@ if sys.platform == 'win32': from rpython.rlib.rwin32 import INVALID_HANDLE_VALUE - def __init__(self, handle, flags): - W_BaseConnection.__init__(self, flags) + def __init__(self, space, handle, flags): + W_BaseConnection.__init__(self, space, flags) self.handle = handle @unwrap_spec(readable=bool, writable=bool) @@ -375,7 +378,7 @@ flags = (readable and READABLE) | (writable and WRITABLE) self = space.allocate_instance(W_PipeConnection, w_subtype) - W_PipeConnection.__init__(self, handle, flags) + W_PipeConnection.__init__(self, space, handle, flags) return space.wrap(self) def descr_repr(self, space): diff --git a/pypy/module/_multiprocessing/interp_semaphore.py b/pypy/module/_multiprocessing/interp_semaphore.py --- a/pypy/module/_multiprocessing/interp_semaphore.py +++ b/pypy/module/_multiprocessing/interp_semaphore.py @@ -430,11 +430,12 @@ class W_SemLock(W_Root): - def __init__(self, handle, kind, maxvalue): + def __init__(self, space, handle, kind, maxvalue): self.handle = handle self.kind = kind self.count = 0 self.maxvalue = maxvalue + self.register_finalizer(space) def kind_get(self, space): return space.newint(self.kind) @@ -508,7 +509,7 @@ @unwrap_spec(kind=int, maxvalue=int) def rebuild(space, w_cls, w_handle, kind, maxvalue): self = space.allocate_instance(W_SemLock, w_cls) - self.__init__(handle_w(space, w_handle), kind, maxvalue) + self.__init__(space, handle_w(space, w_handle), kind, maxvalue) return space.wrap(self) def enter(self, space): @@ -517,7 +518,7 @@ def exit(self, space, __args__): self.release(space) - def __del__(self): + def _finalize_(self): delete_semaphore(self.handle) @unwrap_spec(kind=int, value=int, maxvalue=int) @@ -534,7 +535,7 @@ raise wrap_oserror(space, e) self = space.allocate_instance(W_SemLock, w_subtype) - self.__init__(handle, kind, maxvalue) + self.__init__(space, handle, kind, maxvalue) return space.wrap(self) diff --git a/pypy/module/_pickle_support/maker.py b/pypy/module/_pickle_support/maker.py --- a/pypy/module/_pickle_support/maker.py +++ b/pypy/module/_pickle_support/maker.py @@ -4,7 +4,7 @@ from pypy.interpreter.function import Function, Method from pypy.interpreter.module import Module from pypy.interpreter.pytraceback import PyTraceback -from pypy.interpreter.generator import GeneratorIteratorWithDel +from pypy.interpreter.generator import GeneratorIterator from rpython.rlib.objectmodel import instantiate from pypy.interpreter.gateway import unwrap_spec from pypy.objspace.std.iterobject import W_SeqIterObject, W_ReverseSeqIterObject @@ -59,7 +59,7 @@ return space.wrap(tb) def generator_new(space): - new_generator = instantiate(GeneratorIteratorWithDel) + new_generator = instantiate(GeneratorIterator) return space.wrap(new_generator) @unwrap_spec(current=int, remaining=int, step=int) diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py --- a/pypy/module/_ssl/interp_ssl.py +++ b/pypy/module/_ssl/interp_ssl.py @@ -278,6 +278,8 @@ sock_fd = space.int_w(space.call_method(w_sock, "fileno")) self.ssl = libssl_SSL_new(w_ctx.ctx) # new ssl struct + self.register_finalizer(space) + index = compute_unique_id(self) libssl_SSL_set_app_data(self.ssl, rffi.cast(rffi.VOIDP, index)) SOCKET_STORAGE.set(index, self) @@ -317,16 +319,15 @@ self.ssl_sock_weakref_w = None return self - def __del__(self): - self.enqueue_for_destruction(self.space, _SSLSocket.destructor, - '__del__() method of ') - - def destructor(self): - assert isinstance(self, _SSLSocket) - if self.peer_cert: - libssl_X509_free(self.peer_cert) - if self.ssl: - libssl_SSL_free(self.ssl) + def _finalize_(self): + peer_cert = self.peer_cert + if peer_cert: + self.peer_cert = lltype.nullptr(X509.TO) + libssl_X509_free(peer_cert) + ssl = self.ssl + if ssl: + self.ssl = lltype.nullptr(SSL.TO) + libssl_SSL_free(ssl) @unwrap_spec(data='bufferstr') def write(self, space, data): @@ -1285,6 +1286,7 @@ self = space.allocate_instance(_SSLContext, w_subtype) self.ctx = ctx self.check_hostname = False + self.register_finalizer(space) options = SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS if protocol != PY_SSL_VERSION_SSL2: options |= SSL_OP_NO_SSLv2 @@ -1308,8 +1310,11 @@ return self - def __del__(self): - libssl_SSL_CTX_free(self.ctx) + def _finalize_(self): + ctx = self.ctx + if ctx: + self.ctx = lltype.nullptr(SSL_CTX.TO) + libssl_SSL_CTX_free(ctx) @unwrap_spec(server_side=int) def descr_wrap_socket(self, space, w_sock, server_side, w_server_hostname=None, w_ssl_sock=None): diff --git a/pypy/module/_weakref/interp__weakref.py b/pypy/module/_weakref/interp__weakref.py --- a/pypy/module/_weakref/interp__weakref.py +++ b/pypy/module/_weakref/interp__weakref.py @@ -3,7 +3,8 @@ from pypy.interpreter.error import oefmt from pypy.interpreter.gateway import interp2app, ObjSpace from pypy.interpreter.typedef import TypeDef -from rpython.rlib import jit +from pypy.interpreter.executioncontext import AsyncAction, report_error +from rpython.rlib import jit, rgc from rpython.rlib.rshrinklist import AbstractShrinkList from rpython.rlib.objectmodel import specialize from rpython.rlib.rweakref import dead_ref @@ -16,9 +17,12 @@ class WeakrefLifeline(W_Root): + typedef = None + cached_weakref = None cached_proxy = None other_refs_weak = None + has_callbacks = False def __init__(self, space): self.space = space @@ -99,31 +103,10 @@ return w_ref return space.w_None - -class WeakrefLifelineWithCallbacks(WeakrefLifeline): - - def __init__(self, space, oldlifeline=None): - self.space = space - if oldlifeline is not None: - self.cached_weakref = oldlifeline.cached_weakref - self.cached_proxy = oldlifeline.cached_proxy - self.other_refs_weak = oldlifeline.other_refs_weak - - def __del__(self): - """This runs when the interp-level object goes away, and allows - its lifeline to go away. The purpose of this is to activate the - callbacks even if there is no __del__ method on the interp-level - W_Root subclass implementing the object. - """ - if self.other_refs_weak is None: - return - items = self.other_refs_weak.items() - for i in range(len(items)-1, -1, -1): - w_ref = items[i]() - if w_ref is not None and w_ref.w_callable is not None: - w_ref.enqueue_for_destruction(self.space, - W_WeakrefBase.activate_callback, - 'weakref callback of ') + def enable_callbacks(self): + if not self.has_callbacks: + self.space.finalizer_queue.register_finalizer(self) + self.has_callbacks = True @jit.dont_look_inside def make_weakref_with_callback(self, w_subtype, w_obj, w_callable): @@ -131,6 +114,7 @@ w_ref = space.allocate_instance(W_Weakref, w_subtype) W_Weakref.__init__(w_ref, space, w_obj, w_callable) self.append_wref_to(w_ref) + self.enable_callbacks() return w_ref @jit.dont_look_inside @@ -141,8 +125,33 @@ else: w_proxy = W_Proxy(space, w_obj, w_callable) self.append_wref_to(w_proxy) + self.enable_callbacks() return w_proxy + def _finalize_(self): + """This is called at the end, if enable_callbacks() was invoked. + It activates the callbacks. + """ + if self.other_refs_weak is None: + return + # + # If this is set, then we're in the 'gc.disable()' mode. In that + # case, don't invoke the callbacks now. + if self.space.user_del_action.gc_disabled(self): + return + # + items = self.other_refs_weak.items() + self.other_refs_weak = None + for i in range(len(items)-1, -1, -1): + w_ref = items[i]() + if w_ref is not None and w_ref.w_callable is not None: + try: + w_ref.activate_callback() + except Exception as e: + report_error(self.space, e, + "weakref callback ", w_ref.w_callable) + + # ____________________________________________________________ @@ -163,7 +172,6 @@ self.w_obj_weak = dead_ref def activate_callback(w_self): - assert isinstance(w_self, W_WeakrefBase) w_self.space.call_function(w_self.w_callable, w_self) def descr__repr__(self, space): @@ -227,32 +235,16 @@ w_obj.setweakref(space, lifeline) return lifeline -def getlifelinewithcallbacks(space, w_obj): - lifeline = w_obj.getweakref() - if not isinstance(lifeline, WeakrefLifelineWithCallbacks): # or None - oldlifeline = lifeline - lifeline = WeakrefLifelineWithCallbacks(space, oldlifeline) - w_obj.setweakref(space, lifeline) - return lifeline - - -def get_or_make_weakref(space, w_subtype, w_obj): - return getlifeline(space, w_obj).get_or_make_weakref(w_subtype, w_obj) - - -def make_weakref_with_callback(space, w_subtype, w_obj, w_callable): - lifeline = getlifelinewithcallbacks(space, w_obj) - return lifeline.make_weakref_with_callback(w_subtype, w_obj, w_callable) - def descr__new__weakref(space, w_subtype, w_obj, w_callable=None, __args__=None): if __args__.arguments_w: raise oefmt(space.w_TypeError, "__new__ expected at most 2 arguments") + lifeline = getlifeline(space, w_obj) if space.is_none(w_callable): - return get_or_make_weakref(space, w_subtype, w_obj) + return lifeline.get_or_make_weakref(w_subtype, w_obj) else: - return make_weakref_with_callback(space, w_subtype, w_obj, w_callable) + return lifeline.make_weakref_with_callback(w_subtype, w_obj, w_callable) W_Weakref.typedef = TypeDef("weakref", __doc__ = """A weak reference to an object 'obj'. A 'callback' can be given, @@ -308,23 +300,15 @@ return space.call_args(w_obj, __args__) -def get_or_make_proxy(space, w_obj): - return getlifeline(space, w_obj).get_or_make_proxy(w_obj) - - -def make_proxy_with_callback(space, w_obj, w_callable): - lifeline = getlifelinewithcallbacks(space, w_obj) - return lifeline.make_proxy_with_callback(w_obj, w_callable) - - def proxy(space, w_obj, w_callable=None): """Create a proxy object that weakly references 'obj'. 'callback', if given, is called with the proxy as an argument when 'obj' is about to be finalized.""" + lifeline = getlifeline(space, w_obj) if space.is_none(w_callable): - return get_or_make_proxy(space, w_obj) + return lifeline.get_or_make_proxy(w_obj) else: - return make_proxy_with_callback(space, w_obj, w_callable) + return lifeline.make_proxy_with_callback(w_obj, w_callable) def descr__new__proxy(space, w_subtype, w_obj, w_callable=None): raise oefmt(space.w_TypeError, "cannot create 'weakproxy' instances") @@ -345,7 +329,7 @@ proxy_typedef_dict = {} callable_proxy_typedef_dict = {} -special_ops = {'repr': True, 'userdel': True, 'hash': True} +special_ops = {'repr': True, 'hash': True} for opname, _, arity, special_methods in ObjSpace.MethodTable: if opname in special_ops or not special_methods: diff --git a/pypy/module/_weakref/test/test_weakref.py b/pypy/module/_weakref/test/test_weakref.py --- a/pypy/module/_weakref/test/test_weakref.py +++ b/pypy/module/_weakref/test/test_weakref.py @@ -1,6 +1,9 @@ class AppTestWeakref(object): spaceconfig = dict(usemodules=('_weakref',)) - + + def setup_class(cls): + cls.w_runappdirect = cls.space.wrap(cls.runappdirect) + def test_simple(self): import _weakref, gc class A(object): @@ -287,6 +290,9 @@ assert a1 is None def test_del_and_callback_and_id(self): + if not self.runappdirect: + skip("the id() doesn't work correctly in __del__ and " + "callbacks before translation") import gc, weakref seen_del = [] class A(object): diff --git a/pypy/module/bz2/interp_bz2.py b/pypy/module/bz2/interp_bz2.py --- a/pypy/module/bz2/interp_bz2.py +++ b/pypy/module/bz2/interp_bz2.py @@ -518,8 +518,14 @@ def __init__(self, space, compresslevel): self.space = space self.bzs = lltype.malloc(bz_stream.TO, flavor='raw', zero=True) - self.running = False - self._init_bz2comp(compresslevel) + try: + self.running = False + self._init_bz2comp(compresslevel) + except: + lltype.free(self.bzs, flavor='raw') + self.bzs = lltype.nullptr(bz_stream.TO) + raise + self.register_finalizer(space) def _init_bz2comp(self, compresslevel): if compresslevel < 1 or compresslevel > 9: @@ -532,9 +538,12 @@ self.running = True - def __del__(self): - BZ2_bzCompressEnd(self.bzs) - lltype.free(self.bzs, flavor='raw') + def _finalize_(self): + bzs = self.bzs + if bzs: + self.bzs = lltype.nullptr(bz_stream.TO) + BZ2_bzCompressEnd(bzs) + lltype.free(bzs, flavor='raw') @unwrap_spec(data='bufferstr') def compress(self, data): @@ -621,10 +630,16 @@ self.space = space self.bzs = lltype.malloc(bz_stream.TO, flavor='raw', zero=True) - self.running = False - self.unused_data = "" + try: + self.running = False + self.unused_data = "" - self._init_bz2decomp() + self._init_bz2decomp() + except: + lltype.free(self.bzs, flavor='raw') + self.bzs = lltype.nullptr(bz_stream.TO) + raise + self.register_finalizer(space) def _init_bz2decomp(self): bzerror = BZ2_bzDecompressInit(self.bzs, 0, 0) @@ -633,9 +648,12 @@ self.running = True - def __del__(self): - BZ2_bzDecompressEnd(self.bzs) - lltype.free(self.bzs, flavor='raw') + def _finalize_(self): + bzs = self.bzs + if bzs: + self.bzs = lltype.nullptr(bz_stream.TO) + BZ2_bzDecompressEnd(bzs) + lltype.free(bzs, flavor='raw') @unwrap_spec(data='bufferstr') def decompress(self, data): diff --git a/pypy/module/bz2/test/support.py b/pypy/module/bz2/test/support.py --- a/pypy/module/bz2/test/support.py +++ b/pypy/module/bz2/test/support.py @@ -10,5 +10,6 @@ # while tries and ll2ctypes.ALLOCATED: gc.collect() # to make sure we disallocate buffers + self.space.getexecutioncontext()._run_finalizers_now() tries -= 1 assert not ll2ctypes.ALLOCATED diff --git a/pypy/module/cppyy/interp_cppyy.py b/pypy/module/cppyy/interp_cppyy.py --- a/pypy/module/cppyy/interp_cppyy.py +++ b/pypy/module/cppyy/interp_cppyy.py @@ -1020,9 +1020,12 @@ class W_CPPInstance(W_Root): - _attrs_ = ['space', 'cppclass', '_rawobject', 'isref', 'python_owns'] + _attrs_ = ['space', 'cppclass', '_rawobject', 'isref', 'python_owns', + 'finalizer_registered'] _immutable_fields_ = ["cppclass", "isref"] + finalizer_registered = False + def __init__(self, space, cppclass, rawobject, isref, python_owns): self.space = space self.cppclass = cppclass @@ -1032,6 +1035,12 @@ assert not isref or not python_owns self.isref = isref self.python_owns = python_owns + self._opt_register_finalizer() + + def _opt_register_finalizer(self): + if self.python_owns and not self.finalizer_registered: + self.register_finalizer(self.space) + self.finalizer_registered = True def _nullcheck(self): if not self._rawobject or (self.isref and not self.get_rawobject()): @@ -1045,6 +1054,7 @@ @unwrap_spec(value=bool) def fset_python_owns(self, space, value): self.python_owns = space.is_true(value) + self._opt_register_finalizer() def get_cppthis(self, calling_scope): return self.cppclass.get_cppthis(self, calling_scope) @@ -1143,16 +1153,14 @@ (self.cppclass.name, rffi.cast(rffi.ULONG, self.get_rawobject()))) def destruct(self): - assert isinstance(self, W_CPPInstance) if self._rawobject and not self.isref: memory_regulator.unregister(self) capi.c_destruct(self.space, self.cppclass, self._rawobject) self._rawobject = capi.C_NULL_OBJECT - def __del__(self): + def _finalize_(self): if self.python_owns: - self.enqueue_for_destruction(self.space, W_CPPInstance.destruct, - '__del__() method of ') + self.destruct() W_CPPInstance.typedef = TypeDef( 'CPPInstance', diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py --- a/pypy/module/cpyext/slotdefs.py +++ b/pypy/module/cpyext/slotdefs.py @@ -374,7 +374,75 @@ header = pypy_decl if mangle_name('', typedef.name) is None: header = None - if name == 'tp_setattro': + handled = False + # unary functions + for tp_name, attr in [('tp_as_number.c_nb_int', '__int__'), + ('tp_as_number.c_nb_long', '__long__'), + ('tp_as_number.c_nb_float', '__float__'), + ('tp_as_number.c_nb_negative', '__neg__'), + ('tp_as_number.c_nb_positive', '__pos__'), + ('tp_as_number.c_nb_absolute', '__abs__'), + ('tp_as_number.c_nb_invert', '__invert__'), + ('tp_as_number.c_nb_index', '__index__'), + ('tp_str', '__str__'), + ('tp_repr', '__repr__'), + ('tp_iter', '__iter__'), + ]: + if name == tp_name: + slot_fn = w_type.getdictvalue(space, attr) + if slot_fn is None: + return + + @cpython_api([PyObject], PyObject, header=header) + @func_renamer("cpyext_%s_%s" % (name.replace('.', '_'), typedef.name)) + def slot_func(space, w_self): + return space.call_function(slot_fn, w_self) + api_func = slot_func.api_func + handled = True + + # binary functions + for tp_name, attr in [('tp_as_number.c_nb_add', '__add__'), + ('tp_as_number.c_nb_subtract', '__subtract__'), + ('tp_as_number.c_nb_multiply', '__mul__'), + ('tp_as_number.c_nb_divide', '__div__'), + ('tp_as_number.c_nb_remainder', '__mod__'), + ('tp_as_number.c_nb_divmod', '__divmod__'), + ('tp_as_number.c_nb_lshift', '__lshift__'), + ('tp_as_number.c_nb_rshift', '__rshift__'), + ('tp_as_number.c_nb_and', '__and__'), + ('tp_as_number.c_nb_xor', '__xor__'), + ('tp_as_number.c_nb_or', '__or__'), + ]: + if name == tp_name: + slot_fn = w_type.getdictvalue(space, attr) + if slot_fn is None: + return + + @cpython_api([PyObject, PyObject], PyObject, header=header) + @func_renamer("cpyext_%s_%s" % (name.replace('.', '_'), typedef.name)) + def slot_func(space, w_self, w_arg): + return space.call_function(slot_fn, w_self, w_arg) + api_func = slot_func.api_func + handled = True + + # ternary functions + for tp_name, attr in [('tp_as_number.c_nb_power', ''), + ]: + if name == tp_name: + slot_fn = w_type.getdictvalue(space, attr) + if slot_fn is None: + return + + @cpython_api([PyObject, PyObject, PyObject], PyObject, header=header) + @func_renamer("cpyext_%s_%s" % (name.replace('.', '_'), typedef.name)) + def slot_func(space, w_self, w_arg1, w_arg2): + return space.call_function(slot_fn, w_self, w_arg1, w_arg2) + api_func = slot_func.api_func + handled = True + + if handled: + pass + elif name == 'tp_setattro': setattr_fn = w_type.getdictvalue(space, '__setattr__') delattr_fn = w_type.getdictvalue(space, '__delattr__') if setattr_fn is None: @@ -401,28 +469,6 @@ return space.call_function(getattr_fn, w_self, w_name) api_func = slot_tp_getattro.api_func - elif name == 'tp_as_number.c_nb_int': - int_fn = w_type.getdictvalue(space, '__int__') - if int_fn is None: - return - - @cpython_api([PyObject], PyObject, header=header) - @func_renamer("cpyext_%s_%s" % (name.replace('.', '_'), typedef.name)) - def slot_nb_int(space, w_self): - return space.call_function(int_fn, w_self) - api_func = slot_nb_int.api_func - - elif name == 'tp_as_number.c_nb_float': - float_fn = w_type.getdictvalue(space, '__float__') - if float_fn is None: - return - - @cpython_api([PyObject], PyObject, header=header) - @func_renamer("cpyext_%s_%s" % (name.replace('.', '_'), typedef.name)) - def slot_nb_float(space, w_self): - return space.call_function(float_fn, w_self) - api_func = slot_nb_float.api_func - elif name == 'tp_call': call_fn = w_type.getdictvalue(space, '__call__') if call_fn is None: @@ -436,28 +482,6 @@ return space.call_args(call_fn, args) api_func = slot_tp_call.api_func - elif name == 'tp_str': - str_fn = w_type.getdictvalue(space, '__str__') - if str_fn is None: - return - - @cpython_api([PyObject], PyObject, header=header) - @func_renamer("cpyext_%s_%s" % (name.replace('.', '_'), typedef.name)) - def slot_tp_str(space, w_self): - return space.call_function(str_fn, w_self) - api_func = slot_tp_str.api_func - - elif name == 'tp_iter': - iter_fn = w_type.getdictvalue(space, '__iter__') - if iter_fn is None: - return - - @cpython_api([PyObject], PyObject, header=header) - @func_renamer("cpyext_%s_%s" % (name.replace('.', '_'), typedef.name)) - def slot_tp_iter(space, w_self): - return space.call_function(iter_fn, w_self) - api_func = slot_tp_iter.api_func - elif name == 'tp_iternext': iternext_fn = w_type.getdictvalue(space, 'next') if iternext_fn is None: @@ -501,6 +525,7 @@ return space.call_args(space.get(new_fn, w_self), args) api_func = slot_tp_new.api_func else: + # missing: tp_as_number.nb_nonzero, tp_as_number.nb_coerce return return lambda: llhelper(api_func.functype, api_func.get_wrapper(space)) diff --git a/pypy/module/cpyext/src/abstract.c b/pypy/module/cpyext/src/abstract.c --- a/pypy/module/cpyext/src/abstract.c +++ b/pypy/module/cpyext/src/abstract.c @@ -326,3 +326,9 @@ return tmp; } +/* for binary compatibility with 5.1 */ +PyAPI_FUNC(void) PyPyObject_Del(PyObject *); +void PyPyObject_Del(PyObject *op) +{ + PyObject_FREE(op); +} diff --git a/pypy/module/cpyext/test/test_api.py b/pypy/module/cpyext/test/test_api.py --- a/pypy/module/cpyext/test/test_api.py +++ b/pypy/module/cpyext/test/test_api.py @@ -1,4 +1,4 @@ -import py +import py, pytest from rpython.rtyper.lltypesystem import rffi, lltype from pypy.interpreter.baseobjspace import W_Root from pypy.module.cpyext.state import State @@ -100,7 +100,8 @@ PyPy_TypedefTest2(space, ppos) lltype.free(ppos, flavor='raw') - +@pytest.mark.skipif(os.environ.get('USER')=='root', + reason='root can write to all files') def test_copy_header_files(tmpdir): api.copy_header_files(tmpdir, True) def check(name): diff --git a/pypy/module/cpyext/test/test_bytesobject.py b/pypy/module/cpyext/test/test_bytesobject.py --- a/pypy/module/cpyext/test/test_bytesobject.py +++ b/pypy/module/cpyext/test/test_bytesobject.py @@ -40,7 +40,7 @@ #endif if(s->ob_type->tp_basicsize != expected_size) { - printf("tp_basicsize==%ld\\n", s->ob_type->tp_basicsize); + printf("tp_basicsize==%zd\\n", s->ob_type->tp_basicsize); result = 0; } Py_DECREF(s); diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py --- a/pypy/module/cpyext/test/test_typeobject.py +++ b/pypy/module/cpyext/test/test_typeobject.py @@ -921,3 +921,105 @@ ' multiple bases have instance lay-out conflict') else: raise AssertionError("did not get TypeError!") + + def test_call_tp_dealloc_when_created_from_python(self): + module = self.import_extension('foo', [ + ("fetchFooType", "METH_VARARGS", + """ + PyObject *o; + Foo_Type.tp_basicsize = sizeof(FooObject); + Foo_Type.tp_dealloc = &dealloc_foo; + Foo_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES + | Py_TPFLAGS_BASETYPE; + Foo_Type.tp_new = &new_foo; + Foo_Type.tp_free = &PyObject_Del; + if (PyType_Ready(&Foo_Type) < 0) return NULL; + + o = PyObject_New(PyObject, &Foo_Type); + init_foo(o); + Py_DECREF(o); /* calls dealloc_foo immediately */ + + Py_INCREF(&Foo_Type); + return (PyObject *)&Foo_Type; + """), + ("newInstance", "METH_O", + """ + PyTypeObject *tp = (PyTypeObject *)args; + PyObject *e = PyTuple_New(0); + PyObject *o = tp->tp_new(tp, e, NULL); _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit