18
18
import inspect
19
19
import itertools
20
20
import logging
21
- from contextlib import contextmanager
21
+ from contextlib import asynccontextmanager , contextmanager
22
22
from typing import (
23
23
Any ,
24
+ AsyncIterator ,
24
25
Awaitable ,
25
26
Callable ,
26
27
Collection ,
40
41
)
41
42
42
43
import attr
43
- from typing_extensions import ContextManager , Literal
44
+ from typing_extensions import AsyncContextManager , Literal
44
45
45
46
from twisted .internet import defer
46
47
from twisted .internet .defer import CancelledError
@@ -491,7 +492,7 @@ class ReadWriteLock:
491
492
492
493
Example:
493
494
494
- with await read_write_lock.read("test_key"):
495
+ async with read_write_lock.read("test_key"):
495
496
# do some work
496
497
"""
497
498
@@ -514,22 +515,24 @@ def __init__(self) -> None:
514
515
# Latest writer queued
515
516
self .key_to_current_writer : Dict [str , defer .Deferred ] = {}
516
517
517
- async def read (self , key : str ) -> ContextManager :
518
- new_defer : "defer.Deferred[None]" = defer .Deferred ()
518
+ def read (self , key : str ) -> AsyncContextManager :
519
+ @asynccontextmanager
520
+ async def _ctx_manager () -> AsyncIterator [None ]:
521
+ new_defer : "defer.Deferred[None]" = defer .Deferred ()
519
522
520
- curr_readers = self .key_to_current_readers .setdefault (key , set ())
521
- curr_writer = self .key_to_current_writer .get (key , None )
523
+ curr_readers = self .key_to_current_readers .setdefault (key , set ())
524
+ curr_writer = self .key_to_current_writer .get (key , None )
522
525
523
- curr_readers .add (new_defer )
526
+ curr_readers .add (new_defer )
524
527
525
- # We wait for the latest writer to finish writing. We can safely ignore
526
- # any existing readers... as they're readers.
527
- if curr_writer :
528
- await make_deferred_yieldable (curr_writer )
529
-
530
- @contextmanager
531
- def _ctx_manager () -> Iterator [None ]:
532
528
try :
529
+ # We wait for the latest writer to finish writing. We can safely ignore
530
+ # any existing readers... as they're readers.
531
+ # May raise a `CancelledError` if the `Deferred` wrapping us is
532
+ # cancelled. The `Deferred` we are waiting on must not be cancelled,
533
+ # since we do not own it.
534
+ if curr_writer :
535
+ await make_deferred_yieldable (stop_cancellation (curr_writer ))
533
536
yield
534
537
finally :
535
538
with PreserveLoggingContext ():
@@ -538,29 +541,35 @@ def _ctx_manager() -> Iterator[None]:
538
541
539
542
return _ctx_manager ()
540
543
541
- async def write (self , key : str ) -> ContextManager :
542
- new_defer : "defer.Deferred[None]" = defer .Deferred ()
544
+ def write (self , key : str ) -> AsyncContextManager :
545
+ @asynccontextmanager
546
+ async def _ctx_manager () -> AsyncIterator [None ]:
547
+ new_defer : "defer.Deferred[None]" = defer .Deferred ()
543
548
544
- curr_readers = self .key_to_current_readers .get (key , set ())
545
- curr_writer = self .key_to_current_writer .get (key , None )
549
+ curr_readers = self .key_to_current_readers .get (key , set ())
550
+ curr_writer = self .key_to_current_writer .get (key , None )
546
551
547
- # We wait on all latest readers and writer.
548
- to_wait_on = list (curr_readers )
549
- if curr_writer :
550
- to_wait_on .append (curr_writer )
552
+ # We wait on all latest readers and writer.
553
+ to_wait_on = list (curr_readers )
554
+ if curr_writer :
555
+ to_wait_on .append (curr_writer )
551
556
552
- # We can clear the list of current readers since the new writer waits
553
- # for them to finish.
554
- curr_readers .clear ()
555
- self .key_to_current_writer [key ] = new_defer
557
+ # We can clear the list of current readers since `new_defer` waits
558
+ # for them to finish.
559
+ curr_readers .clear ()
560
+ self .key_to_current_writer [key ] = new_defer
556
561
557
- await make_deferred_yieldable (defer .gatherResults (to_wait_on ))
558
-
559
- @contextmanager
560
- def _ctx_manager () -> Iterator [None ]:
562
+ to_wait_on_defer = defer .gatherResults (to_wait_on )
561
563
try :
564
+ # Wait for all current readers and the latest writer to finish.
565
+ # May raise a `CancelledError` immediately after the wait if the
566
+ # `Deferred` wrapping us is cancelled. We must only release the lock
567
+ # once we have acquired it, hence the use of `delay_cancellation`
568
+ # rather than `stop_cancellation`.
569
+ await make_deferred_yieldable (delay_cancellation (to_wait_on_defer ))
562
570
yield
563
571
finally :
572
+ # Release the lock.
564
573
with PreserveLoggingContext ():
565
574
new_defer .callback (None )
566
575
# `self.key_to_current_writer[key]` may be missing if there was another
0 commit comments