unless defined? Thread.exclusive

def Thread.exclusive
  old = Thread.critical
  Thread.critical = true
  yield
ensure
  Thread.critical = old
end

end

def Thread.nonexclusive

old = Thread.critical
Thread.critical = false
yield

ensure

Thread.critical = old

end

module FSDB

class Modex

SH = :SH
EX = :EX

def initialize
  @waiting  = []
  @locked   = []
  @mode     = nil
  @first    = true
end

def try_lock mode
  Thread.exclusive do
    thread = Thread.current
    raise ThreadError if @locked.include?(thread)

    if @mode == mode and mode == SH and @waiting.empty? # strict queue
      @locked << thread
      true
    elsif not @mode
      @mode = mode
      @locked << thread
      true
    end
  end
end

# the block is executed in the exclusive context
def lock mode
  Thread.exclusive do
    thread = Thread.current
    raise ThreadError if @locked.include?(thread)

    if @mode == mode and mode == SH and @waiting.empty? # strict queue
      @locked << thread
    elsif not @mode
      @mode = mode
      @locked << thread
    else
      @waiting << thread << mode
      Thread.stop
      Thread.critical = true
    end

    yield if block_given?


    self
  end
end

# the block is executed in the exclusive context
def unlock
  raise ThreadError unless @mode

  t = Thread.exclusive do
    yield if block_given?
    @locked.delete Thread.current
    wake_next_waiter if @locked.empty?
  end

  self
end

def synchronize mode, do_when_first = nil, do_when_last = nil, arg = nil
  lock mode do
    if @first
      @first = false

      if do_when_first
        if mode == SH
          @mode = EX
        end

        Thread.nonexclusive { do_when_first[arg] }

        if mode == SH
          @mode = SH
          wake_waiting_sharers
        end
      end
    end
  end

  yield

ensure
  unlock do
    if @locked.size == 1
      if do_when_last
        @mode = EX
        Thread.nonexclusive { do_when_last[arg] }
      end
      @first = true
    end
  end
end

def remove_dead
  Thread.exclusive do
    waiting = @waiting; @waiting = []
    until waiting.empty?

      t = waiting.shift; m = waiting.shift
      @waiting << t << m if t.alive?
    end

    @locked = @locked.select {|t| t.alive?}
    wake_next_waiter if @locked.empty?
  end
end

private
def wake_next_waiter
  first = @waiting.shift; @mode = @waiting.shift && EX
  if first
    first.wakeup
    @locked << first
  end
  first
rescue ThreadError
  retry
end

def wake_waiting_sharers
  while @waiting[1] == SH  # note strict queue order
    t = @waiting.shift; @waiting.shift
    @locked << t
    t.wakeup
  end
rescue ThreadError
  retry
end

module ForkSafely
  def fork
    super do
      ObjectSpace.each_object(Modex) { |m| m.remove_dead }
      yield
    end
  end
end

end

module ForkSafely

include Modex::ForkSafely

end include ForkSafely

end # module FSDB

if __FILE__ == $0

# Stress test is in fsdb/test/test-modex.rb. This is just to show fork usage.

include FSDB::ForkSafely

m = FSDB::Modex.new

SH = FSDB::Modex::SH

Thread.new { m.synchronize(SH) { sleep 1 } }

fork do
  m.synchronize(SH) do
   puts "Didn't get here if you used standard mutex or fork."
  end
end

m.synchronize(SH) { puts "Got here." }

Process.wait

end