On Tue, Sep 21, 2004 at 02:38:55AM +0900, Bill Kelly wrote:
> Hi,
> 
> From: "Brian Schr?der" <ruby / brian-schroeder.de>
> 
> > in the program only one thread at a time is reading, and only one at a
> > time is writing. I wanted to know if I could read and write to the same
> > socket from two different threads simultaneously. And I think this is a
> > subset of the answer, so I take it for "yes".
> 
> I didn't know if two threads simultaneously accessing the same
> socket was legal

I believe it is, if you think about how Ruby implements threads internally.
It doesn't use any O/S threading at all (it even works under MS-DOS, not
that I've tried it myself :-)

Rather, the Ruby interpreter chugs along the annotated syntax tree, changes
to another Thread, chugs along another big of syntax tree, and so on. For
threads which are blocked on I/O, it uses select() to determine when they
are ready to be scheduled again.

> http://bwk.homeip.net/ftp/dorkbuster/wallfly/buffered-io.rb
> 
> The above is an IO class whose read thread just slurps data
> as fast as it can in the background.  And whose write thread
> writes data out, similarly, when it can.  So the code interfacing
> with this class gets a non-blocking read & write, with infinite
> buffer size.  (Up to available RAM of course.)  For my purposes
> it has been convenient... dunno if it would be useful to anyone
> else.  If so I could put it on RAA (?)

You stuff data down one socket and read it back from the same socket, like a
loopback? If so I think it could be written much more simply; for example it
is superfluous to write

        if select([@sock_rd], nil, nil, nil)
          dat = @sock_rd.recv(65536)

when you can just do

          dat = @sock_rd.recv(65536)

because Ruby handles the select() behind the scenes to prevent one thread
blocking another, as outlined above; recv deschedules the thread until at
least one byte is available.

In fact I think the core could be re-written as something like this:
-----------------------------------------------------------------------
require 'thread'

class BufferedIO
  def initialize(sock)
    @sock = sock
    @queue = Queue.new
    @rd_thread = Thread.new { background_read }
    @wr_thread = Thread.new { background_write }
  end

  def background_read
    while true
      dat = @sock.recv(65536)
      break if dat.nil? or dat.empty?
      @queue.push(dat)
    end
    @queue.push(nil)  # EOF indication
  end

  def background_write
    while dat = @queue.pop
      @sock.write(dat)
    end
  end

  def close
    @sock.close
    @rd_thread.join
    @wr_thread.join
  end
end
-----------------------------------------------------------------------

which looks a lot less like C and a lot more like Ruby :-)

However that doesn't include your 'signal' functionality, nor do I have
access to your timed-wait.rb, so I can't prove it against your Unit tests.

Not sure how useful such a thing would be for RAA though. There is already
the "Queue" class in thread.rb, which is a queue of objects rather than a
queue of bytes, as I've used above. I think that's a more generic and useful
pattern. There is also a SizedQueue which limits the maximum number of
objects it contains.

Queue and SizedQueue are thread-safe, which is why there are no Mutexes in
the code above, although if paranoid you might want one to ensure that
@sock.read, @sock.write and @sock.close are mutually exclusive. (But then,
if @sock.write blocked, that would prevent @sock.read from running, which I
don't think is what you want)

> It's been stress tested with as many variations of reads, writes,
> random & non random timing delays as I could think of to devise,
> running for days under these stress conditions without a hiccup.
> I realize that doesn't prove anything, but I have a good feeling
> about its reliability at this point.

That's definitely good :-)

Regards,

Brian.