From 49e52a8f4aab690046a9d8ca8e2f65b9b1b8e598 Mon Sep 17 00:00:00 2001 From: vaibhavatlan <188950052+vaibhavatlan@users.noreply.github.com> Date: Sat, 13 Jun 2026 22:13:16 +0530 Subject: [PATCH] Update pyo3 to 0.29 Bump pyo3, pyo3-async-runtimes, and pythonize from 0.25 to 0.29 to pick up the fix for RUSTSEC-2026-0176 / GHSA-36hh-v3qg-5jq4, an out-of-bounds read in the PyList/PyTuple nth/nth_back iterators, first patched in pyo3 0.29.0. Adapt the bridge to the pyo3 0.26+ API: - Python::with_gil -> Python::attach - PyObject -> Py (alias removed) - implement Runtime::spawn_blocking for TokioRuntime - add the Send + 'static bound now required by future_into_py - opt the Clone pyclasses back into FromPyObject via from_py_object - TaskLocals::clone_ref -> clone --- temporalio/bridge/Cargo.lock | 60 ++++++++---------------------- temporalio/bridge/Cargo.toml | 6 +-- temporalio/bridge/src/client.rs | 2 +- temporalio/bridge/src/envconfig.rs | 18 ++++----- temporalio/bridge/src/metric.rs | 6 +-- temporalio/bridge/src/runtime.rs | 22 ++++++----- temporalio/bridge/src/worker.rs | 38 +++++++++---------- 7 files changed, 64 insertions(+), 88 deletions(-) diff --git a/temporalio/bridge/Cargo.lock b/temporalio/bridge/Cargo.lock index 616722968..8b37057f4 100644 --- a/temporalio/bridge/Cargo.lock +++ b/temporalio/bridge/Cargo.lock @@ -1008,15 +1008,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - [[package]] name = "instant" version = "0.1.13" @@ -1222,15 +1213,6 @@ version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "mime" version = "0.3.17" @@ -1744,30 +1726,28 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" +checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c" dependencies = [ "anyhow", - "indoc", "inventory", "libc", - "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "unindent", ] [[package]] name = "pyo3-async-runtimes" -version = "0.25.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d73cc6b1b7d8b3cef02101d37390dbdfe7e450dfea14921cae80a9534ba59ef2" +checksum = "b3ef68daa7316a3fac65e5e18b2203f010346de1c1c53456811a2624673ab046" dependencies = [ - "futures", + "futures-channel", + "futures-util", "once_cell", "pin-project-lite", "pyo3", @@ -1776,19 +1756,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" +checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" +checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b" dependencies = [ "libc", "pyo3-build-config", @@ -1796,9 +1775,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50" +checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1808,22 +1787,21 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc" +checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362" dependencies = [ "heck", "proc-macro2", - "pyo3-build-config", "quote", "syn", ] [[package]] name = "pythonize" -version = "0.25.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597907139a488b22573158793aa7539df36ae863eba300c75f3a0d65fc475e27" +checksum = "6ec376e1216e0c929a74964ce2020012a1a39f32d80e78aa688721219ea7fb89" dependencies = [ "pyo3", "serde", @@ -3135,12 +3113,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "unindent" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" - [[package]] name = "untrusted" version = "0.9.0" diff --git a/temporalio/bridge/Cargo.toml b/temporalio/bridge/Cargo.toml index ade771d73..1e21e136d 100644 --- a/temporalio/bridge/Cargo.toml +++ b/temporalio/bridge/Cargo.toml @@ -20,14 +20,14 @@ anyhow = "1.0" async-trait = "0.1" futures = "0.3" prost = "0.14" -pyo3 = { version = "0.25", features = [ +pyo3 = { version = "0.29", features = [ "extension-module", "abi3-py310", "anyhow", "multiple-pymethods", ] } -pyo3-async-runtimes = { version = "0.25", features = ["tokio-runtime"] } -pythonize = "0.25" +pyo3-async-runtimes = { version = "0.29", features = ["tokio-runtime"] } +pythonize = "0.29" temporalio-client = { version = "0.4", path = "./sdk-core/crates/client" } temporalio-common = { version = "0.4", path = "./sdk-core/crates/common", features = [ "envconfig", "otel" diff --git a/temporalio/bridge/src/client.rs b/temporalio/bridge/src/client.rs index 906da5287..85dedef94 100644 --- a/temporalio/bridge/src/client.rs +++ b/temporalio/bridge/src/client.rs @@ -197,7 +197,7 @@ where match res { Ok(resp) => Ok(resp.get_ref().encode_to_vec()), Err(err) => { - Python::with_gil(move |py| { + Python::attach(move |py| { // Create tuple of "status", "message", and optional "details" let code = err.code() as u32; let message = err.message().to_owned(); diff --git a/temporalio/bridge/src/envconfig.rs b/temporalio/bridge/src/envconfig.rs index fb0da290c..40ef9b638 100644 --- a/temporalio/bridge/src/envconfig.rs +++ b/temporalio/bridge/src/envconfig.rs @@ -14,7 +14,7 @@ use temporalio_common::envconfig::{ pyo3::create_exception!(temporal_sdk_bridge, ConfigError, PyRuntimeError); -fn data_source_to_dict(py: Python, ds: &DataSource) -> PyResult { +fn data_source_to_dict(py: Python, ds: &DataSource) -> PyResult> { let dict = PyDict::new(py); match ds { DataSource::Path(p) => dict.set_item("path", p)?, @@ -23,7 +23,7 @@ fn data_source_to_dict(py: Python, ds: &DataSource) -> PyResult { Ok(dict.into()) } -fn tls_to_dict(py: Python, tls: &CoreClientConfigTLS) -> PyResult { +fn tls_to_dict(py: Python, tls: &CoreClientConfigTLS) -> PyResult> { let dict = PyDict::new(py); dict.set_item("disabled", tls.disabled)?; if let Some(v) = &tls.client_cert { @@ -42,7 +42,7 @@ fn tls_to_dict(py: Python, tls: &CoreClientConfigTLS) -> PyResult { Ok(dict.into()) } -fn codec_to_dict(py: Python, codec: &ClientConfigCodec) -> PyResult { +fn codec_to_dict(py: Python, codec: &ClientConfigCodec) -> PyResult> { let dict = PyDict::new(py); if let Some(v) = &codec.endpoint { dict.set_item("endpoint", v)?; @@ -53,7 +53,7 @@ fn codec_to_dict(py: Python, codec: &ClientConfigCodec) -> PyResult { Ok(dict.into()) } -fn profile_to_dict(py: Python, profile: &CoreClientConfigProfile) -> PyResult { +fn profile_to_dict(py: Python, profile: &CoreClientConfigProfile) -> PyResult> { let dict = PyDict::new(py); if let Some(v) = &profile.address { dict.set_item("address", v)?; @@ -76,7 +76,7 @@ fn profile_to_dict(py: Python, profile: &CoreClientConfigProfile) -> PyResult PyResult { +fn core_config_to_dict(py: Python, core_config: &CoreClientConfig) -> PyResult> { let profiles_dict = PyDict::new(py); for (name, profile) in &core_config.profiles { let connect_dict = profile_to_dict(py, profile)?; @@ -90,7 +90,7 @@ fn load_client_config_inner( config_source: Option, config_file_strict: bool, env_vars: Option>, -) -> PyResult { +) -> PyResult> { let options = LoadClientConfigOptions { config_source, config_file_strict, @@ -109,7 +109,7 @@ fn load_client_connect_config_inner( disable_env: bool, config_file_strict: bool, env_vars: Option>, -) -> PyResult { +) -> PyResult> { let options = LoadClientConfigProfileOptions { config_source, config_file_profile: profile, @@ -132,7 +132,7 @@ pub fn load_client_config( data: Option>, config_file_strict: bool, env_vars: Option>, -) -> PyResult { +) -> PyResult> { let config_source = match (path, data) { (Some(p), None) => Some(DataSource::Path(p)), (None, Some(d)) => Some(DataSource::Data(d)), @@ -158,7 +158,7 @@ pub fn load_client_connect_config( disable_env: bool, config_file_strict: bool, env_vars: Option>, -) -> PyResult { +) -> PyResult> { let config_source = match (path, data) { (Some(p), None) => Some(DataSource::Path(p)), (None, Some(d)) => Some(DataSource::Data(d)), diff --git a/temporalio/bridge/src/metric.rs b/temporalio/bridge/src/metric.rs index 445adfea9..276933f6d 100644 --- a/temporalio/bridge/src/metric.rs +++ b/temporalio/bridge/src/metric.rs @@ -19,7 +19,7 @@ pub struct MetricMeterRef { default_attributes: MetricAttributesRef, } -#[pyclass] +#[pyclass(from_py_object)] #[derive(Clone)] pub struct MetricAttributesRef { attrs: metrics::MetricAttributes, @@ -216,7 +216,7 @@ impl MetricAttributesRef { &self, py: Python, meter: &MetricMeterRef, - new_attrs: HashMap, + new_attrs: HashMap>, ) -> PyResult { let attrs = meter.meter.extend_attributes( self.attrs.clone(), @@ -234,7 +234,7 @@ impl MetricAttributesRef { fn metric_key_value_from_py( py: Python, k: String, - obj: PyObject, + obj: Py, ) -> PyResult { let val = if let Ok(v) = obj.extract::(py) { metrics::MetricValue::String(v) diff --git a/temporalio/bridge/src/runtime.rs b/temporalio/bridge/src/runtime.rs index 94cf5a025..26fd3482b 100644 --- a/temporalio/bridge/src/runtime.rs +++ b/temporalio/bridge/src/runtime.rs @@ -47,7 +47,7 @@ pub struct TelemetryConfig { #[derive(FromPyObject)] pub struct LoggingConfig { filter: String, - forward_to: Option, + forward_to: Option>, } #[pyclass] @@ -105,7 +105,7 @@ pub fn init_runtime(options: RuntimeOptions) -> PyResult { let telemetry_build = TelemetryOptions::builder(); // Build logging config, capturing forwarding info to start later - let mut log_forwarding: Option<(Receiver, PyObject)> = None; + let mut log_forwarding: Option<(Receiver, Py)> = None; let maybe_logging = if let Some(logging_conf) = logging { Some(if let Some(forward_to) = logging_conf.forward_to { // Note, actual log forwarding is started later @@ -177,7 +177,7 @@ pub fn init_runtime(options: RuntimeOptions) -> PyResult { .collect::>(); // We silently swallow errors here because logging them could // cause a bad loop and we don't want to assume console presence - let _ = Python::with_gil(|py| callback.call1(py, (entries,))); + let _ = Python::attach(|py| callback.call1(py, (entries,))); } })) }); @@ -204,7 +204,7 @@ impl Runtime { pub fn future_into_py<'a, F, T>(&self, py: Python<'a>, fut: F) -> PyResult> where F: Future> + Send + 'static, - T: for<'py> IntoPyObject<'py>, + T: for<'py> IntoPyObject<'py> + Send + 'static, { let _guard = self.core.tokio_handle().enter(); pyo3_async_runtimes::generic::future_into_py::(py, fut) @@ -310,7 +310,7 @@ impl BufferedLogEntry { } #[getter] - fn fields(&self, py: Python<'_>) -> PyResult> { + fn fields(&self, py: Python<'_>) -> PyResult>> { self.core_log .fields .iter() @@ -413,6 +413,13 @@ impl pyo3_async_runtimes::generic::Runtime for TokioRuntime { { tokio::runtime::Handle::current().spawn(fut) } + + fn spawn_blocking(f: F) -> Self::JoinHandle + where + F: FnOnce() + Send + 'static, + { + tokio::runtime::Handle::current().spawn_blocking(f) + } } impl pyo3_async_runtimes::generic::ContextExt for TokioRuntime { @@ -431,10 +438,7 @@ impl pyo3_async_runtimes::generic::ContextExt for TokioRuntime { fn get_task_locals() -> Option { TASK_LOCALS - .try_with(|c| { - c.get() - .map(|locals| Python::with_gil(|py| locals.clone_ref(py))) - }) + .try_with(|c| c.get().cloned()) .unwrap_or_default() } } diff --git a/temporalio/bridge/src/worker.rs b/temporalio/bridge/src/worker.rs index d37226614..e530e89bb 100644 --- a/temporalio/bridge/src/worker.rs +++ b/temporalio/bridge/src/worker.rs @@ -231,9 +231,9 @@ impl SlotReserveCtx { #[pyclass] pub struct SlotMarkUsedCtx { #[pyo3(get)] - slot_info: PyObject, + slot_info: Py, #[pyo3(get)] - permit: PyObject, + permit: Py, } // NOTE: this is dumb because we already have the generated proto code, we just can't use @@ -268,9 +268,9 @@ pub struct NexusSlotInfo { #[pyclass] pub struct SlotReleaseCtx { #[pyo3(get)] - slot_info: Option, + slot_info: Option>, #[pyo3(get)] - permit: PyObject, + permit: Py, } fn slot_info_to_py_obj<'py>(py: Python<'py>, info: SlotInfo) -> PyResult> { @@ -300,14 +300,14 @@ fn slot_info_to_py_obj<'py>(py: Python<'py>, info: SlotInfo) -> PyResult, + inner: Arc>, } struct CustomSlotSupplierOfType { - inner: Arc, + inner: Arc>, event_loop_task_locals: Arc>, _phantom: PhantomData, } @@ -315,7 +315,7 @@ struct CustomSlotSupplierOfType { #[pymethods] impl CustomSlotSupplier { #[new] - fn new(inner: PyObject) -> Self { + fn new(inner: Py) -> Self { CustomSlotSupplier { inner: Arc::new(inner), } @@ -329,23 +329,23 @@ impl CustomSlotSupplier { #[pyclass] struct CreatedTaskForSlotCallback { - stored_task: Arc>, + stored_task: Arc>>, } #[pymethods] impl CreatedTaskForSlotCallback { - fn __call__(&self, task: PyObject) -> PyResult<()> { + fn __call__(&self, task: Py) -> PyResult<()> { self.stored_task.set(task).expect("must only be set once"); Ok(()) } } struct TaskCanceller { - stored_task: Arc>, + stored_task: Arc>>, } impl TaskCanceller { - fn new(stored_task: Arc>) -> Self { + fn new(stored_task: Arc>>) -> Self { TaskCanceller { stored_task } } } @@ -353,7 +353,7 @@ impl TaskCanceller { impl Drop for TaskCanceller { fn drop(&mut self) { if let Some(task) = self.stored_task.get() { - Python::with_gil(|py| { + Python::attach(|py| { task.call_method0(py, "cancel") .expect("Failed to cancel task"); }); @@ -369,7 +369,7 @@ impl SlotSupplierTrait for CustomSlotSupplierOfType< loop { let stored_task = Arc::new(OnceLock::new()); let _task_canceller = TaskCanceller::new(stored_task.clone()); - let pypermit = match Python::with_gil(|py| { + let pypermit = match Python::attach(|py| { let py_obj = self.inner.bind(py); let called = py_obj.call_method1( "reserve_slot", @@ -404,7 +404,7 @@ impl SlotSupplierTrait for CustomSlotSupplierOfType< } fn try_reserve_slot(&self, ctx: &dyn SlotReservationContext) -> Option { - Python::with_gil(|py| { + Python::attach(|py| { let py_obj = self.inner.bind(py); let pa = py_obj.call_method1( "try_reserve_slot", @@ -425,10 +425,10 @@ impl SlotSupplierTrait for CustomSlotSupplierOfType< } fn mark_slot_used(&self, ctx: &dyn SlotMarkUsedContext) { - if let Err(e) = Python::with_gil(|py| { + if let Err(e) = Python::attach(|py| { let permit = ctx .permit() - .user_data::() + .user_data::>() .map(|o| o.clone_ref(py)) .unwrap_or_else(|| py.None()); let py_obj = self.inner.bind(py); @@ -446,10 +446,10 @@ impl SlotSupplierTrait for CustomSlotSupplierOfType< } fn release_slot(&self, ctx: &dyn SlotReleaseContext) { - if let Err(e) = Python::with_gil(|py| { + if let Err(e) = Python::attach(|py| { let permit = ctx .permit() - .user_data::() + .user_data::>() .map(|o| o.clone_ref(py)) .unwrap_or_else(|| py.None()); let py_obj = self.inner.bind(py);