Skip to content

Commit bdaa1ff

Browse files
authored
feat: add way to register post-__init__ callbacks (#66)
(cherry picked from commit 7ef6c56)
1 parent 2b31808 commit bdaa1ff

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

src/DataSets.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,56 @@ function __init__()
173173
=# project=proj exception=(exc,catch_backtrace())
174174
end
175175
end
176+
# Call any post-__init__() callbacks that were registered before __init__() was called,
177+
# or had chance to finish.
178+
lock(_PROJECT_INIT_LOCK) do
179+
_PROJECT_INITIALIZED[] = true
180+
for f in _PROJECT_INIT_CALLBACKS
181+
_invoke_init_cb(f)
182+
end
183+
# No need to keep the callbacks around, and maybe the GC can free some memory.
184+
empty!(_PROJECT_INIT_CALLBACKS)
185+
end
186+
end
187+
end
188+
189+
# The register_post_init_callback() can be used to add a callback that will get called
190+
# when DataSets.__init__() has run. Note: if f() throws an error, it does not cause a crash.
191+
#
192+
# This is useful for sysimages where the module is already be loaded (in Base.loaded_modules),
193+
# but __init__() has not been called yet. In particular, this means that other packages' __init__
194+
# functions can be sure that when they call initialization code that affects DataSets (in particular,
195+
# DataSets.PROJECT), then that code runs after __init__() has run.
196+
#
197+
# In the non-sysimage case, DataSets.__init__() would normally have already been called when
198+
# once register_post_init_callback() becomes available, and so in those situations, the callback
199+
# gets called immediately. However, in a system image, DataSets may have to queue up (FIFO) the
200+
# callback functions and wait until DataSets.__init__() has finished.
201+
#
202+
# Since the __init__() functions in sysimages can run in parallel, we use a lock just in case,
203+
# to make sure that two parallel calls would succeed.
204+
const _PROJECT_INIT_LOCK = ReentrantLock()
205+
const _PROJECT_INITIALIZED = Ref{Bool}(false)
206+
const _PROJECT_INIT_CALLBACKS = Base.Callable[]
207+
function register_post_init_callback(f::Base.Callable)
208+
invoke = lock(_PROJECT_INIT_LOCK) do
209+
if _PROJECT_INITIALIZED[]
210+
return true
211+
end
212+
push!(_PROJECT_INIT_CALLBACKS, f)
213+
return false
214+
end
215+
# We'll invoke outside of the lock, so that a long-running f() call
216+
# wouldn't block other calls to register_post_init_callback()
217+
invoke && _invoke_init_cb(f)
218+
return nothing
219+
end
220+
221+
function _invoke_init_cb(f::Base.Callable)
222+
try
223+
Base.invokelatest(f)
224+
catch e
225+
@error "Failed to run init callback: $f" exception = (e, catch_backtrace())
176226
end
177227
end
178228

test/runtests.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ using ResourceContexts
77

88
using DataSets: FileSystemRoot
99

10-
#-------------------------------------------------------------------------------
10+
@testset "register_post_init_callback" begin
11+
init_was_called = Ref(false)
12+
DataSets.register_post_init_callback() do
13+
init_was_called[] = true
14+
end
15+
@test init_was_called[]
16+
end
17+
1118
@testset "DataSet config" begin
1219
proj = DataSets.load_project("Data.toml")
1320

0 commit comments

Comments
 (0)