@@ -9,7 +9,7 @@ import Base.DL_LOAD_PATH
9
9
10
10
export DL_LOAD_PATH, RTLD_DEEPBIND, RTLD_FIRST, RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL,
11
11
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
13
13
14
14
"""
15
15
DL_LOAD_PATH
@@ -45,6 +45,9 @@ applicable.
45
45
"""
46
46
(RTLD_DEEPBIND, RTLD_FIRST, RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL, RTLD_NODELETE, RTLD_NOLOAD, RTLD_NOW)
47
47
48
+ # The default flags for `dlopen()`
49
+ const default_rtld_flags = RTLD_LAZY | RTLD_DEEPBIND
50
+
48
51
"""
49
52
dlsym(handle, sym; throw_error::Bool = true)
50
53
72
75
Look up a symbol from a shared library handle, silently return `C_NULL` on lookup failure.
73
76
This method is now deprecated in favor of `dlsym(handle, sym; throw_error=false)`.
74
77
"""
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 )
77
80
end
78
81
79
82
"""
@@ -110,10 +113,10 @@ If the library cannot be found, this method throws an error, unless the keyword
110
113
"""
111
114
function dlopen end
112
115
113
- dlopen (s:: Symbol , flags:: Integer = RTLD_LAZY | RTLD_DEEPBIND ; kwargs... ) =
116
+ dlopen (s:: Symbol , flags:: Integer = default_rtld_flags ; kwargs... ) =
114
117
dlopen (string (s), flags; kwargs... )
115
118
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 )
117
120
ret = ccall (:jl_load_dynamic_library , Ptr{Cvoid}, (Cstring,UInt32,Cint), s, flags, Cint (throw_error))
118
121
if ret == C_NULL
119
122
return nothing
@@ -138,10 +141,10 @@ vendor = dlopen("libblas") do lib
138
141
end
139
142
```
140
143
"""
141
- function dlopen (f:: Function , args... ; kwargs... )
144
+ function dlopen (f:: Function , name, args... ; kwargs... )
142
145
hdl = nothing
143
146
try
144
- hdl = dlopen (args... ; kwargs... )
147
+ hdl = dlopen (name, args... ; kwargs... )
145
148
f (hdl)
146
149
finally
147
150
dlclose (hdl)
@@ -314,4 +317,135 @@ function dllist()
314
317
return dynamic_libraries
315
318
end
316
319
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))
317
451
end # module Libdl
0 commit comments