From ec16c5899589ea9cebf99b3b0a367ccfa551f4c5 Mon Sep 17 00:00:00 2001 From: Nick Amorim Date: Mon, 3 Feb 2025 14:51:26 -0500 Subject: [PATCH] feat: I/O timeouts --- lib/dalli/socket.rb | 21 ++++++++++++++++----- test/integration/test_network.rb | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/lib/dalli/socket.rb b/lib/dalli/socket.rb index bf25fb3d..ad0d35c7 100644 --- a/lib/dalli/socket.rb +++ b/lib/dalli/socket.rb @@ -124,12 +124,16 @@ def self.init_socket_options(sock, options) return unless options[:socket_timeout] - seconds, fractional = options[:socket_timeout].divmod(1) - microseconds = fractional * 1_000_000 - timeval = [seconds, microseconds].pack('l_2') + if sock.respond_to?(:timeout=) + sock.timeout = options[:socket_timeout] + else + seconds, fractional = options[:socket_timeout].divmod(1) + microseconds = fractional * 1_000_000 + timeval = [seconds, microseconds].pack('l_2') - sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVTIMEO, timeval) - sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDTIMEO, timeval) + sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVTIMEO, timeval) + sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDTIMEO, timeval) + end end def self.wrapping_ssl_socket(tcp_socket, host, ssl_context) @@ -168,9 +172,16 @@ def self.open(path, options = {}) Timeout.timeout(options[:socket_timeout]) do sock = new(path) sock.options = { path: path }.merge(options) + init_socket_options(sock, options) sock end end + + def self.init_socket_options(sock, options) + # https://man7.org/linux/man-pages/man7/unix.7.html + sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf] + sock.timeout = options[:socket_timeout] if options[:socket_timeout] && sock.respond_to?(:timeout=) + end end end end diff --git a/test/integration/test_network.rb b/test/integration/test_network.rb index bc1fe8a3..bd10d75e 100644 --- a/test/integration/test_network.rb +++ b/test/integration/test_network.rb @@ -74,6 +74,24 @@ end end + it 'handles operation timeouts' do + next if p == :binary + + memcached_mock(lambda { |sock| + # handle initial version call + sock.gets + sock.write("VERSION 1.6.0\r\n") + + sleep(0.3) + }) do + dc = Dalli::Client.new('localhost:19123', socket_timeout: 0.1, protocol: p, socket_max_failures: 0, + socket_failure_delay: 0.0, down_retry_delay: 0.0) + assert_raises Dalli::RingError, message: 'No server available' do + dc.get('abc') + end + end + end + it 'opens a standard TCP connection when ssl_context is not configured' do memcached_persistent(p) do |dc| server = dc.send(:ring).servers.first