-
Notifications
You must be signed in to change notification settings - Fork 67
/
Copy pathGC.jl
154 lines (129 loc) · 3.87 KB
/
GC.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"""
module PythonCall.GC
Garbage collection of Python objects.
See [`gc`](@ref).
"""
module GC
using ..C: C
const QUEUE = (; items = C.PyPtr[], lock = Threads.SpinLock())
const HOOK = Ref{WeakRef}()
"""
PythonCall.GC.disable()
Do nothing.
!!! note
Historically this would disable the PythonCall garbage collector. This was required
for safety in multi-threaded code but is no longer needed, so this is now a no-op.
"""
function disable()
Base.depwarn(
"disabling the PythonCall GC is no longer needed for thread-safety",
:disable,
)
nothing
end
"""
PythonCall.GC.enable()
Do nothing.
!!! note
Historically this would enable the PythonCall garbage collector. This was required
for safety in multi-threaded code but is no longer needed, so this is now a no-op.
"""
function enable()
Base.depwarn(
"disabling the PythonCall GC is no longer needed for thread-safety",
:enable,
)
nothing
end
"""
PythonCall.GC.gc()
Free any Python objects waiting to be freed.
These are objects that were finalized from a thread that was not holding the Python
GIL at the time.
Like most PythonCall functions, this must only be called from the main thread (i.e. the
thread currently holding the Python GIL.)
"""
function gc()
if C.CTX.is_initialized
unsafe_free_queue()
end
nothing
end
function unsafe_free_queue()
Base.@lock QUEUE.lock begin
for ptr in QUEUE.items
if ptr != C.PyNULL
C.Py_DecRef(ptr)
end
end
empty!(QUEUE.items)
end
nothing
end
function enqueue(ptr::C.PyPtr)
# If the ptr is NULL there is nothing to free.
# If C.CTX.is_initialized is false then the Python interpreter hasn't started yet
# or has been finalized; either way attempting to free will cause an error.
if ptr != C.PyNULL && C.CTX.is_initialized
if C.PyGILState_Check() == 1
# If the current thread holds the GIL, then we can immediately free.
C.Py_DecRef(ptr)
# We may as well also free any other enqueued objects.
if !isempty(QUEUE.items)
unsafe_free_queue()
end
else
# Otherwise we push the pointer onto the queue to be freed later, either:
# (a) If a future Python object is finalized on the thread holding the GIL
# in the branch above.
# (b) If the GCHook() object below is finalized in an ordinary GC.
# (c) If the user calls PythonCall.GC.gc().
Base.@lock QUEUE.lock push!(QUEUE.items, ptr)
end
end
nothing
end
function enqueue_all(ptrs)
if any(!=(C.PYNULL), ptrs) && C.CTX.is_initialized
if C.PyGILState_Check() == 1
for ptr in ptrs
if ptr != C.PyNULL
C.Py_DecRef(ptr)
end
end
if !isempty(QUEUE.items)
unsafe_free_queue()
end
else
Base.@lock QUEUE.lock append!(QUEUE.items, ptrs)
end
end
nothing
end
"""
GCHook()
An immortal object which frees any pending Python objects when Julia's GC runs.
This works by creating it but not holding any strong reference to it, so it is eligible
to be finalized by Julia's GC. The finalizer empties the PythonCall GC queue if
possible. The finalizer also re-attaches itself, so the object does not actually get
collected and so the finalizer will run again at next GC.
"""
mutable struct GCHook
function GCHook()
finalizer(_gchook_finalizer, new())
end
end
function _gchook_finalizer(x)
if C.CTX.is_initialized
finalizer(_gchook_finalizer, x)
if !isempty(QUEUE.items) && C.PyGILState_Check() == 1
unsafe_free_queue()
end
end
nothing
end
function __init__()
HOOK[] = WeakRef(GCHook())
nothing
end
end # module GC