Skip to content

Commit b307d36

Browse files
committed
Extract gil management functions to separate header
1 parent fe84587 commit b307d36

File tree

2 files changed

+194
-167
lines changed

2 files changed

+194
-167
lines changed

include/pybind11/gil.h

+193
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
pybind11/gil.h: RAII helpers for managing the GIL
3+
4+
Copyright (c) 2016 Wenzel Jakob <[email protected]>
5+
6+
All rights reserved. Use of this source code is governed by a
7+
BSD-style license that can be found in the LICENSE file.
8+
*/
9+
10+
#pragma once
11+
12+
#include "detail/common.h"
13+
#include "detail/internals.h"
14+
15+
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
16+
17+
18+
PYBIND11_NAMESPACE_BEGIN(detail)
19+
20+
// forward declarations
21+
PyThreadState *get_thread_state_unchecked();
22+
23+
PYBIND11_NAMESPACE_END(detail)
24+
25+
26+
#if defined(WITH_THREAD) && !defined(PYPY_VERSION)
27+
28+
/* The functions below essentially reproduce the PyGILState_* API using a RAII
29+
* pattern, but there are a few important differences:
30+
*
31+
* 1. When acquiring the GIL from an non-main thread during the finalization
32+
* phase, the GILState API blindly terminates the calling thread, which
33+
* is often not what is wanted. This API does not do this.
34+
*
35+
* 2. The gil_scoped_release function can optionally cut the relationship
36+
* of a PyThreadState and its associated thread, which allows moving it to
37+
* another thread (this is a fairly rare/advanced use case).
38+
*
39+
* 3. The reference count of an acquired thread state can be controlled. This
40+
* can be handy to prevent cases where callbacks issued from an external
41+
* thread would otherwise constantly construct and destroy thread state data
42+
* structures.
43+
*
44+
* See the Python bindings of NanoGUI (http://github.com/wjakob/nanogui) for an
45+
* example which uses features 2 and 3 to migrate the Python thread of
46+
* execution to another thread (to run the event loop on the original thread,
47+
* in this case).
48+
*/
49+
50+
class gil_scoped_acquire {
51+
public:
52+
PYBIND11_NOINLINE gil_scoped_acquire() {
53+
auto const &internals = detail::get_internals();
54+
tstate = (PyThreadState *) PYBIND11_TLS_GET_VALUE(internals.tstate);
55+
56+
if (!tstate) {
57+
/* Check if the GIL was acquired using the PyGILState_* API instead (e.g. if
58+
calling from a Python thread). Since we use a different key, this ensures
59+
we don't create a new thread state and deadlock in PyEval_AcquireThread
60+
below. Note we don't save this state with internals.tstate, since we don't
61+
create it we would fail to clear it (its reference count should be > 0). */
62+
tstate = PyGILState_GetThisThreadState();
63+
}
64+
65+
if (!tstate) {
66+
tstate = PyThreadState_New(internals.istate);
67+
#if !defined(NDEBUG)
68+
if (!tstate)
69+
pybind11_fail("scoped_acquire: could not create thread state!");
70+
#endif
71+
tstate->gilstate_counter = 0;
72+
PYBIND11_TLS_REPLACE_VALUE(internals.tstate, tstate);
73+
} else {
74+
release = detail::get_thread_state_unchecked() != tstate;
75+
}
76+
77+
if (release) {
78+
PyEval_AcquireThread(tstate);
79+
}
80+
81+
inc_ref();
82+
}
83+
84+
void inc_ref() {
85+
++tstate->gilstate_counter;
86+
}
87+
88+
PYBIND11_NOINLINE void dec_ref() {
89+
--tstate->gilstate_counter;
90+
#if !defined(NDEBUG)
91+
if (detail::get_thread_state_unchecked() != tstate)
92+
pybind11_fail("scoped_acquire::dec_ref(): thread state must be current!");
93+
if (tstate->gilstate_counter < 0)
94+
pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!");
95+
#endif
96+
if (tstate->gilstate_counter == 0) {
97+
#if !defined(NDEBUG)
98+
if (!release)
99+
pybind11_fail("scoped_acquire::dec_ref(): internal error!");
100+
#endif
101+
PyThreadState_Clear(tstate);
102+
if (active)
103+
PyThreadState_DeleteCurrent();
104+
PYBIND11_TLS_DELETE_VALUE(detail::get_internals().tstate);
105+
release = false;
106+
}
107+
}
108+
109+
/// This method will disable the PyThreadState_DeleteCurrent call and the
110+
/// GIL won't be acquired. This method should be used if the interpreter
111+
/// could be shutting down when this is called, as thread deletion is not
112+
/// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and
113+
/// protect subsequent code.
114+
PYBIND11_NOINLINE void disarm() {
115+
active = false;
116+
}
117+
118+
PYBIND11_NOINLINE ~gil_scoped_acquire() {
119+
dec_ref();
120+
if (release)
121+
PyEval_SaveThread();
122+
}
123+
private:
124+
PyThreadState *tstate = nullptr;
125+
bool release = true;
126+
bool active = true;
127+
};
128+
129+
class gil_scoped_release {
130+
public:
131+
explicit gil_scoped_release(bool disassoc = false) : disassoc(disassoc) {
132+
// `get_internals()` must be called here unconditionally in order to initialize
133+
// `internals.tstate` for subsequent `gil_scoped_acquire` calls. Otherwise, an
134+
// initialization race could occur as multiple threads try `gil_scoped_acquire`.
135+
const auto &internals = detail::get_internals();
136+
tstate = PyEval_SaveThread();
137+
if (disassoc) {
138+
auto key = internals.tstate;
139+
PYBIND11_TLS_DELETE_VALUE(key);
140+
}
141+
}
142+
143+
/// This method will disable the PyThreadState_DeleteCurrent call and the
144+
/// GIL won't be acquired. This method should be used if the interpreter
145+
/// could be shutting down when this is called, as thread deletion is not
146+
/// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and
147+
/// protect subsequent code.
148+
PYBIND11_NOINLINE void disarm() {
149+
active = false;
150+
}
151+
152+
~gil_scoped_release() {
153+
if (!tstate)
154+
return;
155+
// `PyEval_RestoreThread()` should not be called if runtime is finalizing
156+
if (active)
157+
PyEval_RestoreThread(tstate);
158+
if (disassoc) {
159+
auto key = detail::get_internals().tstate;
160+
PYBIND11_TLS_REPLACE_VALUE(key, tstate);
161+
}
162+
}
163+
private:
164+
PyThreadState *tstate;
165+
bool disassoc;
166+
bool active = true;
167+
};
168+
#elif defined(PYPY_VERSION)
169+
class gil_scoped_acquire {
170+
PyGILState_STATE state;
171+
public:
172+
gil_scoped_acquire() { state = PyGILState_Ensure(); }
173+
~gil_scoped_acquire() { PyGILState_Release(state); }
174+
void disarm() {}
175+
};
176+
177+
class gil_scoped_release {
178+
PyThreadState *state;
179+
public:
180+
gil_scoped_release() { state = PyEval_SaveThread(); }
181+
~gil_scoped_release() { PyEval_RestoreThread(state); }
182+
void disarm() {}
183+
};
184+
#else
185+
class gil_scoped_acquire {
186+
void disarm() {}
187+
};
188+
class gil_scoped_release {
189+
void disarm() {}
190+
};
191+
#endif
192+
193+
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

include/pybind11/pybind11.h

+1-167
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#endif
4444

4545
#include "attr.h"
46+
#include "gil.h"
4647
#include "options.h"
4748
#include "detail/class.h"
4849
#include "detail/init.h"
@@ -2091,173 +2092,6 @@ void print(Args &&...args) {
20912092
detail::print(c.args(), c.kwargs());
20922093
}
20932094

2094-
#if defined(WITH_THREAD) && !defined(PYPY_VERSION)
2095-
2096-
/* The functions below essentially reproduce the PyGILState_* API using a RAII
2097-
* pattern, but there are a few important differences:
2098-
*
2099-
* 1. When acquiring the GIL from an non-main thread during the finalization
2100-
* phase, the GILState API blindly terminates the calling thread, which
2101-
* is often not what is wanted. This API does not do this.
2102-
*
2103-
* 2. The gil_scoped_release function can optionally cut the relationship
2104-
* of a PyThreadState and its associated thread, which allows moving it to
2105-
* another thread (this is a fairly rare/advanced use case).
2106-
*
2107-
* 3. The reference count of an acquired thread state can be controlled. This
2108-
* can be handy to prevent cases where callbacks issued from an external
2109-
* thread would otherwise constantly construct and destroy thread state data
2110-
* structures.
2111-
*
2112-
* See the Python bindings of NanoGUI (http://github.com/wjakob/nanogui) for an
2113-
* example which uses features 2 and 3 to migrate the Python thread of
2114-
* execution to another thread (to run the event loop on the original thread,
2115-
* in this case).
2116-
*/
2117-
2118-
class gil_scoped_acquire {
2119-
public:
2120-
PYBIND11_NOINLINE gil_scoped_acquire() {
2121-
auto const &internals = detail::get_internals();
2122-
tstate = (PyThreadState *) PYBIND11_TLS_GET_VALUE(internals.tstate);
2123-
2124-
if (!tstate) {
2125-
/* Check if the GIL was acquired using the PyGILState_* API instead (e.g. if
2126-
calling from a Python thread). Since we use a different key, this ensures
2127-
we don't create a new thread state and deadlock in PyEval_AcquireThread
2128-
below. Note we don't save this state with internals.tstate, since we don't
2129-
create it we would fail to clear it (its reference count should be > 0). */
2130-
tstate = PyGILState_GetThisThreadState();
2131-
}
2132-
2133-
if (!tstate) {
2134-
tstate = PyThreadState_New(internals.istate);
2135-
#if !defined(NDEBUG)
2136-
if (!tstate)
2137-
pybind11_fail("scoped_acquire: could not create thread state!");
2138-
#endif
2139-
tstate->gilstate_counter = 0;
2140-
PYBIND11_TLS_REPLACE_VALUE(internals.tstate, tstate);
2141-
} else {
2142-
release = detail::get_thread_state_unchecked() != tstate;
2143-
}
2144-
2145-
if (release) {
2146-
PyEval_AcquireThread(tstate);
2147-
}
2148-
2149-
inc_ref();
2150-
}
2151-
2152-
void inc_ref() {
2153-
++tstate->gilstate_counter;
2154-
}
2155-
2156-
PYBIND11_NOINLINE void dec_ref() {
2157-
--tstate->gilstate_counter;
2158-
#if !defined(NDEBUG)
2159-
if (detail::get_thread_state_unchecked() != tstate)
2160-
pybind11_fail("scoped_acquire::dec_ref(): thread state must be current!");
2161-
if (tstate->gilstate_counter < 0)
2162-
pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!");
2163-
#endif
2164-
if (tstate->gilstate_counter == 0) {
2165-
#if !defined(NDEBUG)
2166-
if (!release)
2167-
pybind11_fail("scoped_acquire::dec_ref(): internal error!");
2168-
#endif
2169-
PyThreadState_Clear(tstate);
2170-
if (active)
2171-
PyThreadState_DeleteCurrent();
2172-
PYBIND11_TLS_DELETE_VALUE(detail::get_internals().tstate);
2173-
release = false;
2174-
}
2175-
}
2176-
2177-
/// This method will disable the PyThreadState_DeleteCurrent call and the
2178-
/// GIL won't be acquired. This method should be used if the interpreter
2179-
/// could be shutting down when this is called, as thread deletion is not
2180-
/// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and
2181-
/// protect subsequent code.
2182-
PYBIND11_NOINLINE void disarm() {
2183-
active = false;
2184-
}
2185-
2186-
PYBIND11_NOINLINE ~gil_scoped_acquire() {
2187-
dec_ref();
2188-
if (release)
2189-
PyEval_SaveThread();
2190-
}
2191-
private:
2192-
PyThreadState *tstate = nullptr;
2193-
bool release = true;
2194-
bool active = true;
2195-
};
2196-
2197-
class gil_scoped_release {
2198-
public:
2199-
explicit gil_scoped_release(bool disassoc = false) : disassoc(disassoc) {
2200-
// `get_internals()` must be called here unconditionally in order to initialize
2201-
// `internals.tstate` for subsequent `gil_scoped_acquire` calls. Otherwise, an
2202-
// initialization race could occur as multiple threads try `gil_scoped_acquire`.
2203-
const auto &internals = detail::get_internals();
2204-
tstate = PyEval_SaveThread();
2205-
if (disassoc) {
2206-
auto key = internals.tstate;
2207-
PYBIND11_TLS_DELETE_VALUE(key);
2208-
}
2209-
}
2210-
2211-
/// This method will disable the PyThreadState_DeleteCurrent call and the
2212-
/// GIL won't be acquired. This method should be used if the interpreter
2213-
/// could be shutting down when this is called, as thread deletion is not
2214-
/// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and
2215-
/// protect subsequent code.
2216-
PYBIND11_NOINLINE void disarm() {
2217-
active = false;
2218-
}
2219-
2220-
~gil_scoped_release() {
2221-
if (!tstate)
2222-
return;
2223-
// `PyEval_RestoreThread()` should not be called if runtime is finalizing
2224-
if (active)
2225-
PyEval_RestoreThread(tstate);
2226-
if (disassoc) {
2227-
auto key = detail::get_internals().tstate;
2228-
PYBIND11_TLS_REPLACE_VALUE(key, tstate);
2229-
}
2230-
}
2231-
private:
2232-
PyThreadState *tstate;
2233-
bool disassoc;
2234-
bool active = true;
2235-
};
2236-
#elif defined(PYPY_VERSION)
2237-
class gil_scoped_acquire {
2238-
PyGILState_STATE state;
2239-
public:
2240-
gil_scoped_acquire() { state = PyGILState_Ensure(); }
2241-
~gil_scoped_acquire() { PyGILState_Release(state); }
2242-
void disarm() {}
2243-
};
2244-
2245-
class gil_scoped_release {
2246-
PyThreadState *state;
2247-
public:
2248-
gil_scoped_release() { state = PyEval_SaveThread(); }
2249-
~gil_scoped_release() { PyEval_RestoreThread(state); }
2250-
void disarm() {}
2251-
};
2252-
#else
2253-
class gil_scoped_acquire {
2254-
void disarm() {}
2255-
};
2256-
class gil_scoped_release {
2257-
void disarm() {}
2258-
};
2259-
#endif
2260-
22612095
error_already_set::~error_already_set() {
22622096
if (m_type) {
22632097
gil_scoped_acquire gil;

0 commit comments

Comments
 (0)