class PhusionPassenger::Utils::UnseekableSocket
Some frameworks (e.g. Merb) call `seek` and `rewind` on the input stream if it responds to these methods. In case of Phusion Passenger, the input stream is a socket, and altough socket objects respond to `seek` and `rewind`, calling these methods will raise an exception. We don't want this to happen so in AbstractRequestHandler we wrap the client socket into an UnseekableSocket wrapper, which doesn't respond to these methods.
We used to dynamically undef `seek` and `rewind` on sockets, but this blows the Ruby interpreter's method cache and made things slower. Wrapping a socket is faster despite extra method calls.
Furthermore, all exceptions originating from the wrapped socket will be annotated. One can check whether a certain exception originates from the wrapped socket by calling source_of_exception?
Public Class Methods
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 44 def self.wrap(socket) return new.wrap(socket) end
Public Instance Methods
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 99 def addr @socket.addr rescue => e raise annotate(e) end
Already set to binary mode.
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 87 def binmode end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 225 def close @socket.close rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 231 def close_read @socket.close_read rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 237 def close_write @socket.close_write rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 219 def closed? @socket.closed? rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 207 def each(&block) @socket.each(&block) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 213 def eof? @socket.eof? rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 95 def fileno @socket.fileno end
Socket is sync'ed so flushing shouldn't do anything.
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 83 def flush end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 159 def gets @socket.gets rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 153 def puts(*args) @socket.puts(*args) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 165 def read(*args) @socket.read(*args) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 171 def read_nonblock(*args) @socket.read_nonblock(*args) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 183 def readline @socket.readline rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 177 def readpartial(*args) @socket.readpartial(*args) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 189 def recv(*args) @socket.recv(*args) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 195 def recvfrom(*args) @socket.recvfrom(*args) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 201 def recvfrom_nonblock(*args) @socket.recvfrom_nonblock(*args) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 135 def send(*args) @socket.send(*args) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 141 def sendmsg(*args) @socket.sendmsg(*args) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 147 def sendmsg_nonblock(*args) @socket.sendmsg_nonblock(*args) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 243 def source_of_exception?(exception) return exception.instance_variable_get(:"@from_unseekable_socket") == @socket.object_id end
Don't allow disabling of sync.
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 79 def sync=(value) end
This makes select() work.
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 91 def to_io @socket end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 48 def wrap(socket) # Some people report that sometimes their Ruby (MRI/REE) # processes get stuck with 100% CPU usage. Upon further # inspection with strace, it turns out that these Ruby # processes are continuously calling lseek() on a socket, # which of course returns ESPIPE as error. gdb reveals # lseek() is called by fwrite(), which in turn is called # by rb_fwrite(). The affected socket is the # AbstractRequestHandler client socket. # # I inspected the MRI source code and didn't find # anything that would explain this behavior. This makes # me think that it's a glibc bug, but that's very # unlikely. # # The rb_fwrite() implementation takes an entirely # different code path if I set 'sync' to true: it will # skip fwrite() and use write() instead. So here we set # 'sync' to true in the hope that this will work around # the problem. socket.sync = true # There's no need to set the encoding for Ruby 1.9 because # abstract_request_handler.rb is tagged with 'encoding: binary'. @socket = socket return self end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 105 def write(string) @socket.write(string) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 111 def write_nonblock(string) @socket.write_nonblock(string) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 117 def writev(components) @socket.writev(components) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 123 def writev2(components, components2) @socket.writev2(components, components2) rescue => e raise annotate(e) end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 129 def writev3(components, components2, components3) @socket.writev3(components, components2, components3) rescue => e raise annotate(e) end
Private Instance Methods
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 248 def annotate(exception) exception.instance_variable_set(:"@from_unseekable_socket", @socket.object_id) return exception end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 258 def binary_string(str) return ''.force_encoding('binary') end
# File lib/phusion_passenger/utils/unseekable_socket.rb, line 253 def raise_error_because_activity_disallowed! raise IOError, "It is not possible to read or write from the client socket because the current." end