Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add invokelatest to circumvent world-age problems #19784

Merged
merged 9 commits into from
Apr 27, 2017
Merged
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))
Copy link
Member

Choose a reason for hiding this comment

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

our libuv callbacks should never be invoking callbacks directly. we've refactored most of them, but it looks like this one still needs to be fixed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, but clearly a matter for a different PR...

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