How to avoid multiple __init__ calls?

I use project wide config used by many modules. And sometimes I observe its __init__ function called many times, I assume during compilation.

How to avoid that is the proper way to use ccall(:jl_generating_output, Cint, ()) == 1 && return nothing?

The config

module Config

now::Date = Date(0)

const clear_cache_cbs = Function[]
clear_cache!() = for cb in clear_cache_cbs; cb() end
clear_cache!(cb::Function) = push!(clear_cache_cbs, cb)

set_now(d::Date) = begin
  Config.now == d && return
  Config.now = d
  clear_cache!()
  @info "config" (; now=Config.now)
end

add_load_path!(p) = begin
  p = realpath(p); p ∉ LOAD_PATH && push!(LOAD_PATH, p)
end

__init__() = begin
  # ccall(:jl_generating_output, Cint, ()) == 1 && return nothing

  set_now(haskey(ENV, "now") ? Date(ENV["now"]) : Date(Dates.now(UTC)))
  haskey(ENV, "now") && @warn "config" (; now=Config.now)

  add_load_path!("$(@__DIR__)/reports")
  add_load_path!("$(@__DIR__)/lib")
  add_load_path!("$(@__DIR__)/SV")
  add_load_path!("$(@__DIR__)/options")
  add_load_path!("$(@__DIR__)/garch")
end

end

I think this needs more context, e.g. on how (by which mechanism) it is used by multiple modules and what you want to achieve with the config. Manipulating LOAD_PATH from an __init__ function looks scary to me. There have to be better alternatives.

The . added to LOAD_PATH in julia global startup file, and Revise.

The main.jl then can look like:

import Config # Always loaded first, it defines project paths and other settings
import SVJ # located in ./SV/SVJ.jl

println("hi")

Any module will be automatically loaded on demand and changes tracked and reloaded.

Config also imported (imported, not included) by many other modules, like SVJ.jl.

Everything loaded and reloaded automatically. No need to explicitly use include, define packages etc.

It works well, but I assume during compilation Julia reloads modules in some strange way, and Config.__init__ called multiple times, I made it idempotent so it already works, but would like to fix it properly.

I would say that the proper fix is to use include, define packages, etc.

But if you absolutely don’t want to do that, you can investigate whether this new Julia 1.12 feature is of any help to you:

OncePerProcess{T}(init::Function)() -> T

Calling a OncePerProcess object returns a value of type T by running the function initializer exactly once per process. All concurrent and future calls in the same process will return exactly the same value. This is useful in code that will be precompiled, as it allows setting up caches or other state which won’t get serialized.

__init__() shouldn’t be repeated unless it’s being explicitly called. If you mean you observe it during precompilation, that’s happening for the first times in isolated child processes. Haven’t confirmed it personally, but I think the child processes would get a new copy of the environment variables changed by ENV in the parent process, but LOAD_PATH is totally reset.

That is one way, but it’s usually used for different states that directly obstruct precompilation (devdocs source below), and it’s preferred to refactor or clean those states up in other ways. Like GunnarFarneback said, I’m not sure about manipulating LOAD_PATH to potentially import dependencies that could sidestep Pkg.resolve. Also not a fan of a dependency automatically tweaking ENV.

Fixing precompilation hangs due to open tasks or IO · The Julia Language

If you mean you observe it during precompilation, that’s happening for the first times in isolated child processes.

Yes, thanks, it looks like that case.

It aready works well, the main problem - misleading logs, in my case logs are important and I read it. And when something unexpected get printed it’s annoying.