Add safe context watcher API - #6227
Conversation
| /// storage. State can still be shared through safe static synchronization primitives. | ||
| /// | ||
| /// Panics and returned [`PyErr`][crate::PyErr] values are reported as unraisable exceptions and | ||
| /// never unwind across the C boundary. |
There was a problem hiding this comment.
Should we mention this as well?
/// # Thread Safety
///
/// On GIL-enabled builds, callbacks are invoked synchronously. On free-threaded
/// builds (when this API is available), callbacks may be invoked concurrently.
/// Ensure your callback is thread-safe if needed.
There was a problem hiding this comment.
That's true, It should definitely be mentioned if we enable the API on free threaded builds.
The problem is that PyContext_AddWatcher and PyContext_ClearWatcher while available on free threaded are not callable concurrently and require external synchronization, which is not something we can guarantee from an extension.
I was thinking of making this API only available on GIL-enabled builds for this reason.
There was a problem hiding this comment.
I think we can should requireSend + Sync + 'static on the provided closure.
There was a problem hiding this comment.
The API in this PR accepts a function path, which is coerced to a function pointer. Function pointers carry no captured state and are already Send + Sync + 'static, so there is nothing additional to enforce.
CPython’s watcher API does not expose a user-data pointer where we could store a closure, which is why I did not try to implement a closure registration api.
There was a problem hiding this comment.
@bschoenmaeckers can I ask you to give it another look ? Thanks
There was a problem hiding this comment.
The problem is that
PyContext_AddWatcherandPyContext_ClearWatcherwhile available on free threaded are not callable concurrently and require external synchronization, which is not something we can guarantee from an extension.
Where do you conclude this? We should make a CPython issue for this if not already present.
There was a problem hiding this comment.
Absolutely agree, I raised an issue in CPython for this already: python/cpython#155619
eb0dff3 to
66ad857
Compare
7259b06 to
69b141c
Compare
|
LGTM, but im not a maintainer |
|
Still, thank you for taking a look. |
b253b69 to
2041732
Compare
## Description Add a generic CPython 3.14+ publisher for the internal `python.context.switch` event. It registers the CPython `PyContext_AddWatcher` API from the native extension and dispatches the event after each context switch. The C callback catches Rust panics at the FFI boundary and preserves any exception already pending when CPython invokes it. This PR is stacked on #19604, which adds the public `DD_TRACE_OTEL_CTX_ENABLED` configuration and activation-listener setup. The setting defaults to `true`; setting it to `false` disables both the listener and this Python 3.14 watcher. It also complements #19336, which independently provides the asyncio, uvloop, AnyIO, and greenlet emitters for older Python versions. ## Testing - `scripts/lint checks` - `reno lint` - CPython 3.14 native context watcher: 4 passed - Linux CPython 3.14.5 OTel thread context: 6 passed ## Risks Low ## Additional Notes - The PR contains raw external C bindings to CPython. I am currently upstreaming them to PyO3; once that work is merged and released, we can rely on the PyO3 bindings and simplify this code: - PyO3/pyo3#6204 - PyO3/pyo3#6227 Co-authored-by: florentin.labelle <florentin.labelle@datadoghq.com>
d0a8de6 to
cf95e72
Compare
Merging this PR will not alter performance
Comparing Footnotes
|
|
I would quite like to avoid the macro for this API, it seems unfortunate. I understand why it's there, however. I'm thinking about what we can do as alternatives. |
davidhewitt
left a comment
There was a problem hiding this comment.
I opened florentinl#1 which has a suggestion how I'd like this API to look.
Also placed a few other comments here.
| /// storage. State can still be shared through safe static synchronization primitives. | ||
| /// | ||
| /// Panics and returned [`PyErr`][crate::PyErr] values are reported as unraisable exceptions and | ||
| /// never unwind across the C boundary. |
There was a problem hiding this comment.
The problem is that
PyContext_AddWatcherandPyContext_ClearWatcherwhile available on free threaded are not callable concurrently and require external synchronization, which is not something we can guarantee from an extension.
Where do you conclude this? We should make a CPython issue for this if not already present.
Fix the newsfragment's overclaim that PyContext requires GIL-enabled CPython 3.14+ (it's available on all supported versions; only the watcher API needs 3.14+), clarify the SAFETY comments on ContextWatcherGuard around single-interpreter attachment, and drop a redundant doctest cfg attribute already covered by the module-level gate in lib.rs.
cf95e72 to
e4ac1c4
Compare
|
Thank you a lot @davidhewitt for the thorough review and your design suggestion. I updated the PR to remove the macro API in favor of the design you suggested and tried to address your other comments. |
Adds
PyContextand safe context-watcher bindings for GIL-enabled CPython 3.14:Tested with unit tests, doctests, and Clippy on Python 3.14.