Skip to content

Commit

Permalink
Detect GIL on python 3.12 (#713)
Browse files Browse the repository at this point in the history
Use the InterpreterState._gil member to figure out both
if the gil is locked, and if so which thread is holding the GIL
  • Loading branch information
benfred authored Oct 30, 2024
1 parent 78f9a1f commit 114e698
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 14 deletions.
3 changes: 2 additions & 1 deletion src/coredump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,8 @@ impl PythonCoreDump {

// lets us figure out which thread has the GIL
let config = Config::default();
let threadstate_address = get_threadstate_address(&python_info, &version, &config)?;
let threadstate_address =
get_threadstate_address(interpreter_address, &python_info, &version, &config)?;
info!("found threadstate at 0x{:016x}", threadstate_address);

Ok(PythonCoreDump {
Expand Down
18 changes: 14 additions & 4 deletions src/python_interpreters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This means we can't dereference them directly.
use crate::python_bindings::{
v2_7_15, v3_10_0, v3_11_0, v3_12_0, v3_3_7, v3_5_5, v3_6_6, v3_7_0, v3_8_0, v3_9_5,
};
use crate::utils::offset_of;

pub trait InterpreterState {
type ThreadState: ThreadState;
Expand All @@ -22,6 +23,7 @@ pub trait InterpreterState {
type ListObject: ListObject;
type TupleObject: TupleObject;
fn head(&self) -> *mut Self::ThreadState;
fn gil_locked(&self) -> Option<bool>;
fn modules(&self) -> *mut Self::Object;
}

Expand Down Expand Up @@ -100,10 +102,6 @@ pub trait TypeObject {
fn flags(&self) -> usize;
}

fn offset_of<T, M>(object: *const T, member: *const M) -> usize {
member as usize - object as usize
}

/// This macro provides a common impl for PyThreadState/PyFrameObject/PyCodeObject traits
/// (this code is identical across python versions, we are only abstracting the struct layouts here).
/// String handling changes substantially between python versions, and is handled separately.
Expand All @@ -115,9 +113,13 @@ macro_rules! PythonCommonImpl {
type StringObject = $py::$stringobject;
type ListObject = $py::PyListObject;
type TupleObject = $py::PyTupleObject;

fn head(&self) -> *mut Self::ThreadState {
self.tstate_head
}
fn gil_locked(&self) -> Option<bool> {
None
}
fn modules(&self) -> *mut Self::Object {
self.modules
}
Expand Down Expand Up @@ -415,9 +417,14 @@ impl InterpreterState for v3_12_0::PyInterpreterState {
type StringObject = v3_12_0::PyUnicodeObject;
type ListObject = v3_12_0::PyListObject;
type TupleObject = v3_12_0::PyTupleObject;

fn head(&self) -> *mut Self::ThreadState {
self.threads.head
}
fn gil_locked(&self) -> Option<bool> {
Some(self._gil.locked._value != 0)
}

fn modules(&self) -> *mut Self::Object {
self.imports.modules
}
Expand Down Expand Up @@ -506,6 +513,9 @@ impl InterpreterState for v3_11_0::PyInterpreterState {
fn head(&self) -> *mut Self::ThreadState {
self.threads.head
}
fn gil_locked(&self) -> Option<bool> {
None
}
fn modules(&self) -> *mut Self::Object {
self.modules
}
Expand Down
12 changes: 11 additions & 1 deletion src/python_process_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,14 +529,24 @@ where
}

pub fn get_threadstate_address(
interpreter_address: usize,
python_info: &PythonProcessInfo,
version: &Version,
config: &Config,
) -> Result<usize, Error> {
let threadstate_address = match version {
Version {
major: 3,
minor: 7..=12,
minor: 12,
..
} => {
let interp: v3_12_0::_is = Default::default();
let offset = crate::utils::offset_of(&interp, &interp._gil.last_holder._value);
interpreter_address + offset
}
Version {
major: 3,
minor: 7..=11,
..
} => match python_info.get_symbol("_PyRuntime") {
Some(&addr) => {
Expand Down
16 changes: 9 additions & 7 deletions src/python_spy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ impl PythonSpy {
info!("Found interpreter at 0x{:016x}", interpreter_address);

// lets us figure out which thread has the GIL
let threadstate_address = get_threadstate_address(&python_info, &version, config)?;
let threadstate_address =
get_threadstate_address(interpreter_address, &python_info, &version, config)?;

#[cfg(feature = "unwind")]
let native = if config.native {
Expand Down Expand Up @@ -206,18 +207,19 @@ impl PythonSpy {
None
};

// TODO: hoist most of this code out to stack_trace.rs, and
// then annotate the output of that with things like native stack traces etc
// have moved in gil / locals etc
let gil_thread_id =
get_gil_threadid::<I, Process>(self.threadstate_address, &self.process)?;

// Get the python interpreter, and loop over all the python threads
let interp: I = self
.process
.copy_struct(self.interpreter_address)
.context("Failed to copy PyInterpreterState from process")?;

// get the threadid of the gil if appropriate
let gil_thread_id = if interp.gil_locked().unwrap_or(true) {
get_gil_threadid::<I, Process>(self.threadstate_address, &self.process)?
} else {
0
};

let mut traces = Vec::new();
let mut threads = interp.head();
while !threads.is_null() {
Expand Down
6 changes: 5 additions & 1 deletion src/stack_trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ where
I: InterpreterState,
P: ProcessMemory,
{
let gil_thread_id = get_gil_threadid::<I, P>(threadstate_address, process)?;
let gil_thread_id = if interpreter.gil_locked().unwrap_or(true) {
get_gil_threadid::<I, P>(threadstate_address, process)?
} else {
0
};

let mut ret = Vec::new();
let mut threads = interpreter.head();
Expand Down
4 changes: 4 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ pub fn resolve_filename(filename: &str, modulename: &str) -> Option<String> {

None
}

pub fn offset_of<T, M>(object: *const T, member: *const M) -> usize {
member as usize - object as usize
}

0 comments on commit 114e698

Please sign in to comment.