Class: Dalli::Protocol::ConnectionManager

Inherits:
Object
  • Object
show all
Defined in:
lib/dalli/protocol/connection_manager.rb

Overview

Manages the socket connection to the server, including ensuring liveness and retries.

Constant Summary collapse

DEFAULTS =
{
  # seconds between trying to contact a remote server
  down_retry_delay: 30,
  # connect/read/write timeout for socket operations
  socket_timeout: 1,
  # times a socket operation may fail before considering the server dead
  socket_max_failures: 2,
  # amount of time to sleep between retries when a failure occurs
  socket_failure_delay: 0.1,
  # Set keepalive
  keepalive: true
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hostname, port, socket_type, client_options) ⇒ ConnectionManager

Returns a new instance of ConnectionManager.



32
33
34
35
36
37
38
39
40
41
42
# File 'lib/dalli/protocol/connection_manager.rb', line 32

def initialize(hostname, port, socket_type, client_options)
  @hostname = hostname
  @port = port
  @socket_type = socket_type
  @options = DEFAULTS.merge(client_options)
  @request_in_progress = false
  @sock = nil
  @pid = nil

  reset_down_info
end

Instance Attribute Details

#hostnameObject

Returns the value of attribute hostname.



29
30
31
# File 'lib/dalli/protocol/connection_manager.rb', line 29

def hostname
  @hostname
end

#optionsObject

Returns the value of attribute options.



29
30
31
# File 'lib/dalli/protocol/connection_manager.rb', line 29

def options
  @options
end

#portObject

Returns the value of attribute port.



29
30
31
# File 'lib/dalli/protocol/connection_manager.rb', line 29

def port
  @port
end

#sockObject (readonly)

Returns the value of attribute sock.



30
31
32
# File 'lib/dalli/protocol/connection_manager.rb', line 30

def sock
  @sock
end

#socket_typeObject

Returns the value of attribute socket_type.



29
30
31
# File 'lib/dalli/protocol/connection_manager.rb', line 29

def socket_type
  @socket_type
end

Instance Method Details

#abort_request!Object



145
146
147
# File 'lib/dalli/protocol/connection_manager.rb', line 145

def abort_request!
  @request_in_progress = false
end

#closeObject



112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/dalli/protocol/connection_manager.rb', line 112

def close
  return unless @sock

  begin
    @sock.close
  rescue StandardError
    nil
  end
  @sock = nil
  @pid = nil
  abort_request!
end

#close_on_forkObject



222
223
224
225
226
227
228
229
# File 'lib/dalli/protocol/connection_manager.rb', line 222

def close_on_fork
  message = 'Fork detected, re-connecting child process...'
  Dalli.logger.info { message }
  # Close socket on a fork, setting us up for reconnect
  # on next request.
  close
  raise Dalli::NetworkError, message
end

#confirm_in_progress!Object



106
107
108
109
110
# File 'lib/dalli/protocol/connection_manager.rb', line 106

def confirm_in_progress!
  raise '[Dalli] No request in progress. This may be a bug in Dalli.' unless request_in_progress?

  close_on_fork if fork_detected?
end

#confirm_ready!Object



101
102
103
104
# File 'lib/dalli/protocol/connection_manager.rb', line 101

def confirm_ready!
  close if request_in_progress?
  close_on_fork if fork_detected?
end

#connected?Boolean

Returns:

  • (Boolean)


125
126
127
# File 'lib/dalli/protocol/connection_manager.rb', line 125

def connected?
  !@sock.nil?
end

#down!Object

Marks the server instance as down. Updates the down_at state and raises an Dalli::NetworkError that includes the underlying error in the message. Calls close to clean up socket state



84
85
86
87
88
89
90
91
# File 'lib/dalli/protocol/connection_manager.rb', line 84

def down!
  close
  log_down_detected

  @error = $ERROR_INFO&.class&.name
  @msg ||= $ERROR_INFO&.message
  raise_down_error
end

#error_on_request!(err_or_string) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/dalli/protocol/connection_manager.rb', line 179

def error_on_request!(err_or_string)
  log_warn_message(err_or_string)

  @fail_count += 1
  if @fail_count >= max_allowed_failures
    down!
  else
    # Closes the existing socket, setting up for a reconnect
    # on next request
    reconnect!('Socket operation failed, retrying...')
  end
end

#establish_connectionObject



52
53
54
55
56
57
58
59
60
61
# File 'lib/dalli/protocol/connection_manager.rb', line 52

def establish_connection
  Dalli.logger.debug { "Dalli::Server#connect #{name}" }

  @sock = memcached_socket
  @pid = PIDCache.pid
  @request_in_progress = false
rescue SystemCallError, *TIMEOUT_ERRORS, EOFError, SocketError => e
  # SocketError = DNS resolution failure
  error_on_request!(e)
end

#finish_request!Object



139
140
141
142
143
# File 'lib/dalli/protocol/connection_manager.rb', line 139

def finish_request!
  raise '[Dalli] No request in progress. This may be a bug in Dalli.' unless @request_in_progress

  @request_in_progress = false
end

#fork_detected?Boolean

Returns:

  • (Boolean)


231
232
233
# File 'lib/dalli/protocol/connection_manager.rb', line 231

def fork_detected?
  @pid && @pid != PIDCache.pid
end

#log_down_detectedObject



235
236
237
238
239
240
241
242
243
244
245
# File 'lib/dalli/protocol/connection_manager.rb', line 235

def log_down_detected
  @last_down_at = Time.now

  if @down_at
    time = Time.now - @down_at
    Dalli.logger.debug { format('%<name>s is still down (for %<time>.3f seconds now)', name: name, time: time) }
  else
    @down_at = @last_down_at
    Dalli.logger.warn("#{name} is down")
  end
end

#log_up_detectedObject



247
248
249
250
251
252
# File 'lib/dalli/protocol/connection_manager.rb', line 247

def log_up_detected
  return unless @down_at

  time = Time.now - @down_at
  Dalli.logger.warn { format('%<name>s is back (downtime was %<time>.3f seconds)', name: name, time: time) }
end

#log_warn_message(err_or_string) ⇒ Object



214
215
216
217
218
219
220
# File 'lib/dalli/protocol/connection_manager.rb', line 214

def log_warn_message(err_or_string)
  detail = err_or_string.is_a?(String) ? err_or_string : "#{err_or_string.class}: #{err_or_string.message}"
  Dalli.logger.warn do
    detail = err_or_string.is_a?(String) ? err_or_string : "#{err_or_string.class}: #{err_or_string.message}"
    "#{name} failed (count: #{@fail_count}) #{detail}"
  end
end

#max_allowed_failuresObject



175
176
177
# File 'lib/dalli/protocol/connection_manager.rb', line 175

def max_allowed_failures
  @max_allowed_failures ||= @options[:socket_max_failures] || 2
end

#memcached_socketObject



206
207
208
209
210
211
212
# File 'lib/dalli/protocol/connection_manager.rb', line 206

def memcached_socket
  if socket_type == :unix
    Dalli::Socket::UNIX.open(hostname, options)
  else
    Dalli::Socket::TCP.open(hostname, port, options)
  end
end

#nameObject



44
45
46
47
48
49
50
# File 'lib/dalli/protocol/connection_manager.rb', line 44

def name
  if socket_type == :unix
    hostname
  else
    "#{hostname}:#{port}"
  end
end

#raise_down_errorObject



93
94
95
# File 'lib/dalli/protocol/connection_manager.rb', line 93

def raise_down_error
  raise Dalli::NetworkError, "#{name} is down: #{@error} #{@msg}"
end

#read(count) ⇒ Object



157
158
159
160
161
# File 'lib/dalli/protocol/connection_manager.rb', line 157

def read(count)
  @sock.readfull(count)
rescue SystemCallError, *TIMEOUT_ERRORS, EOFError => e
  error_on_request!(e)
end

#read_lineObject



149
150
151
152
153
154
155
# File 'lib/dalli/protocol/connection_manager.rb', line 149

def read_line
  data = @sock.gets("\r\n")
  error_on_request!('EOF in read_line') if data.nil?
  data
rescue SystemCallError, *TIMEOUT_ERRORS, EOFError => e
  error_on_request!(e)
end

#read_nonblockObject

Non-blocking read. Here to support the operation of the get_multi operation



171
172
173
# File 'lib/dalli/protocol/connection_manager.rb', line 171

def read_nonblock
  @sock.read_available
end

#reconnect!(message) ⇒ Object



192
193
194
195
196
# File 'lib/dalli/protocol/connection_manager.rb', line 192

def reconnect!(message)
  close
  sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
  raise Dalli::NetworkError, message
end

#reconnect_down_server?Boolean

Returns:

  • (Boolean)


63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/dalli/protocol/connection_manager.rb', line 63

def reconnect_down_server?
  return true unless @last_down_at

  time_to_next_reconnect = @last_down_at + options[:down_retry_delay] - Time.now
  return true unless time_to_next_reconnect.positive?

  Dalli.logger.debug do
    format('down_retry_delay not reached for %<name>s (%<time>.3f seconds left)', name: name,
                                                                                  time: time_to_next_reconnect)
  end
  false
end

#request_in_progress?Boolean

Returns:

  • (Boolean)


129
130
131
# File 'lib/dalli/protocol/connection_manager.rb', line 129

def request_in_progress?
  @request_in_progress
end

#reset_down_infoObject



198
199
200
201
202
203
204
# File 'lib/dalli/protocol/connection_manager.rb', line 198

def reset_down_info
  @fail_count = 0
  @down_at = nil
  @last_down_at = nil
  @msg = nil
  @error = nil
end

#socket_timeoutObject



97
98
99
# File 'lib/dalli/protocol/connection_manager.rb', line 97

def socket_timeout
  @socket_timeout ||= @options[:socket_timeout]
end

#start_request!Object



133
134
135
136
137
# File 'lib/dalli/protocol/connection_manager.rb', line 133

def start_request!
  raise '[Dalli] Request already in progress. This may be a bug in Dalli.' if @request_in_progress

  @request_in_progress = true
end

#up!Object



76
77
78
79
# File 'lib/dalli/protocol/connection_manager.rb', line 76

def up!
  log_up_detected
  reset_down_info
end

#write(bytes) ⇒ Object



163
164
165
166
167
# File 'lib/dalli/protocol/connection_manager.rb', line 163

def write(bytes)
  @sock.write(bytes)
rescue SystemCallError, *TIMEOUT_ERRORS => e
  error_on_request!(e)
end