[pypy-commit] pypy default: hg merge rlock-in-rpython

arigo pypy.commits at gmail.com
Fri Oct 26 12:43:42 EDT 2018


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r95255:55e3c71a37fa
Date: 2018-10-26 18:42 +0200
http://bitbucket.org/pypy/pypy/changeset/55e3c71a37fa/

Log:	hg merge rlock-in-rpython

diff --git a/lib-python/2.7/threading.py b/lib-python/2.7/threading.py
--- a/lib-python/2.7/threading.py
+++ b/lib-python/2.7/threading.py
@@ -36,6 +36,10 @@
 _allocate_lock = thread.allocate_lock
 _get_ident = thread.get_ident
 ThreadError = thread.error
+try:
+    _CRLock = thread.RLock
+except AttributeError:
+    _CRLock = None
 del thread
 
 
@@ -120,7 +124,9 @@
     acquired it.
 
     """
-    return _RLock(*args, **kwargs)
+    if _CRLock is None or args or kwargs:
+        return _PyRLock(*args, **kwargs)
+    return _CRLock(_active)
 
 class _RLock(_Verbose):
     """A reentrant lock must be released by the thread that acquired it. Once a
@@ -238,6 +244,8 @@
     def _is_owned(self):
         return self.__owner == _get_ident()
 
+_PyRLock = _RLock
+
 
 def Condition(*args, **kwargs):
     """Factory function that returns a new condition variable object.
diff --git a/pypy/module/thread/__init__.py b/pypy/module/thread/__init__.py
--- a/pypy/module/thread/__init__.py
+++ b/pypy/module/thread/__init__.py
@@ -18,6 +18,7 @@
         'allocate_lock':          'os_lock.allocate_lock',
         'allocate':               'os_lock.allocate_lock',  # obsolete synonym
         'LockType':               'os_lock.Lock',
+        'RLock':                  'os_lock.W_RLock',   # pypy only, issue #2905
         '_local':                 'os_local.Local',
         'error':                  'space.fromcache(error.Cache).w_error',
     }
diff --git a/pypy/module/thread/os_lock.py b/pypy/module/thread/os_lock.py
--- a/pypy/module/thread/os_lock.py
+++ b/pypy/module/thread/os_lock.py
@@ -8,8 +8,8 @@
 from pypy.interpreter.baseobjspace import W_Root
 from pypy.interpreter.gateway import interp2app, unwrap_spec
 from pypy.interpreter.typedef import TypeDef, make_weakref_descr
-from pypy.interpreter.error import oefmt
-from rpython.rlib.rarithmetic import r_longlong, ovfcheck_float_to_longlong
+from pypy.interpreter.error import OperationError, oefmt
+from rpython.rlib.rarithmetic import r_longlong, ovfcheck, ovfcheck_float_to_longlong
 
 
 RPY_LOCK_FAILURE, RPY_LOCK_ACQUIRED, RPY_LOCK_INTR = range(3)
@@ -53,6 +53,12 @@
             break
     return result
 
+def try_release(space, lock):
+    try:
+        lock.release()
+    except rthread.error:
+        raise wrap_thread_error(space, "release unlocked lock")
+
 
 class Lock(W_Root):
     "A box around an interp-level lock object."
@@ -97,10 +103,7 @@
         """Release the lock, allowing another thread that is blocked waiting for
 the lock to acquire the lock.  The lock must be in the locked state,
 but it needn't be locked by the same thread that unlocks it."""
-        try:
-            self.lock.release()
-        except rthread.error:
-            raise wrap_thread_error(space, "release unlocked lock")
+        try_release(space, self.lock)
 
     def descr_lock_locked(self, space):
         """Return whether the lock is in the locked state."""
@@ -162,3 +165,149 @@
     """Create a new lock object.  (allocate() is an obsolete synonym.)
 See LockType.__doc__ for information about locks."""
     return Lock(space)
+
+class W_RLock(W_Root):
+    # Does not exist in CPython 2.x. Back-ported from PyPy3. See issue #2905
+
+    def __init__(self, space, w_active=None):
+        self.rlock_count = 0
+        self.rlock_owner = 0
+        self.w_active = w_active    # dictionary 'threading._active'
+        try:
+            self.lock = rthread.allocate_lock()
+        except rthread.error:
+            raise wrap_thread_error(space, "cannot allocate lock")
+
+    def descr__new__(space, w_subtype, w_active=None):
+        self = space.allocate_instance(W_RLock, w_subtype)
+        W_RLock.__init__(self, space, w_active)
+        return self
+
+    def descr__repr__(self, space):
+        w_type = space.type(self)
+        classname = w_type.name
+        if self.rlock_owner == 0:
+            owner = "None"
+        else:
+            owner = str(self.rlock_owner)
+            if self.w_active is not None:
+                try:
+                    w_owner = space.getitem(self.w_active,
+                                                space.newint(self.rlock_owner))
+                    w_name = space.getattr(w_owner, space.newtext('name'))
+                    owner = space.str_w(space.repr(w_name))
+                except OperationError as e:
+                    if e.async(space):
+                        raise
+        return space.newtext("<%s owner=%s count=%d>" % (
+            classname, owner, self.rlock_count))
+
+    @unwrap_spec(blocking=int)
+    def acquire_w(self, space, blocking=1):
+        """Acquire a lock, blocking or non-blocking.
+
+        When invoked without arguments: if this thread already owns the lock,
+        increment the recursion level by one, and return immediately. Otherwise,
+        if another thread owns the lock, block until the lock is unlocked. Once
+        the lock is unlocked (not owned by any thread), then grab ownership, set
+        the recursion level to one, and return. If more than one thread is
+        blocked waiting until the lock is unlocked, only one at a time will be
+        able to grab ownership of the lock. There is no return value in this
+        case.
+
+        When invoked with the blocking argument set to true, do the same thing
+        as when called without arguments, and return true.
+
+        When invoked with the blocking argument set to false, do not block. If a
+        call without an argument would block, return false immediately;
+        otherwise, do the same thing as when called without arguments, and
+        return true.
+
+        """
+        tid = rthread.get_ident()
+        if tid == self.rlock_owner:
+            try:
+                self.rlock_count = ovfcheck(self.rlock_count + 1)
+            except OverflowError:
+                raise oefmt(space.w_OverflowError,
+                            "internal lock count overflowed")
+            return space.w_True
+
+        rc = self.lock.acquire(blocking != 0)
+        if rc:
+            self.rlock_owner = tid
+            self.rlock_count = 1
+        return space.newbool(rc)
+
+    def release_w(self, space):
+        """Release a lock, decrementing the recursion level.
+
+        If after the decrement it is zero, reset the lock to unlocked (not owned
+        by any thread), and if any other threads are blocked waiting for the
+        lock to become unlocked, allow exactly one of them to proceed. If after
+        the decrement the recursion level is still nonzero, the lock remains
+        locked and owned by the calling thread.
+
+        Only call this method when the calling thread owns the lock. A
+        RuntimeError is raised if this method is called when the lock is
+        unlocked.
+
+        There is no return value.
+
+        """
+        if self.rlock_owner != rthread.get_ident():
+            raise oefmt(space.w_RuntimeError,
+                        "cannot release un-acquired lock")
+        self.rlock_count -= 1
+        if self.rlock_count == 0:
+            self.rlock_owner = 0
+            try_release(space, self.lock)
+
+    def is_owned_w(self, space):
+        """For internal use by `threading.Condition`."""
+        return space.newbool(self.rlock_owner == rthread.get_ident())
+
+    def acquire_restore_w(self, space, w_count_owner):
+        """For internal use by `threading.Condition`."""
+        # saved_state is the value returned by release_save()
+        w_count, w_owner = space.unpackiterable(w_count_owner, 2)
+        count = space.int_w(w_count)
+        owner = space.int_w(w_owner)
+        self.lock.acquire(True)
+        self.rlock_count = count
+        self.rlock_owner = owner
+
+    def release_save_w(self, space):
+        """For internal use by `threading.Condition`."""
+        if self.rlock_count == 0:
+            raise oefmt(space.w_RuntimeError,
+                        "cannot release un-acquired lock")
+        count, self.rlock_count = self.rlock_count, 0
+        owner, self.rlock_owner = self.rlock_owner, 0
+        try_release(space, self.lock)
+        return space.newtuple([space.newint(count), space.newint(owner)])
+
+    def descr__enter__(self, space):
+        self.acquire_w(space)
+        return self
+
+    def descr__exit__(self, space, __args__):
+        self.release_w(space)
+
+    def descr__note(self, space, __args__):
+        pass   # compatibility with the _Verbose base class in Python
+
+W_RLock.typedef = TypeDef(
+    "thread.RLock",
+    __new__ = interp2app(W_RLock.descr__new__.im_func),
+    acquire = interp2app(W_RLock.acquire_w),
+    release = interp2app(W_RLock.release_w),
+    _is_owned = interp2app(W_RLock.is_owned_w),
+    _acquire_restore = interp2app(W_RLock.acquire_restore_w),
+    _release_save = interp2app(W_RLock.release_save_w),
+    __enter__ = interp2app(W_RLock.descr__enter__),
+    __exit__ = interp2app(W_RLock.descr__exit__),
+    __weakref__ = make_weakref_descr(W_RLock),
+    __repr__ = interp2app(W_RLock.descr__repr__),
+    _note = interp2app(W_RLock.descr__note),
+    )
diff --git a/pypy/module/thread/test/test_lock.py b/pypy/module/thread/test/test_lock.py
--- a/pypy/module/thread/test/test_lock.py
+++ b/pypy/module/thread/test/test_lock.py
@@ -138,6 +138,62 @@
     test_lock_again = AppTestLock.test_lock.im_func
 
 
+class AppTestRLock(GenericTestThread):
+    """
+    Tests for recursive locks.
+    """
+    def test_reacquire(self):
+        import thread
+        lock = thread.RLock()
+        lock.acquire()
+        lock.acquire()
+        lock.release()
+        lock.acquire()
+        lock.release()
+        lock.release()
+
+    def test_release_unacquired(self):
+        # Cannot release an unacquired lock
+        import thread
+        lock = thread.RLock()
+        raises(RuntimeError, lock.release)
+        lock.acquire()
+        lock.acquire()
+        lock.release()
+        lock.acquire()
+        lock.release()
+        lock.release()
+        raises(RuntimeError, lock.release)
+
+    def test_release_save(self):
+        import thread
+        lock = thread.RLock()
+        raises(RuntimeError, lock._release_save)
+        lock.acquire()
+        state = lock._release_save()
+        lock._acquire_restore(state)
+        lock.release()
+
+    def test__is_owned(self):
+        import thread
+        lock = thread.RLock()
+        assert lock._is_owned() is False
+        lock.acquire()
+        assert lock._is_owned() is True
+        lock.acquire()
+        assert lock._is_owned() is True
+        lock.release()
+        assert lock._is_owned() is True
+        lock.release()
+        assert lock._is_owned() is False
+
+    def test_context_manager(self):
+        import thread
+        lock = thread.RLock()
+        with lock:
+            assert lock._is_owned() is True
+
+
 class AppTestLockSignals(GenericTestThread):
     pytestmark = py.test.mark.skipif("os.name != 'posix'")
 
@@ -178,6 +234,10 @@
         import thread
         self.acquire_retries_on_intr(thread.allocate_lock())
 
+    def test_rlock_acquire_retries_on_intr(self):
+        import thread
+        self.acquire_retries_on_intr(thread.RLock())
+
     def w_alarm_interrupt(self, sig, frame):
         raise KeyboardInterrupt
 
@@ -209,3 +269,20 @@
             assert dt < 8.0
         finally:
             signal.signal(signal.SIGALRM, oldalrm)
+
+
+class AppTestLockRepr(GenericTestThread):
+
+    def test_rlock_repr(self):
+        import thread
+        class MyThread:
+            name = "foobar"
+        actives = {thread.get_ident(): MyThread()}
+        rlock = thread.RLock(actives)
+        assert repr(rlock) == "<thread.RLock owner=None count=0>"
+        rlock.acquire()
+        rlock.acquire()
+        assert repr(rlock) == "<thread.RLock owner='foobar' count=2>"
+        actives.clear()
+        assert repr(rlock) == "<thread.RLock owner=%d count=2>" % (
+            thread.get_ident(),)


More information about the pypy-commit mailing list