Julia Distributed @fetchfrom changes remote variable

Viewed 61

I was trying to better understand how Julia's Distributed library deals with remote executions of variables, and noticed that it is possible for a remote variable to change without being explicitly told so.

Consider this code:

using Distributed

addprocs(1)

@everywhere begin
    a = myid()
    println("After definition: ", a)
end

@everywhere begin
    println("Inside @everywhere: ", a)
end

@fetchfrom 2 Core.eval(Main, :(println("Inside first remote eval: ", a)))

@fetchfrom 2 begin
    println("Inside bare @fetchfrom: ", a)
end

@fetchfrom 2 Core.eval(Main, :(println("Inside second remote eval: ", a)))

Output:

After definition: 1
      From worker 2:    After definition: 2
Inside @everywhere: 1
      From worker 2:    Inside @everywhere: 2
      From worker 2:    Inside first remote eval: 2
      From worker 2:    Inside bare @fetchfrom: 1
      From worker 2:    Inside second remote eval: 1

After the @fetchfrom 2 begin ... end statement, the remote value of a changes from 2 to 1, even though there was no explicit assignment. The two remote evaluations before and after the change are exactly the same, yet they print different values for a.

I understand some of the issues that might cause the @fetchfrom statement to capture variables local to process 1, but don't see how this would cause an actual change to the value in process 2.

1 Answers

Your bare @fetchfrom here:

@fetchfrom 2 begin
    println("Inside bare @fetchfrom: ", a)
end

pulls the value of the a variable of the scripted initial process (process 1) into a closure within the scope of thread 2.

If you look at the documentation for @fetchfrom, it calls @spawnat. @spawnat then, per its documentation, creates a closure around your println expression, which captures the a of the initial thread.

You later print a again, but the a you print last from thread 2 is the a you earlier brought over from thread 1. See the below:

using Distributed

addprocs(2)

@everywhere begin
    a = myid()
    println("After definition: ", a)
end

println("In starting thread: ", a)
a += 100
println("In starting thread after + 100: ", a)


@everywhere begin
    println("Inside @everywhere: ", a)
end

@fetchfrom 2 Core.eval(Main, :(println("Inside first remote eval: ", a)))

@fetchfrom 2 begin
    println("Inside bare @fetchfrom: ", a)
end

println()

@fetchfrom 2 Core.eval(Main, :(println("Inside second remote eval: ", a)))

@fetchfrom 3 Core.eval(Main, :(println("Inside second remote eval: ", a)))

println()

a += 200
println("In starting thread after + 200: ", a)

@fetchfrom 2 Core.eval(Main, :(println("Inside third remote eval: ", a)))

which outputs on my system:

After definition: 1
    From worker 3:    After definition: 3
    From worker 2:    After definition: 2
In starting thread: 1
In starting thread after + 100: 101
Inside @everywhere: 101
    From worker 2:    Inside @everywhere: 2
    From worker 3:    Inside @everywhere: 3
    From worker 2:    Inside first remote eval: 2
    From worker 2:    Inside bare @fetchfrom: 101

    From worker 2:    Inside second remote eval: 101
    From worker 3:    Inside second remote eval: 3

In starting thread after + 200: 301
    From worker 2:    Inside third remote eval: 101

So, the issue is which a you are addressing. In the case of the second eval, it is the a corresponding to the value in the bare fetchfrom.

Related