Last active
November 1, 2018 15:40
-
-
Save allemangD/bba8dc2d059310623f752ebf65bb6cdc to your computer and use it in GitHub Desktop.
Show how Context Managers do not correctly support suspended execution
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import asyncio | |
class CM: | |
mode = 0 | |
def __init__(self, m): | |
self.m = m | |
def __enter__(self): | |
self.old = CM.mode | |
CM.mode = self.m | |
def __exit__(self, *_): | |
CM.mode = self.old | |
async def long_op(): | |
with CM('long'): | |
print(f'- - mode {CM.mode}') | |
await asyncio.sleep(3) | |
print(f'- - mode {CM.mode} done') | |
async def short_op(): | |
with CM('short'): | |
for i in range(5): | |
print(f'{i} - mode {CM.mode}') | |
await asyncio.sleep(1) | |
print(f'{i} - mode {CM.mode} done') | |
async def main(): | |
await asyncio.gather(short_op(), | |
long_op()) | |
asyncio.run(main()) |
This is how your example can be fixed with the contextvars
module:
import asyncio
import contextvars
cm_mode = contextvars.ContextVar('cm_mode', default=None)
class CM:
def __init__(self, m):
self.m = m
@property
def mode(self):
return cm_mode.get()
def __enter__(self):
self.old = cm_mode.get()
cm_mode.set(self.m)
return self
def __exit__(self, *_):
cm_mode.set(self.old)
async def long_op():
with CM('long') as cm:
print(f'- - mode {cm.mode}')
await asyncio.sleep(3)
print(f'- - mode {cm.mode} done')
async def short_op():
with CM('short') as cm:
for i in range(5):
print(f'{i} - mode {cm.mode}')
await asyncio.sleep(1)
print(f'{i} - mode {cm.mode} done')
async def main():
await asyncio.gather(short_op(),
long_op())
asyncio.run(main())
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that the
short_op
iterations have the wrong mode whilelong_op
is waiting.You would expect this to have
mode
be'short'
for all iterations, but this is not the case - the value ofmode
depends on the context manager inlong()
. Note how theshort()
iterations have the wrong mode whilelong()
is still waiting:The expected output is:
Where each iteration of
short_op
occurs in the correct context, regardless of the state oflong_op
. This would be accomplished by extending the definition ofCM
with__suspend__
and__resume__
methods:Or by using a
suspendable
decorator: