chalk-remote-call-python 1.10.7__tar.gz → 1.10.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {chalk_remote_call_python-1.10.7/chalk_remote_call_python.egg-info → chalk_remote_call_python-1.10.9}/PKG-INFO +1 -1
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/Cargo.lock +1 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-server/Cargo.toml +3 -0
- chalk_remote_call_python-1.10.9/chalk-remote-call-rs/chalk-remote-call-server/build.rs +15 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-server/src/python_bridge.rs +243 -14
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-server/src/self_consumer.rs +1 -1
- chalk_remote_call_python-1.10.9/chalk_remote_call/_version.py +1 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9/chalk_remote_call_python.egg-info}/PKG-INFO +1 -1
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call_python.egg-info/SOURCES.txt +1 -0
- chalk_remote_call_python-1.10.7/chalk_remote_call/_version.py +0 -1
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/MANIFEST.in +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/README.md +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/Cargo.toml +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-proto/Cargo.toml +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.auth.v1.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.common.v1.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.runtime.v1.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.runtime.v1.tonic.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.utils.v1.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/descriptor.bin +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-proto/src/lib.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-server/src/async_service.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-server/src/coalesce.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-server/src/fnq_client.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-server/src/lib.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-server/src/metrics.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-server/src/server.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/chalk-remote-call-server/src/service.rs +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/rust-toolchain.toml +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/__init__.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/__main__.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/__init__.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/__init__.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/auth/__init__.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/auth/v1/__init__.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/auth/v1/permissions_pb2.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/auth/v1/permissions_pb2_grpc.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/common/__init__.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/common/v1/__init__.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/common/v1/chalk_error_pb2.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/common/v1/chalk_error_pb2_grpc.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/runtime/__init__.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/runtime/v1/__init__.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/runtime/v1/fnq_consumer_pb2.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/runtime/v1/fnq_consumer_pb2_grpc.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/runtime/v1/remote_python_call_pb2.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/runtime/v1/remote_python_call_pb2_grpc.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/utils/__init__.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/utils/v1/__init__.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/utils/v1/encoding_pb2.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/utils/v1/encoding_pb2_grpc.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/utils/v1/field_change_pb2.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/utils/v1/field_change_pb2_grpc.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/utils/v1/large_pb2.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/utils/v1/large_pb2_grpc.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/utils/v1/sensitive_pb2.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_gen/chalk/utils/v1/sensitive_pb2_grpc.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_native.pyi +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/arrow_utils.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/cli.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/errors.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/handler_loader.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/input_transform.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/logging.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/server.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/servicer.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/tracing.py +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call_python.egg-info/dependency_links.txt +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call_python.egg-info/entry_points.txt +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call_python.egg-info/requires.txt +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call_python.egg-info/top_level.txt +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/pyproject.toml +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/setup.cfg +0 -0
- {chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/setup.py +0 -0
|
@@ -11,6 +11,9 @@ crate-type = ["cdylib"]
|
|
|
11
11
|
default = ["extension-module"]
|
|
12
12
|
extension-module = ["pyo3/extension-module"]
|
|
13
13
|
|
|
14
|
+
[build-dependencies]
|
|
15
|
+
pyo3-build-config = { version = "0.29", features = ["resolve-config"] }
|
|
16
|
+
|
|
14
17
|
[dependencies]
|
|
15
18
|
chalk-remote-call-proto = { path = "../chalk-remote-call-proto" }
|
|
16
19
|
redis = { version = "1.0.2", features = ["aio", "tokio-comp", "streams", "connection-manager"] }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
fn main() {
|
|
2
|
+
println!("cargo:rerun-if-env-changed=CARGO_FEATURE_EXTENSION_MODULE");
|
|
3
|
+
if std::env::var_os("CARGO_FEATURE_EXTENSION_MODULE").is_some() {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
|
8
|
+
if matches!(target_os.as_str(), "linux" | "macos") {
|
|
9
|
+
// Embedding test binaries need the same Python library that PyO3 links,
|
|
10
|
+
// even when a managed interpreter installs it outside system paths.
|
|
11
|
+
if let Some(lib_dir) = pyo3_build_config::get().lib_dir() {
|
|
12
|
+
println!("cargo:rustc-link-arg=-Wl,-rpath,{lib_dir}");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
use std::cell::OnceCell;
|
|
1
2
|
use std::collections::HashMap;
|
|
2
3
|
use std::sync::{Arc, Mutex};
|
|
3
4
|
|
|
@@ -8,6 +9,46 @@ use pyo3::types::{PyBytes, PyDict, PyList};
|
|
|
8
9
|
use tokio::sync::{mpsc, Notify};
|
|
9
10
|
use tonic::Status;
|
|
10
11
|
|
|
12
|
+
thread_local! {
|
|
13
|
+
// This value belongs only to its OS thread, including its destructor.
|
|
14
|
+
static THREAD_ATTACHMENT: OnceCell<ThreadAttachment> = const { OnceCell::new() };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
struct ThreadAttachment {
|
|
18
|
+
state: pyo3::ffi::PyGILState_STATE,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl ThreadAttachment {
|
|
22
|
+
fn new(_py: Python<'_>) -> Self {
|
|
23
|
+
// SAFETY: the Python token guarantees an attached thread. This nested
|
|
24
|
+
// Ensure returns LOCKED and pins its state after the outer guard detaches.
|
|
25
|
+
// The outer PyO3 guard still releases the GIL between invocations.
|
|
26
|
+
let state = unsafe { pyo3::ffi::PyGILState_Ensure() };
|
|
27
|
+
Self { state }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl Drop for ThreadAttachment {
|
|
32
|
+
fn drop(&mut self) {
|
|
33
|
+
// Tokio joins its blocking threads while start_server's caller is detached.
|
|
34
|
+
// Reattach before releasing our matching LOCKED token; PyO3 then releases
|
|
35
|
+
// its outer token, deleting the state without retaining the GIL.
|
|
36
|
+
let _ = Python::try_attach(|_| unsafe {
|
|
37
|
+
// SAFETY: creation and destruction run on this same OS thread under
|
|
38
|
+
// the GIL, and this releases exactly the nested Ensure above.
|
|
39
|
+
pyo3::ffi::PyGILState_Release(self.state);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn retain_thread_state(py: Python<'_>) {
|
|
45
|
+
// pybind11 can cache this state in native TLS. Its lifetime must match the
|
|
46
|
+
// blocking worker rather than an individual Python invocation.
|
|
47
|
+
THREAD_ATTACHMENT.with(|attachment| {
|
|
48
|
+
attachment.get_or_init(|| ThreadAttachment::new(py));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
11
52
|
/// Holds references to the Python handler and bridge function.
|
|
12
53
|
pub struct PythonHandler {
|
|
13
54
|
/// The user's handler callable.
|
|
@@ -45,7 +86,7 @@ impl PythonHandler {
|
|
|
45
86
|
/// into a shared buffer). The only difference between the two paths is which
|
|
46
87
|
/// emitter object `process_batches` is handed.
|
|
47
88
|
async fn call_with_emitter<F>(
|
|
48
|
-
&
|
|
89
|
+
self: &Arc<Self>,
|
|
49
90
|
ipc_bytes: Vec<u8>,
|
|
50
91
|
function_name: String,
|
|
51
92
|
metadata: HashMap<String, String>,
|
|
@@ -55,12 +96,14 @@ impl PythonHandler {
|
|
|
55
96
|
where
|
|
56
97
|
F: FnOnce(Python<'_>) -> PyResult<Py<PyAny>> + Send + 'static,
|
|
57
98
|
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
let arg_names = self.arg_names.clone();
|
|
61
|
-
|
|
99
|
+
// GIL acquisition can block, so only clone the Rust owner on the runtime worker.
|
|
100
|
+
let owner = Arc::clone(self);
|
|
62
101
|
tokio::task::spawn_blocking(move || {
|
|
63
102
|
Python::attach(|py| -> Result<(), PythonError> {
|
|
103
|
+
retain_thread_state(py);
|
|
104
|
+
let handler = owner.handler.clone_ref(py);
|
|
105
|
+
let process_fn = &owner.process_fn;
|
|
106
|
+
let arg_names = &owner.arg_names;
|
|
64
107
|
let py_bytes = PyBytes::new(py, &ipc_bytes).into_any().unbind();
|
|
65
108
|
let py_ctx = build_context(py, &function_name, &metadata, &peer)?
|
|
66
109
|
.into_any()
|
|
@@ -81,7 +124,7 @@ impl PythonHandler {
|
|
|
81
124
|
/// Non-batched streaming dispatch. Calls `process_batches(..., emit)` so
|
|
82
125
|
/// generator results are sent to the gRPC stream as each yield is encoded.
|
|
83
126
|
pub async fn call_streaming(
|
|
84
|
-
&
|
|
127
|
+
self: &Arc<Self>,
|
|
85
128
|
ipc_bytes: Vec<u8>,
|
|
86
129
|
function_name: String,
|
|
87
130
|
metadata: HashMap<String, String>,
|
|
@@ -100,7 +143,7 @@ impl PythonHandler {
|
|
|
100
143
|
/// value, so completion never depends on emitter teardown timing (which is
|
|
101
144
|
/// what caused a Python 3.10 hang in the channel-drain version).
|
|
102
145
|
pub async fn call_into_buffer(
|
|
103
|
-
&
|
|
146
|
+
self: &Arc<Self>,
|
|
104
147
|
ipc_bytes: Vec<u8>,
|
|
105
148
|
function_name: String,
|
|
106
149
|
metadata: HashMap<String, String>,
|
|
@@ -117,15 +160,17 @@ impl PythonHandler {
|
|
|
117
160
|
/// `process_batches_coalesced`. Returns one IPC-encoded response per
|
|
118
161
|
/// caller, in input order.
|
|
119
162
|
pub async fn call_coalesced(
|
|
120
|
-
&
|
|
163
|
+
self: &Arc<Self>,
|
|
121
164
|
callers: Vec<CallerInput>,
|
|
122
165
|
) -> Result<Vec<Vec<u8>>, PythonError> {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
let arg_names = self.arg_names.clone();
|
|
126
|
-
|
|
166
|
+
// GIL acquisition can block, so only clone the Rust owner on the runtime worker.
|
|
167
|
+
let owner = Arc::clone(self);
|
|
127
168
|
tokio::task::spawn_blocking(move || {
|
|
128
169
|
Python::attach(|py| -> Result<Vec<Vec<u8>>, PythonError> {
|
|
170
|
+
retain_thread_state(py);
|
|
171
|
+
let handler = &owner.handler;
|
|
172
|
+
let process_fn = &owner.process_fn;
|
|
173
|
+
let arg_names = &owner.arg_names;
|
|
129
174
|
let py_inputs = PyList::empty(py);
|
|
130
175
|
let py_contexts = PyList::empty(py);
|
|
131
176
|
for c in &callers {
|
|
@@ -139,8 +184,8 @@ impl PythonHandler {
|
|
|
139
184
|
let py_arg_names = build_arg_names(py, arg_names.as_deref())?;
|
|
140
185
|
invoke_process_fn(
|
|
141
186
|
py,
|
|
142
|
-
|
|
143
|
-
|
|
187
|
+
process_fn,
|
|
188
|
+
handler,
|
|
144
189
|
py_inputs.into_any().unbind(),
|
|
145
190
|
py_arg_names,
|
|
146
191
|
py_contexts.into_any().unbind(),
|
|
@@ -389,3 +434,187 @@ fn format_python_traceback(py: Python<'_>, err: &PyErr) -> PyResult<String> {
|
|
|
389
434
|
let lines: Vec<String> = formatted.extract()?;
|
|
390
435
|
Ok(lines.concat().trim_end().to_string())
|
|
391
436
|
}
|
|
437
|
+
|
|
438
|
+
#[cfg(test)]
|
|
439
|
+
mod tests {
|
|
440
|
+
use super::*;
|
|
441
|
+
use std::future::{poll_fn, Future};
|
|
442
|
+
use std::task::Poll;
|
|
443
|
+
use std::time::Duration;
|
|
444
|
+
|
|
445
|
+
#[derive(Clone, Copy)]
|
|
446
|
+
enum Dispatch {
|
|
447
|
+
Streaming,
|
|
448
|
+
Buffered,
|
|
449
|
+
Coalesced,
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Polling a dispatch must yield even while another thread owns the GIL:
|
|
453
|
+
// otherwise Python contention blocks the worker that also serves health RPCs.
|
|
454
|
+
fn dispatch_yields_while_gil_is_held(dispatch: Dispatch) {
|
|
455
|
+
Python::initialize();
|
|
456
|
+
let (yielded, worker) = Python::attach(|py| {
|
|
457
|
+
let handler = Arc::new(PythonHandler::new(
|
|
458
|
+
py.None(),
|
|
459
|
+
py.eval(pyo3::ffi::c_str!("lambda *args: []"), None, None)
|
|
460
|
+
.unwrap()
|
|
461
|
+
.unbind(),
|
|
462
|
+
None,
|
|
463
|
+
));
|
|
464
|
+
let (polled_tx, polled_rx) = std::sync::mpsc::channel();
|
|
465
|
+
let worker = std::thread::spawn(move || {
|
|
466
|
+
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
467
|
+
.build()
|
|
468
|
+
.unwrap();
|
|
469
|
+
runtime.block_on(async move {
|
|
470
|
+
let call = async {
|
|
471
|
+
match dispatch {
|
|
472
|
+
Dispatch::Streaming => {
|
|
473
|
+
let (tx, _rx) = mpsc::channel(1);
|
|
474
|
+
handler
|
|
475
|
+
.call_streaming(
|
|
476
|
+
Vec::new(),
|
|
477
|
+
String::new(),
|
|
478
|
+
HashMap::new(),
|
|
479
|
+
String::new(),
|
|
480
|
+
tx,
|
|
481
|
+
)
|
|
482
|
+
.await
|
|
483
|
+
}
|
|
484
|
+
Dispatch::Buffered => {
|
|
485
|
+
handler
|
|
486
|
+
.call_into_buffer(
|
|
487
|
+
Vec::new(),
|
|
488
|
+
String::new(),
|
|
489
|
+
HashMap::new(),
|
|
490
|
+
String::new(),
|
|
491
|
+
ChunkBuffer::new().into_shared(),
|
|
492
|
+
)
|
|
493
|
+
.await
|
|
494
|
+
}
|
|
495
|
+
Dispatch::Coalesced => {
|
|
496
|
+
handler.call_coalesced(Vec::new()).await.map(|_| ())
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
tokio::pin!(call);
|
|
501
|
+
poll_fn(|cx| {
|
|
502
|
+
assert!(call.as_mut().poll(cx).is_pending());
|
|
503
|
+
let _ = polled_tx.send(());
|
|
504
|
+
Poll::Ready(())
|
|
505
|
+
})
|
|
506
|
+
.await;
|
|
507
|
+
call.await.unwrap();
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
// No Python bytecode runs here, so CPython cannot periodically release
|
|
511
|
+
// the GIL and accidentally let a blocking attach pass the test.
|
|
512
|
+
let yielded = polled_rx.recv_timeout(Duration::from_secs(5)).is_ok();
|
|
513
|
+
(yielded, worker)
|
|
514
|
+
});
|
|
515
|
+
// Release the GIL before joining so even a regressed implementation exits.
|
|
516
|
+
worker.join().unwrap();
|
|
517
|
+
assert!(
|
|
518
|
+
yielded,
|
|
519
|
+
"dispatch blocked its Tokio worker acquiring the GIL"
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
#[test]
|
|
524
|
+
fn streaming_dispatch_yields_under_gil_contention() {
|
|
525
|
+
dispatch_yields_while_gil_is_held(Dispatch::Streaming);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
#[test]
|
|
529
|
+
fn buffered_dispatch_yields_under_gil_contention() {
|
|
530
|
+
dispatch_yields_while_gil_is_held(Dispatch::Buffered);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
#[test]
|
|
534
|
+
fn coalesced_dispatch_yields_under_gil_contention() {
|
|
535
|
+
dispatch_yields_while_gil_is_held(Dispatch::Coalesced);
|
|
536
|
+
}
|
|
537
|
+
fn contains_thread_state(_py: Python<'_>, id: i64) -> bool {
|
|
538
|
+
// SAFETY: interpreter state iteration and ID access occur under the GIL.
|
|
539
|
+
unsafe {
|
|
540
|
+
let interpreter =
|
|
541
|
+
pyo3::ffi::PyThreadState_GetInterpreter(pyo3::ffi::PyThreadState_Get());
|
|
542
|
+
let mut state = pyo3::ffi::PyInterpreterState_ThreadHead(interpreter);
|
|
543
|
+
while !state.is_null() {
|
|
544
|
+
if pyo3::ffi::PyThreadState_GetID(state) == id {
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
state = pyo3::ffi::PyThreadState_Next(state);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
false
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async fn blocking_thread_state_id() -> i64 {
|
|
554
|
+
tokio::task::spawn_blocking(|| {
|
|
555
|
+
Python::attach(|py| {
|
|
556
|
+
retain_thread_state(py);
|
|
557
|
+
// SAFETY: this call runs under the live attachment above.
|
|
558
|
+
unsafe { pyo3::ffi::PyThreadState_GetID(pyo3::ffi::PyThreadState_Get()) }
|
|
559
|
+
})
|
|
560
|
+
})
|
|
561
|
+
.await
|
|
562
|
+
.unwrap()
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
#[test]
|
|
566
|
+
fn blocking_worker_reuses_state_and_releases_it_at_shutdown() {
|
|
567
|
+
Python::initialize();
|
|
568
|
+
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
569
|
+
.max_blocking_threads(1)
|
|
570
|
+
.build()
|
|
571
|
+
.unwrap();
|
|
572
|
+
let ids = runtime.block_on(async {
|
|
573
|
+
let mut ids = Vec::new();
|
|
574
|
+
for _ in 0..40 {
|
|
575
|
+
ids.push(blocking_thread_state_id().await);
|
|
576
|
+
}
|
|
577
|
+
ids
|
|
578
|
+
});
|
|
579
|
+
assert!(ids.iter().all(|id| *id == ids[0]));
|
|
580
|
+
assert!(Python::attach(|py| contains_thread_state(py, ids[0])));
|
|
581
|
+
let failed = runtime.block_on(async {
|
|
582
|
+
tokio::task::spawn_blocking(|| {
|
|
583
|
+
Python::attach(|py| {
|
|
584
|
+
retain_thread_state(py);
|
|
585
|
+
panic!("test blocking job panic");
|
|
586
|
+
});
|
|
587
|
+
})
|
|
588
|
+
.await
|
|
589
|
+
.unwrap_err()
|
|
590
|
+
});
|
|
591
|
+
assert!(failed.is_panic());
|
|
592
|
+
assert_eq!(ids[0], runtime.block_on(blocking_thread_state_id()));
|
|
593
|
+
// Joining must happen with this test thread detached so TLS can clean up.
|
|
594
|
+
drop(runtime);
|
|
595
|
+
assert!(!Python::attach(|py| contains_thread_state(py, ids[0])));
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
#[test]
|
|
599
|
+
fn idle_blocking_worker_releases_state_before_replacement() {
|
|
600
|
+
Python::initialize();
|
|
601
|
+
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
602
|
+
.max_blocking_threads(1)
|
|
603
|
+
.thread_keep_alive(Duration::from_millis(20))
|
|
604
|
+
.build()
|
|
605
|
+
.unwrap();
|
|
606
|
+
let original = runtime.block_on(blocking_thread_state_id());
|
|
607
|
+
let deadline = std::time::Instant::now() + Duration::from_secs(5);
|
|
608
|
+
while Python::attach(|py| contains_thread_state(py, original)) {
|
|
609
|
+
assert!(
|
|
610
|
+
std::time::Instant::now() < deadline,
|
|
611
|
+
"idle worker state was not released"
|
|
612
|
+
);
|
|
613
|
+
std::thread::sleep(Duration::from_millis(5));
|
|
614
|
+
}
|
|
615
|
+
let replacement = runtime.block_on(blocking_thread_state_id());
|
|
616
|
+
assert_ne!(original, replacement);
|
|
617
|
+
drop(runtime);
|
|
618
|
+
assert!(!Python::attach(|py| contains_thread_state(py, replacement)));
|
|
619
|
+
}
|
|
620
|
+
}
|
|
@@ -1351,7 +1351,7 @@ fn ensure_traceparent(headers: &mut HashMap<String, String>) -> String {
|
|
|
1351
1351
|
/// `end`, or `error`) to the call's result stream, updating meta status along
|
|
1352
1352
|
/// the way.
|
|
1353
1353
|
async fn execute_and_write(
|
|
1354
|
-
handler: &PythonHandler
|
|
1354
|
+
handler: &Arc<PythonHandler>,
|
|
1355
1355
|
output: &mut CallOutput,
|
|
1356
1356
|
work: QueuedCall,
|
|
1357
1357
|
flush_timeout: Duration,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.10.9"
|
|
@@ -14,6 +14,7 @@ chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.runtime.v1.tonic.rs
|
|
|
14
14
|
chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.utils.v1.rs
|
|
15
15
|
chalk-remote-call-rs/chalk-remote-call-proto/src/gen/descriptor.bin
|
|
16
16
|
chalk-remote-call-rs/chalk-remote-call-server/Cargo.toml
|
|
17
|
+
chalk-remote-call-rs/chalk-remote-call-server/build.rs
|
|
17
18
|
chalk-remote-call-rs/chalk-remote-call-server/src/async_service.rs
|
|
18
19
|
chalk-remote-call-rs/chalk-remote-call-server/src/coalesce.rs
|
|
19
20
|
chalk-remote-call-rs/chalk-remote-call-server/src/fnq_client.rs
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.10.7"
|
|
File without changes
|
|
File without changes
|
{chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk-remote-call-rs/Cargo.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/__init__.py
RENAMED
|
File without changes
|
{chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/_native.pyi
RENAMED
|
File without changes
|
{chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/arrow_utils.py
RENAMED
|
File without changes
|
{chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/cli.py
RENAMED
|
File without changes
|
{chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/errors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/logging.py
RENAMED
|
File without changes
|
{chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/server.py
RENAMED
|
File without changes
|
{chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/servicer.py
RENAMED
|
File without changes
|
{chalk_remote_call_python-1.10.7 → chalk_remote_call_python-1.10.9}/chalk_remote_call/tracing.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|