/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
base/c.jl
543 строки
19 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 # definitions related to C interface """ CFunction struct Garbage-collection handle for the return value from `@cfunction` when the first argument is annotated with '\\\$'. Like all `cfunction` handles, it should be passed to `ccall` as a `Ptr{Cvoid}`, and will be converted automatically at the call site to the appropriate type. See [`@cfunction`](@ref). """ mutable struct CFunction <: Ref{Cvoid} ptr::Ptr{Cvoid} f::Any _1::Ptr{Cvoid} _2::Ptr{Cvoid} let constructor = false end end unsafe_convert(::Type{Ptr{Cvoid}}, cf::CFunction) = cf.ptr """ @cfunction(callable, ReturnType, (ArgumentTypes...,)) -> Ptr{Cvoid} @cfunction(\$callable, ReturnType, (ArgumentTypes...,)) -> CFunction Generate a C-callable function pointer from the Julia function `callable` for the given type signature. To pass the return value to a `ccall`, use the argument type `Ptr{Cvoid}` in the signature. Note that the argument type tuple must be a literal tuple, and not a tuple-valued variable or expression (although it can include a splat expression). And that these arguments will be evaluated in global scope during compile-time (not deferred until runtime). Adding a '\\\$' in front of the function argument changes this to instead create a runtime closure over the local variable `callable` (this is not supported on all architectures). See [manual section on ccall and cfunction usage](@ref Calling-C-and-Fortran-Code). # Examples ```julia-repl julia> function foo(x::Int, y::Int) return x + y end julia> @cfunction(foo, Int, (Int, Int)) Ptr{Cvoid} @0x000000001b82fcd0 ``` """ macro cfunction(f, rt, at) if !(isa(at, Expr) && at.head === :tuple) throw(ArgumentError("@cfunction argument types must be a literal tuple")) end at.head = :call pushfirst!(at.args, GlobalRef(Core, :svec)) if isa(f, Expr) && f.head === :$ fptr = f.args[1] typ = CFunction else fptr = QuoteNode(f) typ = Ptr{Cvoid} end cfun = Expr(:cfunction, typ, fptr, rt, at, QuoteNode(:ccall)) return esc(cfun) end if ccall(:jl_is_char_signed, Ref{Bool}, ()) const Cchar = Int8 else const Cchar = UInt8 end """ Cchar Equivalent to the native `char` c-type. """ Cchar # The ccall here is equivalent to Sys.iswindows(), but that's not defined yet if ccall(:jl_get_UNAME, Any, ()) === :NT const Clong = Int32 const Culong = UInt32 const Cwchar_t = UInt16 else const Clong = Int const Culong = UInt const Cwchar_t = Int32 end """ Clong Equivalent to the native `signed long` c-type. """ Clong """ Culong Equivalent to the native `unsigned long` c-type. """ Culong """ Cwchar_t Equivalent to the native `wchar_t` c-type ([`Int32`](@ref)). """ Cwchar_t if ccall(:jl_get_UNAME, Any, ()) !== :NT const sizeof_mode_t = ccall(:jl_sizeof_mode_t, Cint, ()) if sizeof_mode_t == 2 const Cmode_t = Int16 elseif sizeof_mode_t == 4 const Cmode_t = Int32 elseif sizeof_mode_t == 8 const Cmode_t = Int64 else error("invalid sizeof mode_t") end end # deferring (or un-deferring) ctrl-c handler for external C code that # is not interrupt safe (see also issue #2622). The sigatomic_begin/end # functions should always be called in matched pairs, ideally via: # disable_sigint() do .. end # reennable_sigint is provided so that immediate ctrl-c handling is # re-enabled within a sigatomic region, e.g. inside a Julia callback function # within a long-running C routine. sigatomic_begin() = ccall(:jl_sigatomic_begin, Cvoid, ()) sigatomic_end() = ccall(:jl_sigatomic_end, Cvoid, ()) """ disable_sigint(f::Function) Disable Ctrl-C handler during execution of a function on the current task, for calling external code that may call julia code that is not interrupt safe. Intended to be called using `do` block syntax as follows: disable_sigint() do # interrupt-unsafe code ... end External functions that do not call julia code or julia runtime automatically disable sigint during their execution. """ function disable_sigint(f::Function) depwarn("`disable_sigint` no longer defers Ctrl-C: SIGINT is delivered as a " * "cancellation of the current ^C scope and observed at cancellation " * "points regardless of the sigatomic region this establishes. Shield " * "a region from cancellation by scoping " * "`Base.CANCEL_TOKEN => nothing` over it instead.", :disable_sigint) sigatomic_begin() res = f() # Exception unwind sigatomic automatically sigatomic_end() res end """ reenable_sigint(f::Function) Re-enable Ctrl-C handler during execution of a function. Temporarily reverses the effect of [`disable_sigint`](@ref). """ function reenable_sigint(f::Function) sigatomic_end() res = f() # Exception unwind sigatomic automatically sigatomic_begin() res end """ exit_on_sigint(on::Bool) Set `exit_on_sigint` flag of the julia runtime. If `false`, Ctrl-C (SIGINT) cancels the current ^C episode's cancellation scope and is observed at cancellation points as a [`Base.CancellationRequest`](@ref), which is capturable in a `try` block. This is the default behavior in REPL, any code run via `-e` and `-E` and in Julia script run with `-i` option. If `true`, Ctrl-C terminates the process directly. Running code upon such event requires [`atexit`](@ref). This is the default behavior in Julia script run without `-i` option. !!! compat "Julia 1.5" Function `exit_on_sigint` requires at least Julia 1.5. """ function exit_on_sigint(on::Bool) ccall(:jl_exit_on_sigint, Cvoid, (Cint,), on) end function _ccallable(name::Union{Nothing, String}, rt::Type, sigt::Type) ccall(:jl_extern_c, Cvoid, (Any, Any, Any), name, rt, sigt) end function expand_ccallable(name, rt, def) if isa(def,Expr) && (def.head === :(=) || def.head === :function) sig = def.args[1] if sig.head === :(::) if rt === nothing rt = sig.args[2] end sig = sig.args[1] end if rt === nothing error("@ccallable requires a return type") end if sig.head === :call f = sig.args[1] if isa(f,Expr) && f.head === :(::) f = f.args[end] else f = :(typeof($f)) end at = Any[let a = sig.args[i] if isa(a,Expr) && a.head === :(::) a.args[end] else :Any end end for i in 2:length(sig.args)] return quote @__doc__ $(esc(def)) _ccallable($name, $(esc(rt)), $(Expr(:curly, :Tuple, esc(f), map!(esc, at, at)...))) end end end error("expected method definition in @ccallable") end """ @ccallable ["name"] function f(...)::RetType ... end Make the annotated function be callable from C using its name. This can, for example, be used to expose functionality as a C API when creating a custom Julia sysimage. If the first argument is a string, it is used as the external name of the function. """ macro ccallable(def) expand_ccallable(nothing, nothing, def) end macro ccallable(rt, def) if rt isa String expand_ccallable(rt, nothing, def) else expand_ccallable(nothing, rt, def) end end # @ccall implementation """ ccall_macro_parse(expression) `ccall_macro_parse` is an implementation detail of `@ccall`. It takes an expression like `:(printf("%d"::Cstring, value::Cuint)::Cvoid)` returns: a tuple of `(function_name, return_type, arg_types, args)` The above input outputs this: (:printf, :Cvoid, [:Cstring, :Cuint], ["%d", :value]) """ function ccall_macro_parse(exprs) gc_safe = false cancel = nothing expr = nothing if exprs isa Expr expr = exprs else # leading `name = value` options, then the call expression i = 1 while i < length(exprs) && isexpr(exprs[i], :(=)) opt = exprs[i]::Expr name = opt.args[1] value = opt.args[2] if name === :gc_safe if value === true gc_safe = true elseif value === false gc_safe = false else throw(ArgumentError("gc_safe must be true or false")) end elseif name === :cancel_handler if !(isexpr(value, :tuple) && length(value.args) == 2) throw(ArgumentError("cancel_handler must be a `(handler, state)` tuple")) end cancel = (value.args[1], value.args[2]) else throw(ArgumentError("@ccall options are `gc_safe = <bool>` and `cancel_handler = (handler, state)`")) end i += 1 end if i != length(exprs) throw(ArgumentError("@ccall needs a function signature with a return type")) end expr = exprs[i] end # setup and check for errors if !isexpr(expr, :(::)) throw(ArgumentError("@ccall needs a function signature with a return type")) end rettype = expr.args[2] call = expr.args[1] if !isexpr(call, :call) throw(ArgumentError("@ccall has to take a function call")) end # get the function symbols func = let f = call.args[1] if isexpr(f, :.) Expr(:tuple, f.args[2], f.args[1]) elseif isexpr(f, :$) func = f.args[1] if isa(func, String) || (isa(func, QuoteNode) && !isa(func.value, Ptr)) || isa(func, Tuple) || isexpr(func, :tuple) throw(ArgumentError("interpolated value should be a variable or expression, not a literal name or tuple")) end func elseif f isa Symbol Expr(:tuple, QuoteNode(f)) else throw(ArgumentError("@ccall function name must be a symbol, a `.` node (e.g. `libc.printf`) or an interpolated function pointer (with `\$`)")) end end # detect varargs varargs = nothing argstart = 2 callargs = call.args if length(callargs) >= 2 && isexpr(callargs[2], :parameters) argstart = 3 varargs = callargs[2].args end # collect args and types args = [] types = [] function pusharg!(arg) if !isexpr(arg, :(::)) throw(ArgumentError("args in @ccall need type annotations. '$arg' doesn't have one.")) end push!(args, arg.args[1]) push!(types, arg.args[2]) end for i in argstart:length(callargs) pusharg!(callargs[i]) end # add any varargs if necessary nreq = 0 if varargs !== nothing if length(args) == 0 throw(ArgumentError("C ABI prohibits vararg without one required argument")) end nreq = length(args) for a in varargs pusharg!(a) end end return func, rettype, types, args, gc_safe, cancel, nreq end function ccall_macro_lower(convention, func, rettype, types, args, gc_safe, cancel, nreq) have_cancel = cancel !== nothing # `Base.@assume_effects :reset_safe @ccall ...` arrives as the # CCALL_EFFECT_RESET_SAFE bit above the standard effects overrides in # the `@ccall_effects` word (see `@assume_effects` in expr.jl): split it # back out into its dedicated calling-convention slot here. reset_safe = false if convention isa Tuple cc_sym, effects = convention reset_safe = (effects & CCALL_EFFECT_RESET_SAFE) != 0x0000 base_cconv = (cc_sym, effects & ~CCALL_EFFECT_RESET_SAFE, gc_safe) else base_cconv = (convention, UInt16(0), gc_safe) end if !have_cancel cconv = reset_safe ? Expr(:cconv, (base_cconv..., false, true), nreq) : Expr(:cconv, base_cconv, nreq) return Expr(:call, :ccall, esc(func), cconv, esc(rettype), Expr(:tuple, map!(esc, types, types)...), map!(esc, args, args)...) end # Our internal ABI for cancellation handlers is f(func, state, args...). Codegen # inlines the establishment of the handler and the ultimate call to `f` has the # ordinary ABI without the extra arguments. fex, sex = cancel nreq > 0 && (nreq += 2) cconv = Expr(:cconv, reset_safe ? (base_cconv..., true, true) : (base_cconv..., true), nreq) return Expr(:call, :ccall, esc(func), cconv, esc(rettype), Expr(:tuple, :(Ptr{Cvoid}), :(Ptr{Cvoid}), map!(esc, types, types)...), esc(fex), esc(sex), map!(esc, args, args)...) end """ @ccall library.function_name(argvalue1::argtype1, ...)::returntype @ccall function_name(argvalue1::argtype1, ...)::returntype @ccall \$function_pointer(argvalue1::argtype1, ...)::returntype Call a function in a C-exported shared library, specified by `library.function_name`, where `library` is a string constant or literal. The library may be omitted, in which case the `function_name` is resolved in the current process. Alternatively, `@ccall` may also be used to call a function pointer `\$function_pointer`, such as one returned by `dlsym`. Each `argvalue` to `@ccall` is converted to the corresponding `argtype`, by automatic insertion of calls to `unsafe_convert(argtype, cconvert(argtype, argvalue))`. (See also the documentation for [`unsafe_convert`](@ref Base.unsafe_convert) and [`cconvert`](@ref Base.cconvert) for further details.) In most cases, this simply results in a call to `convert(argtype, argvalue)`. # Examples @ccall strlen(s::Cstring)::Csize_t This calls the C standard library function: size_t strlen(char *) with a Julia variable named `s`. See also `ccall`. Varargs are supported with the following convention: @ccall printf("%s = %d"::Cstring ; "foo"::Cstring, foo::Cint)::Cint The semicolon is used to separate required arguments (of which there must be at least one) from variadic arguments. Example using an external library: # C signature of g_uri_escape_string: # char *g_uri_escape_string(const char *unescaped, const char *reserved_chars_allowed, gboolean allow_utf8); const glib = "libglib-2.0" @ccall glib.g_uri_escape_string(my_uri::Cstring, ":/"::Cstring, true::Cint)::Cstring The string literal could also be used directly before the function name, if desired `"libglib-2.0".g_uri_escape_string(...` It's possible to declare the ccall as `gc_safe` by using the `gc_safe = true` option: @ccall gc_safe=true strlen(s::Cstring)::Csize_t This allows the garbage collector to run concurrently with the ccall, which can be useful whenever the `ccall` may block outside of julia. !!! warning This option should be used with caution, as it can lead to undefined behavior if the ccall calls back into the julia runtime. (`@cfunction`/`@ccallable` are safe however) !!! compat "Julia 1.12" The `gc_safe` argument requires Julia 1.12 or higher. # Extended help ## Cancellation ### Semantics of reset_safe annotation When annotated `@assume_effects :reset_safe`, e.g.: Base.@assume_effects :reset_safe @ccall libgmp.__gmpz_mul(x::mpz_t, a::mpz_t, b::mpz_t)::Cvoid The compiler has license to extend an earlier reset region through the entire execution of the called C function. In particular, as a result, the function may be abandoned at any point (and reset to the appropriate cancellation point). This imposes strict requirements on the foreign code. In particular, it is generally unsafe to call into most C standard library functions (the Julia runtime will handle properly protecting the symbol lookup itself), modify memory other than allocated since the most recent cancellation point (or which the cancellation will explicitly clean up). Additionally, any memory access to memory that may be read after a reset must be annotated `volatile`. This list of conditions is meant to be illustrative, not exhaustive and may be extended with additional warnings in the future. Co-operating foreign code may use the Julia C API to temporarily protect regions from reset, although this API is not yet stable and requires source modifications. !!! compat "Julia 1.14" The `:reset_safe` effect requires Julia 1.14 or higher. ## Cancellation handlers Because the `:reset_safe` effect imposes such strict requirements, another option is provided to allow for cancellation of cooperating foreign code. In particular, a long-running foreign call can be made cancellable with the `cancel_handler = (handler, state)` option: @ccall cancel_handler=(CANCEL_FPTR, ref) lib.solve(ref::Ptr{Cvoid})::Cvoid `handler` is a C-callable function pointer `void (*)(void *state, uint8_t sev)` (typically from [`@cfunction`](@ref)). If the cancellation token source (`Base.CancellationTokenSource`) bound to the calling task is cancelled while the call runs, the runtime invokes `handler(state, sev)` *on the thread executing the call*, like a signal handler: at an arbitrary point of the foreign code, on the same stack, resuming the interrupted call when the handler returns (`sev` is the request state of the cancelled source). The handler performs the library-specific work to make the foreign call return early - typically setting a flag or calling the library's own cancellation entry point. Both `CANCEL_FPTR` and `ref` are handled as ordinary ccall arguments (both of C type `Ptr{Cvoid}`), including for purposes of rooting. Note that since `CANCEL_FPTR` runs as a signal handler, it must be async-signal-safe in the ordinary sense, although unlike `:reset_safe`, no requirements are imposed on the called function itself. A function may be annotated with both `:reset_safe` and `cancel_handler`, in which case the cancel_handler takes precedence. However, in such a situation, the compiler will attempt to preserve the reset region into the ccall and it may be read by the cancel_handler (which can then perform some library specific cleanup before performing the reset as usual). However, this is not required nor assumed. A cancel handler could instead cause the function to return early with an error code. Note however, that there is no automatic cancellation point implied by the `cancel_handler` attribute. It is the responsibility of the user to set an appropriate cancellation point either before or after (possibly conditional on an appropriate error return code) the ccall. !!! compat "Julia 1.14" The `cancel_handler` option requires Julia 1.14 or higher. """ macro ccall(exprs...) return ccall_macro_lower((:ccall), ccall_macro_parse(exprs)...) end macro ccall_effects(effects::UInt16, exprs...) return ccall_macro_lower((:ccall, effects), ccall_macro_parse(exprs)...) end