@@ -9,6 +9,18 @@ module GIL
99
1010using .. C: C
1111
12+ # Ensure that only one Julia thread tries to acquire the Python GIL
13+ # PyGILState_Ensure and PyGILState_Release may not be thread safe.
14+ # https://github.com/JuliaPy/PythonCall.jl/issues/627
15+ const _jl_gil_lock = ReentrantLock ()
16+
17+ """
18+ hasgil()
19+
20+ Returns `true` if the current thread has the GIL or `false` otherwise.
21+ """
22+ hasgil () = C. PyGILState_Check () == Cint (1 )
23+
1224"""
1325 lock(f)
1426
@@ -21,11 +33,16 @@ threads. Since the main Julia thread holds the GIL by default, you will need to
2133See [`@lock`](@ref) for the macro form.
2234"""
2335function lock (f)
24- state = C . PyGILState_Ensure ( )
36+ Base . lock (_jl_gil_lock )
2537 try
26- f ()
38+ state = C. PyGILState_Ensure ()
39+ try
40+ f ()
41+ finally
42+ C. PyGILState_Release (state)
43+ end
2744 finally
28- C . PyGILState_Release (state )
45+ Base . unlock (_jl_gil_lock )
2946 end
3047end
3148
@@ -42,11 +59,16 @@ The macro equivalent of [`lock`](@ref).
4259"""
4360macro lock (expr)
4461 quote
45- state = C . PyGILState_Ensure ( )
62+ Base . lock (_jl_gil_lock )
4663 try
47- $ (esc (expr))
64+ state = C. PyGILState_Ensure ()
65+ try
66+ $ (esc (expr))
67+ finally
68+ C. PyGILState_Release (state)
69+ end
4870 finally
49- C . PyGILState_Release (state )
71+ Base . unlock (_jl_gil_lock )
5072 end
5173 end
5274end
@@ -63,11 +85,17 @@ Python code. That other thread can be a Julia thread, which must lock the GIL us
6385See [`@unlock`](@ref) for the macro form.
6486"""
6587function unlock (f)
66- state = C. PyEval_SaveThread ()
88+ _locked = Base. islocked (_jl_gil_lock)
89+ _locked && Base. unlock (_jl_gil_lock)
6790 try
68- f ()
91+ state = C. PyEval_SaveThread ()
92+ try
93+ f ()
94+ finally
95+ C. PyEval_RestoreThread (state)
96+ end
6997 finally
70- C . PyEval_RestoreThread (state )
98+ _locked && Base . lock (_jl_gil_lock )
7199 end
72100end
73101
@@ -84,13 +112,26 @@ The macro equivalent of [`unlock`](@ref).
84112"""
85113macro unlock (expr)
86114 quote
87- state = C. PyEval_SaveThread ()
115+ _locked = Base. islocked (_jl_gil_lock)
116+ _locked && Base. unlock (_jl_gil_lock)
88117 try
89- $ (esc (expr))
118+ state = C. PyEval_SaveThread ()
119+ try
120+ $ (esc (expr))
121+ finally
122+ C. PyEval_RestoreThread (state)
123+ end
90124 finally
91- C . PyEval_RestoreThread (state )
125+ _locked && Base . lock (_jl_gil_lock )
92126 end
93127 end
94128end
95129
130+ # If the main thread already has the GIL, we should lock _jl_gil_lock.
131+ function __init__ ()
132+ if hasgil ()
133+ Base. lock (_jl_gil_lock)
134+ end
135+ end
136+
96137end
0 commit comments