@@ -9,7 +9,7 @@ import Base.DL_LOAD_PATH
99
1010export DL_LOAD_PATH, RTLD_DEEPBIND, RTLD_FIRST, RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL,
1111 RTLD_NODELETE, RTLD_NOLOAD, RTLD_NOW, dlclose, dlopen, dlopen_e, dlsym, dlsym_e,
12- dlpath, find_library, dlext, dllist
12+ dlpath, find_library, dlext, dllist, LazyLibrary, LazyStringFunc
1313
1414"""
1515 DL_LOAD_PATH
@@ -45,6 +45,9 @@ applicable.
4545"""
4646(RTLD_DEEPBIND, RTLD_FIRST, RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL, RTLD_NODELETE, RTLD_NOLOAD, RTLD_NOW)
4747
48+ # The default flags for `dlopen()`
49+ const default_rtld_flags = RTLD_LAZY | RTLD_DEEPBIND
50+
4851"""
4952 dlsym(handle, sym; throw_error::Bool = true)
5053
7275Look up a symbol from a shared library handle, silently return `C_NULL` on lookup failure.
7376This method is now deprecated in favor of `dlsym(handle, sym; throw_error=false)`.
7477"""
75- function dlsym_e (hnd :: Ptr , s :: Union{Symbol,AbstractString} )
76- return something (dlsym (hnd, s ; throw_error= false ), C_NULL )
78+ function dlsym_e (args ... )
79+ return something (dlsym (args ... ; throw_error= false ), C_NULL )
7780end
7881
7982"""
@@ -110,10 +113,10 @@ If the library cannot be found, this method throws an error, unless the keyword
110113"""
111114function dlopen end
112115
113- dlopen (s:: Symbol , flags:: Integer = RTLD_LAZY | RTLD_DEEPBIND ; kwargs... ) =
116+ dlopen (s:: Symbol , flags:: Integer = default_rtld_flags ; kwargs... ) =
114117 dlopen (string (s), flags; kwargs... )
115118
116- function dlopen (s:: AbstractString , flags:: Integer = RTLD_LAZY | RTLD_DEEPBIND ; throw_error:: Bool = true )
119+ function dlopen (s:: AbstractString , flags:: Integer = default_rtld_flags ; throw_error:: Bool = true )
117120 ret = ccall (:jl_load_dynamic_library , Ptr{Cvoid}, (Cstring,UInt32,Cint), s, flags, Cint (throw_error))
118121 if ret == C_NULL
119122 return nothing
@@ -314,4 +317,98 @@ function dllist()
314317 return dynamic_libraries
315318end
316319
320+
321+ const LazyLibraryLock = Base. ReentrantLock ()
322+ """
323+ LazyLibrary(name, flags = <default dlopen flags>,
324+ dependencies = LazyLibrary[], on_load_callback = nothing)
325+
326+ Represents a lazily-loaded library that opens itself and its dependencies on first usage
327+ in a `dlopen()`, `dlsym()`, or `ccall()` usage. While this structure contains the
328+ ability to run arbitrary code on first load via `on_load_callback`, we caution that this
329+ should be used sparingly, as it is not expected that `ccall()` should result in large
330+ amounts of Julia code being run. You may call `ccall()` from within the
331+ `on_load_callback` but only for the current library and its dependencies.
332+ """
333+ mutable struct LazyLibrary
334+ # Name and flags to open with
335+ const name:: AbstractString
336+ const flags:: UInt32
337+
338+ # Dependencies that must be loaded before we can load
339+ dependencies:: Vector{LazyLibrary}
340+
341+ # Function that gets called once upon initial load with the pointer as an argument
342+ on_load_callback:: Union{Nothing,Function}
343+
344+ # Pointer that we eventually fill out upon first `dlopen()`
345+ @atomic handle:: Ptr{Cvoid}
346+ function LazyLibrary (name; flags = default_rtld_flags, dependencies = LazyLibrary[],
347+ on_load_callback = nothing )
348+ return new (
349+ name,
350+ UInt32 (flags),
351+ collect (dependencies),
352+ on_load_callback,
353+ C_NULL ,
354+ )
355+ end
356+ end
357+
358+ # Register this LazyLibrary type with the C code during bootstrap,
359+ # so that `ccall()` lowering knows what a `LazyLibrary` is, and how
360+ # to `dlopen()` it. This hack brought to you by Valentin. \[0_o]/
361+ Base. unsafe_store! (cglobal (:jl_lazy_library_type , Any), LazyLibrary)
362+ Base. unsafe_store! (cglobal (:jl_libdl_dlopen_func , Any), dlopen)
363+
364+ """
365+ LazyStringFunc
366+
367+ Helper type for `LazyString` to be used with `LazyLibrary` so that runtime-generated
368+ library paths can be used with lazy libraries. Note that the value will only be computed
369+ once; it is cached after initial computation. Typical usage:
370+
371+ ```
372+ libfoo = LazyLibrary(LazyStringFunc(() -> joinpath(artifact"foo/lib/libfoo.so.1.2.3")))
373+ ```
374+ """
375+ LazyStringFunc (f) = LazyString (_LazyStringFunc (f))
376+
377+ struct _LazyStringFunc <: AbstractString
378+ f:: Function
379+ end
380+ Base. print (io:: IO , lsf:: _LazyStringFunc ) = print (io, lsf. f ())
381+
382+ function dlopen (ll:: LazyLibrary , flags:: Integer = ll. flags; kwargs... )
383+ handle = @atomic :acquire ll. handle
384+ if handle != C_NULL
385+ return handle
386+ end
387+
388+ # Ensure that all dependencies are loaded
389+ for dep in ll. dependencies
390+ dlopen (dep; kwargs... )
391+ end
392+
393+ # Only let a single thread run callbacks at a time
394+ @lock LazyLibraryLock begin
395+ # Check to see if another thread has already run this
396+ handle = @atomic :acquire ll. handle
397+ if handle == C_NULL
398+ # Load our library
399+ handle = dlopen (ll. name, flags; kwargs... )
400+
401+ # Invoke our on load callback, if it exists
402+ if ll. on_load_callback != = nothing
403+ ll. on_load_callback (handle)
404+ end
405+
406+ @atomic :release ll. handle = handle
407+ end
408+ end
409+
410+ return handle
411+ end
412+ dlsym (ll:: LazyLibrary , args... ; kwargs... ) = dlsym (dlopen (ll), args... ; kwargs... )
413+ dlpath (ll:: LazyLibrary ) = dlpath (dlopen (ll))
317414end # module Libdl
0 commit comments