Skip to content

Disable signal handlers and limit threading for libraries via jl_options shim#113

Open
gbaraldi wants to merge 5 commits intomainfrom
gb/jl-options-shim
Open

Disable signal handlers and limit threading for libraries via jl_options shim#113
gbaraldi wants to merge 5 commits intomainfrom
gb/jl-options-shim

Conversation

@gbaraldi
Copy link
Member

Summary

  • 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
  • Adds --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)
  • Thread parsing mirrors jloptions.c semantics including interactive thread pool support
  • Uses dladdr/dlsym on Unix and direct &jl_options on Windows to resolve the correct symbol from the linked libjulia dependency
  • Validates option names against an allowlist (handle-signals, threads) and values against Julia's expected formats

Test plan

  • Unit tests for auto-population of library defaults
  • Unit tests for validation (unknown keys, invalid values)
  • CLI parsing tests for --jl-option (space/equals separators, multiple flags, error cases)
  • Runtime verification: compile exe with --jl-option, run it, check Base.JLOptions() fields
  • Runtime verification: compile privatized library, dlopen from fresh Julia process, call ccallable functions to verify options applied
  • Full test suite passes

Closes #112

🤖 Generated with Claude Code

gbaraldi and others added 2 commits February 18, 2026 15:56
…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>
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>
@gbaraldi gbaraldi force-pushed the gb/jl-options-shim branch 4 times, most recently from bc12bcf to 6bdb164 Compare February 18, 2026 19:24
@topolarity
Copy link
Member

I am happy with the C shim here, but is it possible to do the arg parsing, etc. on the Julia side via a jl_setup_args (or similar) function that updates a jl_options_t based on a provided args string?

@gbaraldi
Copy link
Member Author

Not sure if get what you mean?

@topolarity
Copy link
Member

topolarity commented Feb 19, 2026

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 jl_options, we should add that functionality to libjulia and use it here IMO.

@gbaraldi gbaraldi force-pushed the gb/jl-options-shim branch 2 times, most recently from 7908d36 to 3853d61 Compare February 19, 2026 16:39
…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>
Copy link
Member

@topolarity topolarity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Member

@topolarity topolarity Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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])")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only unix?

Copy link
Member

@topolarity topolarity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JuliaC libraries should not install signal handlers

2 participants