@@ -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, LazyLibraryPath, BundledLazyLibraryPath
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,132 @@ function dllist()
314317 return dynamic_libraries
315318end
316319
320+
321+ """
322+ LazyLibraryPath
323+
324+ Helper type for lazily constructed library paths for use with `LazyLibrary`.
325+ Arguments are passed to `joinpath()`. Arguments must be able to have
326+ `string()` called on them.
327+
328+ ```
329+ libfoo = LazyLibrary(LazyLibraryPath(prefix, "lib/libfoo.so.1.2.3"))
330+ ```
331+ """
332+ struct LazyLibraryPath
333+ pieces:: Vector
334+ LazyLibraryPath (pieces:: Vector ) = new (pieces)
335+ end
336+ LazyLibraryPath (args... ) = LazyLibraryPath (collect (args))
337+ Base. string (llp:: LazyLibraryPath ) = joinpath (string .(llp. pieces)... )
338+ # Define `print` so that we can wrap this in a `LazyString`
339+ Base. print (io:: IO , llp:: LazyLibraryPath ) = print (io, string (llp))
340+
341+ # Helper to get `Sys.BINDIR` at runtime
342+ struct SysBindirGetter; end
343+ Base. string (:: SysBindirGetter ) = Sys. BINDIR
344+
345+ """
346+ BundledLazyLibraryPath
347+
348+ Helper type for lazily constructed library paths that are stored within the
349+ bundled Julia distribution, primarily for use by Base modules.
350+
351+ ```
352+ libfoo = LazyLibrary(BundledLazyLibraryPath("lib/libfoo.so.1.2.3"))
353+ ```
354+ """
355+ BundledLazyLibraryPath (subpath) = LazyLibraryPath (SysBindirGetter (), subpath)
356+
357+
358+ """
359+ LazyLibrary(name, flags = <default dlopen flags>,
360+ dependencies = LazyLibrary[], on_load_callback = nothing)
361+
362+ Represents a lazily-loaded library that opens itself and its dependencies on first usage
363+ in a `dlopen()`, `dlsym()`, or `ccall()` usage. While this structure contains the
364+ ability to run arbitrary code on first load via `on_load_callback`, we caution that this
365+ should be used sparingly, as it is not expected that `ccall()` should result in large
366+ amounts of Julia code being run. You may call `ccall()` from within the
367+ `on_load_callback` but only for the current library and its dependencies, and user should
368+ not call `wait()` on any tasks within the on load callback.
369+ """
370+ mutable struct LazyLibrary
371+ # Name and flags to open with
372+ const path
373+ const flags:: UInt32
374+
375+ # Dependencies that must be loaded before we can load
376+ dependencies:: Vector{LazyLibrary}
377+
378+ # Function that get called once upon initial load
379+ on_load_callback
380+ const lock:: Base.ReentrantLock
381+
382+ # Pointer that we eventually fill out upon first `dlopen()`
383+ @atomic handle:: Ptr{Cvoid}
384+ function LazyLibrary (path; flags = default_rtld_flags, dependencies = LazyLibrary[],
385+ on_load_callback = nothing )
386+ return new (
387+ path,
388+ UInt32 (flags),
389+ collect (dependencies),
390+ on_load_callback,
391+ Base. ReentrantLock (),
392+ C_NULL ,
393+ )
394+ end
395+ end
396+
397+ # We support adding dependencies only because of very special situations
398+ # such as LBT needing to have OpenBLAS_jll added as a dependency dynamically.
399+ function add_dependency! (ll:: LazyLibrary , dep:: LazyLibrary )
400+ @lock ll. lock begin
401+ push! (ll. dependencies, dep)
402+ end
403+ end
404+
405+ # Register `jl_libdl_dlopen_func` so that `ccall()` lowering knows
406+ # how to call `dlopen()`; This hack brought to you by Valentin. \[0_o]/
407+ Base. unsafe_store! (cglobal (:jl_libdl_dlopen_func , Any), dlopen)
408+
409+ function dlopen (ll:: LazyLibrary , flags:: Integer = ll. flags; kwargs... )
410+ handle = @atomic :acquire ll. handle
411+ if handle == C_NULL
412+ @lock ll. lock begin
413+ # Check to see if another thread has already run this
414+ if ll. handle == C_NULL
415+ # Ensure that all dependencies are loaded
416+ for dep in ll. dependencies
417+ dlopen (dep; kwargs... )
418+ end
419+
420+ # Load our library
421+ handle = dlopen (string (ll. path), flags; kwargs... )
422+ @atomic :release ll. handle = handle
423+
424+ # Only the thread that loaded the library calls the `on_load_callback()`.
425+ if ll. on_load_callback != = nothing
426+ ll. on_load_callback ()
427+ end
428+ end
429+ end
430+ else
431+ # Invoke our on load callback, if it exists
432+ if ll. on_load_callback != = nothing
433+ # This empty lock protects against the case where we have updated
434+ # `ll.handle` in the branch above, but not exited the lock. We want
435+ # a second thread that comes in at just the wrong time to have to wait
436+ # for that lock to be released (and thus for the on_load_callback to
437+ # have finished), hence the empty lock here. But we want the
438+ # on_load_callback thread to bypass this, which will be happen thanks
439+ # to the fact that we're using a reentrant lock here.
440+ @lock ll. lock begin end
441+ end
442+ end
443+
444+ return handle
445+ end
446+ dlsym (ll:: LazyLibrary , args... ; kwargs... ) = dlsym (dlopen (ll), args... ; kwargs... )
447+ dlpath (ll:: LazyLibrary ) = dlpath (dlopen (ll))
317448end # module Libdl
0 commit comments