Author: Ronan Lamy <ronan.l...@gmail.com> Branch: py3k Changeset: r84408:7f5d673727e3 Date: 2016-05-12 21:01 +0100 http://bitbucket.org/pypy/pypy/changeset/7f5d673727e3/
Log: hg merge default diff too long, truncating to 2000 out of 6936 lines diff --git a/dotviewer/graphserver.py b/dotviewer/graphserver.py --- a/dotviewer/graphserver.py +++ b/dotviewer/graphserver.py @@ -143,6 +143,11 @@ if __name__ == '__main__': if len(sys.argv) != 2: + if len(sys.argv) == 1: + # start locally + import sshgraphserver + sshgraphserver.ssh_graph_server(['LOCAL']) + sys.exit(0) print >> sys.stderr, __doc__ sys.exit(2) if sys.argv[1] == '--stdio': diff --git a/dotviewer/sshgraphserver.py b/dotviewer/sshgraphserver.py --- a/dotviewer/sshgraphserver.py +++ b/dotviewer/sshgraphserver.py @@ -4,11 +4,14 @@ Usage: sshgraphserver.py hostname [more args for ssh...] + sshgraphserver.py LOCAL This logs in to 'hostname' by passing the arguments on the command-line to ssh. No further configuration is required: it works for all programs using the dotviewer library as long as they run on 'hostname' under the same username as the one sshgraphserver logs as. + +If 'hostname' is the string 'LOCAL', then it starts locally without ssh. """ import graphserver, socket, subprocess, random @@ -18,12 +21,19 @@ s1 = socket.socket() s1.bind(('127.0.0.1', socket.INADDR_ANY)) localhost, localport = s1.getsockname() - remoteport = random.randrange(10000, 20000) - # ^^^ and just hope there is no conflict - args = ['ssh', '-S', 'none', '-C', '-R%d:127.0.0.1:%d' % (remoteport, localport)] - args = args + sshargs + ['python -u -c "exec input()"'] - print ' '.join(args[:-1]) + if sshargs[0] != 'LOCAL': + remoteport = random.randrange(10000, 20000) + # ^^^ and just hope there is no conflict + + args = ['ssh', '-S', 'none', '-C', '-R%d:127.0.0.1:%d' % ( + remoteport, localport)] + args = args + sshargs + ['python -u -c "exec input()"'] + else: + remoteport = localport + args = ['python', '-u', '-c', 'exec input()'] + + print ' '.join(args) p = subprocess.Popen(args, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE) diff --git a/lib-python/2.7/test/test_descr.py b/lib-python/2.7/test/test_descr.py --- a/lib-python/2.7/test/test_descr.py +++ b/lib-python/2.7/test/test_descr.py @@ -1735,7 +1735,6 @@ ("__reversed__", reversed, empty_seq, set(), {}), ("__length_hint__", list, zero, set(), {"__iter__" : iden, "next" : stop}), - ("__sizeof__", sys.getsizeof, zero, set(), {}), ("__instancecheck__", do_isinstance, return_true, set(), {}), ("__missing__", do_dict_missing, some_number, set(("__class__",)), {}), @@ -1747,6 +1746,8 @@ ("__format__", format, format_impl, set(), {}), ("__dir__", dir, empty_seq, set(), {}), ] + if test_support.check_impl_detail(): + specials.append(("__sizeof__", sys.getsizeof, zero, set(), {})) class Checker(object): def __getattr__(self, attr, test=self): @@ -1768,10 +1769,6 @@ raise MyException for name, runner, meth_impl, ok, env in specials: - if name == '__length_hint__' or name == '__sizeof__': - if not test_support.check_impl_detail(): - continue - class X(Checker): pass for attr, obj in env.iteritems(): 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 @@ -1,19 +1,127 @@ -.. XXX armin, what do we do with this? +Ordering finalizers in the MiniMark GC +====================================== -Ordering finalizers in the SemiSpace GC -======================================= +RPython interface +----------------- -Goal ----- +In RPython programs like PyPy, we need a fine-grained method of +controlling the RPython- as well as the app-level ``__del__()``. To +make it possible, the RPython interface is now the following one (from +May 2016): -After a collection, the SemiSpace GC should call the finalizers on +* RPython objects can have ``__del__()``. These are called + immediately by the GC when the last reference to the object goes + away, like in CPython. However, the long-term goal is that all + ``__del__()`` methods should only contain simple enough code. If + they do, we call them "destructors". They can't use operations that + would resurrect the object, for example. Use the decorator + ``@rgc.must_be_light_finalizer`` to ensure they are destructors. + +* RPython-level ``__del__()`` that are not passing the destructor test + are supported for backward compatibility, but deprecated. The rest + of this document assumes that ``__del__()`` are all destructors. + +* For any more advanced usage --- in particular for any app-level + object with a __del__ --- we don't use the RPython-level + ``__del__()`` method. Instead we use + ``rgc.FinalizerController.register_finalizer()``. This allows us to + attach a finalizer method to the object, giving more control over + the ordering than just an RPython ``__del__()``. + +We try to consistently call ``__del__()`` a destructor, to distinguish +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, and the GC frees it +immediately. + + +Destructors +----------- + +A destructor is an RPython ``__del__()`` method that is called directly +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. +Right now you can't call any external C function either. + +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 +------------------ + +The interface for full finalizers is made with PyPy in mind, but should +be generally useful. + +The idea is that you subclass the ``rgc.FinalizerQueue`` class:: + +* You must give a class-level attribute ``base_class``, which is the + base class of all instances with a finalizer. (If you need + finalizers on several unrelated classes, you need several unrelated + ``FinalizerQueue`` subclasses.) + +* You override the ``finalizer_trigger()`` method; see below. + +Then you create one global (or space-specific) instance of this +subclass; call it ``fin``. At runtime, you call +``fin.register_finalizer(obj)`` for every instance ``obj`` that needs +a finalizer. Each ``obj`` must be an instance of ``fin.base_class``, +but not every such instance needs to have a finalizer registered; +typically we try to register a finalizer on as few objects as possible +(e.g. only if it is an object which has an app-level ``__del__()`` +method). + +After a major collection, the GC finds all objects ``obj`` on which a +finalizer was registered and which are unreachable, and mark them as +reachable again, as well as all objects they depend on. It then picks +a topological ordering (breaking cycles randomly, if any) and enqueues +the objects and their registered finalizer functions in that order, in +a queue specific to the prebuilt ``fin`` instance. Finally, when the +major collection is done, it calls ``fin.finalizer_trigger()``. + +This method ``finalizer_trigger()`` can either do some work directly, +or delay it to be done later (e.g. between two bytecodes). If it does +work directly, note that it cannot (directly or indirectly) cause the +GIL to be released. + +To find the queued items, call ``fin.next_dead()`` repeatedly. It +returns the next queued item, or ``None`` when the queue is empty. + +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 +---------------------- + +After a collection, the MiniMark GC should call the finalizers on *some* of the objects that have one and that have become unreachable. Basically, if there is a reference chain from an object a to an object b then it should not call the finalizer for b immediately, but just keep b alive and try again to call its finalizer after the next collection. -This basic idea fails when there are cycles. It's not a good idea to +(Note that this creates rare but annoying issues as soon as the program +creates chains of objects with finalizers more quickly than the rate at +which major collections go (which is very slow). In August 2013 we tried +instead to call all finalizers of all objects found unreachable at a major +collection. That branch, ``gc-del``, was never merged. It is still +unclear what the real consequences would be on programs in the wild.) + +The basic idea fails in the presence of cycles. It's not a good idea to keep the objects alive forever or to never call any of the finalizers. The model we came up with is that in this case, we could just call the finalizer of one of the objects in the cycle -- but only, of course, if @@ -33,6 +141,7 @@ detach the finalizer (so that it's not called more than once) call the finalizer + Algorithm --------- @@ -136,28 +245,8 @@ that doesn't change the state of an object, we don't follow its children recursively. -In practice, in the SemiSpace, Generation and Hybrid GCs, we can encode -the 4 states with a single extra bit in the header: - - ===== ============= ======== ==================== - state is_forwarded? bit set? bit set in the copy? - ===== ============= ======== ==================== - 0 no no n/a - 1 no yes n/a - 2 yes yes yes - 3 yes whatever no - ===== ============= ======== ==================== - -So the loop above that does the transition from state 1 to state 2 is -really just a copy(x) followed by scan_copied(). We must also clear the -bit in the copy at the end, to clean up before the next collection -(which means recursively bumping the state from 2 to 3 in the final -loop). - -In the MiniMark GC, the objects don't move (apart from when they are -copied out of the nursery), but we use the flag GCFLAG_VISITED to mark -objects that survive, so we can also have a single extra bit for -finalizers: +In practice, in the MiniMark GCs, we can encode +the 4 states with a combination of two bits in the header: ===== ============== ============================ state GCFLAG_VISITED GCFLAG_FINALIZATION_ORDERING @@ -167,3 +256,8 @@ 2 yes yes 3 yes no ===== ============== ============================ + +So the loop above that does the transition from state 1 to state 2 is +really just a recursive visit. We must also clear the +FINALIZATION_ORDERING bit at the end (state 2 to state 3) to clean up +before the next collection. 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 @@ -61,3 +61,35 @@ calls PyXxx", we now silently acquire/release the GIL. Helps with CPython C extension modules that call some PyXxx() functions without holding the GIL (arguably, they are theorically buggy). + +.. branch: cpyext-test-A + +Get the cpyext tests to pass with "-A" (i.e. when tested directly with +CPython). + +.. branch: oefmt + +.. branch: cpyext-werror + +Compile c snippets with -Werror in cpyext + +.. branch: gc-del-3 + +Add rgc.FinalizerQueue, documented in pypy/doc/discussion/finalizer-order.rst. +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.) + +.. branch: ufunc-outer + +Implement ufunc.outer on numpypy diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -12,7 +12,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 @@ -52,6 +52,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): @@ -159,9 +160,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: @@ -174,25 +174,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): @@ -396,7 +408,7 @@ self.interned_strings = make_weak_value_dictionary(self, unicode, 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 @@ -1877,7 +1889,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 @@ -1,7 +1,7 @@ import sys from pypy.interpreter.error import OperationError, get_cleared_operation_error from rpython.rlib.unroll import unrolling_iterable -from rpython.rlib import jit +from rpython.rlib import jit, rgc TICK_COUNTER_STEP = 100 @@ -140,6 +140,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 @@ -455,6 +461,13 @@ list = self.fired_actions if list is not None: self.fired_actions = None + # NB. in case there are several actions, we reset each + # 'action._fired' to false only when we're about to call + # 'action.perform()'. This means that if + # 'action.fire()' happens to be called any time before + # the corresponding perform(), the fire() has no + # effect---which is the effect we want, because + # perform() will be called anyway. for action in list: action._fired = False action.perform(ec, frame) @@ -510,79 +523,100 @@ """ -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 - self._invoke_immediately = False - - 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 - if not self._invoke_immediately: - self.fire() - else: - self.perform(None, None) + 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: @@ -214,7 +217,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, @@ -287,25 +289,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_argument.py b/pypy/interpreter/test/test_argument.py --- a/pypy/interpreter/test/test_argument.py +++ b/pypy/interpreter/test/test_argument.py @@ -710,3 +710,20 @@ assert e.value.args[0] == "f() got an unexpected keyword argument 'ü'" """ + def test_starstarargs_dict_subclass(self): + def f(**kwargs): + return kwargs + class DictSubclass(dict): + def __iter__(self): + yield 'x' + # CPython, as an optimization, looks directly into dict internals when + # passing one via **kwargs. + x =DictSubclass() + assert f(**x) == {} + x['a'] = 1 + assert f(**x) == {'a': 1} + + def test_starstarargs_module_dict(self): + def f(**kwargs): + return kwargs + assert f(**globals()) == globals() 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 @@ -129,10 +129,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 @@ -188,35 +185,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)], @@ -238,29 +220,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.get('__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.objspace.std.mapdict import (BaseUserClassMapdict, @@ -131,11 +127,10 @@ typedef = cls.typedef name = cls.__name__ + "User" - mixins_needed = [] if cls is W_ObjectObject: - 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 @@ -146,44 +141,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/_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 bool(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 @@ -260,7 +260,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/_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 @@ -955,9 +955,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 @@ -60,6 +60,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 @@ -72,13 +74,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: @@ -94,6 +90,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 @@ -41,7 +41,7 @@ return res # r = self.HzStreamReader(FakeFile(b"!~{abcd~}xyz~{efgh")) - for expected in '!\u5f95\u6c85xyz\u5f50\u73b7': + for expected in u'!\u5f95\u6c85xyz\u5f50\u73b7': c = r.read(1) assert c == expected c = r.read(1) @@ -56,13 +56,13 @@ # r = self.HzStreamReader(FakeFile(b"!~{a"), "replace") c = r.read() - assert c == '!\ufffd' + assert c == u'!\ufffd' # r = self.HzStreamReader(FakeFile(b"!~{a")) r.errors = "replace" assert r.errors == "replace" c = r.read() - assert c == '!\ufffd' + assert c == u'!\ufffd' def test_writer(self): class FakeFile: @@ -72,7 +72,7 @@ self.output.append(data) # w = self.HzStreamWriter(FakeFile()) - for input in '!\u5f95\u6c85xyz\u5f50\u73b7': + for input in u'!\u5f95\u6c85xyz\u5f50\u73b7': w.write(input) w.reset() assert w.stream.output == [b'!', b'~{ab', b'cd', b'~}x', b'y', b'z', @@ -86,7 +86,19 @@ self.output.append(data) # w = self.ShiftJisx0213StreamWriter(FakeFile()) - w.write('\u30ce') - w.write('\u304b') - w.write('\u309a') + w.write(u'\u30ce') + w.write(u'\u304b') + w.write(u'\u309a') assert w.stream.output == [b'\x83m', b'', b'\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: @@ -243,7 +246,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) @@ -364,8 +367,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) @@ -376,7 +379,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) def longrangeiter_new(space, w_start, w_step, w_len, w_index): 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 @@ -108,6 +108,7 @@ constants["_OPENSSL_API_VERSION"] = version_info constants["OPENSSL_VERSION"] = SSLEAY_VERSION + def ssl_error(space, msg, errno=0, w_errtype=None, errcode=0): reason_str = None lib_str = None @@ -136,7 +137,6 @@ space.wrap(lib_str) if lib_str else space.w_None) return OperationError(w_exception_class, w_exception) - class SSLNpnProtocols(object): def __init__(self, ctx, protos): @@ -312,12 +312,17 @@ self.peer_cert = lltype.nullptr(X509.TO) self.shutdown_seen_zero = False self.handshake_done = False + self.register_finalizer(space) - def __del__(self): - 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): @@ -932,6 +937,7 @@ return space.newtuple([w_name, w_value]) + def _get_aia_uri(space, certificate, nid): info = rffi.cast(AUTHORITY_INFO_ACCESS, libssl_X509_get_ext_d2i( certificate, NID_info_access, None, None)) @@ -1314,6 +1320,7 @@ rgc.add_memory_pressure(10 * 1024 * 1024) self.check_hostname = False + self.register_finalizer(space) # Defaults libssl_SSL_CTX_set_verify(self.ctx, SSL_VERIFY_NONE, None) @@ -1339,9 +1346,11 @@ finally: libssl_EC_KEY_free(key) - def __del__(self): - if self.ctx: - 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) @staticmethod @unwrap_spec(protocol=int) @@ -1360,9 +1369,6 @@ libssl_ERR_clear_error() raise ssl_error(space, "No cipher can be selected.") - def __del__(self): - libssl_SSL_CTX_free(self.ctx) - @unwrap_spec(server_side=int) def wrap_socket_w(self, space, w_sock, server_side, w_server_hostname=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): @@ -289,6 +292,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/_winreg/interp_winreg.py b/pypy/module/_winreg/interp_winreg.py --- a/pypy/module/_winreg/interp_winreg.py +++ b/pypy/module/_winreg/interp_winreg.py @@ -14,11 +14,13 @@ space.wrap(message)])) class W_HKEY(W_Root): - def __init__(self, hkey): + def __init__(self, space, hkey): self.hkey = hkey + self.space = space + self.register_finalizer(space) - def descr_del(self, space): - self.Close(space) + def _finalize_(self): + self.Close(self.space) def as_int(self): return rffi.cast(rffi.SIZE_T, self.hkey) @@ -64,7 +66,7 @@ @unwrap_spec(key=int) def new_HKEY(space, w_subtype, key): hkey = rffi.cast(rwinreg.HKEY, key) - return space.wrap(W_HKEY(hkey)) + return space.wrap(W_HKEY(space, hkey)) descr_HKEY_new = interp2app(new_HKEY) W_HKEY.typedef = TypeDef( @@ -91,7 +93,6 @@ __int__ - Converting a handle to an integer returns the Win32 handle. __cmp__ - Handle objects are compared using the handle value.""", __new__ = descr_HKEY_new, - __del__ = interp2app(W_HKEY.descr_del), __repr__ = interp2app(W_HKEY.descr_repr), __int__ = interp2app(W_HKEY.descr_int), __bool__ = interp2app(W_HKEY.descr_bool), @@ -478,7 +479,7 @@ ret = rwinreg.RegCreateKey(hkey, subkey, rethkey) if ret != 0: raiseWindowsError(space, ret, 'CreateKey') - return space.wrap(W_HKEY(rethkey[0])) + return space.wrap(W_HKEY(space, rethkey[0])) @unwrap_spec(sub_key=str, reserved=int, access=rffi.r_uint) def CreateKeyEx(space, w_key, sub_key, reserved=0, access=rwinreg.KEY_WRITE): @@ -500,7 +501,7 @@ lltype.nullptr(rwin32.LPDWORD.TO)) if ret != 0: raiseWindowsError(space, ret, 'CreateKeyEx') - return space.wrap(W_HKEY(rethkey[0])) + return space.wrap(W_HKEY(space, rethkey[0])) @unwrap_spec(subkey=str) def DeleteKey(space, w_hkey, subkey): @@ -547,7 +548,7 @@ ret = rwinreg.RegOpenKeyEx(hkey, sub_key, reserved, access, rethkey) if ret != 0: raiseWindowsError(space, ret, 'RegOpenKeyEx') - return space.wrap(W_HKEY(rethkey[0])) + return space.wrap(W_HKEY(space, rethkey[0])) @unwrap_spec(index=int) def EnumValue(space, w_hkey, index): @@ -686,7 +687,7 @@ ret = rwinreg.RegConnectRegistry(machine, hkey, rethkey) if ret != 0: raiseWindowsError(space, ret, 'RegConnectRegistry') - return space.wrap(W_HKEY(rethkey[0])) + return space.wrap(W_HKEY(space, rethkey[0])) @unwrap_spec(source=unicode) def ExpandEnvironmentStrings(space, source): 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 @@ -160,7 +160,7 @@ raise oefmt(space.w_SystemError, "the bz2 library has received wrong parameters") elif bzerror == BZ_MEM_ERROR: - raise OperationError(space.w_MemoryError, space.wrap("")) + raise OperationError(space.w_MemoryError, space.w_None) elif bzerror in (BZ_DATA_ERROR, BZ_DATA_ERROR_MAGIC): raise oefmt(space.w_IOError, "invalid data stream") elif bzerror == BZ_IO_ERROR: @@ -253,8 +253,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: @@ -267,9 +273,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') def descr_getstate(self): raise oefmt(self.space.w_TypeError, "cannot serialize '%T' object", self) @@ -360,10 +369,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) @@ -372,9 +387,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') def descr_getstate(self): raise oefmt(self.space.w_TypeError, "cannot serialize '%T' object", self) 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 _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit