diff options
author | Michael Wright <mjw@methodanalysis.com> | 2021-10-28 12:14:20 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-28 14:14:20 +0300 |
commit | 3c25d0c3bdd9e1fcedf60b879f164f8363a64395 (patch) | |
tree | 5fcc40c2a5e5f5cb82b2645930729a506f94ca06 | |
parent | 8904a339f2df3bd7958e73142d640cd3d0a9245c (diff) | |
download | eventlet-3c25d0c3bdd9e1fcedf60b879f164f8363a64395.tar.gz |
green.thread: unlocked Lock().release() should raise exception, returned True
https://github.com/eventlet/eventlet/issues/697
https://github.com/eventlet/eventlet/pull/721
Co-authored-by: Sergey Shepelev <temotor@gmail.com>
Co-authored-by: Tim Burke <tim.burke@gmail.com>
-rw-r--r-- | eventlet/green/thread.py | 5 | ||||
-rw-r--r-- | eventlet/lock.py | 28 | ||||
-rw-r--r-- | eventlet/semaphore.py | 5 | ||||
-rw-r--r-- | tests/semaphore_test.py | 21 | ||||
-rw-r--r-- | tests/thread_test.py | 24 |
5 files changed, 55 insertions, 28 deletions
diff --git a/eventlet/green/thread.py b/eventlet/green/thread.py index 5800b30..e26f6b3 100644 --- a/eventlet/green/thread.py +++ b/eventlet/green/thread.py @@ -3,15 +3,16 @@ from six.moves import _thread as __thread import six from eventlet.support import greenlets as greenlet from eventlet import greenthread -from eventlet.semaphore import Semaphore as LockType +from eventlet.lock import Lock import sys __patched__ = ['get_ident', 'start_new_thread', 'start_new', 'allocate_lock', 'allocate', 'exit', 'interrupt_main', 'stack_size', '_local', - 'LockType', '_count'] + 'LockType', 'Lock', '_count'] error = __thread.error +LockType = Lock __threadcount = 0 diff --git a/eventlet/lock.py b/eventlet/lock.py new file mode 100644 index 0000000..c665e19 --- /dev/null +++ b/eventlet/lock.py @@ -0,0 +1,28 @@ +from eventlet.semaphore import Semaphore + + +class Lock(Semaphore): + + """A lock. + This is API-compatible with :class:`threading.Lock`. + + It is a context manager, and thus can be used in a with block:: + + lock = Lock() + with lock: + do_some_stuff() + """ + + def release(self, blocking=True): + """Modify behaviour vs :class:`Semaphore` to raise a RuntimeError + exception if the value is greater than zero. This corrects behaviour + to realign with :class:`threading.Lock`. + """ + if self.counter > 0: + raise RuntimeError("release unlocked lock") + + return super(Lock, self).release(blocking=blocking) + + def _at_fork_reinit(self): + self.counter = 1 + self._waiters.clear() diff --git a/eventlet/semaphore.py b/eventlet/semaphore.py index 5e2b5e3..18b5b05 100644 --- a/eventlet/semaphore.py +++ b/eventlet/semaphore.py @@ -39,7 +39,6 @@ class Semaphore(object): if value < 0: msg = 'Semaphore() expect value >= 0, actual: {0}'.format(repr(value)) raise ValueError(msg) - self._original_value = value self.counter = value self._waiters = collections.deque() @@ -52,10 +51,6 @@ class Semaphore(object): params = (self.__class__.__name__, self.counter, len(self._waiters)) return '<%s c=%s _w[%s]>' % params - def _at_fork_reinit(self): - self.counter = self._original_value - self._waiters.clear() - def locked(self): """Returns true if a call to acquire would block. """ diff --git a/tests/semaphore_test.py b/tests/semaphore_test.py index cf6a29d..d6c11d1 100644 --- a/tests/semaphore_test.py +++ b/tests/semaphore_test.py @@ -42,27 +42,6 @@ class TestSemaphore(tests.LimitedTestCase): sem = eventlet.Semaphore() self.assertRaises(ValueError, sem.acquire, blocking=False, timeout=1) - def test_reinit(self): - # py39+ expects locks to have a _at_fork_reinit() method; since we - # patch in Semaphores in eventlet.green.thread, they need it, too - sem = eventlet.Semaphore() - sem.acquire() - sem._at_fork_reinit() - self.assertEqual(sem.acquire(blocking=False), True) - self.assertEqual(sem.acquire(blocking=False), False) - - sem = eventlet.Semaphore(0) - sem.release() - sem._at_fork_reinit() - self.assertEqual(sem.acquire(blocking=False), False) - - sem = eventlet.Semaphore(2) - sem.acquire() - sem._at_fork_reinit() - self.assertEqual(sem.acquire(blocking=False), True) - self.assertEqual(sem.acquire(blocking=False), True) - self.assertEqual(sem.acquire(blocking=False), False) - def test_semaphore_contention(): g_mutex = eventlet.Semaphore() diff --git a/tests/thread_test.py b/tests/thread_test.py index 44de95d..76e9b24 100644 --- a/tests/thread_test.py +++ b/tests/thread_test.py @@ -5,6 +5,7 @@ import eventlet from eventlet import corolocal from eventlet import event from eventlet import greenthread +from eventlet import patcher from eventlet.green import thread import six @@ -99,3 +100,26 @@ class Locals(LimitedTestCase): gc.collect() # at this point all our coros have terminated self.assertEqual(len(refs), 1) + + +def test_compat_lock_release(): + # https://github.com/eventlet/eventlet/issues/697 + for mod in (patcher.original("threading"), thread): + try: + mod.Lock().release() + except RuntimeError as e: + # python3 + assert "release unlocked lock" in str(e).lower(), str((mod, e)) + except thread.error as e: + # python2.7 + assert "release unlocked lock" in str(e).lower(), str((mod, e)) + + +def test_reinit(): + # py39+ expects locks to have a _at_fork_reinit() method + # https://github.com/eventlet/eventlet/pull/721#pullrequestreview-769377850 + lk = thread.Lock() + lk.acquire() + lk._at_fork_reinit() + assert lk.acquire(blocking=False) + assert not lk.acquire(blocking=False) |