# mutexm - Mutex Module
#
# $Date: 2001/06/05 17:33:55 $
# Copyright (c) 2000 Masatoshi SEKI
#
# mutexm.rb is copyrighted free software by Masatoshi SEKI.
# You can redistribute it and/or modify it under the same term as Ruby.
#
# original from mutex_m.rb by Keiju ISHITSUKA

module MutexM
  def MutexM.append_features(cl)
    super
    unless cl.instance_of?(Module)
      cl.module_eval %q{
	alias locked? mu_locked?
	alias lock mu_lock
	alias unlock mu_unlock
	alias try_lock mu_try_lock
	alias synchronize mu_synchronize
	alias in_synchronize mu_in_synchronize
      }
    end
    def cl.synchronize_reader(*arg)
      for attr in arg
	begin
	  attr = attr.id2name
	rescue NameError
	end
	module_eval("def #{attr}; synchronize do return @#{attr} end; end")
      end
    end
    def cl.synchronize_writer(*arg)
      for attr in arg
	begin
	  attr = attr.id2name
	rescue NameError
	end
	module_eval("def #{attr}=(p); synchronize do @#{attr}=p end; end")
      end
    end
    def cl.synchronize_accessor(*arg)
      synchronize_reader(*arg)
      synchronize_writer(*arg)
    end

    def cl.lock_reader(*arg)
      for attr in arg
	begin
	  attr = attr.id2name
	rescue NameError
	end
	module_eval("def #{attr}; mu_in_synchronize; return @#{attr}; end")
      end
    end
    def cl.lock_writer(*arg)
      for attr in arg
	begin
	  attr = attr.id2name
	rescue NameError
	end
	module_eval("def #{attr}=(p); mu_in_synchronize; @#{attr}=p ; end")
      end
    end
    def cl.lock_accessor(*arg)
      lock_reader(*arg)
      lock_writer(*arg)
    end

    def cl.synchronized(*arg)
      for method in arg
	begin
	  method = method.id2name
	rescue NameError
	end
	org = "_mu_#{method}"
	module_eval("alias #{org} #{method}")
	module_eval("def #{method}(*a,&b); synchronize do #{org}(*a,&b) end; end")
	private(org)
      end
    end
    return self
  end
  
  def MutexM.extend_object(obj)
    super
    obj.mu_extended
  end

  def mu_extended
    unless (defined? locked? and
	    defined? lock and
	    defined? unlock and
	    defined? try_lock and
	    defined? synchronize)
      eval "class << self
	alias locked? mu_locked?
	alias lock mu_lock
	alias unlock mu_unlock
	alias try_lock mu_try_lock
	alias synchronize mu_synchronize
	alias in_synchronize mu_in_synchronize
      end"
    end
    mu_initialize
  end
  
  # locking 
  def mu_synchronize
    return yield if @mu_locked == Thread.current
    begin
      mu_lock
      yield
    ensure
      mu_unlock
    end
  end
  
  def mu_locked?
    @mu_locked ? true : false
  end
  
  def mu_try_lock
    result = false
    Thread.critical = true
    unless @mu_locked
      @mu_locked = Thread.current
      result = true
    end
    Thread.critical = false
    result
  end

  def mu_lock
    while (Thread.critical = true; @mu_locked)
      @mu_waiting = [] unless @mu_waiting
      @mu_waiting.push Thread.current
      Thread.stop
    end
    @mu_locked = Thread.current
    mu_on_lock
    Thread.critical = false
    self
  end
  
  def mu_unlock
    return unless @mu_locked == Thread.current
    Thread.critical = true
    wait = @mu_waiting || []
    @mu_waiting = []
    @mu_locked = nil
    mu_on_unlock
    Thread.critical = false
    for w in wait.uniq
      begin
	w.run
      rescue ThreadError
      end
    end
    self
  end

  def mu_in_synchronize
    raise(ThreadError, 'no lock') unless @mu_locked == Thread.current
  end

  private
  # hook
  def mu_on_lock
  end

  def mu_on_unlock
  end

  def mu_initialize
    @mu_waiting = []
    @mu_locked = nil
  end

  def initialize(*args)
    mu_initialize
    super
  end
end

# sample script
if __FILE__ == $0
  class Foo
    include MutexM

    def initialize
      super
      @foo = nil
      @bar = nil
      @count = 0
    end
    synchronize_accessor(:foo)
    lock_accessor("bar")

    def commit
      in_synchronize
      p [@foo, @bar]
    end

    def sleep_and_set
      sleep 0.5
      @foo = @foo + @foo
      sleep 0.5
      @bar = @bar + @foo
      [@foo, @bar]
    end

    synchronized :sleep_and_set
  end

  f = Foo.new
  f.foo = 1
  p f
  f.synchronize do
    f.foo = 3
    f.bar = 2
    f.commit
  end
  p f
  begin
    f.bar = 4		
  rescue ThreadError
    p $!
  end
  p f

  Thread.new do
    p f.sleep_and_set
  end
  sleep 0.2
  f.foo = 5		

  sleep 2
  p f
end
