Skip to content

Commit

Permalink
add invokelatest to circumvent world-age problems (#19784)
Browse files Browse the repository at this point in the history
add invokelatest function to work around world-age problems (#19774) without using eval

use invokelatest in libuv getaddrinfo callback

update manual to mention invokelatest
  • Loading branch information
stevengj authored and JeffBezanson committed Apr 27, 2017
1 parent f2f3959 commit 2815324
Show file tree
Hide file tree
Showing 10 changed files with 48 additions and 12 deletions.
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,10 @@ Library improvements
* `logging` can now be used to redirect `info`, `warn`, and `error` messages
either universally or on a per-module/function basis ([#16213]).

* New function `Base.invokelatest(f, args...)` to call the latest version
of a function in circumstances where an older version may be called
instead (e.g. in a function calling `eval`) ([#19784]).

* A new `iszero(x)` function was added, to quickly check whether `x` is zero
(or is all zeros, for an array) ([#19950]).

Expand Down
12 changes: 12 additions & 0 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,15 @@ function vector_any(xs::ANY...)
end

isempty(itr) = done(itr, start(itr))

"""
invokelatest(f, args...)
Calls `f(args...)`, but guarantees that the most recent method of `f`
will be executed. This is useful in specialized circumstances,
e.g. long-running event loops or callback functions that may
call obsolete versions of a function `f`.
(The drawback is that `invokelatest` is somewhat slower than calling
`f` directly, and the type of the result cannot be inferred by the compiler.)
"""
invokelatest(f, args...) = Core._apply_latest(f, args)
6 changes: 3 additions & 3 deletions base/socket.jl
Original file line number Diff line number Diff line change
Expand Up @@ -589,18 +589,18 @@ function uv_getaddrinfocb(req::Ptr{Void}, status::Cint, addrinfo::Ptr{Void})
cb = unsafe_pointer_to_objref(data)::Function
pop!(callback_dict,cb) # using pop forces an error if cb not in callback_dict
if status != 0 || addrinfo == C_NULL
cb(UVError("uv_getaddrinfocb received an unexpected status code", status))
invokelatest(cb, UVError("uv_getaddrinfocb received an unexpected status code", status))
else
freeaddrinfo = addrinfo
while addrinfo != C_NULL
sockaddr = ccall(:jl_sockaddr_from_addrinfo, Ptr{Void}, (Ptr{Void},), addrinfo)
if ccall(:jl_sockaddr_is_ip4, Int32, (Ptr{Void},), sockaddr) == 1
cb(IPv4(ntoh(ccall(:jl_sockaddr_host4, UInt32, (Ptr{Void},), sockaddr))))
invokelatest(cb, IPv4(ntoh(ccall(:jl_sockaddr_host4, UInt32, (Ptr{Void},), sockaddr))))
break
#elseif ccall(:jl_sockaddr_is_ip6, Int32, (Ptr{Void},), sockaddr) == 1
# host = Vector{UInt128}(1)
# scope_id = ccall(:jl_sockaddr_host6, UInt32, (Ptr{Void}, Ptr{UInt128}), sockaddr, host)
# cb(IPv6(ntoh(host[1])))
# invokelatest(cb, IPv6(ntoh(host[1])))
# break
end
addrinfo = ccall(:jl_next_from_addrinfo, Ptr{Void}, (Ptr{Void},), addrinfo)
Expand Down
11 changes: 3 additions & 8 deletions doc/src/manual/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,17 +455,12 @@ In the example above, we see that the "current world" (in which the method `newf
is one greater than the task-local "runtime world" that was fixed when the execution of `tryeval` started.

Sometimes it is necessary to get around this (for example, if you are implementing the above REPL).
Well, don't despair, since there's an easy solution: just call `eval` a second time.
For example, here we create a zero-argument closure over `ans` and `eval` a call to it:
Fortunately, there is an easy solution: call the function using [`Base.invokelatest`](@ref):

```jldoctest
julia> function tryeval2()
ans = (@eval newfun2() = 1)
res = eval(Expr(:call,
function()
return ans() + 1
end))
return res
@eval newfun2() = 2
Base.invokelatest(newfun2)
end
tryeval2 (generic function with 1 method)
Expand Down
1 change: 1 addition & 0 deletions doc/src/stdlib/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ Base.instances
Base.method_exists
Core.applicable
Core.invoke
Base.invokelatest
Base.:(|>)
Base.:(∘)
```
Expand Down
1 change: 1 addition & 0 deletions src/builtin_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ DECLARE_BUILTIN(throw); DECLARE_BUILTIN(is);
DECLARE_BUILTIN(typeof); DECLARE_BUILTIN(sizeof);
DECLARE_BUILTIN(issubtype); DECLARE_BUILTIN(isa);
DECLARE_BUILTIN(_apply); DECLARE_BUILTIN(_apply_pure);
DECLARE_BUILTIN(_apply_latest);
DECLARE_BUILTIN(isdefined); DECLARE_BUILTIN(nfields);
DECLARE_BUILTIN(tuple); DECLARE_BUILTIN(svec);
DECLARE_BUILTIN(getfield); DECLARE_BUILTIN(setfield);
Expand Down
13 changes: 13 additions & 0 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,18 @@ JL_CALLABLE(jl_f__apply_pure)
return ret;
}

// this is like `_apply`, but always runs in the newest world
JL_CALLABLE(jl_f__apply_latest)
{
jl_ptls_t ptls = jl_get_ptls_states();
size_t last_age = ptls->world_age;
if (!ptls->in_pure_callback)
ptls->world_age = jl_world_counter;
jl_value_t *ret = jl_f__apply(NULL, args, nargs);
ptls->world_age = last_age;
return ret;
}

// eval -----------------------------------------------------------------------

JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex)
Expand Down Expand Up @@ -1089,6 +1101,7 @@ void jl_init_primitives(void)
add_builtin_func("apply_type", jl_f_apply_type);
add_builtin_func("_apply", jl_f__apply);
add_builtin_func("_apply_pure", jl_f__apply_pure);
add_builtin_func("_apply_latest", jl_f__apply_latest);
add_builtin_func("_expr", jl_f__expr);
add_builtin_func("svec", jl_f_svec);

Expand Down
1 change: 1 addition & 0 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6761,6 +6761,7 @@ static void init_julia_llvm_env(Module *m)
builtin_func_map[jl_f_typeassert] = jlcall_func_to_llvm("jl_f_typeassert", &jl_f_typeassert, m);
builtin_func_map[jl_f__apply] = jlcall_func_to_llvm("jl_f__apply", &jl_f__apply, m);
builtin_func_map[jl_f__apply_pure] = jlcall_func_to_llvm("jl_f__apply_pure", &jl_f__apply_pure, m);
builtin_func_map[jl_f__apply_latest] = jlcall_func_to_llvm("jl_f__apply_latest", &jl_f__apply_latest, m);
builtin_func_map[jl_f_throw] = jlcall_func_to_llvm("jl_f_throw", &jl_f_throw, m);
builtin_func_map[jl_f_tuple] = jlcall_func_to_llvm("jl_f_tuple", &jl_f_tuple, m);
builtin_func_map[jl_f_svec] = jlcall_func_to_llvm("jl_f_svec", &jl_f_svec, m);
Expand Down
2 changes: 1 addition & 1 deletion src/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ static htable_t fptr_to_id;
static const jl_fptr_t id_to_fptrs[] = {
NULL, NULL,
jl_f_throw, jl_f_is, jl_f_typeof, jl_f_issubtype, jl_f_isa,
jl_f_typeassert, jl_f__apply, jl_f__apply_pure, jl_f_isdefined,
jl_f_typeassert, jl_f__apply, jl_f__apply_pure, jl_f__apply_latest, jl_f_isdefined,
jl_f_tuple, jl_f_svec, jl_f_intrinsic_call, jl_f_invoke_kwsorter,
jl_f_getfield, jl_f_setfield, jl_f_fieldtype, jl_f_nfields,
jl_f_arrayref, jl_f_arrayset, jl_f_arraysize, jl_f_apply_type,
Expand Down
9 changes: 9 additions & 0 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -669,3 +669,12 @@ if Bool(parse(Int,(get(ENV, "JULIA_TESTFULL", "0"))))
@test_throws StackOverflowError f_19433(+, 1, 2)
end
end

# invokelatest function for issue #19774
issue19774(x) = 1
let foo() = begin
eval(:(issue19774(x::Int) = 2))
return Base.invokelatest(issue19774, 0)
end
@test foo() == 2
end

0 comments on commit 2815324

Please sign in to comment.