Open michaeldiscala opened 3 years ago
@michaeldiscala As a baseline "simplest possible" solution to thread-safety in this context, would it be sufficient to document that:
Mutex
Note, I am not at all ruling out investigating approaches to thread safety within Docile. But for now, I want to check my understanding, does the above make sense?
For example, does the following adaptation work for you?
require 'parallel'
require 'docile'
class MyClass
def run_in_threads
Parallel.each(
[
[Array.new, Mutex.new],
[Array.new, Mutex.new]
],
in_threads: 2
) do |array, mutex|
mutex.synchronize do
Docile.dsl_eval_with_block_return(array, &block)
end
end
end
def block
lambda { do_some_work }
end
def do_some_work
sleep(rand)
push 1
end
end
MyClass.new.run_in_threads
UPDATE: the above does not work -- the singleton class of the lexical context needs a single mutex for all access, which is more to your point above! Apologies
@michaeldiscala I'm tagging this to future Docile 2.0, where I intend to change the implementation to never mutate the DSL nor block contexts, by removing the need to instrument method_missing
for fallbacks.
Hi Marc!
I'm experimenting with some multi-threaded code that uses Docile and I think I may have encountered a thread safety issue. I'm able to reproduce with the following code (depends on the https://github.com/grosser/parallel gem):
This pretty reliably gives me:
When I run this with only a single thread (changing
in_threads: 1
), the output comes as expected:From my reading of the code, it seems that Docile achieves the ability for
do_some_work
to call methods on the DSL object by adding amethod_missing
call to the base class (see https://github.com/ms-ati/docile/blob/master/lib/docile/fallback_context_proxy.rb#L54). The gem then cleans up after itself after the calls are complete https://github.com/ms-ati/docile/blob/master/lib/docile/fallback_context_proxy.rb#L54.My hunch is that I get the method missing error because the clean up happens in one thread and then removes the methods for the other one. I could also imagine getting into cases where the
push
method ends up writing to the wrong array, depending on the timing. Is that understanding correct?If so, it seems like this may be difficult to make thread safe. The best I can think of is to (1) assign the receiver to a thread variable in https://github.com/ms-ati/docile/blob/master/lib/docile/fallback_context_proxy.rb#L5, (2) have the method missing definition dispatch calls to the receiver pulled from the thread variable, and (3) never cleanup the method_missing definition.
This doesn't quite seem worth it to support to me, but am curious for your take on all of the above!
Happy holidays & hope all is well! --Mike