dogstatsd-ruby 4.8.1 → 5.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ # Sender is using a companion thread to flush and pack messages
6
+ # in a `MessageBuffer`.
7
+ # The communication with this thread is done using a `Queue`.
8
+ # If the thread is dead, it is starting a new one to avoid having a blocked
9
+ # Sender with no companion thread to communicate with (most of the time, having
10
+ # a dead companion thread means that a fork just happened and that we are
11
+ # running in the child process).
12
+ class Sender
13
+ CLOSEABLE_QUEUES = Queue.instance_methods.include?(:close)
14
+
15
+ def initialize(message_buffer, telemetry: nil, queue_size: UDP_DEFAULT_BUFFER_SIZE, logger: nil, flush_interval: nil, queue_class: Queue, thread_class: Thread)
16
+ @message_buffer = message_buffer
17
+ @telemetry = telemetry
18
+ @queue_size = queue_size
19
+ @logger = logger
20
+ @mx = Mutex.new
21
+ @queue_class = queue_class
22
+ @thread_class = thread_class
23
+ @flush_timer = if flush_interval
24
+ Datadog::Statsd::Timer.new(flush_interval) { flush(sync: true) }
25
+ else
26
+ nil
27
+ end
28
+ end
29
+
30
+ def flush(sync: false)
31
+ # keep a copy around in case another thread is calling #stop while this method is running
32
+ current_message_queue = message_queue
33
+
34
+ # don't try to flush if there is no message_queue instantiated or
35
+ # no companion thread running
36
+ if !current_message_queue
37
+ @logger.debug { "Statsd: can't flush: no message queue ready" } if @logger
38
+ return
39
+ end
40
+ if !sender_thread.alive?
41
+ @logger.debug { "Statsd: can't flush: no sender_thread alive" } if @logger
42
+ return
43
+ end
44
+
45
+ current_message_queue.push(:flush)
46
+ rendez_vous if sync
47
+ end
48
+
49
+ def rendez_vous
50
+ # could happen if #start hasn't be called
51
+ return unless message_queue
52
+
53
+ # Initialize and get the thread's sync queue
54
+ queue = (@thread_class.current[:statsd_sync_queue] ||= @queue_class.new)
55
+ # tell sender-thread to notify us in the current
56
+ # thread's queue
57
+ message_queue.push(queue)
58
+ # wait for the sender thread to send a message
59
+ # once the flush is done
60
+ queue.pop
61
+ end
62
+
63
+ def add(message)
64
+ raise ArgumentError, 'Start sender first' unless message_queue
65
+
66
+ # if the thread does not exist, we assume we are running in a forked process,
67
+ # empty the message queue and message buffers (these messages belong to
68
+ # the parent process) and spawn a new companion thread.
69
+ if !sender_thread.alive?
70
+ @mx.synchronize {
71
+ # a call from another thread has already re-created
72
+ # the companion thread before this one acquired the lock
73
+ break if sender_thread.alive?
74
+ @logger.debug { "Statsd: companion thread is dead, re-creating one" } if @logger
75
+
76
+ message_queue.close if CLOSEABLE_QUEUES
77
+ @message_queue = nil
78
+ message_buffer.reset
79
+ start
80
+ @flush_timer.start if @flush_timer && @flush_timer.stop?
81
+ }
82
+ end
83
+
84
+ if message_queue.length <= @queue_size
85
+ message_queue << message
86
+ else
87
+ if @telemetry
88
+ bytesize = message.respond_to?(:bytesize) ? message.bytesize : 0
89
+ @telemetry.dropped_queue(packets: 1, bytes: bytesize)
90
+ end
91
+ end
92
+ end
93
+
94
+ def start
95
+ raise ArgumentError, 'Sender already started' if message_queue
96
+
97
+ # initialize a new message queue for the background thread
98
+ @message_queue = @queue_class.new
99
+ # start background thread
100
+ @sender_thread = @thread_class.new(&method(:send_loop))
101
+ @sender_thread.name = "Statsd Sender" unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
102
+ @flush_timer.start if @flush_timer
103
+ end
104
+
105
+ if CLOSEABLE_QUEUES
106
+ # when calling stop, make sure that no other threads is trying
107
+ # to close the sender nor trying to continue to `#add` more message
108
+ # into the sender.
109
+ def stop(join_worker: true)
110
+ @flush_timer.stop if @flush_timer
111
+
112
+ message_queue = @message_queue
113
+ message_queue.close if message_queue
114
+
115
+ sender_thread = @sender_thread
116
+ sender_thread.join if sender_thread && join_worker
117
+ end
118
+ else
119
+ # when calling stop, make sure that no other threads is trying
120
+ # to close the sender nor trying to continue to `#add` more message
121
+ # into the sender.
122
+ def stop(join_worker: true)
123
+ @flush_timer.stop if @flush_timer
124
+
125
+ message_queue = @message_queue
126
+ message_queue << :close if message_queue
127
+
128
+ sender_thread = @sender_thread
129
+ sender_thread.join if sender_thread && join_worker
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ attr_reader :message_buffer
136
+ attr_reader :message_queue
137
+ attr_reader :sender_thread
138
+
139
+ if CLOSEABLE_QUEUES
140
+ def send_loop
141
+ until (message = message_queue.pop).nil? && message_queue.closed?
142
+ # skip if message is nil, e.g. when message_queue
143
+ # is empty and closed
144
+ next unless message
145
+
146
+ case message
147
+ when :flush
148
+ message_buffer.flush
149
+ when @queue_class
150
+ message.push(:go_on)
151
+ else
152
+ message_buffer.add(message)
153
+ end
154
+ end
155
+
156
+ @message_queue = nil
157
+ @sender_thread = nil
158
+ end
159
+ else
160
+ def send_loop
161
+ loop do
162
+ message = message_queue.pop
163
+
164
+ next unless message
165
+
166
+ case message
167
+ when :close
168
+ break
169
+ when :flush
170
+ message_buffer.flush
171
+ when @queue_class
172
+ message.push(:go_on)
173
+ else
174
+ message_buffer.add(message)
175
+ end
176
+ end
177
+
178
+ @message_queue = nil
179
+ @sender_thread = nil
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -48,7 +48,11 @@ module Datadog
48
48
  end
49
49
 
50
50
  if event.bytesize > MAX_EVENT_SIZE
51
- raise "Event #{title} payload is too big (more that 8KB), event discarded"
51
+ if options[:truncate_if_too_long]
52
+ event.slice!(MAX_EVENT_SIZE..event.length)
53
+ else
54
+ raise "Event #{title} payload is too big (more that 8KB), event discarded"
55
+ end
52
56
  end
53
57
  end
54
58
  end
@@ -6,34 +6,24 @@ module Datadog
6
6
  class StatSerializer
7
7
  def initialize(prefix, global_tags: [])
8
8
  @prefix = prefix
9
+ @prefix_str = prefix.to_s
9
10
  @tag_serializer = TagSerializer.new(global_tags)
10
11
  end
11
12
 
12
13
  def format(name, delta, type, tags: [], sample_rate: 1)
13
- String.new.tap do |stat|
14
- stat << prefix if prefix
15
-
16
- # stat value
17
- stat << formated_name(name)
18
- stat << ':'
19
- stat << delta.to_s
20
-
21
- # stat type
22
- stat << '|'
23
- stat << type
24
-
25
- # sample_rate
26
- if sample_rate != 1
27
- stat << '|'
28
- stat << '@'
29
- stat << sample_rate.to_s
30
- end
14
+ name = formated_name(name)
31
15
 
32
- # tags
16
+ if sample_rate != 1
17
+ if tags_list = tag_serializer.format(tags)
18
+ "#{@prefix_str}#{name}:#{delta}|#{type}|@#{sample_rate}|##{tags_list}"
19
+ else
20
+ "#{@prefix_str}#{name}:#{delta}|#{type}|@#{sample_rate}"
21
+ end
22
+ else
33
23
  if tags_list = tag_serializer.format(tags)
34
- stat << '|'
35
- stat << '#'
36
- stat << tags_list
24
+ "#{@prefix_str}#{name}:#{delta}|#{type}|##{tags_list}"
25
+ else
26
+ "#{@prefix_str}#{name}:#{delta}|#{type}"
37
27
  end
38
28
  end
39
29
  end
@@ -43,18 +33,21 @@ module Datadog
43
33
  end
44
34
 
45
35
  private
36
+
46
37
  attr_reader :prefix
47
38
  attr_reader :tag_serializer
48
39
 
49
40
  def formated_name(name)
50
- formated = name.is_a?(String) ? name.dup : name.to_s
51
-
52
- formated.tap do |f|
53
- # replace Ruby module scoping with '.'
54
- f.gsub!('::', '.')
55
- # replace reserved chars (: | @) with underscores.
56
- f.tr!(':|@', '_')
41
+ if name.is_a?(String)
42
+ # DEV: gsub is faster than dup.gsub!
43
+ formated = name.gsub('::', '.')
44
+ else
45
+ formated = name.to_s
46
+ formated.gsub!('::', '.')
57
47
  end
48
+
49
+ formated.tr!(':|@', '_')
50
+ formated
58
51
  end
59
52
  end
60
53
  end
@@ -13,18 +13,25 @@ module Datadog
13
13
 
14
14
  # Convert to tag list and set
15
15
  @global_tags = to_tags_list(global_tags)
16
+ if @global_tags.any?
17
+ @global_tags_formatted = @global_tags.join(',')
18
+ else
19
+ @global_tags_formatted = nil
20
+ end
16
21
  end
17
22
 
18
23
  def format(message_tags)
19
- # fast return global tags if there's no message_tags
20
- # to avoid more allocations
21
- tag_list = if message_tags && message_tags.any?
22
- global_tags + to_tags_list(message_tags)
23
- else
24
- global_tags
25
- end
24
+ if !message_tags || message_tags.empty?
25
+ return @global_tags_formatted
26
+ end
26
27
 
27
- tag_list.join(',') if tag_list.any?
28
+ tags = if @global_tags_formatted
29
+ [@global_tags_formatted, to_tags_list(message_tags)]
30
+ else
31
+ to_tags_list(message_tags)
32
+ end
33
+
34
+ tags.join(',')
28
35
  end
29
36
 
30
37
  attr_reader :global_tags
@@ -51,19 +58,17 @@ module Datadog
51
58
  def to_tags_list(tags)
52
59
  case tags
53
60
  when Hash
54
- tags.each_with_object([]) do |tag_pair, formatted_tags|
55
- if tag_pair.last.nil?
56
- formatted_tags << "#{tag_pair.first}"
61
+ tags.map do |name, value|
62
+ if value
63
+ escape_tag_content("#{name}:#{value}")
57
64
  else
58
- formatted_tags << "#{tag_pair.first}:#{tag_pair.last}"
65
+ escape_tag_content(name)
59
66
  end
60
67
  end
61
68
  when Array
62
- tags.dup
69
+ tags.map { |tag| escape_tag_content(tag) }
63
70
  else
64
71
  []
65
- end.map! do |tag|
66
- escape_tag_content(tag)
67
72
  end
68
73
  end
69
74
 
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ # The SingleThreadSender is a sender synchronously buffering messages
6
+ # in a `MessageBuffer`.
7
+ # It is using current Process.PID to check it is the result of a recent fork
8
+ # and it is reseting the MessageBuffer if that's the case.
9
+ class SingleThreadSender
10
+ def initialize(message_buffer, logger: nil, flush_interval: nil, queue_size: 1)
11
+ @message_buffer = message_buffer
12
+ @logger = logger
13
+ @mx = Mutex.new
14
+ @message_queue_size = queue_size
15
+ @message_queue = []
16
+ @flush_timer = if flush_interval
17
+ Datadog::Statsd::Timer.new(flush_interval) { flush }
18
+ else
19
+ nil
20
+ end
21
+ # store the pid for which this sender has been created
22
+ update_fork_pid
23
+ end
24
+
25
+ def add(message)
26
+ @mx.synchronize {
27
+ # we have just forked, meaning we have messages in the buffer that we should
28
+ # not send, they belong to the parent process, let's clear the buffer.
29
+ if forked?
30
+ @message_buffer.reset
31
+ @message_queue.clear
32
+ @flush_timer.start if @flush_timer && @flush_timer.stop?
33
+ update_fork_pid
34
+ end
35
+
36
+ @message_queue << message
37
+ if @message_queue.size >= @message_queue_size
38
+ drain_message_queue
39
+ end
40
+ }
41
+ end
42
+
43
+ def flush(*)
44
+ @mx.synchronize {
45
+ drain_message_queue
46
+ @message_buffer.flush()
47
+ }
48
+ end
49
+
50
+ def start()
51
+ @flush_timer.start if @flush_timer
52
+ end
53
+
54
+ def stop()
55
+ @flush_timer.stop if @flush_timer
56
+ end
57
+
58
+ # Compatibility with `Sender`
59
+ def rendez_vous()
60
+ end
61
+
62
+ private
63
+
64
+ def drain_message_queue
65
+ while msg = @message_queue.shift
66
+ @message_buffer.add(msg)
67
+ end
68
+ end
69
+
70
+ # below are "fork management" methods to be able to clean the MessageBuffer
71
+ # if it detects that it is running in a unknown PID.
72
+
73
+ def forked?
74
+ Process.pid != @fork_pid
75
+ end
76
+
77
+ def update_fork_pid
78
+ @fork_pid = Process.pid
79
+ end
80
+ end
81
+ end
82
+ end
@@ -9,12 +9,17 @@ module Datadog
9
9
  attr_reader :service_checks
10
10
  attr_reader :bytes_sent
11
11
  attr_reader :bytes_dropped
12
+ attr_reader :bytes_dropped_queue
13
+ attr_reader :bytes_dropped_writer
12
14
  attr_reader :packets_sent
13
15
  attr_reader :packets_dropped
14
- attr_reader :estimate_max_size
16
+ attr_reader :packets_dropped_queue
17
+ attr_reader :packets_dropped_writer
15
18
 
16
- def initialize(disabled, flush_interval, global_tags: [], transport_type: :udp)
17
- @disabled = disabled
19
+ # Rough estimation of maximum telemetry message size without tags
20
+ MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS = 50 # bytes
21
+
22
+ def initialize(flush_interval, global_tags: [], transport_type: :udp)
18
23
  @flush_interval = flush_interval
19
24
  @global_tags = global_tags
20
25
  @transport_type = transport_type
@@ -27,15 +32,10 @@ module Datadog
27
32
  client_version: VERSION,
28
33
  client_transport: transport_type,
29
34
  ).format(global_tags)
35
+ end
30
36
 
31
- # estimate_max_size is an estimation or the maximum size of the
32
- # telemetry payload. Since we don't want our packet to go over
33
- # 'max_buffer_bytes', we have to adjust with the size of the telemetry
34
- # (and any tags used). The telemetry payload size will change depending
35
- # on the actual value of metrics: metrics received, packet dropped,
36
- # etc. This is why we add a 63bytes margin: 9 bytes for each of the 7
37
- # telemetry metrics.
38
- @estimate_max_size = disabled ? 0 : flush.length + 9 * 7
37
+ def would_fit_in?(max_buffer_payload_size)
38
+ MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS + serialized_tags.size < max_buffer_payload_size
39
39
  end
40
40
 
41
41
  def reset
@@ -44,8 +44,12 @@ module Datadog
44
44
  @service_checks = 0
45
45
  @bytes_sent = 0
46
46
  @bytes_dropped = 0
47
+ @bytes_dropped_queue = 0
48
+ @bytes_dropped_writer = 0
47
49
  @packets_sent = 0
48
50
  @packets_dropped = 0
51
+ @packets_dropped_queue = 0
52
+ @packets_dropped_writer = 0
49
53
  @next_flush_time = now_in_s + @flush_interval
50
54
  end
51
55
 
@@ -58,32 +62,47 @@ module Datadog
58
62
  @packets_sent += packets
59
63
  end
60
64
 
61
- def dropped(bytes: 0, packets: 0)
65
+ def dropped_queue(bytes: 0, packets: 0)
62
66
  @bytes_dropped += bytes
67
+ @bytes_dropped_queue += bytes
63
68
  @packets_dropped += packets
69
+ @packets_dropped_queue += packets
64
70
  end
65
71
 
66
- def flush?
72
+ def dropped_writer(bytes: 0, packets: 0)
73
+ @bytes_dropped += bytes
74
+ @bytes_dropped_writer += bytes
75
+ @packets_dropped += packets
76
+ @packets_dropped_writer += packets
77
+ end
78
+
79
+ def should_flush?
67
80
  @next_flush_time < now_in_s
68
81
  end
69
82
 
70
83
  def flush
71
- return '' if @disabled
72
-
73
- # using shorthand syntax to reduce the garbage collection
74
- %Q(
75
- datadog.dogstatsd.client.metrics:#{@metrics}|#{COUNTER_TYPE}|##{serialized_tags}
76
- datadog.dogstatsd.client.events:#{@events}|#{COUNTER_TYPE}|##{serialized_tags}
77
- datadog.dogstatsd.client.service_checks:#{@service_checks}|#{COUNTER_TYPE}|##{serialized_tags}
78
- datadog.dogstatsd.client.bytes_sent:#{@bytes_sent}|#{COUNTER_TYPE}|##{serialized_tags}
79
- datadog.dogstatsd.client.bytes_dropped:#{@bytes_dropped}|#{COUNTER_TYPE}|##{serialized_tags}
80
- datadog.dogstatsd.client.packets_sent:#{@packets_sent}|#{COUNTER_TYPE}|##{serialized_tags}
81
- datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{serialized_tags})
84
+ [
85
+ sprintf(pattern, 'metrics', @metrics),
86
+ sprintf(pattern, 'events', @events),
87
+ sprintf(pattern, 'service_checks', @service_checks),
88
+ sprintf(pattern, 'bytes_sent', @bytes_sent),
89
+ sprintf(pattern, 'bytes_dropped', @bytes_dropped),
90
+ sprintf(pattern, 'bytes_dropped_queue', @bytes_dropped_queue),
91
+ sprintf(pattern, 'bytes_dropped_writer', @bytes_dropped_writer),
92
+ sprintf(pattern, 'packets_sent', @packets_sent),
93
+ sprintf(pattern, 'packets_dropped', @packets_dropped),
94
+ sprintf(pattern, 'packets_dropped_queue', @packets_dropped_queue),
95
+ sprintf(pattern, 'packets_dropped_writer', @packets_dropped_writer),
96
+ ]
82
97
  end
83
98
 
84
99
  private
85
100
  attr_reader :serialized_tags
86
101
 
102
+ def pattern
103
+ @pattern ||= "datadog.dogstatsd.client.%s:%d|#{COUNTER_TYPE}|##{serialized_tags}"
104
+ end
105
+
87
106
  if Kernel.const_defined?('Process') && Process.respond_to?(:clock_gettime)
88
107
  def now_in_s
89
108
  Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ class Timer
6
+ def initialize(interval, &callback)
7
+ @mx = Mutex.new
8
+ @cv = ConditionVariable.new
9
+ @interval = interval
10
+ @callback = callback
11
+ @stop = true
12
+ @thread = nil
13
+ end
14
+
15
+ def start
16
+ return unless stop?
17
+
18
+ @stop = false
19
+ @thread = Thread.new do
20
+ last_execution_time = current_time
21
+ @mx.synchronize do
22
+ until @stop
23
+ timeout = @interval - (current_time - last_execution_time)
24
+ @cv.wait(@mx, timeout > 0 ? timeout : 0)
25
+ last_execution_time = current_time
26
+ @callback.call
27
+ end
28
+ end
29
+ end
30
+ @thread.name = 'Statsd Timer' unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
31
+ end
32
+
33
+ def stop
34
+ return if @thread.nil?
35
+
36
+ @stop = true
37
+ @mx.synchronize do
38
+ @cv.signal
39
+ end
40
+ @thread.join
41
+ @thread = nil
42
+ end
43
+
44
+ def stop?
45
+ @thread.nil? || @thread.stop?
46
+ end
47
+
48
+ private
49
+
50
+ if Process.const_defined?(:CLOCK_MONOTONIC)
51
+ def current_time
52
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
53
+ end
54
+ else
55
+ def current_time
56
+ Time.now
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -5,32 +5,43 @@ require_relative 'connection'
5
5
  module Datadog
6
6
  class Statsd
7
7
  class UDPConnection < Connection
8
- DEFAULT_HOST = '127.0.0.1'
9
- DEFAULT_PORT = 8125
10
-
11
- # StatsD host. Defaults to 127.0.0.1.
8
+ # StatsD host.
12
9
  attr_reader :host
13
10
 
14
- # StatsD port. Defaults to 8125.
11
+ # StatsD port.
15
12
  attr_reader :port
16
13
 
17
- def initialize(host, port, logger, telemetry)
18
- super(telemetry)
19
- @host = host || ENV.fetch('DD_AGENT_HOST', DEFAULT_HOST)
20
- @port = port || ENV.fetch('DD_DOGSTATSD_PORT', DEFAULT_PORT).to_i
21
- @logger = logger
14
+ def initialize(host, port, **kwargs)
15
+ super(**kwargs)
16
+
17
+ @host = host
18
+ @port = port
19
+ @socket = nil
20
+ end
21
+
22
+ def close
23
+ @socket.close if @socket
24
+ @socket = nil
22
25
  end
23
26
 
24
27
  private
25
28
 
26
29
  def connect
27
- UDPSocket.new.tap do |socket|
28
- socket.connect(host, port)
29
- end
30
+ close if @socket
31
+
32
+ family = Addrinfo.udp(host, port).afamily
33
+
34
+ @socket = UDPSocket.new(family)
35
+ @socket.connect(host, port)
30
36
  end
31
37
 
38
+ # send_message is writing the message in the socket, it may create the socket if nil
39
+ # It is not thread-safe but since it is called by either the Sender bg thread or the
40
+ # SingleThreadSender (which is using a mutex while Flushing), only one thread must call
41
+ # it at a time.
32
42
  def send_message(message)
33
- socket.send(message, 0)
43
+ connect unless @socket
44
+ @socket.send(message, 0)
34
45
  end
35
46
  end
36
47
  end
@@ -10,24 +10,35 @@ module Datadog
10
10
  # DogStatsd unix socket path
11
11
  attr_reader :socket_path
12
12
 
13
- def initialize(socket_path, logger, telemetry)
14
- super(telemetry)
13
+ def initialize(socket_path, **kwargs)
14
+ super(**kwargs)
15
+
15
16
  @socket_path = socket_path
16
- @logger = logger
17
+ @socket = nil
18
+ end
19
+
20
+ def close
21
+ @socket.close if @socket
22
+ @socket = nil
17
23
  end
18
24
 
19
25
  private
20
26
 
21
27
  def connect
22
- socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
23
- socket.connect(Socket.pack_sockaddr_un(@socket_path))
24
- socket
28
+ close if @socket
29
+
30
+ @socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
31
+ @socket.connect(Socket.pack_sockaddr_un(@socket_path))
25
32
  end
26
33
 
34
+ # send_message is writing the message in the socket, it may create the socket if nil
35
+ # It is not thread-safe but since it is called by either the Sender bg thread or the
36
+ # SingleThreadSender (which is using a mutex while Flushing), only one thread must call
37
+ # it at a time.
27
38
  def send_message(message)
28
- socket.sendmsg_nonblock(message)
39
+ connect unless @socket
40
+ @socket.sendmsg_nonblock(message)
29
41
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENOENT => e
30
- @socket = nil
31
42
  # TODO: FIXME: This error should be considered as a retryable error in the
32
43
  # Connection class. An even better solution would be to make BadSocketError inherit
33
44
  # from a specific retryable error class in the Connection class.