Example code I've seen that uses a ConditionVariable seems to have a
timing flaw. For example, in Pickaxe 2, page 147 in the
Player/Customer example, the player thread does

  plays_pending.wait_while { playlist.empty? }

This assumes that ok_to_shutdown gets set and detected in the previous
line before wait_while is called. If the timing works out such that
ok_to_shutdown doesn't get set to true until after wait_while is
called, I believe it will hang forever.

Isn't it the case that the customer thread could finish and the player
thread could reach the call to wait_while before ok_to_shutdown gets
set to true?

I think what needs to happen is what I show in the following example.
Note how the wait_while checks whether the producer thread is alive
and how I do a condition.signal at the bottom when I know the producer
thread has terminated ... which causes wait_while to evaluate the
condition again.

The call to sleep at the end of the producer thread is critical to
illustrating the problem. If I take that out then I can get lucky with
the timing and don't need to test whether the producer thread is alive
in wait_while.

Am I off my rocker?  ;-)

require 'monitor'

#----------------------------------------------------------------------

class Widget
  attr_accessor :id

  def initialize(id)
    self.id = id
  end

  def to_s
    id
  end
end

#----------------------------------------------------------------------

bin = []
bin.extend MonitorMixin
condition = bin.new_cond # type is MonitorMixin::ConditionVariable

#----------------------------------------------------------------------

producer = Thread.new do
  widget_id = 0

  5.times do
    widget_id += 1
    widget = Widget.new(widget_id)
    puts "produced #{widget.to_s}"

    bin.synchronize do
      bin << widget
      condition.signal # there is a widget to consume
    end
  end

  # Delay termination of producer thread
  # to demonstrate need for another condition.signal.
  sleep 1
end

#----------------------------------------------------------------------

consumer = Thread.new do
  finished = false
  until (finished) do
    bin.synchronize do
      finished = (not producer.alive?) and bin.empty?
      break if finished

      # The condition will be tested once,
      # and then again every time condition.signal is called.
      condition.wait_while { producer.alive? and bin.empty? }

      widget = bin.shift # removes first Widget from bin
      puts "consumed #{widget.to_s}" if widget != nil
    end
  end
end

#----------------------------------------------------------------------

producer.join
bin.synchronize do
  # Make consumer retest condition after producer terminates.
  condition.signal
end
consumer.join

--
R. Mark Volkmann
Partner, Object Computing, Inc.