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