Skip to content

Commit de9cddc

Browse files
Yun-Kimmergify[bot]
authored andcommitted
feat(tracer): add Python 3.11 support (#4125)
Python 3.11 has made some new changes to their C internal API that we need to address in a couple of places, mostly in our profiler. I've moved the profiler changes into #4343 and changed this PR to focus on the tracer and integrations supporting Python 3.11. Note that some of the profiler changes were kept in this PR to ensure that the profiler can compile with Python 3.11 per our setup/install rules. The major tracer-related fix is: Python 3.11 moved _PyFloat_Pack8() into their internal C API and replaced it with PyFloat_Pack8() to their public C API (Link to CPython issue). We use it once in ddtrace.internal.pack_template.h where I've added a Python version check to use the correct corresponding function. Note: grpcio with pytest-asyncio is causing Python segmentation faults on the test_unary_exception and test_unary_cancellation test cases, and from the traceback it doesn't look like dd-trace-py is involved. You can reproduce this using this gist which does not involve dd-trace-py. As a result I am skipping the grpcio_asyncio tests with Python 3.11. Co-authored-by: Kyle Verhoog <[email protected]> (cherry picked from commit c94cc14) # Conflicts: # tox.ini
1 parent d5f8acf commit de9cddc

File tree

17 files changed

+302
-130
lines changed

17 files changed

+302
-130
lines changed

.circleci/config.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,10 @@ jobs:
398398
steps:
399399
- run_test:
400400
pattern: "tracer"
401+
- run_tox_scenario:
402+
# Riot venvs break with Py 3.11 importlib, specifically with hypothesis (test_http.py).
403+
# We skip the test_http.py tests in riot and run the test_http.py tests through tox.
404+
pattern: '^py.\+-tracer_test_http'
401405

402406
telemetry:
403407
<<: *machine_executor
@@ -419,7 +423,7 @@ jobs:
419423
parallelism: 7
420424
steps:
421425
- run_tox_scenario:
422-
pattern: '^py..-opentracer'
426+
pattern: '^py.\+-opentracer'
423427

424428
# Building gevent (for which we never want wheels because they crash)
425429
# on Python 2.7 requires Microsoft Visual C++ 9.0 which is not installed. :(

ddtrace/debugging/_debugger.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ class Debugger(Service):
152152
def enable(cls, run_module=False):
153153
# type: (bool) -> None
154154
"""Enable the debugger (idempotent)."""
155+
if sys.version_info >= (3, 11, 0):
156+
raise RuntimeError(
157+
"Dynamic Instrumentation is not yet compatible with Python 3.11. "
158+
"See tracking issue for more details: https://github.com/DataDog/dd-trace-py/issues/4149"
159+
)
155160
if cls._instance is not None:
156161
log.debug("%s already enabled", cls.__name__)
157162
return

ddtrace/internal/pack_template.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,12 @@ static inline int msgpack_pack_double(msgpack_packer* x, double d)
558558
{
559559
unsigned char buf[9];
560560
buf[0] = 0xcb;
561-
_PyFloat_Pack8(d, &buf[1], 0);
561+
// Python 3.11 introduced PyFloat_Pack8() to the public C API and moved _PyFloat_Pack8() to the internal C API
562+
#if PY_VERSION_HEX <= 0x030B0000
563+
_PyFloat_Pack8(d, &buf[1], 0);
564+
#else
565+
PyFloat_Pack8(d, &buf[1], 0);
566+
#endif
562567
msgpack_pack_append_buffer(x, buf, 9);
563568
}
564569

ddtrace/profiling/collector/stack.pyx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,17 @@ IF UNAME_SYSNAME != "Windows" and PY_MAJOR_VERSION >= 3 and PY_MINOR_VERSION >=
165165

166166
_PyErr_StackItem * _PyErr_GetTopmostException(PyThreadState *tstate)
167167

168-
ctypedef struct _PyErr_StackItem:
169-
PyObject* exc_type
170-
PyObject* exc_value
171-
PyObject* exc_traceback
168+
IF PY_MINOR_VERSION < 11:
169+
ctypedef struct _PyErr_StackItem:
170+
PyObject* exc_type
171+
PyObject* exc_value
172+
PyObject* exc_traceback
173+
ELSE:
174+
ctypedef struct _PyErr_StackItem:
175+
PyObject* exc_value
176+
177+
PyObject* PyException_GetTraceback(PyObject* exc)
178+
PyObject* Py_TYPE(PyObject* ob)
172179

173180
IF PY_MINOR_VERSION == 7:
174181
# Python 3.7
@@ -198,6 +205,8 @@ IF UNAME_SYSNAME != "Windows" and PY_MAJOR_VERSION >= 3 and PY_MINOR_VERSION >=
198205
# Needed for accessing _PyGC_FINALIZED when we build with -DPy_BUILD_CORE
199206
cdef extern from "<internal/pycore_gc.h>":
200207
pass
208+
cdef extern from "<Python.h>":
209+
PyObject* PyThreadState_GetFrame(PyThreadState* tstate)
201210
ELSE:
202211
from cpython.ref cimport Py_DECREF
203212

@@ -214,7 +223,8 @@ cdef collect_threads(thread_id_ignore_list, thread_time, thread_span_links) with
214223
cdef PyThreadState* tstate
215224
cdef _PyErr_StackItem* exc_info
216225
cdef PyThread_type_lock lmutex = _PyRuntime.interpreters.mutex
217-
226+
cdef PyObject* exc_type
227+
cdef PyObject* exc_tb
218228
cdef dict running_threads = {}
219229

220230
# This is an internal lock but we do need it.
@@ -230,13 +240,22 @@ cdef collect_threads(thread_id_ignore_list, thread_time, thread_span_links) with
230240
tstate = PyInterpreterState_ThreadHead(interp)
231241
while tstate:
232242
# The frame can be NULL
233-
if tstate.frame:
234-
running_threads[tstate.thread_id] = <object>tstate.frame
235-
243+
# Python 3.9 added helper function to public C API to access PyFrameObject from tstate,
244+
# Python 3.11 moved PyFrameObject to internal C API and cannot be directly accessed from tstate
245+
IF PY_MINOR_VERSION >= 9:
246+
frame = PyThreadState_GetFrame(tstate)
247+
ELSE:
248+
frame = tstate.frame
249+
if frame:
250+
running_threads[tstate.thread_id] = <object>frame
236251
exc_info = _PyErr_GetTopmostException(tstate)
237-
if exc_info and exc_info.exc_type and exc_info.exc_traceback:
238-
current_exceptions[tstate.thread_id] = (<object>exc_info.exc_type, <object>exc_info.exc_traceback)
239-
252+
if exc_info and exc_info.exc_value:
253+
# Python 3.11 removed exc_type, exc_traceback from exception representations, can instead
254+
# derive exc_type and exc_traceback from remaining exc_value field
255+
exc_type = Py_TYPE(exc_info.exc_value)
256+
exc_tb = PyException_GetTraceback(exc_info.exc_value)
257+
if exc_type and exc_tb:
258+
current_exceptions[tstate.thread_id] = (<object>exc_type, <object>exc_tb)
240259
tstate = PyThreadState_Next(tstate)
241260

242261
interp = PyInterpreterState_Next(interp)

ddtrace/profiling/profiler.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- encoding: utf-8 -*-
22
import logging
33
import os
4+
import sys
45
import typing
56
from typing import List
67
from typing import Optional
@@ -57,6 +58,11 @@ def start(self, stop_on_exit=True, profile_children=True):
5758
:param profile_children: Whether to start a profiler in child processes.
5859
"""
5960

61+
if sys.version_info >= (3, 11, 0):
62+
raise RuntimeError(
63+
"Profiling is not yet compatible with Python 3.11. "
64+
"See tracking issue for more details: https://github.com/DataDog/dd-trace-py/issues/4149"
65+
)
6066
if profile_children:
6167
try:
6268
uwsgi.check_uwsgi(self._restart_on_fork, atexit=self.stop if stop_on_exit else None)

docs/versioning.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,15 @@ Supported runtimes
108108
* - Linux
109109
- x86-64, i686, AArch64
110110
- CPython
111-
- 2.7, 3.5-3.10
111+
- 2.7, 3.5-3.11
112112
- ``<2``
113113
* - MacOS
114114
- Intel, Apple Silicon
115115
- CPython
116-
- 2.7, 3.5-3.10
116+
- 2.7, 3.5-3.11
117117
- ``<2``
118118
* - Windows
119119
- 64bit, 32bit
120120
- CPython
121-
- 2.7, 3.5-3.10
121+
- 2.7, 3.5-3.11
122122
- ``<2``
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
prelude: >
3+
Initial library support has been added for Python 3.11. Continuous Profiler and Dynamic Instrumentation are not yet compatible and must be disabled in order to use the library with Python 3.11. Support for Continuous Profiler and Dynamic Instrumentation will be added in a future release.
4+
For full Python 3.11 support status please see: https://github.com/DataDog/dd-trace-py/issues/4149
5+
features:
6+
- |
7+
tracer: added support for Python 3.11.
8+
upgrade:
9+
- |
10+
Python 3.11: Continuous Profiler and Dynamic Instrumentation must be disabled as they do not current support Python 3.11.

0 commit comments

Comments
 (0)