-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Add cancellation support to ReadWriteLock
#12120
Changes from 1 commit
89fe787
f1f363d
9744823
3241adc
68f5abe
28dbe41
c9f85e4
716cded
c4a2a58
31a2bb2
1c1b46a
65f97fa
cadfe0a
1cd035b
4c47827
1b9ec9b
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 | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -12,6 +12,8 @@ | |||||||
# See the License for the specific language governing permissions and | ||||||||
# limitations under the License. | ||||||||
|
||||||||
from typing import AsyncContextManager, Callable, Tuple | ||||||||
|
||||||||
from twisted.internet import defer | ||||||||
from twisted.internet.defer import Deferred | ||||||||
|
||||||||
|
@@ -32,58 +34,71 @@ def _assert_called_before_not_after(self, lst, first_false): | |||||||
|
||||||||
def test_rwlock(self): | ||||||||
rwlock = ReadWriteLock() | ||||||||
key = "key" | ||||||||
|
||||||||
def start_reader_or_writer( | ||||||||
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. it's a bit confusing that this is slightly different to the class-level 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. Yep, let's combine them. |
||||||||
read_or_write: Callable[[str], AsyncContextManager] | ||||||||
) -> Tuple["Deferred[None]", "Deferred[None]"]: | ||||||||
acquired_d: "Deferred[None]" = Deferred() | ||||||||
release_d: "Deferred[None]" = Deferred() | ||||||||
|
||||||||
async def action(): | ||||||||
async with read_or_write(key): | ||||||||
acquired_d.callback(None) | ||||||||
await release_d | ||||||||
|
||||||||
key = object() | ||||||||
defer.ensureDeferred(action()) | ||||||||
return acquired_d, release_d | ||||||||
|
||||||||
ds = [ | ||||||||
rwlock.read(key), # 0 | ||||||||
rwlock.read(key), # 1 | ||||||||
rwlock.write(key), # 2 | ||||||||
rwlock.write(key), # 3 | ||||||||
rwlock.read(key), # 4 | ||||||||
rwlock.read(key), # 5 | ||||||||
rwlock.write(key), # 6 | ||||||||
start_reader_or_writer(rwlock.read), # 0 | ||||||||
start_reader_or_writer(rwlock.read), # 1 | ||||||||
start_reader_or_writer(rwlock.write), # 2 | ||||||||
start_reader_or_writer(rwlock.write), # 3 | ||||||||
start_reader_or_writer(rwlock.read), # 4 | ||||||||
start_reader_or_writer(rwlock.read), # 5 | ||||||||
start_reader_or_writer(rwlock.write), # 6 | ||||||||
] | ||||||||
ds = [defer.ensureDeferred(d) for d in ds] | ||||||||
# `Deferred`s that resolve when each reader or writer acquires the lock. | ||||||||
acquired_ds = [acquired_d for acquired_d, _release_d in ds] | ||||||||
# `Deferred`s that will trigger the release of locks when resolved. | ||||||||
release_ds = [release_d for _acquired_d, release_d in ds] | ||||||||
|
||||||||
self._assert_called_before_not_after(ds, 2) | ||||||||
self._assert_called_before_not_after(acquired_ds, 2) | ||||||||
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. it's hard to follow what Actually generally this test could do with a few comments, for example:
Suggested change
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 found this test case hard to understand too. I'll try to make it more readable. |
||||||||
|
||||||||
with ds[0].result: | ||||||||
self._assert_called_before_not_after(ds, 2) | ||||||||
self._assert_called_before_not_after(ds, 2) | ||||||||
self._assert_called_before_not_after(acquired_ds, 2) | ||||||||
release_ds[0].callback(None) | ||||||||
self._assert_called_before_not_after(acquired_ds, 2) | ||||||||
|
||||||||
with ds[1].result: | ||||||||
self._assert_called_before_not_after(ds, 2) | ||||||||
self._assert_called_before_not_after(ds, 3) | ||||||||
self._assert_called_before_not_after(acquired_ds, 2) | ||||||||
release_ds[1].callback(None) | ||||||||
self._assert_called_before_not_after(acquired_ds, 3) | ||||||||
|
||||||||
with ds[2].result: | ||||||||
self._assert_called_before_not_after(ds, 3) | ||||||||
self._assert_called_before_not_after(ds, 4) | ||||||||
self._assert_called_before_not_after(acquired_ds, 3) | ||||||||
release_ds[2].callback(None) | ||||||||
self._assert_called_before_not_after(acquired_ds, 4) | ||||||||
|
||||||||
with ds[3].result: | ||||||||
self._assert_called_before_not_after(ds, 4) | ||||||||
self._assert_called_before_not_after(ds, 6) | ||||||||
self._assert_called_before_not_after(acquired_ds, 4) | ||||||||
release_ds[3].callback(None) | ||||||||
self._assert_called_before_not_after(acquired_ds, 6) | ||||||||
|
||||||||
with ds[5].result: | ||||||||
self._assert_called_before_not_after(ds, 6) | ||||||||
self._assert_called_before_not_after(ds, 6) | ||||||||
self._assert_called_before_not_after(acquired_ds, 6) | ||||||||
release_ds[5].callback(None) | ||||||||
self._assert_called_before_not_after(acquired_ds, 6) | ||||||||
|
||||||||
with ds[4].result: | ||||||||
self._assert_called_before_not_after(ds, 6) | ||||||||
self._assert_called_before_not_after(ds, 7) | ||||||||
self._assert_called_before_not_after(acquired_ds, 6) | ||||||||
release_ds[4].callback(None) | ||||||||
self._assert_called_before_not_after(acquired_ds, 7) | ||||||||
|
||||||||
with ds[6].result: | ||||||||
pass | ||||||||
release_ds[6].callback(None) | ||||||||
|
||||||||
d = defer.ensureDeferred(rwlock.write(key)) | ||||||||
self.assertTrue(d.called) | ||||||||
with d.result: | ||||||||
pass | ||||||||
acquired_d, release_d = start_reader_or_writer(rwlock.write) | ||||||||
self.assertTrue(acquired_d.called) | ||||||||
release_d.callback(None) | ||||||||
|
||||||||
d = defer.ensureDeferred(rwlock.read(key)) | ||||||||
self.assertTrue(d.called) | ||||||||
with d.result: | ||||||||
pass | ||||||||
acquired_d, release_d = start_reader_or_writer(rwlock.read) | ||||||||
self.assertTrue(acquired_d.called) | ||||||||
release_d.callback(None) | ||||||||
|
||||||||
def test_lock_handoff_to_nonblocking_writer(self): | ||||||||
"""Test a writer handing the lock to another writer that completes instantly.""" | ||||||||
|
@@ -93,11 +108,11 @@ def test_lock_handoff_to_nonblocking_writer(self): | |||||||
unblock: "Deferred[None]" = Deferred() | ||||||||
|
||||||||
async def blocking_write(): | ||||||||
with await rwlock.write(key): | ||||||||
async with rwlock.write(key): | ||||||||
await unblock | ||||||||
|
||||||||
async def nonblocking_write(): | ||||||||
with await rwlock.write(key): | ||||||||
async with rwlock.write(key): | ||||||||
pass | ||||||||
|
||||||||
d1 = defer.ensureDeferred(blocking_write()) | ||||||||
|
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.
I'm wondering if we should, for strict correctness, do all the
to_wait_on
calculation etc inside thectx_manager
. Otherwise it's possible for someone to do:... which is a stupid thing to do, but still.
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.
I'm all in favour of removing footguns. I'lk move the setup of
read()
andwrite()
into the context manager.