diff --git a/Cargo.lock b/Cargo.lock index 5bd51f4..f32c00d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,7 +22,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "gil-knocker" -version = "0.4.0" +version = "0.4.1" dependencies = [ "parking_lot", "pyo3", @@ -52,9 +52,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.6.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ "autocfg", ] @@ -99,9 +99,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.17.3" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "268be0c73583c183f2b14052337465768c07726936a260f480f0857cb95ba543" +checksum = "cfb848f80438f926a9ebddf0a539ed6065434fd7aae03a89312a9821f81b8501" dependencies = [ "cfg-if", "indoc", @@ -116,9 +116,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.17.3" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28fcd1e73f06ec85bf3280c48c67e731d8290ad3d730f8be9dc07946923005c8" +checksum = "98a42e7f42e917ce6664c832d5eee481ad514c98250c49e0b03b20593e2c7ed0" dependencies = [ "once_cell", "target-lexicon", @@ -126,9 +126,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.17.3" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6cb136e222e49115b3c51c32792886defbfb0adead26a688142b346a0b9ffc" +checksum = "a0707f0ab26826fe4ccd59b69106e9df5e12d097457c7b8f9c0fd1d2743eec4d" dependencies = [ "libc", "pyo3-build-config", @@ -136,9 +136,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.17.3" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94144a1266e236b1c932682136dc35a9dee8d3589728f68130c7c3861ef96b28" +checksum = "978d18e61465ecd389e1f235ff5a467146dc4e3c3968b90d274fe73a5dd4a438" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.17.3" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8df9be978a2d2f0cdebabb03206ed73b11314701a5bfe71b0d753b81997777f" +checksum = "8e0e1128f85ce3fca66e435e08aa2089a2689c1c48ce97803e13f63124058462" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 79e8420..78f6434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gil-knocker" -version = "0.4.0" +version = "0.4.1" edition = "2021" authors = ["Miles Granger "] license = "MIT" @@ -19,5 +19,5 @@ codegen-units = 1 opt-level = 3 [dependencies] -pyo3 = { version = "0.17.3", features = ["extension-module"] } +pyo3 = { version = "0.18.2", features = ["extension-module"] } parking_lot = "^0.12" diff --git a/src/lib.rs b/src/lib.rs index a132ff1..c8816b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,10 +2,8 @@ use parking_lot::{const_rwlock, RwLock}; use pyo3::ffi::{PyEval_InitThreads, PyEval_ThreadsInitialized}; use pyo3::prelude::*; -use pyo3::{ - exceptions::{PyBrokenPipeError, PyRuntimeError, PyTimeoutError}, - PyResult, -}; +use pyo3::{AsPyPointer, PyResult}; +use std::ops::DerefMut; use std::{ mem::take, sync::{ @@ -120,46 +118,69 @@ impl KnockKnock { } /// Reset the contention metric/monitoring state - pub fn reset_contention_metric(&mut self) -> PyResult<()> { + pub fn reset_contention_metric(&mut self, py: Python) -> PyResult<()> { if let Some(tx) = &self.tx { // notify thread to reset metric and timers - tx.send(Message::Reset) - .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + if let Err(e) = tx.send(Message::Reset) { + let warning = py.get_type::(); + PyErr::warn(py, warning, &e.to_string(), 0)?; + } // wait for ack - self.rx + if let Err(e) = self + .rx .as_ref() .unwrap() // if tx is set, then rx is as well. .recv_timeout(self.timeout) - .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + { + let warning = py.get_type::(); + PyErr::warn(py, warning, &e.to_string(), 0)?; + } } + *(*self.contention_metric).write() = 0f32; Ok(()) } /// Start polling the GIL to check if it's locked. - pub fn start(&mut self, py: Python) -> () { + fn start(mut slf: PyRefMut<'_, Self>) -> PyResult<()> { unsafe { if PyEval_ThreadsInitialized() == 0 { PyEval_InitThreads(); } } + // Register atexit function to stop gilknocker thread + // which reduces the chance of odd 'no Python frame' core dumps + // when trying to acquire the GIL when the process has exited. + { + let ptr = slf.as_ptr(); + let py = slf.py(); + let __knocker = unsafe { PyObject::from_borrowed_ptr(py, ptr) }; + let atexit = py.import("atexit")?; + let locals = pyo3::types::PyDict::new(py); + locals.set_item("__knocker", __knocker)?; + locals.set_item("atexit", atexit)?; + py.run("atexit.register(__knocker.stop)", None, Some(locals))?; + } + + let self_: &mut KnockKnock = slf.deref_mut(); + // send messages to thread let (tx, recv) = channel(); - self.tx = Some(tx); + self_.tx = Some(tx); // recieve messages from thread let (send, rx) = channel(); - self.rx = Some(rx); + self_.rx = Some(rx); let contention_metric = Arc::new(const_rwlock(0_f32)); - self.contention_metric = contention_metric.clone(); + self_.contention_metric = contention_metric.clone(); - let polling_interval = self.polling_interval; - let sampling_interval = self.sampling_interval; - let sleeping_interval = self.sleeping_interval; + let polling_interval = self_.polling_interval; + let sampling_interval = self_.sampling_interval; + let sleeping_interval = self_.sleeping_interval; - let handle = py.allow_threads(move || { + let handle = { thread::spawn(move || { let mut total_time_waiting = Duration::from_millis(0); let mut total_time_sampling = Duration::from_millis(0); @@ -213,8 +234,9 @@ impl KnockKnock { } } }) - }); - self.handle = Some(handle); + }; + self_.handle = Some(handle); + Ok(()) } /// Is the GIL knocker thread running? @@ -224,23 +246,25 @@ impl KnockKnock { } /// Stop polling the GIL. - pub fn stop(&mut self) -> PyResult<()> { + pub fn stop(&mut self, py: Python) -> PyResult<()> { if let Some(handle) = take(&mut self.handle) { if let Some(send) = take(&mut self.tx) { - send.send(Message::Stop) - .map_err(|e| PyBrokenPipeError::new_err(e.to_string()))?; + if let Err(e) = send.send(Message::Stop) { + let warning = py.get_type::(); + PyErr::warn(py, warning, &e.to_string(), 0)?; + } let start = Instant::now(); while !handle.is_finished() { if start.elapsed() > self.timeout { - return Err(PyTimeoutError::new_err("Failed to stop knocker thread.")); + let warning = py.get_type::(); + PyErr::warn(py, warning, "Timed out waiting for sampling thread.", 0)?; + return Ok(()); } thread::sleep(Duration::from_millis(100)); } } - handle - .join() - .map_err(|_| PyRuntimeError::new_err("Failed to join knocker thread."))?; + handle.join().ok(); // Just ignore any potential panic from sampling thread. } Ok(()) }