Skip to content

Commit 321c8c0

Browse files
fix(native): shutdown in thread for uwsgi
1 parent 3776154 commit 321c8c0

4 files changed

Lines changed: 37 additions & 13 deletions

File tree

ddtrace/internal/native/_native.pyi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,15 @@ class SharedRuntime:
180180
def shutdown(self, timeout_ms: Optional[int] = None) -> None:
181181
"""Gracefully shut down the shared runtime.
182182
183+
Args:
184+
timeout_ms: Maximum time in milliseconds to wait for shutdown.
185+
If None, waits indefinitely.
186+
"""
187+
...
188+
def shutdown_in_thread(self, timeout_ms: Optional[int] = None) -> None:
189+
"""Gracefully shut down the shared runtime.
190+
The code is run in a separate thread to bypass a stale thread local storage.
191+
183192
Args:
184193
timeout_ms: Maximum time in milliseconds to wait for shutdown.
185194
If None, waits indefinitely.

ddtrace/internal/native_runtime.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import importlib.util
12
import logging
23
from typing import Optional
34

@@ -28,6 +29,7 @@ def __init__(self) -> None:
2829
forksafe.register_after_parent(self.after_fork_parent)
2930
forksafe.register(self.after_fork_child)
3031
atexit.register(self._atexit)
32+
atexit.register_on_exit_signal(self._atexit)
3133

3234
def _atexit(self) -> None:
3335
try:
@@ -43,6 +45,11 @@ def shutdown(self, timeout_ms: Optional[int] = None) -> None:
4345
If None, waits indefinitely — only safe if all workers have
4446
already been stopped (e.g. via TraceExporter.shutdown).
4547
"""
48+
using_uwsgi = importlib.util.find_spec("uwsgi") is not None
49+
if not using_uwsgi:
50+
super().shutdown(timeout_ms=_DEFAULT_SHUTDOWN_TIMEOUT_MS)
51+
else:
52+
super().shutdown_in_thread(timeout_ms=_DEFAULT_SHUTDOWN_TIMEOUT_MS)
4653
super().shutdown(timeout_ms=timeout_ms)
4754
atexit.unregister(self._atexit)
4855
forksafe.unregister_before_fork(self.before_fork)

src/native/data_pipeline/mod.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -269,14 +269,6 @@ impl TraceExporterPy {
269269
}
270270
}
271271

272-
impl Drop for TraceExporterPy {
273-
fn drop(&mut self) {
274-
if let Some(exporter) = self.inner.take() {
275-
let _ = exporter.shutdown(Some(Duration::from_secs(3)));
276-
}
277-
}
278-
}
279-
280272
#[pymodule]
281273
pub fn register_data_pipeline(m: &Bound<'_, PyModule>) -> PyResult<()> {
282274
m.add_class::<TraceExporterBuilderPy>()?;

src/native/shared_runtime/mod.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use libdd_shared_runtime::SharedRuntime;
22
use pyo3::prelude::*;
33
use std::sync::Arc;
4+
use std::thread;
45
use std::time::Duration;
56

67
mod exceptions;
@@ -43,12 +44,27 @@ impl SharedRuntimePy {
4344
.map_err(shared_runtime_error_to_pyerr)
4445
}
4546

46-
fn shutdown(&self, timeout_ms: Option<u64>) -> PyResult<()> {
47+
fn shutdown(&self, py: Python<'_>, timeout_ms: Option<u64>) -> PyResult<()> {
4748
let timeout = timeout_ms.map(Duration::from_millis);
48-
self.inner
49-
.clone()
50-
.shutdown(timeout)
51-
.map_err(shared_runtime_error_to_pyerr)
49+
let inner = self.inner.clone();
50+
let result = py.detach(move || inner.shutdown(timeout));
51+
result.map_err(shared_runtime_error_to_pyerr)
52+
}
53+
54+
fn shutdown_in_thread(&self, py: Python<'_>, timeout_ms: Option<u64>) -> PyResult<()> {
55+
let timeout = timeout_ms.map(Duration::from_millis);
56+
let inner = self.inner.clone();
57+
// Run shutdown in a dedicated OS thread so that Tokio's `block_on`
58+
// does not execute on the Python main thread (which avoids conflicts
59+
// with thread-local Tokio runtime state). We release the GIL while
60+
// waiting for the thread to join so that other Python threads can
61+
// continue running during the (potentially slow) shutdown.
62+
let result = py.detach(move || {
63+
thread::spawn(move || inner.shutdown(timeout))
64+
.join()
65+
.unwrap_or_else(|_| panic!("SharedRuntime shutdown thread panicked"))
66+
});
67+
result.map_err(shared_runtime_error_to_pyerr)
5268
}
5369

5470
fn debug(&self) -> String {

0 commit comments

Comments
 (0)