diff options
author | Quan Tian <qtian@vmware.com> | 2017-05-09 00:34:33 -0700 |
---|---|---|
committer | Quan Tian <qtian@vmware.com> | 2017-05-10 19:32:43 -0700 |
commit | f72cc96a70f9e9d23fc6e00d994263829b672be0 (patch) | |
tree | 78a65d5b9ea3fa9f128cd60fed9185106e5a47b9 | |
parent | 7f53465578543156e7251e243c0636e087a8445f (diff) | |
download | eventlet-f72cc96a70f9e9d23fc6e00d994263829b672be0.tar.gz |
patcher: set locked RLocks' owner only when patching existing locks
For Python 2, patching existing locks replaces RLock._RLock__owner with current
thread ID no matter the old lock is locked or not and thus an unlocked RLock
would have a non None owner (e.g. <_RLock owner=140106584489808 count=0>).
Then if we acquire the RLock in the same thread, the method RLock.acquire would
not invoke the _RLock__block.acquire() since it treats this is a recursive
acquire by checking current thread ID. And then the following RLock.release
would invoke the _RLock__block.release method resulting in the counter of
Semaphore being improved to 2.
There should be only two states being expected for RLock:
1. owner != None and count > 0
2. owner == None and count == 0
This patch fixs it by only setting locked RLocks' owner during patching.
-rw-r--r-- | eventlet/patcher.py | 2 | ||||
-rw-r--r-- | tests/__init__.py | 9 | ||||
-rw-r--r-- | tests/isolated/patcher_existing_locks_unlocked.py | 25 | ||||
-rw-r--r-- | tests/patcher_test.py | 5 |
4 files changed, 40 insertions, 1 deletions
diff --git a/eventlet/patcher.py b/eventlet/patcher.py index c0ef377..a929f7f 100644 --- a/eventlet/patcher.py +++ b/eventlet/patcher.py @@ -358,7 +358,7 @@ def _fix_py2_rlock(rlock, tid): rlock._RLock__block = new if old.locked(): new.acquire() - rlock._RLock__owner = tid + rlock._RLock__owner = tid def _fix_py3_rlock(old): diff --git a/tests/__init__.py b/tests/__init__.py index f4b806e..7ebb811 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -123,6 +123,15 @@ def skip_if_no_itimer(func): return skip_unless(has_itimer)(func) +def skip_if_CRLock_exist(func): + """ Decorator that skips a test if the `_thread.RLock` class exists """ + try: + from _thread import RLock + return skipped(func) + except ImportError: + return func + + def skip_if_no_ssl(func): """ Decorator that skips a test if SSL is not available.""" try: diff --git a/tests/isolated/patcher_existing_locks_unlocked.py b/tests/isolated/patcher_existing_locks_unlocked.py new file mode 100644 index 0000000..e6f1a91 --- /dev/null +++ b/tests/isolated/patcher_existing_locks_unlocked.py @@ -0,0 +1,25 @@ +__test__ = False + + +def take(lock, e1, e2): + with lock: + e1.set() + e2.wait() + + +if __name__ == '__main__': + import sys + import threading + lock = threading.RLock() + import eventlet + eventlet.monkey_patch() + + lock.acquire() + lock.release() + + e1, e2 = threading.Event(), threading.Event() + eventlet.spawn(take, lock, e1, e2) + e1.wait() + assert not lock.acquire(blocking=0) + e2.set() + print('pass') diff --git a/tests/patcher_test.py b/tests/patcher_test.py index eb8e100..ff59400 100644 --- a/tests/patcher_test.py +++ b/tests/patcher_test.py @@ -493,6 +493,11 @@ def test_patcher_existing_locks_locked(): tests.run_isolated('patcher_existing_locks_locked.py') +@tests.skip_if_CRLock_exist +def test_patcher_existing_locks_unlocked(): + tests.run_isolated('patcher_existing_locks_unlocked.py') + + def test_importlib_lock(): tests.run_isolated('patcher_importlib_lock.py') |