Skip to content

Commit 3bcd028

Browse files
author
Christopher Doris
committed
use Channel not Vector and make disable/enable a no-op
1 parent f7421f8 commit 3bcd028

File tree

2 files changed

+62
-55
lines changed

2 files changed

+62
-55
lines changed

src/GC/GC.jl

Lines changed: 54 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,93 +3,97 @@
33
44
Garbage collection of Python objects.
55
6-
See `disable` and `enable`.
6+
See [`enable`](@ref), [`disable`](@ref) and [`gc`](@ref).
77
"""
88
module GC
99

1010
using ..C: C
1111

12-
const ENABLED = Ref(true)
13-
const QUEUE = C.PyPtr[]
12+
const QUEUE = Channel{C.PyPtr}(Inf)
13+
const HOOK = WeakRef()
1414

1515
"""
1616
PythonCall.GC.disable()
1717
18-
Disable the PythonCall garbage collector.
18+
Do nothing.
1919
20-
This means that whenever a Python object owned by Julia is finalized, it is not immediately
21-
freed but is instead added to a queue of objects to free later when `enable()` is called.
20+
!!! note
2221
23-
Like most PythonCall functions, you must only call this from the main thread.
22+
Historically this would disable the PythonCall garbage collector. This was required
23+
for safety in multi-threaded code but is no longer needed, so this is now a no-op.
2424
"""
25-
function disable()
26-
ENABLED[] = false
27-
return
28-
end
25+
disable() = nothing
2926

3027
"""
3128
PythonCall.GC.enable()
3229
33-
Re-enable the PythonCall garbage collector.
30+
Do nothing.
3431
35-
This frees any Python objects which were finalized while the GC was disabled, and allows
36-
objects finalized in the future to be freed immediately.
32+
!!! note
3733
38-
Like most PythonCall functions, you must only call this from the main thread.
34+
Historically this would enable the PythonCall garbage collector. This was required
35+
for safety in multi-threaded code but is no longer needed, so this is now a no-op.
3936
"""
40-
function enable()
41-
ENABLED[] = true
42-
if !isempty(QUEUE) && C.PyGILState_Check() == 1
43-
free_queue()
44-
end
45-
return
46-
end
37+
enable() = nothing
4738

48-
function free_queue()
49-
for ptr in QUEUE
50-
if ptr != C.PyNULL
51-
C.Py_DecRef(ptr)
52-
end
39+
"""
40+
PythonCall.GC.gc()
41+
42+
Free any Python objects waiting to be freed.
43+
44+
These are objects that were finalized from a thread that was not holding the Python
45+
GIL at the time.
46+
47+
Like most PythonCall functions, this must only be called from the main thread (i.e. the
48+
thread currently holding the Python GIL.)
49+
"""
50+
function gc()
51+
if C.CTX.is_initialized
52+
unsafe_free_queue()
5353
end
54-
empty!(QUEUE)
5554
nothing
5655
end
5756

58-
function gc()
59-
if ENABLED[] && C.PyGILState_Check() == 1
60-
free_queue()
61-
true
62-
else
63-
false
57+
function unsafe_free_queue()
58+
if isready(QUEUE)
59+
@lock QUEUE while isready(QUEUE)
60+
ptr = take!(QUEUE)
61+
if ptr != C.PyNULL
62+
C.Py_DecRef(ptr)
63+
end
64+
end
6465
end
66+
nothing
6567
end
6668

6769
function enqueue(ptr::C.PyPtr)
6870
if ptr != C.PyNULL && C.CTX.is_initialized
69-
if ENABLED[] && C.PyGILState_Check() == 1
71+
if C.PyGILState_Check() == 1
7072
C.Py_DecRef(ptr)
71-
isempty(QUEUE) || free_queue()
73+
unsafe_free_queue()
7274
else
73-
push!(QUEUE, ptr)
75+
put!(QUEUE, ptr)
7476
end
7577
end
76-
return
78+
nothing
7779
end
7880

7981
function enqueue_all(ptrs)
80-
if C.CTX.is_initialized
81-
if ENABLED[] && C.PyGILState_Check() == 1
82+
if any(ptr -> ptr != C.PYNULL, ptrs) && C.CTX.is_initialized
83+
if C.PyGILState_Check() == 1
8284
for ptr in ptrs
8385
if ptr != C.PyNULL
8486
C.Py_DecRef(ptr)
8587
end
8688
end
87-
isempty(QUEUE) || free_queue()
89+
unsafe_free_queue()
8890
else
89-
append!(QUEUE, ptrs)
91+
for ptr in ptrs
92+
put!(QUEUE, ptr)
93+
end
9094
end
9195
end
92-
return
96+
nothing
9397
end
9498

9599
mutable struct GCHook
@@ -99,13 +103,17 @@ mutable struct GCHook
99103
end
100104

101105
function _gchook_finalizer(x)
102-
gc()
103-
finalizer(_gchook_finalizer, x)
106+
if C.CTX.is_initialized
107+
finalizer(_gchook_finalizer, x)
108+
if isready(QUEUE) && C.PyGILState_Check() == 1
109+
unsafe_free_queue()
110+
end
111+
end
104112
nothing
105113
end
106114

107115
function __init__()
108-
GCHook()
116+
HOOK.value = GCHook()
109117
nothing
110118
end
111119

test/finalize_test_script.jl

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,16 @@ let
88
end
99
end
1010

11-
@show PythonCall.GC.ENABLED[]
12-
@show length(PythonCall.GC.QUEUE)
13-
GC.gc(false)
11+
@show isready(PythonCall.GC.QUEUE)
12+
GC.gc()
1413
# with GCHook, the queue should be empty now (the above gc() triggered GCHook to clear the PythonCall QUEUE)
1514
# without GCHook, gc() has no effect on the QUEUE
16-
@show length(PythonCall.GC.QUEUE)
17-
GC.gc(false)
18-
@show length(PythonCall.GC.QUEUE)
19-
GC.gc(false)
20-
@show length(PythonCall.GC.QUEUE)
15+
@show isready(PythonCall.GC.QUEUE)
16+
GC.gc()
17+
@show isready(PythonCall.GC.QUEUE)
18+
GC.gc()
19+
@show isready(PythonCall.GC.QUEUE)
2120
# with GCHook this is not necessary, GC.gc() is enough
2221
# without GCHook, this is required to free any objects in the PythonCall QUEUE
2322
PythonCall.GC.gc()
24-
@show length(PythonCall.GC.QUEUE)
23+
@show isready(PythonCall.GC.QUEUE)

0 commit comments

Comments
 (0)