Skip to content

Commit

Permalink
minissl, request, server files - refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
MSP-Greg committed Feb 15, 2021
1 parent 7c91d90 commit b54ed51
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 44 deletions.
30 changes: 15 additions & 15 deletions lib/puma/minissl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,27 +114,27 @@ def read_nonblock(size, *_)
end
end

# Elsewhere, most socket writes use `syswrite`. When returning a large
# response body (2MB), Ubuntu works fine with `syswrite`, but macOS &
# Windows have OpenSSL errors on the client.
#
def write(data)
return 0 if data.empty?
n = 0
byte_size = data.bytesize
enc_wr = ''.dup
enc = nil

data_size = data.bytesize
need = data_size
while n < byte_size
n += @engine.write(n == 0 ? data : data.byteslice(n..-1))

while true
wrote = @engine.write data
enc_wr << enc while (enc = @engine.extract)

enc_wr = ''.dup
while (enc = @engine.extract)
enc_wr << enc
end
@socket.write enc_wr unless enc_wr.empty?

need -= wrote

return data_size if need == 0

data = data.byteslice(wrote..-1)
enc_wr.clear
end
enc.clear unless enc.nil?
byte_size
end

alias_method :syswrite, :write
Expand Down Expand Up @@ -184,7 +184,7 @@ def close
# If it can't send more packets within 1s, then give up.
return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
rescue IOError, SystemCallError
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
Thread.current.purge_interrupt_queue if SERVER::PURGE_INTERRUPT_QUEUE
# nothing
ensure
@socket.close
Expand Down
19 changes: 6 additions & 13 deletions lib/puma/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,15 @@ def handle_request(client, lines)
else
fast_write io, part
end
io.flush
end

if chunked
fast_write io, CLOSE_CHUNKED
io.flush
end
fast_write io, CLOSE_CHUNKED if chunked
rescue SystemCallError, IOError
raise ConnectionError, "Connection error detected during write"
end

ensure
uncork_socket io
io.flush

body.close
client.tempfile.unlink if client.tempfile
Expand Down Expand Up @@ -195,21 +191,18 @@ def default_server_port(env)
#
def fast_write(io, str)
n = 0
while true
byte_size = str.bytesize
while n < byte_size
begin
n = io.syswrite str
n += io.syswrite(n == 0 ? str : str.byteslice(n..-1))
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
unless IO.select(nil, [io], nil, WRITE_TIMEOUT)
raise ConnectionError, "Socket timeout writing data"
end

retry
rescue Errno::EPIPE, SystemCallError, IOError
raise ConnectionError, "Socket timeout writing data"
end

return if n == str.bytesize
str = str.byteslice(n..-1)
end
end
private :fast_write
Expand Down
37 changes: 21 additions & 16 deletions lib/puma/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class Server
include Request
extend Forwardable

PURGE_INTERRUPT_QUEUE = ::Thread.current.respond_to? :purge_interrupt_queue

attr_reader :thread
attr_reader :events
attr_reader :min_threads, :max_threads # for #stats
Expand Down Expand Up @@ -114,49 +116,49 @@ def inherit_binder(bind)
class << self
# @!attribute [r] current
def current
Thread.current[ThreadLocalKey]
::Thread.current[ThreadLocalKey]
end

# :nodoc:
# @version 5.0.0
def tcp_cork_supported?
Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
::Socket.const_defined?(:TCP_CORK) && ::Socket.const_defined?(:IPPROTO_TCP)
end

# :nodoc:
# @version 5.0.0
def closed_socket_supported?
Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
::Socket.const_defined?(:TCP_INFO) && ::Socket.const_defined?(:IPPROTO_TCP)
end
private :tcp_cork_supported?
private :closed_socket_supported?
end

# On Linux, use TCP_CORK to better control how the TCP stack
# packetizes our stream. This improves both latency and throughput.
# socket parameter may be an MiniSSL::Socket, so use to_io
# socket parameter may be a `MiniSSL::Socket`, so use to_io
#
if tcp_cork_supported?
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze

# 6 == Socket::IPPROTO_TCP
# 3 == TCP_CORK
# 1/0 == turn on/off
def cork_socket(socket)
skt = socket.to_io
return unless skt.kind_of? ::TCPSocket
begin
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
skt.setsockopt ::Socket::IPPROTO_TCP, ::Socket::TCP_CORK, 1
rescue IOError, SystemCallError
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
Thread.current.purge_interrupt_queue if PURGE_INTERRUPT_QUEUE
end
end

def uncork_socket(socket)
skt = socket.to_io
return unless skt.kind_of? ::TCPSocket
begin
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
skt.setsockopt ::Socket::IPPROTO_TCP, ::Socket::TCP_CORK, 0
rescue IOError, SystemCallError
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
Thread.current.purge_interrupt_queue if PURGE_INTERRUPT_QUEUE
end
end
else
Expand All @@ -168,14 +170,17 @@ def uncork_socket(socket)
end

if closed_socket_supported?
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze

def closed_socket?(socket)
return false unless socket.kind_of? TCPSocket
skt = socket.to_io
return false unless skt.kind_of? ::TCPSocket
return false unless @precheck_closing

begin
tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
tcp_info = skt.getsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_INFO)
rescue IOError, SystemCallError
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
Thread.current.purge_interrupt_queue if PURGE_INTERRUPT_QUEUE
@precheck_closing = false
false
else
Expand Down Expand Up @@ -476,7 +481,7 @@ def process_client(client, buffer)
begin
client.close if close_socket
rescue IOError, SystemCallError
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
Thread.current.purge_interrupt_queue if PURGE_INTERRUPT_QUEUE
# Already closed
rescue StandardError => e
@events.unknown_error e, nil, "Client"
Expand Down Expand Up @@ -589,11 +594,11 @@ def notify_safely(message)
@notify << message
rescue IOError, NoMethodError, Errno::EPIPE
# The server, in another thread, is shutting down
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
Thread.current.purge_interrupt_queue if PURGE_INTERRUPT_QUEUE
rescue RuntimeError => e
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
if e.message.include?('IOError')
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
Thread.current.purge_interrupt_queue if PURGE_INTERRUPT_QUEUE
else
raise e
end
Expand Down

0 comments on commit b54ed51

Please sign in to comment.