functiontrace 0.5.0__tar.gz → 0.5.2__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.
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "_functiontrace_rs"
3
3
  version = "0.1.0"
4
- edition = "2021"
4
+ edition = "2024"
5
5
 
6
6
  [dependencies]
7
7
  color-eyre = { version = "0.6", default-features = false }
@@ -10,17 +10,17 @@ functiontrace-server = { version = "0.8.4", default-features = false, features =
10
10
  jiff = { version = "0.2", default-features = false, features = ["std", "serde"] }
11
11
  libc = "0.2"
12
12
  owo-colors = "4.2"
13
- pyo3 = { version = "0.25", features = ["eyre"] }
14
- reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
13
+ pyo3 = { version = "0.29", features = ["eyre"] }
14
+ reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "rustls"] }
15
15
  rmp-serde = "1.3"
16
16
  serde = "1"
17
- toml = "0.9"
17
+ toml = "1.1"
18
18
  tracing = { version = "0.1", features = ["log"] }
19
19
  tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "env-filter", "fmt", "std"] }
20
20
 
21
21
  [build-dependencies]
22
- toml = { version = "0.9", default-features = false, features = ["parse", "serde"] }
23
- pyo3-build-config = { version = "0.25", features = ["resolve-config"] }
22
+ toml = { version = "1.1", default-features = false, features = ["parse", "serde"] }
23
+ pyo3-build-config = { version = "0.29" }
24
24
 
25
25
  [lib]
26
26
  # See https://github.com/PyO3/pyo3 for details
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: functiontrace
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: A profiling tool that traces your application's execution
5
5
  Author: Alistair Dobke
6
6
  Author-email: Matt Bryant <functiontrace@programsareproofs.com>
@@ -15,7 +15,7 @@ Classifier: Environment :: Console
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3 :: Only
17
17
  Classifier: Programming Language :: Python :: Implementation :: CPython
18
- Requires-Python: >=3.9
18
+ Requires-Python: >=3.10
19
19
  Description-Content-Type: text/x-rst
20
20
  License-File: LICENSE.md
21
21
  Dynamic: license-file
@@ -28,7 +28,7 @@ Dependencies
28
28
  ------------
29
29
 
30
30
  The FunctionTrace server must be installed for this package to work.
31
- See installation instructions at https://functiontrace.com#installation.
31
+ See installation instructions at https://functiontrace.com/installation/.
32
32
 
33
33
  Usage
34
34
  -----
@@ -6,7 +6,7 @@ Dependencies
6
6
  ------------
7
7
 
8
8
  The FunctionTrace server must be installed for this package to work.
9
- See installation instructions at https://functiontrace.com#installation.
9
+ See installation instructions at https://functiontrace.com/installation/.
10
10
 
11
11
  Usage
12
12
  -----
@@ -5,14 +5,24 @@ fn main() -> std::io::Result<()> {
5
5
  let pyproject = std::fs::read_to_string("pyproject.toml")?
6
6
  .parse::<toml::Table>()
7
7
  .expect("pyproject.toml is valid toml");
8
- let version = pyproject
8
+ let project = pyproject
9
9
  .get("project")
10
10
  .and_then(|p| p.as_table())
11
- .and_then(|p| p.get("version"))
11
+ .expect("project field is present in pyproject.toml");
12
+
13
+ let version = project
14
+ .get("version")
12
15
  .and_then(|v| v.as_str())
13
16
  .expect("version field is present in pyproject.toml");
14
17
  println!("cargo::rustc-env=PACKAGE_VERSION={}", version);
15
18
 
19
+ let min_python = project
20
+ .get("requires-python")
21
+ .and_then(|v| v.as_str())
22
+ .and_then(|v| v.strip_prefix(">=3."))
23
+ .expect("requires-python field is present in pyproject.toml");
24
+ println!("cargo::rustc-env=MIN_PYTHON_VERSION={}", min_python);
25
+
16
26
  // Figure out which Python version we're building for
17
27
  pyo3_build_config::use_pyo3_cfgs();
18
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: functiontrace
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: A profiling tool that traces your application's execution
5
5
  Author: Alistair Dobke
6
6
  Author-email: Matt Bryant <functiontrace@programsareproofs.com>
@@ -15,7 +15,7 @@ Classifier: Environment :: Console
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3 :: Only
17
17
  Classifier: Programming Language :: Python :: Implementation :: CPython
18
- Requires-Python: >=3.9
18
+ Requires-Python: >=3.10
19
19
  Description-Content-Type: text/x-rst
20
20
  License-File: LICENSE.md
21
21
  Dynamic: license-file
@@ -28,7 +28,7 @@ Dependencies
28
28
  ------------
29
29
 
30
30
  The FunctionTrace server must be installed for this package to work.
31
- See installation instructions at https://functiontrace.com#installation.
31
+ See installation instructions at https://functiontrace.com/installation/.
32
32
 
33
33
  Usage
34
34
  -----
@@ -19,7 +19,12 @@ exec $(which {python}) -m functiontrace "$@"
19
19
 
20
20
 
21
21
  def setup_dependencies():
22
- # Generate a temp directory to store our wrappers in. We'll temporarily
22
+ if f"{tempfile.gettempdir()}/py-functiontrace" in os.environ["PATH"]:
23
+ # We've already set up dependencies. This only needs to happen once, since otherwise we'll
24
+ # just make a bunch of useless temp directories.
25
+ return
26
+
27
+ # Generate a temp directory to store our wrappers in. We'll temporarily
23
28
  # add this directory to our path.
24
29
  tempdir = tempfile.mkdtemp(prefix="py-functiontrace")
25
30
  os.environ["PATH"] = tempdir + os.pathsep + os.environ["PATH"]
@@ -27,7 +32,8 @@ def setup_dependencies():
27
32
  # Generate wrappers for the various Python versions we support to ensure
28
33
  # they're included in our PATH.
29
34
  wrap_pythons = ["python", "python3"] + [
30
- "python3.{}".format(minor) for minor in range(6, 13)
35
+ "python3.{}".format(minor)
36
+ for minor in range(rust._min_python_version, rust._max_python_version + 1)
31
37
  ]
32
38
  for python in wrap_pythons:
33
39
  with open(os.path.join(tempdir, python), "w") as f:
@@ -88,10 +94,19 @@ def main():
88
94
  # below, which needs to operate in the context of our new module.
89
95
  # NOTE: We use SourceFileLoader, rather than allowing importlib to infer
90
96
  # the proper loader, in order to support files without a .py extension.
91
- spec = util.spec_from_loader(
92
- "__main__", machinery.SourceFileLoader("__main__", target_file)
97
+ mod = util.module_from_spec(
98
+ util.spec_from_loader(
99
+ "__main__", machinery.SourceFileLoader("__main__", target_file)
100
+ )
93
101
  )
94
- mod = util.module_from_spec(spec)
102
+
103
+ # Clear __spec__ to match Python's standard `python foo.py` loading
104
+ mod.__spec__ = None
105
+ # Record the file we're using so `multiprocessing` can load it (in a non-forked context).
106
+ # Without this, `multiprocessing` in `forkserver` contexts is asked to load functions that
107
+ # it can't find.
108
+ mod.__file__ = target_file
109
+
95
110
  sys.modules.update({"__main__": mod})
96
111
 
97
112
  # Ensure we're setup to be able to run.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "functiontrace"
3
- version = "0.5.0"
3
+ version = "0.5.2"
4
4
  description = "A profiling tool that traces your application's execution"
5
5
  authors = [
6
6
  { name = "Matt Bryant", email = "functiontrace@programsareproofs.com"},
@@ -9,13 +9,13 @@ authors = [
9
9
  license = "LicenseRef-Prosperity-Public-License-3.0.0"
10
10
  license-files = ["LICENSE.md"]
11
11
  readme = "README.rst"
12
- # NOTE: Actively tested on Python 3.9 - 3.13 and may plausibly break on greater
13
- # versions due to C extension hacks. However, setting a maximum version causes
14
- # new Python versions to simply install very old versions of Functiontrace that
15
- # didn't have the `requires-python` annotation on them and are also unlikely to
16
- # work. Best to only set the minimum and let bug reports notify us of issues
17
- # with new Python releases.
18
- requires-python = ">=3.9"
12
+ # NOTE: Actively tested on Python 3.10 - 3.14 and may plausibly break on
13
+ # greater versions due to C extension hacks. However, setting a maximum version
14
+ # causes new Python versions to simply install very old versions of
15
+ # Functiontrace that didn't have the `requires-python` annotation on them and
16
+ # are also unlikely to work. Best to only set the minimum and let bug reports
17
+ # notify us of issues with new Python releases.
18
+ requires-python = ">=3.10"
19
19
  keywords = [
20
20
  "profiler",
21
21
  "debugger"
@@ -7,7 +7,7 @@ mod _functiontrace_rs {
7
7
  use serde::{Deserialize, Serialize};
8
8
  use std::borrow::Cow;
9
9
  use std::cell::Cell;
10
- use std::ffi::{CStr, OsString, c_void};
10
+ use std::ffi::{CStr, OsString};
11
11
  use std::io::Write;
12
12
  use std::os::unix::net::UnixStream;
13
13
  use std::sync::atomic::{AtomicBool, Ordering};
@@ -148,7 +148,7 @@ mod _functiontrace_rs {
148
148
  let state = THREAD_STATE.get();
149
149
 
150
150
  if state.is_null() {
151
- // We shouldn't be asked to reset at thread that hasn't yet been registered.
151
+ // We shouldn't be asked to reset a thread that hasn't yet been registered.
152
152
  return None;
153
153
  }
154
154
 
@@ -195,10 +195,20 @@ mod _functiontrace_rs {
195
195
  /// Initialization code run on this module's import (mostly useful for adding constants)
196
196
  #[pymodule_init]
197
197
  fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
198
- // Arbitrary code to run at the module initialization
199
198
  m.add("__version__", env!("PACKAGE_VERSION"))
200
199
  .wrap_err("Failed to expose PACKAGE_VERSION")?;
201
200
 
201
+ // Expose the minimum and maximum Python 3 versions we support
202
+ m.add(
203
+ "_min_python_version",
204
+ env!("MIN_PYTHON_VERSION")
205
+ .parse::<u32>()
206
+ .wrap_err("Failed to convert MIN_PYTHON_VERSION to int")?,
207
+ )
208
+ .wrap_err("Failed to expose _min_python_version")?;
209
+ m.add("_max_python_version", 14)
210
+ .wrap_err("Failed to expose _max_python_version")?;
211
+
202
212
  color_eyre::install().wrap_err("Failed to install color_eyre")?;
203
213
 
204
214
  // If someone else has already installed a tracing subscriber, we'll fail to initialize
@@ -244,6 +254,57 @@ mod _functiontrace_rs {
244
254
  return Err(eyre!("FunctionTrace is already initialized!"));
245
255
  }
246
256
 
257
+ // Store the processes' args, since threads will need it as part of the registration message.
258
+ ARGV.set({
259
+ // Parse args out of `sys.argv`, since it excludes the Python executable and shows the
260
+ // script's path first. This is much more complicated than using `std::env::args`, but
261
+ // is more practical.
262
+ Python::attach(|py| -> Result<String> {
263
+ let argv = py
264
+ .import("sys")
265
+ .and_then(|sys| sys.getattr("argv"))
266
+ .wrap_err("Failed to access sys.argv")?;
267
+
268
+ let args = argv
269
+ .cast::<pyo3::types::PyList>()
270
+ .map_err(|e| eyre!("Failed to read sys.argv as list: {}", e))?;
271
+
272
+ let mut full_args = args.iter().fold(String::new(), |s, arg| {
273
+ // Fetch the next arg as a string if possible.
274
+ let arg = arg
275
+ .cast::<pyo3::types::PyString>()
276
+ .map_err(|e| eyre!("Failed to read sys.argv[x] as string: {}", e))
277
+ .and_then(|arg| {
278
+ arg.to_str()
279
+ .wrap_err("Failed to read Rust string out of PyString")
280
+ })
281
+ .unwrap_or("<UNKNOWN>");
282
+
283
+ s + " " + arg
284
+ });
285
+
286
+ // We'll display this in the UI, so ensure it's under (the rather
287
+ // arbitrary) 100 chars.
288
+ if full_args.chars().count() > 95 {
289
+ full_args.truncate(95);
290
+ full_args.push_str(" ...");
291
+ }
292
+
293
+ // We'll be cloning this
294
+ full_args.shrink_to_fit();
295
+ Ok(full_args)
296
+ })
297
+ .unwrap_or_else(|_err| {
298
+ tracing::warn!("Failed to read ARGV");
299
+
300
+ "<UNKNOWN>".into()
301
+ })
302
+ })
303
+ .expect("ARGV is only set in `trace_initialization`");
304
+
305
+ /////////////////////////////////////////////////////////////////////////
306
+ // Server initialization
307
+ /////////////////////////////////////////////////////////////////////////
247
308
  // Check if we're in a subprocess of a command being run under functiontrace. If we are,
248
309
  // we should connect to the same socket. Otherwise, we should setup the full profiling
249
310
  // configuration ourselves.
@@ -253,155 +314,105 @@ mod _functiontrace_rs {
253
314
  SOCKADDR
254
315
  .set(existing_sockaddr)
255
316
  .expect("sockaddr is only set in `trace_initialization`");
256
- return Ok(());
257
- }
317
+ } else {
318
+ // Launch the functiontrace-server as a daemon, then record the address of the socket we'll
319
+ // need to communicate with it on.
320
+ let sockaddr = {
321
+ let server = match std::process::Command::new("functiontrace-server")
322
+ .arg("--directory")
323
+ .arg(output_directory)
324
+ .spawn()
325
+ {
326
+ Ok(server) => server,
327
+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
328
+ eprintln!("Unable to find `functiontrace-server` in the current $PATH.");
329
+ eprintln!(
330
+ "See https://functiontrace.com/installation/ for installation instructions."
331
+ );
258
332
 
259
- // Launch the functiontrace-server as a daemon, then record the address of the socket we'll
260
- // need to communicate with it on.
261
- let sockaddr = {
262
- let server = match std::process::Command::new("functiontrace-server")
263
- .arg("--directory")
264
- .arg(output_directory)
265
- .spawn()
266
- {
267
- Ok(server) => server,
268
- Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
269
- eprintln!("Unable to find `functiontrace-server` in the current $PATH.");
270
- eprintln!(
271
- "See https://functiontrace.com#installation for installation instructions."
272
- );
333
+ // Exit here rather than returning an error, since that'll emit a confusing
334
+ // Python stacktrace.
335
+ std::process::exit(1);
336
+ }
337
+ e => e.wrap_err("Failed to launch functiontrace-server")?,
338
+ };
273
339
 
274
- // Exit here rather than returning an error, since that'll emit a confusing
275
- // Python stacktrace.
276
- std::process::exit(1);
277
- }
278
- e => e.wrap_err("Failed to launch functiontrace-server")?,
340
+ format!("/tmp/functiontrace-server.sock.{}", server.id()).into()
279
341
  };
280
342
 
281
- format!("/tmp/functiontrace-server.sock.{}", server.id()).into()
282
- };
343
+ Python::attach(|_| {
344
+ // Register an env var so subprocesses know how to connect to our
345
+ // profile.
346
+ unsafe {
347
+ // SAFETY: `set_var` can only be done while no one else is reading or writing to the
348
+ // environment. We're holding the GIL, so this _should_ be true, but there's no real
349
+ // way to enforce this.
350
+ //
351
+ // This doesn't seem to blow up in C even when we don't have the GIL, so
352
+ // it's hopefully good enough in practice since there's not really an alternative.
353
+ std::env::set_var(BREADCRUMB, &sockaddr);
354
+ }
355
+ });
283
356
 
284
- Python::with_gil(|_| {
285
- // Register an env var so subprocesses know how to connect to our
286
- // profile.
287
- unsafe {
288
- // SAFETY: `set_var` can only be done while no one else is reading or writing to the
289
- // environment. We're holding the GIL, so this _should_ be true, but there's no real
290
- // way to enforce this.
291
- //
292
- // This doesn't seem to blow up in C even when we don't have the GIL, so
293
- // it's hopefully good enough in practice since there's not really an alternative.
294
- std::env::set_var(BREADCRUMB, &sockaddr);
295
- }
296
- });
357
+ // Store the server's address so our threads know how to talk to it.
358
+ SOCKADDR
359
+ .set(sockaddr)
360
+ .expect("sockaddr is only set in `trace_initialization`");
297
361
 
298
- // Store the server's address so our threads know how to talk to it.
299
- SOCKADDR
300
- .set(sockaddr)
301
- .expect("sockaddr is only set in `trace_initialization`");
302
-
303
- // Send an initialization message to the server with some information about us. This will
304
- // only be sent once, and all future threads/subprocesses will be associated underneath us.
305
- let init = {
306
- let argv = {
307
- // Parse args out of `sys.argv`, since it excludes the Python executable and shows
308
- // the script's path first. This is much more complicated than using
309
- // `std::env::args`, but is more practical.
310
- let args_prefix = Python::with_gil(|py| -> Result<String> {
311
- let argv = py
312
- .import("sys")
313
- .and_then(|sys| sys.getattr("argv"))
314
- .wrap_err("Failed to access sys.argv")?;
315
-
316
- let args = argv
317
- .downcast::<pyo3::types::PyList>()
318
- .map_err(|e| eyre!("Failed to read sys.argv as list: {}", e))?;
319
-
320
- let mut full_args = args.iter().fold(String::new(), |s, arg| {
321
- // Fetch the next arg as a string if possible.
322
- let arg = arg
323
- .downcast::<pyo3::types::PyString>()
324
- .map_err(|e| eyre!("Failed to read sys.argv[x] as string: {}", e))
325
- .and_then(|arg| {
326
- arg.to_str()
327
- .wrap_err("Failed to read Rust string out of PyString")
328
- })
329
- .unwrap_or("<UNKNOWN>");
330
-
331
- s + " " + arg
332
- });
333
-
334
- // We'll display this in the UI, so ensure it's under (the rather
335
- // arbitrary) 100 chars.
336
- if full_args.chars().count() > 95 {
337
- full_args.truncate(95);
338
- full_args.push_str(" ...");
339
- }
362
+ // Send an initialization message to the server with some information about us. This will
363
+ // only be sent once, and all future threads/subprocesses will be associated underneath us.
364
+ let init = {
365
+ let python_version = Python::attach(|py| {
366
+ let version = py.version_info();
367
+
368
+ format!(
369
+ "Python {}.{}.{}",
370
+ version.major, version.minor, version.patch
371
+ )
372
+ });
340
373
 
341
- // We'll be cloning this
342
- full_args.shrink_to_fit();
343
- Ok(full_args)
374
+ let platform = Python::attach(|py| -> Result<String> {
375
+ let platform = py
376
+ .import("sys")
377
+ .and_then(|sys| sys.getattr("platform"))
378
+ .wrap_err("Failed to access sys.platform")?;
379
+
380
+ Ok(platform
381
+ .cast::<pyo3::types::PyString>()
382
+ .map_err(|e| eyre!("Failed to read sys.platform as string: {}", e))?
383
+ .to_str()
384
+ .wrap_err("Failed to read Rust string out of PyString")?
385
+ .into())
344
386
  })
345
387
  .unwrap_or_else(|_err| {
346
- tracing::warn!("Failed to read ARGV");
388
+ tracing::warn!("Failed to read sys.platform");
347
389
 
348
390
  "<UNKNOWN>".into()
349
391
  });
350
392
 
351
- // Store the args, since threads will need it as part of the registration message.
352
- ARGV.set(args_prefix.clone())
353
- .expect("ARGV is only set in `trace_initialization`");
354
-
355
- args_prefix
393
+ function_trace::TraceInitialization {
394
+ program_name: ARGV
395
+ .get()
396
+ .expect("ARGV is set earlier in trace_initialization")
397
+ .clone(),
398
+ program_version: format!("py-functiontrace {}", env!("PACKAGE_VERSION")),
399
+ lang_version: python_version,
400
+ platform,
401
+ time: trace_time(),
402
+ }
356
403
  };
357
404
 
358
- let python_version = Python::with_gil(|py| {
359
- let version = py.version_info();
405
+ let mut socket = message_initialize();
360
406
 
361
- format!(
362
- "Python {}.{}.{}",
363
- version.major, version.minor, version.patch
364
- )
365
- });
366
-
367
- let platform = Python::with_gil(|py| -> Result<String> {
368
- let platform = py
369
- .import("sys")
370
- .and_then(|sys| sys.getattr("platform"))
371
- .wrap_err("Failed to access sys.platform")?;
372
-
373
- Ok(platform
374
- .downcast::<pyo3::types::PyString>()
375
- .map_err(|e| eyre!("Failed to read sys.platform as string: {}", e))?
376
- .to_str()
377
- .wrap_err("Failed to read Rust string out of PyString")?
378
- .into())
379
- })
380
- .unwrap_or_else(|_err| {
381
- tracing::warn!("Failed to read sys.platform");
382
-
383
- "<UNKNOWN>".into()
384
- });
407
+ init.serialize(&mut rmp_serde::encode::Serializer::new(&mut socket))
408
+ .wrap_err("Failed to emit TraceInitialization message")?;
385
409
 
386
- function_trace::TraceInitialization {
387
- program_name: argv,
388
- program_version: format!("py-functiontrace {}", env!("PACKAGE_VERSION")),
389
- lang_version: python_version,
390
- platform,
391
- time: trace_time(),
392
- }
393
- };
394
-
395
- let mut socket = message_initialize();
396
-
397
- init.serialize(&mut rmp_serde::encode::Serializer::new(&mut socket))
398
- .wrap_err("Failed to emit TraceInitialization message")?;
399
-
400
- // Close the initialization socket to trigger `functiontrace-server` to
401
- // read our message, since otherwise it'll wait forever for more data.
402
- socket
403
- .shutdown(std::net::Shutdown::Both)
404
- .wrap_err("Failed to close initialization socket")?;
410
+ // Close the initialization socket to trigger `functiontrace-server` to
411
+ // read our message, since otherwise it'll wait forever for more data.
412
+ socket
413
+ .shutdown(std::net::Shutdown::Both)
414
+ .wrap_err("Failed to close initialization socket")?;
415
+ }
405
416
 
406
417
  ////////////////////////////////////////////////////////////////////////////
407
418
  // Teardown Configuration
@@ -422,7 +433,7 @@ mod _functiontrace_rs {
422
433
  //
423
434
  // NOTE: We specifically do this rather than `Py_AtExit` since we want to run before all
424
435
  // the Python teardown occurs.
425
- Python::with_gil(|py| -> Result<()> {
436
+ Python::attach(|py| -> Result<()> {
426
437
  py.import("atexit")
427
438
  .and_then(|atexit| atexit.getattr("register"))
428
439
  .wrap_err("Failed to access atexit.register")?
@@ -449,12 +460,13 @@ mod _functiontrace_rs {
449
460
 
450
461
  // Register our tracing functions with Python - both the normal one and the multithreaded
451
462
  // setup trampoline.
463
+ // TODO: PyEval_SetProfileAllThreads in >=3.12 should simplify this.
452
464
  unsafe {
453
465
  // SAFETY: both parameters are of the proper type.
454
466
  pyo3::ffi::PyEval_SetProfile(Some(functiontrace), std::ptr::null_mut())
455
467
  };
456
468
 
457
- Python::with_gil(|py| -> Result<()> {
469
+ Python::attach(|py| -> Result<()> {
458
470
  py.import("threading")
459
471
  .and_then(|threading| threading.getattr("setprofile"))
460
472
  .wrap_err("Failed to access threading.setprofile")?
@@ -550,23 +562,6 @@ mod _functiontrace_rs {
550
562
  Ok(())
551
563
  }
552
564
 
553
- #[unsafe(no_mangle)]
554
- extern "C" fn c_thread_teardown(state: *mut c_void) {
555
- if state.is_null() {
556
- // We never fully initialized this thread, so skip teardown.
557
- return;
558
- }
559
-
560
- let writer = unsafe {
561
- // SAFETY: writer is a valid pointer to an initialized `ThreadState`.
562
- &mut (state as *mut ThreadState)
563
- .as_mut()
564
- .expect("state is non-null")
565
- };
566
-
567
- thread_teardown(writer).expect("Failed to tear down FunctionTrace thread")
568
- }
569
-
570
565
  /// Tear down the given thread by flushing any outstanding messages.
571
566
  ///
572
567
  /// This is called when the thread has shut down, including in multithread/process situations.
@@ -625,7 +620,11 @@ mod _functiontrace_rs {
625
620
  /// thread state, before replacing itself with the normal Fprofile_FunctionTrace handler.
626
621
  #[pyfunction]
627
622
  #[pyo3(name = "_thread_trace_trampoline")]
628
- fn thread_trace_trampoline(_frame: PyObject, _event: PyObject, _arg: PyObject) -> Result<()> {
623
+ fn thread_trace_trampoline(
624
+ _frame: Py<PyAny>,
625
+ _event: Py<PyAny>,
626
+ _arg: Py<PyAny>,
627
+ ) -> Result<()> {
629
628
  // Register the current thread for tracing
630
629
  thread_register().wrap_err("Failed to register new FunctionTrace thread")?;
631
630
 
@@ -776,7 +775,7 @@ mod _functiontrace_rs {
776
775
  pyo3::ffi::PyFrame_GetCode(frame) as *mut pyo3::ffi::PyObject
777
776
  };
778
777
 
779
- Python::with_gil(|py| {
778
+ Python::attach(|py| {
780
779
  let code = unsafe {
781
780
  // SAFETY: code is guaranteed to be a valid PyCodeObject since it
782
781
  // came from `PyFrame_GetCode`.
@@ -788,7 +787,7 @@ mod _functiontrace_rs {
788
787
  let unicode_converter = |pyobj: PyResult<Bound<'_, PyAny>>| {
789
788
  pyobj
790
789
  .map(|obj| {
791
- if let Ok(filename) = obj.downcast::<pyo3::types::PyString>() {
790
+ if let Ok(filename) = obj.cast::<pyo3::types::PyString>() {
792
791
  Cow::from(filename.to_string_lossy().into_owned())
793
792
  } else {
794
793
  Cow::Borrowed("<UNKNOWN>")
@@ -845,13 +844,13 @@ mod _functiontrace_rs {
845
844
  if !func.m_module.is_null() {
846
845
  // NOTE: _Typically_ `m_module` is a PyModule, but there are no requirements on
847
846
  // what type it needs to be! In practice, we only see PyModules and PyStrings.
848
- Python::with_gil(|py| {
847
+ Python::attach(|py| {
849
848
  let m_module = unsafe {
850
849
  // SAFETY: m_module is a non-NULL pointer to a valid Python object.
851
850
  Bound::from_borrowed_ptr(py, func.m_module)
852
851
  };
853
852
 
854
- if let Ok(module) = m_module.downcast::<pyo3::types::PyModule>() {
853
+ if let Ok(module) = m_module.cast::<pyo3::types::PyModule>() {
855
854
  // This is a PyModule, so get its name if possible.
856
855
  let name = module.name();
857
856
  let module_name = if let Ok(name) = &name {
@@ -868,7 +867,7 @@ mod _functiontrace_rs {
868
867
  .serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
869
868
  } else {
870
869
  let module_name =
871
- if let Ok(module) = m_module.downcast::<pyo3::types::PyString>() {
870
+ if let Ok(module) = m_module.cast::<pyo3::types::PyString>() {
872
871
  module.to_cow().unwrap_or(Cow::Borrowed("<invalid string>"))
873
872
  } else {
874
873
  if let Ok(typ) = m_module.get_type().qualname().map(|x| x.to_string()) {
@@ -1114,7 +1113,7 @@ mod _functiontrace_rs {
1114
1113
  if let Some(result) = parser(&input) {
1115
1114
  return Ok(result);
1116
1115
  }
1117
- Python::with_gil(|py| {
1116
+ Python::attach(|py| {
1118
1117
  // We need to check with the Python interpreter during loops, otherwise we
1119
1118
  // might not receive ctrl-c events.
1120
1119
  py.check_signals()
@@ -1496,7 +1495,7 @@ mod hooks {
1496
1495
  /// function, then attaching enough information about the original function to call it.
1497
1496
  pub fn install() -> Result<()> {
1498
1497
  for hook in HOOKS.iter() {
1499
- Python::with_gil(|py| -> Result<()> {
1498
+ Python::attach(|py| -> Result<()> {
1500
1499
  let (module_name, method_name) = hook
1501
1500
  .target
1502
1501
  .rsplit_once(".")
@@ -1638,14 +1637,14 @@ mod hooks {
1638
1637
  .get()
1639
1638
  .expect("Wrapper functions can't be called until hooked");
1640
1639
 
1641
- let proxied = Python::with_gil(|py| -> Result<()> {
1640
+ let proxied = Python::attach(|py| -> Result<()> {
1642
1641
  if let Some(mut writer) = crate::_functiontrace_rs::thread_state() {
1643
1642
  let args = unsafe {
1644
1643
  // SAFETY: `args` represents a valid argument tuple
1645
1644
  Bound::from_borrowed_ptr(py, args)
1646
1645
  };
1647
1646
  let args = args
1648
- .downcast()
1647
+ .cast()
1649
1648
  .map_err(Into::<PyErr>::into)
1650
1649
  .wrap_err("Python args must be a tuple")?;
1651
1650
 
@@ -1722,7 +1721,7 @@ mod hooks {
1722
1721
  // `import` takes rather complicated arguments, but in practice we only care about
1723
1722
  // the first argument (name)
1724
1723
  let module = if let Ok(arg) = args.get_item(0) {
1725
- arg.downcast()
1724
+ arg.cast()
1726
1725
  .map_err(Into::into)
1727
1726
  .and_then(|module| module.to_str())
1728
1727
  .map(|module| Cow::Owned(module.to_string()))
File without changes
File without changes
File without changes
File without changes