Disable signal handlers and limit threading for libraries via jl_options shim#113
Disable signal handlers and limit threading for libraries via jl_options shim#113
Conversation
…ons constructor shim Libraries built with --output-lib now automatically set handle_signals=off and threads=1 via an __attribute__((constructor)) C shim that modifies jl_options before jl_init. This prevents JuliaC libraries from installing signal handlers or spawning extra threads when loaded into a host process. The mechanism is exposed as --jl-option <key=value> with the same syntax as Julia CLI flags (e.g. --jl-option handle-signals=no, --jl-option threads=4,2). Thread parsing mirrors jloptions.c semantics including interactive thread pools. Uses dladdr/dlsym on Unix and direct &jl_options on Windows to resolve the correct symbol from the linked libjulia dependency. Closes #112 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On Linux, dlfcn.h only exposes dladdr and Dl_info when _GNU_SOURCE is defined. macOS exposes them unconditionally, which is why local tests passed but CI on ubuntu failed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dfe3525 to
13215fb
Compare
On Linux, dlopen(RTLD_NOLOAD) on the main executable returns NULL, causing the constructor to silently bail out for executables. On macOS, dlsym with a specific handle searches that image and its dependencies, so the dladdr/dlopen/dlsym approach works. Use RTLD_DEFAULT on Linux to search all loaded shared objects. This is safe because our libjulia is the only one loaded at constructor time. Keep the dladdr/dlopen approach on macOS for dependency-scoped resolution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bc12bcf to
6bdb164
Compare
|
I am happy with the C shim here, but is it possible to do the arg parsing, etc. on the Julia side via a |
|
Not sure if get what you mean? |
|
I mean that we probably shouldn't re-implement argument parsing for Julia in JuliaC - it's a lot of maintenance burden and can lead to subtle bugs and inconsistencies, such as if Julia sets up the threadpools internally one way and then JuliaC decides to set up them up in another, no-longer-supported way (or any number of other divergences). If you want to support translating CLI arguments to |
7908d36 to
3853d61
Compare
…ment Instead of generating C code that manually assigns jl_options struct fields, call jl_parse_opts with a synthetic argv. This reuses Julia's own option parsing logic and avoids duplicating the parsing for threads, handle-signals, etc. The constructor saves/restores getopt global state and image_file to avoid interfering with the host process. Options are validated at compile time by running a Julia subprocess with the same flags. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3853d61 to
3319bce
Compare
topolarity
left a comment
There was a problem hiding this comment.
Looking much better without us parsing args ourselves.
A few questions w.r.t. the CLI help, etc.
- Fix out-of-date comments on jl_options field in ImageRecipe - Update help text to mention Julia CLI syntax and list supported options Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| # Generate and compile a C shim that sets jl_options fields via constructor | ||
| if !isempty(recipe.jl_options) | ||
| _validate_jl_options(recipe.jl_options) | ||
| obj = _compile_jl_options_shim(recipe.jl_options; verbose=recipe.verbose) |
There was a problem hiding this comment.
It may be worth compiling and linking this always.
Otherwise, you may hit mysterious bugs where jl_parse_opts, e.g., sets an unexpected default, but you only see the behavior based on whether you provided any jl_options flags or not.
| println(io, " --privatize Privatize bundled libjulia (Unix)") | ||
| println(io, " --trim[=mode] Strip IR/metadata (e.g. --trim=safe)") | ||
| println(io, " --compile-ccallable Export ccallable entrypoints") | ||
| println(io, " --jl-option <key=value> Set a Julia option using CLI syntax (supported: handle-signals, threads)") |
There was a problem hiding this comment.
| println(io, " --jl-option <key=value> Set a Julia option using CLI syntax (supported: handle-signals, threads)") | |
| println(io, " --jl-option <key=value> Set a Julia option using CLI syntax (supported: handle-signals=[yes/no], threads=[N])") |
There was a problem hiding this comment.
This may not be required if you pipe the feedback from args validation back to the user - I just want to make sure that there's some way to learn the valid arguments
Right now Julia is unfortunately not that helpful:
ERROR: julia: invalid argument to --handle-signals (maybe)| end | ||
|
|
||
| @testset "jl_options applied at runtime (library)" begin | ||
| if Sys.isunix() |
topolarity
left a comment
There was a problem hiding this comment.
Looks largely good to me.
I might recommend --jl-cli-option instead of --jl-option since I think it makes it clearer these strings are piped to Julia's CLI args handler.
Either way, I think this is good enough to land if you add a test for the library functionality on Windows
Summary
--output-libnow automatically sethandle_signals=offandthreads=1via an__attribute__((constructor))C shim that modifiesjl_optionsbeforejl_init--jl-option <key=value>CLI flag using the same syntax as Julia CLI flags (e.g.--jl-option handle-signals=no,--jl-option threads=4,2)jloptions.csemantics including interactive thread pool supportdladdr/dlsymon Unix and direct&jl_optionson Windows to resolve the correct symbol from the linked libjulia dependencyhandle-signals,threads) and values against Julia's expected formatsTest plan
--jl-option(space/equals separators, multiple flags, error cases)--jl-option, run it, checkBase.JLOptions()fieldsCloses #112
🤖 Generated with Claude Code