Multiple interpreters

If your process uses multiple interpreters (for example, using the concurrent.interpreters module or a concurrent.futures.InterpreterPoolExecutor), PyStack will show which interpreter each stack is associated with, and will even show where a thread switches from one interpreter to another.

Note

This feature only works for processes running Python 3.11 or newer.

Example program with multiple interpreters

For instance, given this Python 3.14 program:

import os
import signal
from concurrent.futures import InterpreterPoolExecutor


def interpreter1_body(read_fd):
    print("Hello from interpreter 1!")
    open(read_fd, closefd=False).read()
    print("Goodbye from interpreter 1!")


def interpreter2_body(read_fd):
    print("Greetings from interpreter 2!")
    open(read_fd, closefd=False).read()
    print("Farewell from interpreter 2!")


read_fd, write_fd = os.pipe()

with InterpreterPoolExecutor(max_workers=2) as executor:
    executor.submit(interpreter1_body, read_fd)
    executor.submit(interpreter2_body, read_fd)

    try:
        signal.pause()
    except KeyboardInterrupt:
        print("\nCtrl-C received, signaling workers to stop...")
        os.close(write_fd)
os.close(read_fd)

If you run it until it pauses and then analyze it with PyStack, you will see output like this:

$ pystack remote 392462
Traceback for thread 392464 (InterpreterPool) (most recent call last):
  In the main interpreter []
    (Python) File "/usr/lib/python3.14/threading.py", line 1044, in _bootstrap
        self._bootstrap_inner()
    (Python) File "/usr/lib/python3.14/threading.py", line 1082, in _bootstrap_inner
        self._context.run(self.run)
    (Python) File "/usr/lib/python3.14/threading.py", line 1024, in run
        self._target(*self._args, **self._kwargs)
    (Python) File "/usr/lib/python3.14/concurrent/futures/thread.py", line 119, in _worker
        work_item.run(ctx)
    (Python) File "/usr/lib/python3.14/concurrent/futures/thread.py", line 86, in run
        result = ctx.run(self.task)
    (Python) File "/usr/lib/python3.14/concurrent/futures/interpreter.py", line 84, in run
        return self.interp.call(do_call, self.results, *task)
    (Python) File "/usr/lib/python3.14/concurrent/interpreters/__init__.py", line 238, in call
        return self._call(callable, args, kwargs)
    (Python) File "/usr/lib/python3.14/concurrent/interpreters/__init__.py", line 220, in _call
        res, excinfo = _interpreters.call(self._id, callable, args, kwargs, restrict=True)
  In interpreter 2 []
    (Python) File "/usr/lib/python3.14/concurrent/futures/interpreter.py", line 11, in do_call
        return func(*args, **kwargs)
    (Python) File "/tmp/interpreter_demo.py", line 14, in interpreter2_body
        open(read_fd, closefd=False).read()

Traceback for thread 392463 (InterpreterPool) (most recent call last):
  In the main interpreter []
    (Python) File "/usr/lib/python3.14/threading.py", line 1044, in _bootstrap
        self._bootstrap_inner()
    (Python) File "/usr/lib/python3.14/threading.py", line 1082, in _bootstrap_inner
        self._context.run(self.run)
    (Python) File "/usr/lib/python3.14/threading.py", line 1024, in run
        self._target(*self._args, **self._kwargs)
    (Python) File "/usr/lib/python3.14/concurrent/futures/thread.py", line 119, in _worker
        work_item.run(ctx)
    (Python) File "/usr/lib/python3.14/concurrent/futures/thread.py", line 86, in run
        result = ctx.run(self.task)
    (Python) File "/usr/lib/python3.14/concurrent/futures/interpreter.py", line 84, in run
        return self.interp.call(do_call, self.results, *task)
    (Python) File "/usr/lib/python3.14/concurrent/interpreters/__init__.py", line 238, in call
        return self._call(callable, args, kwargs)
    (Python) File "/usr/lib/python3.14/concurrent/interpreters/__init__.py", line 220, in _call
        res, excinfo = _interpreters.call(self._id, callable, args, kwargs, restrict=True)
  In interpreter 1 []
    (Python) File "/usr/lib/python3.14/concurrent/futures/interpreter.py", line 11, in do_call
        return func(*args, **kwargs)
    (Python) File "/tmp/interpreter_demo.py", line 8, in interpreter1_body
        open(read_fd, closefd=False).read()

Traceback for thread 392462 (python3.14) (most recent call last):
  In the main interpreter []
    (Python) File "/tmp/interpreter_demo.py", line 25, in <module>
        signal.pause()

Note that the output now shows which interpreter each thread is running in, and also shows the transition from the main interpreter to the subinterpreters. The GIL status is shown separately for each interpreter, as a thread may hold the GIL for one interpreter but not another. Likewise, the GC status is shown separately, since a thread may be running a GC cycle in one interpreter but not another.