-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixbug threading local in coroutine #120
Changes from all commits
24088d5
874d353
f460a30
eaea4b9
7a1e8aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
-r test_requirements.txt | ||
hiredis | ||
uvloop | ||
contextvars |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
mock | ||
pytest | ||
pytest-asyncio | ||
contextvars |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,10 @@ | ||
from __future__ import with_statement | ||
import pytest | ||
|
||
import asyncio | ||
import time | ||
|
||
import pytest | ||
|
||
from aredis.exceptions import LockError, ResponseError | ||
from aredis.lock import Lock, LuaLock | ||
|
||
|
@@ -18,7 +21,7 @@ async def test_lock(self, r): | |
await r.flushdb() | ||
lock = self.get_lock(r, 'foo') | ||
assert await lock.acquire(blocking=False) | ||
assert await r.get('foo') == lock.local.token | ||
assert await r.get('foo') == lock.local.get() | ||
assert await r.ttl('foo') == -1 | ||
await lock.release() | ||
assert await r.get('foo') is None | ||
|
@@ -63,7 +66,7 @@ async def test_context_manager(self, r): | |
# blocking_timeout prevents a deadlock if the lock can't be acquired | ||
# for some reason | ||
async with self.get_lock(r, 'foo', blocking_timeout=0.2) as lock: | ||
assert await r.get('foo') == lock.local.token | ||
assert await r.get('foo') == lock.local.get() | ||
assert await r.get('foo') is None | ||
|
||
@pytest.mark.asyncio() | ||
|
@@ -87,7 +90,7 @@ async def test_releasing_lock_no_longer_owned_raises_error(self, r): | |
with pytest.raises(LockError): | ||
await lock.release() | ||
# even though we errored, the token is still cleared | ||
assert lock.local.token is None | ||
assert lock.local.get() is None | ||
|
||
@pytest.mark.asyncio() | ||
async def test_extend_lock(self, r): | ||
|
@@ -133,6 +136,23 @@ async def test_extending_lock_no_longer_owned_raises_error(self, r): | |
with pytest.raises(LockError): | ||
await lock.extend(10) | ||
|
||
@pytest.mark.asyncio() | ||
async def test_concurrent_lock_acquire(self, r): | ||
lock = self.get_lock(r, 'test', timeout=1) | ||
|
||
async def coro(lock): | ||
is_error_raised = False | ||
await lock.acquire(blocking=True) | ||
await asyncio.sleep(1.5) | ||
try: | ||
await lock.release() | ||
except LockError as exc: | ||
is_error_raised = True | ||
return is_error_raised | ||
|
||
results = await asyncio.gather(coro(lock), coro(lock)) | ||
assert not (results[0] and results[1]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This assertion fails when I run this test locally. I believe the assertion is incorrect. Both tasks/coroutines sleep for longer than the lifetime of the lock. As a consequence, both will encounter a LockError when attempting to release the lock. Hence, the correct assertion would be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think you are right, the test result is random which is not appropriate |
||
|
||
|
||
class TestLuaLock(TestLock): | ||
lock_class = LuaLock | ||
|
@@ -170,6 +190,7 @@ async def test_lua_compatible_server(self, r, monkeypatch): | |
@classmethod | ||
def mock_register(cls, redis): | ||
return | ||
|
||
monkeypatch.setattr(LuaLock, 'register_scripts', mock_register) | ||
try: | ||
lock = r.lock('foo') | ||
|
@@ -183,6 +204,7 @@ async def test_lua_unavailable(self, r, monkeypatch): | |
@classmethod | ||
def mock_register(cls, redis): | ||
raise ResponseError() | ||
|
||
monkeypatch.setattr(LuaLock, 'register_scripts', mock_register) | ||
try: | ||
lock = r.lock('foo') | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation for contextvars has the following disclaimer:
Declaring the context variable in the__init__
method may result in a memory leak if my understanding is correct. I propose to declare it outside of the class definition. For example:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, moving the contextvar definition to the module level will cause tokens to bleed between lock instances created by the same co-routine. That's a bad idea. Sorry, you can ignore my proposal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, github didn't send me a msg and i saw your code review so late.