The net/http package uses net/protocol to implement buffered IO with timeouts. net/protocol in turn supports timing out IO operations, specifically used while filling its own buffer in response to a read request:
def rbuf_fill
timeout(@read_timeout) {
@rbuf << @io.sysread(1024)
}
end
rbuf_fill is called if not enough bytes are available from the source IO. It reads up to 1024 bytes into its buffer, timeout out if @read_timeout seconds have expired. Simple enough, right? So how does timeout work?
def timeout(sec, exception=Error)
return yield if sec == nil or sec.zero?
raise ThreadError, "timeout within critical session" if Thread.critical
begin
x = Thread.current
y = Thread.start {
sleep sec
x.raise exception, "execution expired" if x.alive?
}
yield sec
# return true
ensure
y.kill if y and y.alive?
end
end
Yes, you're reading that right. For every call to timeout, a new thread is spun up to sleep for sec seconds. If that thread completes sleeping before the provided block completes running. If the block finishes first, the timeout thread is killed and execution continues as normal. The result of this is that for every 1024 bytes read from the stream, a new Thread is spun up...in most cases to be immediately terminated. Under Ruby, this means starting up a new green thread; under JRuby, it means a new native thread, which is obviously far more expensive. This explains a number of things:
- Why Ola sees many hundreds of threads during gem installation
- Why gem downloading is so unbelievably slow (and by extension, why ANY net/http operation will be unbelievably slow)
- Why we get InterruptedExceptions at the end of the download; one last read probably read is probably invoked and perhaps our sysread (blocking, non-nio) impl does not return correctly. The timeout expires, the thread is killed, and we are forced to .interrupt it because it's blocking.
The third item should not be difficult to fix; we need a better sysread impl. The other two items are systemic in the net/* package.
I'll write more on this later once the blindness has subsided. Even with green threads this seems like a gross waste of resources. It confirmed my fear that green threads are already heavily (ab)used in Ruby, and that m:n threading is almost a must-have.
--
Charles Oliver Nutter @ headius.blogspot.com
JRuby Developer @ jruby.sourceforge.net
Application Architect @ www.ventera.com
_______________________________________________ Jruby-devel mailing list Jruby-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/jruby-devel