Anonymous function calls with remotecall is not lexically scoped & MPI vs libuv



When using remotecall the following behavior is observed on Julia stable v0.6.2:

julia> addprocs(1)
1-element Array{Int64,1}:
julia> wait(@spawnat 2 global x = 0)
Future(2, 1, 3, Nullable{Any}())
julia> f = ()->println(x)
(::#5) (generic function with 1 method)
julia> function g()
g (generic function with 1 method)
julia> remotecall_wait(f, 2)
	From worker 2:	0
Future(2, 1, 6, Nullable{Any}())
julia> remotecall_wait(g, 2)
ERROR: On worker 2:
UndefVarError: #g not defined
deserialize_datatype at ./serialize.jl:973
handle_deserialize at ./serialize.jl:677
deserialize at ./serialize.jl:637
handle_deserialize at ./serialize.jl:684
deserialize_msg at ./distributed/messages.jl:98
message_handler_loop at ./distributed/process_messages.jl:161
process_tcp_streams at ./distributed/process_messages.jl:118
#99 at ./event.jl:73
 [1] #remotecall_wait#146(::Array{Any,1}, ::Function, ::Function, ::Base.Distributed.Worker) at ./distributed/remotecall.jl:382
 [2] remotecall_wait(::Function, ::Base.Distributed.Worker) at ./distributed/remotecall.jl:373
 [3] #remotecall_wait#149(::Array{Any,1}, ::Function, ::Function, ::Int64) at ./distributed/remotecall.jl:394
 [4] remotecall_wait(::Function, ::Int64) at ./distributed/remotecall.jl:394

So there are at least two issues here:

  • Lexical scoping does not apply to anonymous functions called remotely, which should be documented.
  • functions are not as first-class as one expect them to be because g cannot be remotely called like f.

I will advice to keep the apparent dynamic scoping behavior and improve documentation, unless the same functionality can be achieved otherwise.

Maybe off topic, but I found the builtin parallelism has somewhat sub-optimal performance by design compared to MPI base implementation ( remote calls has to be serialized, for one example, which hurts if you need fine-grained control of your workers ). But AFAIK currently MPI.jl cannot make MPI calls from an REPL (unless in another worker process through MPIManager, but what’s not the typical REPL-convinience on would expect…).

Given that Julia has a strong focus on numerical computing, it is probable that quite a portion of its user base are comfortable using MPI. What is the rationale behind choosing libuv over MPI as the backend of the builtin parallelism in Julia?


I think top-level named functions need to be defined on workers. In this case, you can use @everywhere.

The following remote calls should work:

wait(@spawnat 2 global x = 0)

f = () -> println(x)
remotecall_fetch(f, 2)

function test()
    g() = println(x)
    remotecall_fetch(g, 2)

@everywhere h() = println(x)
remotecall_fetch(h, 2)


Thank you I didn’t know test() can work.

But it make it more wired that calling a function referring to a remotely available variable works if the function is anonymous, or if it is defined in a closure, but not if it is global and named.

Even though it does work, it needs clarifying whether we are using lexical or dynamic scoping: which x should we be referring to if x is also in scope at process 1?
For example:

julia> test()
	From worker 2:	0
julia> x = 1;
julia> function test2()
           remotecall_fetch(g, 2)
julia> test2()
	From worker 2:	1

So apparently the calling stack of namespaces are taking precedence here over the remote namespaces. But that smells hell to me.

The point is that mixing lexical and dynamic scoping is confusing if not evil.