lib/dalli/socket.rb in dalli-3.0.4 vs lib/dalli/socket.rb in dalli-3.0.5

- old
+ new

@@ -2,102 +2,146 @@ require 'openssl' require 'rbconfig' module Dalli + ## + # Various socket implementations used by Dalli. + ## module Socket + ## + # Common methods for all socket implementations. + ## module InstanceMethods - def readfull(count) - value = +"" + value = +'' loop do result = read_nonblock(count - value.bytesize, exception: false) - if result == :wait_readable - raise Timeout::Error, "IO timeout: #{safe_options.inspect}" unless IO.select([self], nil, nil, options[:socket_timeout]) - elsif result == :wait_writable - raise Timeout::Error, "IO timeout: #{safe_options.inspect}" unless IO.select(nil, [self], nil, options[:socket_timeout]) - elsif result - value << result - else - raise Errno::ECONNRESET, "Connection reset: #{safe_options.inspect}" - end + value << result if append_to_buffer?(result) break if value.bytesize == count end value end def read_available - value = +"" + value = +'' loop do result = read_nonblock(8196, exception: false) - if result == :wait_readable - break - elsif result == :wait_writable - break - elsif result - value << result - else - raise Errno::ECONNRESET, "Connection reset: #{safe_options.inspect}" - end + break if WAIT_RCS.include?(result) + raise Errno::ECONNRESET, "Connection reset: #{logged_options.inspect}" unless result + + value << result end value end - def safe_options - options.reject { |k, v| [:username, :password].include? k } + WAIT_RCS = %i[wait_writable wait_readable].freeze + + def append_to_buffer?(result) + raise Timeout::Error, "IO timeout: #{logged_options.inspect}" if nonblock_timed_out?(result) + raise Errno::ECONNRESET, "Connection reset: #{logged_options.inspect}" unless result + + !WAIT_RCS.include?(result) end + + def nonblock_timed_out?(result) + return true if result == :wait_readable && !wait_readable(options[:socket_timeout]) + + # TODO: Do we actually need this? Looks to be only used in read_nonblock + result == :wait_writable && !wait_writable(options[:socket_timeout]) + end + + FILTERED_OUT_OPTIONS = %i[username password].freeze + def logged_options + options.reject { |k, _| FILTERED_OUT_OPTIONS.include? k } + end end + ## + # Wraps the below TCP socket class in the case where the client + # has configured a TLS/SSL connection between Dalli and the + # Memcached server. + ## class SSLSocket < ::OpenSSL::SSL::SSLSocket include Dalli::Socket::InstanceMethods def options io.options end + + unless method_defined?(:wait_readable) + def wait_readable(timeout = nil) + to_io.wait_readable(timeout) + end + end + + unless method_defined?(:wait_writable) + def wait_writable(timeout = nil) + to_io.wait_writable(timeout) + end + end end + ## + # A standard TCP socket between the Dalli client and the Memcached server. + ## class TCP < TCPSocket include Dalli::Socket::InstanceMethods attr_accessor :options, :server def self.open(host, port, server, options = {}) Timeout.timeout(options[:socket_timeout]) do sock = new(host, port) - sock.options = {host: host, port: port}.merge(options) + sock.options = { host: host, port: port }.merge(options) sock.server = server - sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true) - sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options[:keepalive] - sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVBUF, options[:rcvbuf]) if options[:rcvbuf] - sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf] + init_socket_options(sock, options) - return sock unless options[:ssl_context] - - ssl_socket = Dalli::Socket::SSLSocket.new(sock, options[:ssl_context]) - ssl_socket.hostname = host - ssl_socket.sync_close = true - ssl_socket.connect - ssl_socket + options[:ssl_context] ? wrapping_ssl_socket(sock, host, options[:ssl_context]) : sock end end - end - end - if RbConfig::CONFIG['host_os'] =~ /mingw|mswin/ - class Dalli::Socket::UNIX - def initialize(*args) - raise Dalli::DalliError, 'Unix sockets are not supported on Windows platform.' + def self.init_socket_options(sock, options) + sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true) + sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options[:keepalive] + sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVBUF, options[:rcvbuf]) if options[:rcvbuf] + sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf] end + + def self.wrapping_ssl_socket(tcp_socket, host, ssl_context) + ssl_socket = Dalli::Socket::SSLSocket.new(tcp_socket, ssl_context) + ssl_socket.hostname = host + ssl_socket.sync_close = true + ssl_socket.connect + ssl_socket + end end - else - class Dalli::Socket::UNIX < UNIXSocket - include Dalli::Socket::InstanceMethods - attr_accessor :options, :server - def self.open(path, server, options = {}) - Timeout.timeout(options[:socket_timeout]) do - sock = new(path) - sock.options = {path: path}.merge(options) - sock.server = server - sock + if /mingw|mswin/.match?(RbConfig::CONFIG['host_os']) + ## + # UNIX domain sockets are not supported on Windows platforms. + ## + class UNIX + def initialize(*_args) + raise Dalli::DalliError, 'Unix sockets are not supported on Windows platform.' + end + end + else + + ## + # UNIX represents a UNIX domain socket, which is an interprocess communication + # mechanism between processes on the same host. Used when the Memcached server + # is running on the same machine as the Dalli client. + ## + class UNIX < UNIXSocket + include Dalli::Socket::InstanceMethods + attr_accessor :options, :server + + def self.open(path, server, options = {}) + Timeout.timeout(options[:socket_timeout]) do + sock = new(path) + sock.options = { path: path }.merge(options) + sock.server = server + sock + end end end end end end