Description
I wrote up this issue more fully in my blog, at https://bibwild.wordpress.com/2015/01/15/ruby-threads-gotcha-with-local-vars-and-shared-state/
But consider this little demo showing the issue:
value = 100
future = Concurrent::Future.execute do
sleep 1
# DANGER will robinson!
value + 1
end
value = 200
puts future.value # you get 201, not 101!
That 'value' local var is the same one as the outer scope, changes to it in the scope where the thread/future was created effect the thread too.
With stdlib Thread.new, you can get around this with the args to Thread.new:
value = "original"
t = Thread.new(value) do |t_value|
sleep 1
puts value
end
value = "new"
t.join
You still get "original"
, not "new
", because of the use of the arg to Thread.new to pass the value to the block argument.
A raw Concurrent::ThreadPoolExecutor
lets you do this too, eg pool.post(work) do |t_work|
But Future (and Promise?) don't seem to have an API to do this, to pass an arg whose value gets passed to the block arg, to break the 'connection' with the local var in lexical scope, so you can actually fix it's value in the task you are creating and not worry about the outer scope changing it from under you.
Am I missing something? Or do the expected ways one might use Future/Promise mean that this probably won't come up? Or should Future/Promise have an API similar to Thread/ThreadPoolExecutor, to allow parameters to be passed in and fixed?