pybind / pybind/pybind11

gil_scoped_acquire deadlock

Open
#1,273 2 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
18k
Forks
2.3k
Avg merge
5d 17h
Merged PRs (30d)
10

Description

Let's say we have the following minimal example:

#include <pybind11/pybind11.h>
#include <pybind11/embed.h>

#include <iostream>
#include <thread>

static void thread()
{
  pybind11::gil_scoped_acquire acquire;
  std::cout << "Calling Python code from new thread" << std::endl;
}

int main()
{
  pybind11::scoped_interpreter interp;

  {
    pybind11::gil_scoped_acquire acquire;
    std::cout << "Calling Python code from main thread" << std::endl;
  }

  std::thread(thread).join();

  return 0;
}

We would expect this to print:

Calling Python code from main thread
Calling Python code from new thread

But the cout from the new thread is never reached because of a GIL deadlock. The reason this is happening is because PyEval_InitThreads(), in addition to initializing threads, also acquires the GIL and never releases it. gil_scoped_acquire calls PyEval_InitThreads() the first time it runs, and it acquires the GIL a second time, so even when gil_scoped_acquire is destructed, the GIL is still held.

If we change the example to this:

#include <pybind11/pybind11.h>
#include <pybind11/embed.h>

#include <iostream>
#include <thread>

static void thread()
{
  pybind11::gil_scoped_acquire acquire;
  std::cout << "Calling Python code from new thread" << std::endl;
}

static void init_threads()
{
  if (!PyEval_ThreadsInitialized())
  {
    {
      pybind11::gil_scoped_acquire acquire;
    }
    PyEval_SaveThread();
  }
}

int main()
{
  pybind11::scoped_interpreter interp;

  init_threads();

  {
    pybind11::gil_scoped_acquire acquire;
    std::cout << "Calling Python code from main thread" << std::endl;
  }

  std::thread(thread).join();

  return 0;
}

then everything behaves as expected. Is this what I'm supposed to do, or is this a bug in pybind?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the minimal C++ reproducer and inspect pybind11's gil_scoped_acquire behavior around PyEval_InitThreads(), PyEval_ThreadsInitialized(), and PyEval_SaveThread(). Verify the fix by running the example and confirming that both the main-thread and new-thread messages are printed without a deadlock.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, python
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.