Issue #8556 has been updated by headius (Charles Nutter).


=begin
ko1 (Koichi Sasada) wrote:
> (1) Try gem first

We could certainly put this into thread_safe gem, which is now a dependency of Rails and pretty widely deployed as a result. I am not opposed to testing this more in the wild before incorporation into stdlib.

So the rest of this may be moot, but I'll proceed anyway.

> (2) Misleading design
> 
> I'm afraid that this library introduce bugs under misunderstanding.
> 
> For example, people consider that this object is without worry about
> synchronization, people may write the following program.

Someone else raised a concern about += and friends, but there's *no* way in a library to ever make those operations thread safe (actually, atomic). That's what the "atomic" gem provides.

The only way to ever make +=, ||=, and others be atomic in Ruby proper would be to change the way they're parsed and potentially add a method that could be called. But this is even unpredictable because for variables, there's still no way to do it atomically.

FWIW, Java's ++ and -- and += and friends are *also* not atomic.

I don't believe these features being non-atomic is a good enough justification to prevent the addition of a synchronized delegator. The sync delegator explicitly just makes individual method calls synchronized; and += and friends require multiple method calls.

> At first I see this result, I can't understand why.
> Of course, this program is completely bad program.
> It is completely my mistake.
> 
> But I think this design will lead such misunderstanding and bugs easily.

But this is not possible to fix in current Ruby and all other languages I know don't guarantee any atomicity here either.

> To avoid a such bug, I define the inc() and sub() method in Array.

This is an appropriate way to do it, indeed. However, anyone else still doing += mess up the results. If you want atomic mutation of individual elements, we need an AtomicArray or similar.

> This works completely.
> 
> But a person who assumes sdel_ary is free from consideration about locking,
> can write the following program:

This is perhaps a valid concern. SynchronizedDelegate could use a method_added hook to wrap new methods, however. Is it warranted?

  class << SynchronizedDelegator
    def method_added(name)
      unsync_name = :"__unsynchronized_#{name}"
      alias_method unsync_name, name
      define_method name do |*args, &block|+  def method_missing(m, *args, &block)
        begin
          monitor = @monitor
          monitor.mon_enter
          __send__ unsync_name, args, block
        ensure
          monitor.mon_exit
        end
      end
    end
  end

Or something like that.

> Maybe professional about concurrency program can avoid such silly bugs.
> But if we introduce it as standard library, I'm afraid they are not.

I don't claim this solution solves all problems, obviously. But it solves many of them. It is an incremental tool to help improve concurrency capabilities of Ruby.

> (3) Lock based thraed programming
> 
> This is my opinion. So it is weak objection for this proposal.
> 
> I believe lock based thread programming introduced many bugs.
> (Synchronized) Queue or more high-level structures should be used.
> 
> Or use Mutex (or monitor) explicitly for fing-grain locking.
> It bothers programmers, so programmers use other approachs such as Queue (I hope).

Getting explicitly concurrency-friendly collections into stdlib would be great. But this was intended as a small step given that 2.1 is close to finished.

Another data point: Java for years has had java.util.Collections.synchronized{List,Map,Set} for doing a quick and easy wrapper around those collection types. Sometimes it's the best simple solution for making a collection thread-safe.
=end
----------------------------------------
Feature #8556: MutexedDelegator as a trivial way to make an object thread-safe
https://bugs.ruby-lang.org/issues/8556#change-42552

Author: headius (Charles Nutter)
Status: Assigned
Priority: Normal
Assignee: ko1 (Koichi Sasada)
Category: 
Target version: Ruby 2.1.0


=begin
I propose adding (({MutexedDelegator})) as a simple way to wrap any object with a thread-safe wrapper, via existing delegation logic in ((%delegate.rb%)).

(({Delegator})) provides a way to pass method calls through to a wrapped object. (({SimpleDelegator})) is a trivial implementation that just holds the object in an instance variable. (({MutexedDelegator})) would extend (({SimpleDelegator})) and only override initialize and (({method_missing})) as follows:

  class MutexedDelegator < SimpleDelegator
    def initialize(*)
      super
      @mutex = Mutex.new
    end
    
    def method_missing(m, *args, &block)
      target, mutex = self.__getobj__, @mutex
      begin
        mutex.lock
        target.__send__(m, *args, &block)
      ensure
        mutex.unlock
      end
    end
  end

The only changes here are:

* (({Mutex#lock})) and (({unlock})) logic wrapping the send
* No (({respond_to?})) check; I'm not sure why it's there to begin with, since if we're in (({method_missing})) the (({super()})) call will fail just like a normal (({method_missing})) failure anyway
* No backtrace manipulation. This does not work on JRuby and Rubinius anyway, and in this case I feel that the delegator should not hide itself, since there's real behavior change happening.

This is a trivial addition to stdlib that would make it simple to synchronize all calls to a given object in the same way as the JDK's (({Collections.synchronizedSet}))/(({Map}))/(({List})) calls.
=end



-- 
http://bugs.ruby-lang.org/