Skip to content
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

Context manager support for contextvars.Context #99633

Open
rhansen opened this issue Nov 21, 2022 · 27 comments
Open

Context manager support for contextvars.Context #99633

rhansen opened this issue Nov 21, 2022 · 27 comments
Labels
stdlib Python modules in the Lib dir type-feature A feature request or enhancement

Comments

@rhansen
Copy link
Contributor

rhansen commented Nov 21, 2022

Feature or enhancement

I would like to add __enter__ and __exit__ methods to contextvars.Context such that these two are mostly equivalent:

contextvars.copy_context().run(do_something_interesting)

and:

with contextvars.copy_context():
    do_something_interesting()

Pitch

This makes it possible to combine Context enter/exit with other context managers:

import contextlib
import contextvars

@contextlib.contextmanager
def foo():
    with contextvars.copy_context():
        set_some_context_vars()
        yield

My personal motivating interest is controlling ContextVar values in pytest fixtures. For example:

import contextvars
import pytest
import module_under_test

@pytest.fixture
def clean_slate():
    with contextvars.copy_context():
        module_under_test._internal_state_context_var.set(None)
        yield

def test_foo(clean_slate):
    assert not module_under_test.is_initialized()

Previous discussion

https://stackoverflow.com/q/59264158

Linked PRs

@gvanrossum
Copy link
Member

The idea should probably be reviewed by @1st1.

rhansen added a commit to rhansen/cpython that referenced this issue Nov 21, 2022
rhansen added a commit to rhansen/cpython that referenced this issue Feb 2, 2023
@rhansen
Copy link
Contributor Author

rhansen commented Feb 2, 2023

Friendly ping @1st1

@kumaraditya303 kumaraditya303 removed this from asyncio Feb 3, 2023
@iritkatriel iritkatriel added the stdlib Python modules in the Lib dir label Nov 27, 2023
@apostolos-geyer
Copy link

+1

@gvanrossum
Copy link
Member

Maybe someone could contribute a PR?

rhansen added a commit to rhansen/cpython that referenced this issue Jul 10, 2024
@rhansen
Copy link
Contributor Author

rhansen commented Jul 10, 2024

Maybe someone could contribute a PR?

@gvanrossum I opened a PR at the same time I opened this feature request, see #99634. I just rebased it onto latest main and fixed some documentation warnings.

@cjw296
Copy link
Contributor

cjw296 commented Sep 6, 2024

This isn't just asyncio, right? ContextVar is also useful for threading and forked/subprocess applications...

I'm a little surprised a thing called a Context was landed without context manager support 😅 ...

Anyway, the PR looks great, but until Python 3.14 comes out, is there any way we can get this context manager behaviour on 3.12 (or even earlier!)?

@picnixz
Copy link
Member

picnixz commented Sep 6, 2024

Anyway, the PR looks great, but until Python 3.14 comes out, is there any way we can get this context manager behaviour on 3.12 (or even earlier!)?

I'm not sure we can. Neither 3.12 and 3.13 accept features now (per PEP 719, features to 3.13 are no more accepted since May).

@cjw296
Copy link
Contributor

cjw296 commented Sep 7, 2024

That's not what I meant: I was curious if there's any way to do in pure python, rather than doing some kind of backported pypi package containing the C changes, what's in #99634 such that it could be used in, say, 3.11+

@picnixz
Copy link
Member

picnixz commented Sep 7, 2024

Oh sorry for misunderstanding it! Mmh, unless we can implicitly call the run method on the body of the with-statement, then it's maybe possible but otherwise I don't know =/

rhansen added a commit to rhansen/cpython that referenced this issue Sep 8, 2024
rhansen added a commit to rhansen/cpython that referenced this issue Sep 8, 2024
@rhansen
Copy link
Contributor Author

rhansen commented Sep 9, 2024

I was curious if there's any way to do in pure python, rather than doing some kind of backported pypi package containing the C changes, what's in #99634 such that it could be used in, say, 3.11+

No, it's not possible. Probably the easiest solution is to use Cython to create an adapter class that calls the C functions PyContext_Enter from __enter__ and PyContext_Exit from __exit__.

Edit: This won't work; see my comment below.

@1st1
Copy link
Member

1st1 commented Sep 10, 2024

I've left a comment in the PR -- I'm not sure this will jive well with async/await. Let's start by adding tests that would torture this idea to make sure the API is properly composable.

@cjw296
Copy link
Contributor

cjw296 commented Sep 11, 2024

@1st1 - even if it doesn't, I'd still like to see this land: in my context, pardon the pun, I'm not using async at all.

I have to admit, I find the documentation around this for async use pretty confusing: it's not at all clear to me where the scope of the context begins and ends, there's mention of "Task", but there's nothing other than a some stuff the reader has to infer about there the scope of the effect of client_addr_var.set(addr).

Ignoring any implementation details, I feel a context manager should work in the async world, and I think the following would make it very clear where the scope of the context lies:

async def handle_request(reader, writer):
    with contextvars.copy_context():
        addr = writer.transport.get_extra_info('socket').getpeername()
        client_addr_var.set(addr)
    
        # In any code that we call is now possible to get
        # client's address by calling 'client_addr_var.get()'.
    
        while True:
            line = await reader.readline()
            print(line)
            if not line.strip():
                break
            writer.write(line)
    
        writer.write(render_goodbye())
        writer.close()

@rhansen
Copy link
Contributor Author

rhansen commented Sep 14, 2024

In its current state, PR #99634 doesn't work with async/await. Specifically, the following hangs after printing some exceptions:

$ ./python -m asyncio
>>> import asyncio
>>> import contextvars
>>> async def foo():
...     with contextvars.copy_context():
...         await asyncio.sleep(0)
... 
>>> await foo()
Exception in callback <_asyncio.TaskStepMethWrapper object at 0x7a5c946ae800>()
handle: <Handle <_asyncio.TaskStepMethWrapper object at 0x7a5c946ae800>()>
Traceback (most recent call last):
  File "/home/rhansen/floss/cpython/Lib/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: cannot exit context: thread state references a different context object
Exception in callback <_asyncio.TaskStepMethWrapper object at 0x7a5c946ae850>()
handle: <Handle <_asyncio.TaskStepMethWrapper object at 0x7a5c946ae850>()>
Traceback (most recent call last):
  File "/home/rhansen/floss/cpython/Lib/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: cannot enter context: <_contextvars.Context object at 0x7a5c9469c890> is already entered

I spent some time trying to understand coroutine internals to figure out what's going wrong. From what I learned, the issue is that each coroutine "step" (the code executed between each await) is executed via contextvars.Context.run() (called from asyncio.Handle._run), which enters a context, runs the "step", then exits the context. Everything works fine if every coroutine step has balanced enter/exit pairs, which is always true with Context.run(). Unfortunately, it is not true when Context is used as a context manager and there is an await in the with statement suite.

Specifically, in the foo coroutine above, there are two coroutine steps:

  1. The step from coroutine start to the await. This step includes a Context enter but not a matching exit.
  2. The step from the await to the coroutine's return. This step has the Context exit.

Original generators suffer from a similar problem (the current context can change arbitrarily during yield).

It seems to me that Context should be a per-coroutine property that is restored when coroutine execution resumes, and not a per-thread property. Python currently behaves as if Context was a per-coroutine property thanks to the use of Context.run() for each coroutine step, but that is an implementation kludge that causes the balanced enter/exit limitation.

I am willing to code up a fix for this, but I don't know enough about Python's internals to feel comfortable diving in. Any direction would be appreciated. In particular, I'd like some pointers on what code I should touch to make Context a true per-coroutine/generator property rather than a per-thread property.

We could continue with the PR as-is except document that with suites containing await or yield statements are not yet supported. However, I don't think the PR is all that useful without await or yield because Context.run() with a closure is equivalent. So it's probably best to hold off on the PR until we can come up with a more complete solution.

rhansen added a commit to rhansen/cpython that referenced this issue Sep 14, 2024
rhansen added a commit to rhansen/cpython that referenced this issue Sep 14, 2024
rhansen added a commit to rhansen/cpython that referenced this issue Sep 30, 2024
…ched"

Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for pythongh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).
rhansen added a commit to rhansen/cpython that referenced this issue Oct 1, 2024
…ched"

Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for pythongh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).
rhansen added a commit to rhansen/cpython that referenced this issue Oct 1, 2024
Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for pythongh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).
@rhansen
Copy link
Contributor Author

rhansen commented Oct 1, 2024

I opened issue #124872 to replace the "context enter" and "context exit" events with a "context switched" event, and opened pull request #124776 to implement the change.

rhansen added a commit to rhansen/cpython that referenced this issue Oct 2, 2024
Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for pythongh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).
rhansen added a commit to rhansen/cpython that referenced this issue Oct 2, 2024
Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for pythongh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).
rhansen added a commit to rhansen/cpython that referenced this issue Oct 2, 2024
Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for pythongh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).
@1st1
Copy link
Member

1st1 commented Oct 2, 2024

@rhansen

I've been studying the CPython code and I think the following approach would work:
Give each coroutine (including generators) its own context stack (possibly empty).

This was the idea I had in https://peps.python.org/pep-0550/ -- ultimately rejected for its complexity and runtime impact. I'd say it's unlikely that we can convince enough people to do that (and even I myself don't think we need that anymore). That said given that you've already spent a lot of time on this, I recommend you to read that PEP because it did have a complete reference implementation and all of its ideas were validated to work.

That said, I'm looking at this PR's motivation and I think we can add two "semi-private" low-level APIs that would mirror the C API and allow people to build context managers if they absolutely have to. I'd just add Context._enter() and Context._exit() Python methods (mirroring PyContext_Enter() and PyContext_Enter()) and explicitly document their pitfalls (not composable with asyncio out of the box). Hopefully having a leading _` will emphasize that this is a special API for certain edge cases.

rhansen added a commit to rhansen/cpython that referenced this issue Oct 7, 2024
Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for pythongh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).
rhansen added a commit to rhansen/cpython that referenced this issue Oct 7, 2024
Change `PyObject *` to/from `PyContext *` to reduce the amount of
casting and improve readability.
rhansen added a commit to rhansen/cpython that referenced this issue Oct 7, 2024
…rowed

Improve readability by moving destination assignment next to source
reset, and comment that the ref is stolen.
rhansen added a commit to rhansen/cpython that referenced this issue Oct 7, 2024
No public API provides access to the current context yet (only a new
copy), however:

  * This is good defensive practice.
  * This improves code readability.
  * Context watchers are now notified about the initial context.
  * A planned future commit will make it possible for users to access
    the thread's initial context object.  Without this change, users
    would be able to enter the context a second time, causing a cycle
    in the context stack.
rhansen added a commit to rhansen/cpython that referenced this issue Oct 7, 2024
This will make it easier to refactor `_PyContext_Enter` and
`_PyContext_Exit` for a planned feature.
rhansen added a commit to rhansen/cpython that referenced this issue Oct 7, 2024
Add a new `_context` property to generator (and coroutine) objects to
get/set the "current context" that is observed by (and only by) the
generator and the functions it calls.

When `generator._context` is set to `None` (the default), the
generator is called a "dependent generator".  It behaves the same as
it always has: the "current context" observed by the generator is the
thread's context.  This means that the observed context can change
arbitrarily during a `yield`; the generator *depends* on the sender to
enter the appropriate context before it calls `generator.send`.

When `generator._context` is set to a `contextvars.Context` object,
the generator is called an "independent generator".  It acts more like
a separate thread with its own independent context stack.  The value
of `_context` is the head of that independent stack.  Whenever the
generator starts or resumes execution (via `generator.send`), the
current context temporarily becomes the generator's associated
context.  When the generator yields, returns, or propagates an
exception, the current context reverts back to what it was before.
The generator's context is *independent* from the sender's context.

If an independent generator calls `contextvars.Context.run`, then the
value of the `_context` property will (temporarily) change to the
newly entered context.

If an independent generator sends a value to a second independent
generator, the second generator's context will shadow the first
generator's context until the second generator returns or yields.

The `generator._context` property is private for now until experience
and feedback is collected.  Nothing is using this yet, but that will
change in future commits.

Motivations for this change:

  * First, this change makes it possible for a future commit to add
    context manager support to `contextvars.Context`.  A `yield` after
    entering a context causes execution to leave the generator with a
    different context at the top of the context stack than when
    execution started.  Swapping contexts in and out when execution
    suspends and resumes can only be done by the generator itself.

  * Second, this paves the way for a public API that will enable
    developers to guarantee that the context remains consistent
    throughout a generator's execution.  Right now the context can
    change arbitrarily during a `yield`, which can lead to subtle bugs
    that are difficult to root cause.  (Coroutines run by an asyncio
    event loop do not suffer from this same problem because asyncio
    manually sets the context each time it executes a step of an
    asynchronous function.  See the call to `contextvars.Context.run`
    in `asyncio.Handle._run`.)

  * Finally, this makes it possible to move the responsibility for
    activating an async coroutine's context from the event loop to the
    coroutine, where it more naturally belongs (alongside the rest of
    the execution state such as local variable bindings and the
    instruction pointer).  This ensures consistent behavior between
    different event loop implementations.

Example:

```python
import contextvars

cvar = contextvars.ContextVar('cvar', default='initial')

def make_generator():
    yield cvar.get()
    yield cvar.get()
    yield cvar.get()
    yield cvar.get()
    cvar.set('updated by generator')
    yield cvar.get()

gen = make_generator()
print('1.', next(gen))

def callback():
    cvar.set('updated by callback')
    print('2.', next(gen))

contextvars.copy_context().run(callback)
print('3.', next(gen))
cvar.set('updated at top level')
print('4.', next(gen))
print('5.', next(gen))
print('6.', cvar.get())
```

The above prints:

```
1. initial
2. updated by callback
3. initial
4. updated at top level
5. updated by generator
6. updated by generator
```

Now add the following line after the creation of the generator:

```python
gen._context = contextvars.copy_context()
```

With that change, the script now outputs:

```
1. initial
2. initial
3. initial
4. initial
5. updated by generator
6. updated by top level
```
rhansen added a commit to rhansen/cpython that referenced this issue Oct 7, 2024
The functions `_PyGen_ActivateContext` and `_PyGen_DeactivateContext`
are called every time a value or exception is sent to a coroutine.
These functions are no-ops for dependent coroutines (coroutines
without their own independent context stack).  Coroutines are
dependent by default, and the vast majority of performance-sensitive
coroutines are expected to be dependent, so move the check that
determines whether the coroutine is dependent or independent to an
inline function to speed up send calls.
rhansen added a commit to rhansen/cpython that referenced this issue Oct 7, 2024
rhansen added a commit to rhansen/cpython that referenced this issue Oct 10, 2024
Change `PyObject *` to/from `PyContext *` to reduce the amount of
casting and improve readability.
rhansen added a commit to rhansen/cpython that referenced this issue Oct 10, 2024
…rowed

Improve readability by moving destination assignment next to source
reset, and comment that the ref is stolen.
rhansen added a commit to rhansen/cpython that referenced this issue Oct 10, 2024
No public API provides access to the current context yet (only a new
copy), however:

  * This is good defensive practice.
  * This improves code readability.
  * Context watchers are now notified about the initial context.
  * A planned future commit will make it possible for users to access
    the thread's initial context object.  Without this change, users
    would be able to enter the context a second time, causing a cycle
    in the context stack.
rhansen added a commit to rhansen/cpython that referenced this issue Oct 10, 2024
This will make it easier to refactor `_PyContext_Enter` and
`_PyContext_Exit` for a planned feature.
rhansen added a commit to rhansen/cpython that referenced this issue Oct 10, 2024
Add a new `_context` property to generator (and coroutine) objects to
get/set the "current context" that is observed by (and only by) the
generator and the functions it calls.

When `generator._context` is set to `None` (the default), the
generator is called a "dependent generator".  It behaves the same as
it always has: the "current context" observed by the generator is the
thread's context.  This means that the observed context can change
arbitrarily during a `yield`; the generator *depends* on the sender to
enter the appropriate context before it calls `generator.send`.

When `generator._context` is set to a `contextvars.Context` object,
the generator is called an "independent generator".  It acts more like
a separate thread with its own independent context stack.  The value
of `_context` is the head of that independent stack.  Whenever the
generator starts or resumes execution (via `generator.send`), the
current context temporarily becomes the generator's associated
context.  When the generator yields, returns, or propagates an
exception, the current context reverts back to what it was before.
The generator's context is *independent* from the sender's context.

If an independent generator calls `contextvars.Context.run`, then the
value of the `_context` property will (temporarily) change to the
newly entered context.

If an independent generator sends a value to a second independent
generator, the second generator's context will shadow the first
generator's context until the second generator returns or yields.

The `generator._context` property is private for now until experience
and feedback is collected.  Nothing is using this yet, but that will
change in future commits.

Motivations for this change:

  * First, this change makes it possible for a future commit to add
    context manager support to `contextvars.Context`.  A `yield` after
    entering a context causes execution to leave the generator with a
    different context at the top of the context stack than when
    execution started.  Swapping contexts in and out when execution
    suspends and resumes can only be done by the generator itself.

  * Second, this paves the way for a public API that will enable
    developers to guarantee that the context remains consistent
    throughout a generator's execution.  Right now the context can
    change arbitrarily during a `yield`, which can lead to subtle bugs
    that are difficult to root cause.  (Coroutines run by an asyncio
    event loop do not suffer from this same problem because asyncio
    manually sets the context each time it executes a step of an
    asynchronous function.  See the call to `contextvars.Context.run`
    in `asyncio.Handle._run`.)

  * Finally, this makes it possible to move the responsibility for
    activating an async coroutine's context from the event loop to the
    coroutine, where it more naturally belongs (alongside the rest of
    the execution state such as local variable bindings and the
    instruction pointer).  This ensures consistent behavior between
    different event loop implementations.

Example:

```python
import contextvars

cvar = contextvars.ContextVar('cvar', default='initial')

def make_generator():
    yield cvar.get()
    yield cvar.get()
    yield cvar.get()
    yield cvar.get()
    cvar.set('updated by generator')
    yield cvar.get()

gen = make_generator()
print('1.', next(gen))

def callback():
    cvar.set('updated by callback')
    print('2.', next(gen))

contextvars.copy_context().run(callback)
print('3.', next(gen))
cvar.set('updated at top level')
print('4.', next(gen))
print('5.', next(gen))
print('6.', cvar.get())
```

The above prints:

```
1. initial
2. updated by callback
3. initial
4. updated at top level
5. updated by generator
6. updated by generator
```

Now add the following line after the creation of the generator:

```python
gen._context = contextvars.copy_context()
```

With that change, the script now outputs:

```
1. initial
2. initial
3. initial
4. initial
5. updated by generator
6. updated by top level
```
rhansen added a commit to rhansen/cpython that referenced this issue Oct 10, 2024
The functions `_PyGen_ActivateContext` and `_PyGen_DeactivateContext`
are called every time a value or exception is sent to a coroutine.
These functions are no-ops for dependent coroutines (coroutines
without their own independent context stack).  Coroutines are
dependent by default, and the vast majority of performance-sensitive
coroutines are expected to be dependent, so move the check that
determines whether the coroutine is dependent or independent to an
inline function to speed up send calls.
rhansen added a commit to rhansen/cpython that referenced this issue Oct 10, 2024
rhansen added a commit to rhansen/cpython that referenced this issue Oct 12, 2024
Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for pythongh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).
rhansen added a commit to rhansen/cpython that referenced this issue Oct 12, 2024
Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for pythongh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).
1st1 pushed a commit that referenced this issue Oct 14, 2024
Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for gh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).
Eclips4 pushed a commit to Eclips4/cpython that referenced this issue Oct 16, 2024
…4776)

Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for pythongh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).
ambv pushed a commit that referenced this issue Oct 16, 2024
Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for gh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).

Co-authored-by: Richard Hansen <[email protected]>
Co-authored-by: Victor Stinner <[email protected]>
ebonnal pushed a commit to ebonnal/cpython that referenced this issue Jan 12, 2025
…5532)

Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for pythongh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).

Co-authored-by: Richard Hansen <[email protected]>
Co-authored-by: Victor Stinner <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stdlib Python modules in the Lib dir type-feature A feature request or enhancement
Projects
None yet
Development

No branches or pull requests

10 participants