-
Notifications
You must be signed in to change notification settings - Fork 151
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
Manage local storage inherit behavior #84
Comments
I propose removing this logic (at least I don't see any reason to keep it). |
This was intentional, because I'd like to make context shared in such case: async def main():
get_local()['val'] = 123
await loop.create_task(sub())
async def sub():
print(get_local().get('val')) # 123 And yes as you mentioned, this caused side effect that even if the sub task is never awaited, it shares the same context: async def main():
get_local()['val'] = 123
loop.create_task(sub())
async def sub():
print(get_local().get('val')) # still 123 There are cases where not all sub calls are plain coroutines but tasks which need the same context ( So before PEP-550, I suppose we'll need to make decision on whether to share context at task creation, or not to share context between tasks at all. Also if possible, please share a bit more about the strange errors you met, which might be helpful for the decision. |
I always thought of the As for my case. In my app, I have a bunch of background processes, each of them creates a transaction and queries a database once per few seconds. Each process is a coroutine with an infinite loop inside it: async def process():
while True:
await do_some_job()
await asyncio.sleep(5) All of those tasks start in the init procedure (init is async because I want to keep all initialisation, both sync and async, in the same place):
The init function called via As you can see, all background tasks share the same context. This sometimes causes 'wrong release order' and 'connection closed in the middle of operation' errors. An easy solution would be to create a function which enables or disables inheritance: await loop.create_task(inherit_context(sub())) The implementation might look like this: class inherit_context:
def __init__(self, coro=None):
self._coro = coro
self._local = get_local()
async def __await__(self):
# An example implementation. We should think whether we want to update
# or to replace local context completely.
get_local().update(self._local)
return await self._coro |
I see, it is a critical issue in your case then. Thanks for the suggestion, which inspired me to try something, one sec I'll test and update. |
Some proposal: May be you can provide ability to explicitly disable inheritance
sub1 and sub2 create tasks inside gather and must share transaction by default if we need separate tasks - we can do it explicitly
|
This sounds reasonable. I've also realised that cancellation signal would be sent to any subtask that was spawned in the task that's being cancelled and there's a |
…ng to empty dict after spawn before join
@smagafurov That makes sense, thanks for the proposal. Feature added on task object, please see above commit. Should it also accept coroutines/awaitables and create non-inheriting tasks internally? That would require additional parameter "loop". |
Fixed in e8f6d85. Example: from gino.local import reset_local
async def main():
await reset_local(sub())
# or
await loop.create_task(sub()).with_local_reset() |
Updated, thanks for @AmatanHead on this! Example now: from gino import get_local(), reset_local, is_local_root
async def main():
# spawn detached
reset_local(sub())
# spawn attached
await loop.create_task(sub())
# context is always inherited even without joining
loop.create_task(sub())
async def sub():
# you can dynamically reset local to empty dict
if is_local_root():
reset_local()
# but it works only once when current local is inherited
get_local()['val'] = 123
reset_local() # this won't clear val=123 |
May be we should change public API terminology from When pep 550 released, we change our inner realization of separate_transaction to execution context usage But semantic "I want separate transaction for this task" is not changed |
Ah right, makes perfect sense! |
IMHO
|
True,
Though, I believe your attempt was on the right direction (much appreciated!), which inspired me to check how local storage is used and what this function really affects. The local storage is the main part behind the scene of from gino import forget_connection, is_root_connection
async def main():
res = await forget_connection(sub()) # make sub forget current connection if any
loop.create_task(sub_inline())
async def sub_inline():
forget_connection() # try to forget inherited connection
new_conn = await db.acquire() # then we get brand new connection, even with reuse=True
print(new_conn.is_root) # True Meanwhile, I'll deprecate |
I disagree. Task local storage can be used for more than just storing a connection stack. For example, I store logging information, sentry context, a reference to the current request there. By replacing
Why shouldn't they? I mean, you have to understand the tool you are using. And this is critically important when talking about local storage. An example: will transaction be inherited if we use We'll do a disservice to our users by hiding these implementation details.
When pep-550 released, we indeed can hide our implementation, because pep-550 proposes a uniform way to manage local contexts and (ideally) every python programmer will know how it works and how to avoid problems described above. |
I'm kinda with @AmatanHead on this. Please see commit 820990c - |
Just for people who came here with such error and want to make independent context for create_task. I took some time for me, before I got this. event_loop.call_soon(event_loop.create_task, coroutine,
context=contextvars.Context()) |
@Norfolks thanks for the info! See also here for Python < 3.7: #572 (comment) |
Hello!
Is there a reason to do
task.task_local = get_local() or {}
instead of justtask.task_local = {}
? Because now if you spawn a task inside of another task, you get the same local context for both tasks ant this may cause very strange errors.The text was updated successfully, but these errors were encountered: