/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
base/Base.jl
647 строк
22 KB
Keno Fischer
cancellation: Hook up ^C (#62655)
08 авг 2026, 03:45
Не верифицирован
08 авг 2026, 03:45
37ef9ad
Код
Авторство
О чём код?
# This file is a part of Julia. License is MIT: https://julialang.org/license const start_base_include = time_ns() include("reflection.jl") include("refpointer.jl") # now replace the Pair constructor (relevant for NamedTuples) with one that calls our Base.convert delete_method(which(Pair{Any,Any}, (Any, Any))) @eval function (P::Type{Pair{A, B}})(@nospecialize(a), @nospecialize(b)) where {A, B} @inline return $(Expr(:new, :P, :(a isa A ? a : convert(A, a)), :(b isa B ? b : convert(B, b)))) end # The REPL stdlib hooks into Base using this Ref const REPL_MODULE_REF = Ref{Module}(Base) process_sysimg_args!() include(strcat(BUILDROOT, "build_h.jl")) # include($BUILDROOT/base/build_h.jl) include(strcat(BUILDROOT, "version_git.jl")) # include($BUILDROOT/base/version_git.jl) # Initialize DL_LOAD_PATH as early as possible. We are defining things here in # a slightly more verbose fashion than usual, because we're running so early. let os = ccall(:jl_get_UNAME, Any, ()) if os === :Darwin || os === :Apple if DARWIN_FRAMEWORK push!(DL_LOAD_PATH, "@loader_path/Frameworks") end push!(DL_LOAD_PATH, "@loader_path") end end # metaprogramming include("meta.jl") using .Meta using .Meta: is_id_char # Strings include("multimedia.jl") using .Multimedia include("char.jl") function array_new_memory(mem::Memory{UInt8}, newlen::Int) # add an optimization to array_new_memory for StringVector if (@assume_effects :total @ccall jl_genericmemory_owner(mem::Any,)::Any) === mem # TODO: when implemented, this should use a memory growing call return typeof(mem)(undef, newlen) else # If data is in a String, keep it that way. # When implemented, this could use jl_gc_expand_string(oldstr, newlen) as an optimization str = _string_n(newlen) return (@assume_effects :total !:consistent @ccall jl_string_to_genericmemory(str::Any,)::Memory{UInt8}) end end include("strings/basic.jl") include("strings/string.jl") include("strings/substring.jl") include("strings/cstring.jl") include("cartesian.jl") using .Cartesian include("hashing.jl") include("osutils.jl") # subarrays include("subarray.jl") include("views.jl") # String views include("strings/stringview.jl") # numeric operations include("div.jl") include("twiceprecision.jl") include("complex.jl") include("rational.jl") include("multinverses.jl") using .MultiplicativeInverses include("abstractarraymath.jl") include("arraymath.jl") include("slicearray.jl") # SIMD loops sizeof(s::String) = Core.sizeof(s) # needed by gensym as called from simdloop include("simdloop.jl") using .SimdLoop # map-reduce operators include("reduce.jl") ## core structures include("reshapedarray.jl") include("reinterpretarray.jl") # Some type include("some.jl") include("dict.jl") include("set.jl") # Dynamic scopes (types only; the ScopedValues API is included much later) include("scope.jl") # Cancellation tokens (the `cancel` keyword-argument machinery is used from # the I/O layer onwards) include("cancellation.jl") # Core I/O include("io.jl") include("iobuffer.jl") # Concurrency (part 1) include("linked_list.jl") include("park.jl") include("condition.jl") include("threads.jl") include("lock.jl") # strings & printing include("intfuncs.jl") include("strings/strings.jl") #= isdebugbuild is defined here as this is imported in libdl.jl (included in libc.jl) =# """ isdebugbuild() Return `true` if julia is a debug version. """ function isdebugbuild() return ccall(:jl_is_debugbuild, Cint, ()) != 0 end # Enable dynamic library loading module Sys end # Sys is populated in stages during bootstrap Core.eval(Sys, :(include("osinfo.jl"))) module Filesystem end # Filesystem is populated in stages during bootstrap Core.eval(Filesystem, :(include("path.jl"))) using .Filesystem include("libc.jl") # Libdl (include in libc.jl) is required for regex.jl using .Libc: getpid, gethostname, time, memcpy, memset, memmove, memcmp # More strings & printing include("regex.jl") include("parse.jl") include("shell.jl") const IRShow = Compiler.IRShow # an alias for compatibility include("stacktraces.jl") using .StackTraces include("show.jl") include("arrayshow.jl") include("methodshow.jl") # multidimensional arrays include("multidimensional.jl") include("broadcast.jl") using .Broadcast using .Broadcast: broadcasted, broadcasted_kwsyntax, materialize, materialize!, broadcast_preserving_zero_d, andand, oror # missing values include("missing.jl") # version include("version.jl") # system & environment Core.eval(Sys, :(include("sysinfo.jl"))) const USING_STOCK_GC = occursin("stock", GC.gc_active_impl()) # These used to be in build_h.jl and are retained for backwards compatibility. # NOTE: keep in sync with `libblastrampoline_jll.libblastrampoline`. const libblas_name = "libblastrampoline" * (Sys.iswindows() ? "-5" : "") const liblapack_name = libblas_name # Concurrency (part 2) # Note that `atomics.jl` here should be deprecated Core.eval(Threads, :(include("atomics.jl"))) include("channels.jl") include("partr.jl") include("task.jl") include("threads_overloads.jl") include("weakkeydict.jl") # ScopedValues include("scopedvalues.jl") # Logging include("logging/logging.jl") using .CoreLogging include("env.jl") # functions defined in Random function rand end function randn end # I/O include("libuv.jl") include("asyncevent.jl") include("iostream.jl") include("stream.jl") Core.eval(Filesystem, :(include("filesystem.jl"))) include("cmd.jl") include("process.jl") include("terminfo.jl") include("Terminals.jl") # Moved from REPL to reduce invalidations include("secretbuffer.jl") # core math functions include("floatfuncs.jl") include("math.jl") using .Math const (√)=sqrt const (∛)=cbrt const (∜)=fourthroot # now switch to a simple, race-y TLS, relative include for the rest of Base delete_method(which(include, (Module, String))) let SOURCE_PATH = "" global function include(mod::Module, path::String) prev = SOURCE_PATH::String path = normpath(joinpath(dirname(prev), path)) Core.println(path) ccall(:jl_uv_flush, Nothing, (Ptr{Nothing},), Core.io_pointer(Core.stdout)) push!(_included_files, (mod, abspath(path))) SOURCE_PATH = path result = Core.include(mod, path) SOURCE_PATH = prev return result end end # reduction along dims include("reducedim.jl") # macros in this file rely on string.jl include("accumulate.jl") include("permuteddimsarray.jl") using .PermutedDimsArrays # Combinatorics include("sort.jl") using .Sort # Fast math include("fastmath.jl") using .FastMath function deepcopy_internal end # enums include("Enums.jl") using .Enums # BigInts include("gmp.jl") using .GMP # float printing: requires BigInt include("ryu/Ryu.jl") using .Ryu # BigFloats include("mpfr.jl") using .MPFR include("combinatorics.jl") # irrational mathematical constants include("irrationals.jl") include("mathconstants.jl") using .MathConstants: ℯ, π, pi # experimental API's include("experimental.jl") # utilities include("deepcopy.jl") include("download.jl") include("summarysize.jl") include("errorshow.jl") include("util.jl") include("initdefs.jl") # worker threads include("threadcall.jl") # code loading include("uuid.jl") include("pkgid.jl") include("toml/toml.jl") include("linking.jl") include("loading.jl") # BinaryPlatforms, used by Artifacts. Needs `Sort`. include("binaryplatforms.jl") # misc useful functions & macros include("timing.jl") include("client.jl") include("asyncmap.jl") # deprecated functions include("deprecated.jl") # # Some additional basic documentation include("docs/basedocs.jl") # Documentation -- should always be included last in sysimg. include("docs/Docs.jl") using .Docs Docs.loaddocs(CoreDocs.DOCS) @eval CoreDocs DOCS = DocLinkedList() include("precompilation.jl") # finally, now make `include` point to the full version for m in methods(include) delete_method(m) end for m in methods(IncludeInto(Base)) delete_method(m) end # This method is here only to be overwritten during the test suite to test # various sysimg related invalidation scenarios. a_method_to_overwrite_in_test() = inferencebarrier(1) # These functions are duplicated in client.jl/include(::String) for # nicer stacktraces. Modifications here have to be backported there @noinline include(mod::Module, _path::AbstractString) = _include(identity, mod, _path) @noinline include(mapexpr::Function, mod::Module, _path::AbstractString) = _include(mapexpr, mod, _path) (this::IncludeInto)(fname::AbstractString) = include(identity, this.m, fname) (this::IncludeInto)(mapexpr::Function, fname::AbstractString) = include(mapexpr, this.m, fname) # Compatibility with when Compiler was in Core @eval Core const Compiler = $Base.Compiler @eval Compiler const fl_parse = $Base.fl_parse # Compiler frontend Core.println("JuliaSyntax/src/JuliaSyntax.jl") include(@__MODULE__, string(DATAROOT, "julia/JuliaSyntax/src/JuliaSyntax.jl")) # May be replaced in incremental sysimage build after-the-fact const JuliaLowering = nothing # Now that JuliaSyntax is bootstrapped and ready to use, set Base's syntax version. set_syntax_version(Base, VERSION) end_base_include = time_ns() Filesystem.__postinit__() const _sysimage_modules = PkgId[] in_sysimage(pkgid::PkgId) = pkgid in _sysimage_modules if is_primary_base_module # Profiling helper # triggers printing the report and (optionally) saving a heap snapshot after a SIGINFO/SIGUSR1 profile request # Needs to be in Base because Profile is no longer loaded on boot function profile_printing_listener(cond::AsyncCondition) profile = nothing try while _trywait(cond) profile = @something(profile, require_stdlib(PkgId(UUID("9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"), "Profile")))::Module invokelatest(profile.peek_report[]) if get_bool_env("JULIA_PROFILE_PEEK_HEAP_SNAPSHOT", false) === true println(stderr, "Saving heap snapshot...") fname = invokelatest(profile.take_heap_snapshot) println(stderr, "Heap snapshot saved to `$(fname)`") end end catch ex if !isa(ex, InterruptException) @error "Profile printing listener crashed" exception=ex,catch_backtrace() end end nothing end function start_profile_listener() cond = AsyncCondition() uv_unref(cond.handle) t = errormonitor(Threads.@spawn(profile_printing_listener(cond))) atexit() do # destroy this callback when exiting ccall(:jl_set_peek_cond, Cvoid, (Ptr{Cvoid},), C_NULL) # this will prompt any ongoing or pending event to flush also close(cond) # error-propagation is not needed, since the errormonitor will handle printing that better t === current_task() || _wait(t) end finalizer(cond) do c # if something goes south, still make sure we aren't keeping a reference in C to this ccall(:jl_set_peek_cond, Cvoid, (Ptr{Cvoid},), C_NULL) end ccall(:jl_set_peek_cond, Cvoid, (Ptr{Cvoid},), cond.handle) end # The ^C episode source: the cancellation token source governing the current # interactive foreground evaluation. A fresh source is installed per # evaluation (severities are monotonic, so a cancelled source is never # reused); this Ref roots it while the C side mirrors a raw pointer for the # signal thread's lock-free reads. # The current ^C episode: the governing source (rooted here; `nothing` # between episodes) paired with the C-side episode generation the mirror # swap returned - the dispatch pass consumes a pending press only when it # targeted this very generation, so a press for a previous episode can # never cancel a newly installed one. const _sigint_episode = Ref{Tuple{Union{Nothing, CancellationTokenSource}, UInt64}}((nothing, 0)) # The task driving the current foreground evaluation (the caller of # sigint_new_episode!). Nothing in Base reads it yet: the ^C escalation # machinery (follow-up PR) targets it directly when the running computation # has no published token binding, and Distributed points it at the handler # of the most recent remotely-submitted request. const _sigint_foreground_task = Ref{Union{Nothing, Task}}(nothing) """ Base.sigint_new_episode!([src::CancellationTokenSource]) -> CancellationToken Install `src` (a fresh standalone source by default) as the ^C episode source and return its token. The owner of an interactive session (e.g. the REPL backend, or the script driver) calls this before each foreground evaluation and runs the evaluation in a dynamic scope carrying the returned token (`@with Base.CANCEL_TOKEN => tok ...`), so that ^C cancels exactly that evaluation. The caller chooses the source's place in the cancellation graph. The REPL passes a fresh *evaluation* source - a child of the session source (see the session tree in base/client.jl) - so work an evaluation leaves behind stays sweepable via [`cancel_session_work!`](@ref) after its episode closes. The session-covering episode installed by `_start` is deliberately a standalone root: everything outside per-evaluation scopes (including the REPL's own machinery) runs under it, and a session sweep must never cancel that. """ function sigint_new_episode!(src::CancellationTokenSource=CancellationTokenSource()) _sigint_foreground_task[] = current_task() # Publish the C-side mirror. The episode Ref roots the new source; the # signal thread's use of the mirror is GC-excluded rather than rooted, # so nothing needs to keep the outgoing source alive (see # jl_sigint_request_cancellation). gen = ccall(:jl_set_sigint_source, UInt64, (Any,), src) _sigint_episode[] = (src, gen) return CancellationToken(src) end # Close the ^C episode without installing a new source (e.g. when the work # item completes; the session installs a fresh source before its next # evaluation). function sigint_close_episode!() gen = ccall(:jl_set_sigint_source, UInt64, (Any,), nothing) _sigint_episode[] = (nothing, gen) _sigint_foreground_task[] = nothing nothing end # The active severity of the episode source, or `nothing` if it has not # been cancelled (an alias kept for the ^C machinery's consumers). sigint_active_severity(src::CancellationTokenSource) = cancel_severity(src) # One listener task runs per nonempty threadpool (a pool's threads cannot run # another pool's tasks, and any pool may be monopolized by a busy victim). # The listeners race to claim each notification; the pass itself is # level-based (it re-reads the episode state), so serializing claims through # the lock is enough. const sigint_pass_lock = ReentrantLock() # One dispatch pass for a claimed ^C notification. Runs with # `sigint_pass_lock` held; level-based (it re-reads the episode state), so # serializing passes through the lock is enough. function _sigint_dispatch_pass() src, gen = _sigint_episode[] # A press is consumed only if it targeted this episode's generation; a # press for a previous episode self-invalidates (its source was already # marked by the C fast path), so a late pass can never cancel a newly # installed episode (issues #58689, #42072). pending = ccall(:jl_consume_sigint_pending, Cint, (UInt64,), gen) != 0 if src === nothing # No episode source is installed. In an interactive session this # is a between-evaluations window (a fresh source arrives with the # next prompt) - ignore the press. Without a live interactive # evaluator nothing can be cancelled or resumed - exit as an # unhandled ^C would. pending || return backend = active_repl_backend if backend !== nothing && !istaskdone(backend.backend_task::Task) return end istaskdone(roottask) || return exit(128 + 2) # 128 + SIGINT end src = src::CancellationTokenSource if sigint_active_severity(src) === nothing # The source is unmarked: only a press that targeted exactly this # episode warrants delivering to it. pending || return cancel!(src, CANCEL_REQUEST_SAFE) elseif pending # The press was delivered entirely by the C-side fast path with # no wake-up walk ever run - or this is a repeat press. Parked # waiters under the episode source still need their wake; # redeliver! is level-triggered and idempotent (a no-op for # tasks already unwinding). redeliver!(src) end # else: a wakeup without a press for this episode does nothing nothing end # Arbitrate and run pending ^C dispatch. Called from the sigint listener # tasks on their async notification, and inline from any idle thread's # scheduler loop (see jl_dispatch_sigint_inline in src/scheduler.c) - the # pass only needs an ordinary task context, which keeps the event loop out # of the delivery path even when its owning thread is stuck in a foreign # call. `trylock` (never a parking `lock`): if another pass is running, it # drains any claim posted meanwhile, and an unclaimed flag is retried by # the next idle iteration or listener wakeup. function maybe_dispatch_sigint() ccall(:jl_peek_sigint_dispatch, Cint, ()) != 0 || return trylock(sigint_pass_lock) || return try while ccall(:jl_claim_sigint_dispatch, Cint, ()) != 0 try # an error in one pass must not disable the ^C machinery _sigint_dispatch_pass() catch ex try @invokelatest showerror(stderr, ex, catch_backtrace()) println(stderr) catch end end end finally unlock(sigint_pass_lock) end nothing end function sigint_listener(cond::AsyncCondition) while _trywait(cond) maybe_dispatch_sigint() end nothing end # The dispatch path must not hit the JIT on the first ^C: it runs from the # scheduler's idle loop, and a press on a cold or loaded session would # stall interactive response behind the compile - bake it into the image. precompile(Tuple{typeof(maybe_dispatch_sigint)}) precompile(Tuple{typeof(_sigint_dispatch_pass)}) precompile(Tuple{typeof(cancel!), CancellationTokenSource, CancellationRequest}) precompile(Tuple{typeof(redeliver!), CancellationTokenSource}) function start_sigint_listener() cond = AsyncCondition() # N.B.: The condition is deliberately kept ref'd: pending async events on # unreferenced handles are not dispatched once the loop has no live # handles left (as in a headless script), which would make the ^C # notification undeliverable exactly when it matters. The atexit hook # below closes the handle before the event loop is drained for exit. listeners = Task[] Threads.threadpoolsize(:interactive) > 0 && push!(listeners, errormonitor(Threads.@spawn :interactive sigint_listener(cond))) push!(listeners, errormonitor(Threads.@spawn :default sigint_listener(cond))) atexit() do # destroy this callback when exiting ccall(:jl_set_sigint_cond, Cvoid, (Ptr{Cvoid},), C_NULL) # this will prompt any ongoing or pending event to flush also close(cond) # error-propagation is not needed, since the errormonitor will handle printing that better for t in listeners t === current_task() || _wait(t) end end finalizer(cond) do c # if something goes south, still make sure we aren't keeping a reference in C to this ccall(:jl_set_sigint_cond, Cvoid, (Ptr{Cvoid},), C_NULL) end ccall(:jl_set_sigint_cond, Cvoid, (Ptr{Cvoid},), cond.handle) end function __init__() # Base library init global _atexit_hooks_finished = false Filesystem.__postinit__() reinit_stdio() Multimedia.reinit_displays() # since Multimedia.displays uses stdout as fallback # initialize loading init_depot_path() init_load_path() init_active_project() append!(empty!(_sysimage_modules), keys(loaded_modules)) empty!(loaded_precompiles) # If we load a packageimage when building the image this might not be empty for mod in loaded_modules_order push!(get!(Vector{Module}, loaded_precompiles, PkgId(mod)), mod) end if haskey(ENV, "JULIA_MAX_NUM_PRECOMPILE_FILES") MAX_NUM_PRECOMPILE_FILES[] = parse(Int, ENV["JULIA_MAX_NUM_PRECOMPILE_FILES"]) end # Profiling helper @static if !Sys.iswindows() # triggering a profile via signals is not implemented on windows start_profile_listener() end start_sigint_listener() _require_world_age[] = get_world_counter() # Prevent spawned Julia process from getting stuck waiting on Tracy to connect. delete!(ENV, "JULIA_WAIT_FOR_TRACY") if get_bool_env("JULIA_USE_FLISP_PARSER", false) === false JuliaSyntax.enable_in_core!() end if JuliaLowering !== nothing && get_bool_env("JULIA_USE_FLISP_LOWERING", true) === false # This is not available by default, but JuliaLowering can be added to # Base after-the-fact via an incremental sysimage build. JuliaLowering.activate!() end CoreLogging.global_logger(CoreLogging.ConsoleLogger()) nothing end # enable threads support @eval PCRE PCRE_COMPILE_LOCK = Threads.SpinLock() # Record dependency information for files belonging to the Compiler, so that # we know whether the .ji can just give the Base copy or not. # TODO: We may want to do this earlier to avoid TOCTOU issues. const _compiler_require_dependencies = Any[] @Core.latestworld for i = 1:length(_included_files) (mod, file) = _included_files[i] if mod === Compiler || parentmodule(mod) === Compiler || endswith(file, "/Compiler.jl") _include_dependency!(_compiler_require_dependencies, true, mod, file, true, false) end end # Make relative to DATAROOTDIR to allow relocation let basedir = joinpath(Sys.BINDIR, DATAROOTDIR) for i = 1:length(_compiler_require_dependencies) tup = _compiler_require_dependencies[i] _compiler_require_dependencies[i] = (tup[1], relpath(tup[2], basedir), tup[3:end]...) end end @assert length(_compiler_require_dependencies) >= 15 end