Hi Brian,
> 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
Ahh.. That does make sense...
> 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?
Oh.. hehe... No, it's intended to be connected to a remote
socket. (Or at least, to a socket whose other end is
connected to a different process.)
I suppose in practice it's a convenience mechanism. It lets
my main thread blast an arbitrarily large chunk of data at
the BufferedIO#send_nonblock without having to wait. The main
thread can go about its business, knowing that data will be
And similarly, in the reverse direction, BufferedIO is
continually retrieving any data sent by the remote host
to a buffer, that will be available via BufferedIO#recv_nonblock,
whenever the main thread gets around to checking for it.
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.
Good point, thanks !
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
Hmm... I like !
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.
Sorry, timed-wait.rb is there now.
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)
Thanks!
Yes, I guess BufferedIO is somewhat like a Queue (of bytes) for
processes connected between sockets. Ultimately it's an experiment,
I'm interested to see whether it has a simplifying effect on my
"main thread" code, or not. Previously, my application was single-
threaded, and was a typical select() dispatch serving many clients.
I'm sort of injecting BufferedIO into this application to give me
the effect of (as though) arbitrarily large send/recv buffers in
the kernel. So my application is still in an equivalent of its
select() dispatch, only now waiting on this global-signal instead.
And able to toss arbitrarly large results of data at a client
without getting hung up on the transmit.
I don't know if in the end it will be beneficial, or if it
would be better to just rewrite the app to be completely
multi-threaded, and get rid of this main thread dispatch
notion entirely. I have always had a love-hate relationship
with both multithreading, and single-threaded select() dispatch
loops. <grin>
Thanks for your feedback & insights,
Regards,
Bill
···
From: "Brian Candler" <B.Candler@pobox.com>
sent by BufferedIO to the remote host as fast as it can be.