mongo 2.12.0.rc0 → 2.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +2 -1
  5. data/lib/mongo/client.rb +23 -9
  6. data/lib/mongo/client_encryption.rb +1 -1
  7. data/lib/mongo/cluster.rb +6 -2
  8. data/lib/mongo/crypt/auto_decryption_context.rb +3 -5
  9. data/lib/mongo/crypt/auto_encrypter.rb +17 -7
  10. data/lib/mongo/crypt/binding.rb +446 -379
  11. data/lib/mongo/crypt/context.rb +4 -4
  12. data/lib/mongo/crypt/encryption_io.rb +16 -10
  13. data/lib/mongo/crypt/explicit_encrypter.rb +3 -3
  14. data/lib/mongo/crypt/explicit_encryption_context.rb +1 -1
  15. data/lib/mongo/crypt/handle.rb +26 -4
  16. data/lib/mongo/crypt/hooks.rb +1 -1
  17. data/lib/mongo/database.rb +11 -1
  18. data/lib/mongo/error/bulk_write_error.rb +16 -14
  19. data/lib/mongo/error/notable.rb +0 -15
  20. data/lib/mongo/error/parser.rb +1 -1
  21. data/lib/mongo/grid/file/info.rb +1 -1
  22. data/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb +1 -1
  23. data/lib/mongo/operation/insert/command.rb +3 -2
  24. data/lib/mongo/operation/insert/legacy.rb +2 -1
  25. data/lib/mongo/operation/insert/op_msg.rb +1 -1
  26. data/lib/mongo/operation/shared/executable.rb +9 -9
  27. data/lib/mongo/operation/shared/op_msg_or_command.rb +2 -2
  28. data/lib/mongo/operation/shared/read_preference_supported.rb +68 -19
  29. data/lib/mongo/operation/shared/response_handling.rb +1 -1
  30. data/lib/mongo/operation/shared/sessions_supported.rb +44 -3
  31. data/lib/mongo/protocol/bit_vector.rb +2 -1
  32. data/lib/mongo/protocol/message.rb +22 -7
  33. data/lib/mongo/protocol/msg.rb +2 -5
  34. data/lib/mongo/protocol/serializers.rb +32 -11
  35. data/lib/mongo/retryable.rb +1 -1
  36. data/lib/mongo/server/connection.rb +1 -1
  37. data/lib/mongo/server/connection_base.rb +9 -4
  38. data/lib/mongo/server/connection_pool/populator.rb +1 -1
  39. data/lib/mongo/session.rb +1 -1
  40. data/lib/mongo/srv/monitor.rb +73 -42
  41. data/lib/mongo/srv/result.rb +0 -1
  42. data/lib/mongo/uri.rb +1 -1
  43. data/lib/mongo/uri/srv_protocol.rb +1 -1
  44. data/lib/mongo/version.rb +1 -1
  45. data/mongo.gemspec +0 -2
  46. data/spec/README.md +106 -12
  47. data/spec/integration/client_construction_spec.rb +29 -5
  48. data/spec/integration/client_side_encryption/auto_encryption_bulk_writes_spec.rb +6 -4
  49. data/spec/integration/client_side_encryption/auto_encryption_command_monitoring_spec.rb +19 -17
  50. data/spec/integration/client_side_encryption/auto_encryption_mongocryptd_spawn_spec.rb +5 -4
  51. data/spec/integration/client_side_encryption/auto_encryption_old_wire_version_spec.rb +11 -8
  52. data/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb +14 -9
  53. data/spec/integration/client_side_encryption/auto_encryption_spec.rb +46 -45
  54. data/spec/integration/client_side_encryption/bson_size_limit_spec.rb +11 -7
  55. data/spec/integration/client_side_encryption/bypass_mongocryptd_spawn_spec.rb +13 -9
  56. data/spec/integration/client_side_encryption/client_close_spec.rb +10 -6
  57. data/spec/integration/client_side_encryption/corpus_spec.rb +19 -14
  58. data/spec/integration/client_side_encryption/data_key_spec.rb +10 -8
  59. data/spec/integration/client_side_encryption/external_key_vault_spec.rb +12 -8
  60. data/spec/integration/client_side_encryption/views_spec.rb +6 -4
  61. data/spec/integration/client_update_spec.rb +36 -2
  62. data/spec/integration/crud_spec.rb +89 -0
  63. data/spec/integration/read_preference_spec.rb +26 -0
  64. data/spec/integration/srv_monitoring_spec.rb +2 -2
  65. data/spec/kerberos/kerberos_spec.rb +87 -0
  66. data/spec/lite_spec_helper.rb +4 -8
  67. data/spec/mongo/bulk_write/result_spec.rb +11 -7
  68. data/spec/mongo/client_encryption_spec.rb +3 -6
  69. data/spec/mongo/crypt/auto_encrypter_spec.rb +8 -3
  70. data/spec/mongo/crypt/handle_spec.rb +38 -4
  71. data/spec/mongo/error/bulk_write_error_spec.rb +49 -0
  72. data/spec/mongo/error/notable_spec.rb +59 -0
  73. data/spec/mongo/operation/find/legacy_spec.rb +1 -0
  74. data/spec/mongo/operation/read_preference_legacy_spec.rb +351 -0
  75. data/spec/mongo/operation/read_preference_op_msg_spec.rb +194 -0
  76. data/spec/mongo/srv/monitor_spec.rb +88 -69
  77. data/spec/runners/transactions.rb +5 -7
  78. data/spec/spec_tests/client_side_encryption_spec.rb +0 -5
  79. data/spec/spec_tests/data/client_side_encryption/bulk.yml +3 -0
  80. data/spec/spec_tests/data/client_side_encryption/replaceOne.yml +4 -1
  81. data/spec/spec_tests/data/client_side_encryption/updateOne.yml +3 -0
  82. data/spec/support/cluster_tools.rb +6 -1
  83. data/spec/support/crypt.rb +14 -0
  84. data/spec/support/lite_constraints.rb +3 -1
  85. data/spec/support/spec_config.rb +10 -0
  86. data/spec/support/utils.rb +9 -1
  87. metadata +15 -14
  88. metadata.gz.sig +0 -0
  89. data/lib/mongo/cluster/srv_monitor.rb +0 -127
  90. data/lib/mongo/srv/warning_result.rb +0 -35
  91. data/spec/enterprise_auth/kerberos_spec.rb +0 -58
  92. data/spec/mongo/cluster/srv_monitor_spec.rb +0 -214
  93. data/spec/mongo/operation/read_preference_spec.rb +0 -245
@@ -68,7 +68,7 @@ module Mongo
68
68
  # is included in BulkWrite which does not store the session in the
69
69
  # receiver (despite Specifiable doing so).
70
70
  #
71
- # @param [ Session | nil ] Session to consider.
71
+ # @param [ Session | nil ] session Session to consider.
72
72
  def unpin_maybe(session)
73
73
  yield
74
74
  rescue Mongo::Error => e
@@ -123,9 +123,8 @@ module Mongo
123
123
  sel = selector(server).dup
124
124
  add_write_concern!(sel)
125
125
  sel[Protocol::Msg::DATABASE_IDENTIFIER] = db_name
126
- unless server.standalone?
127
- sel['$readPreference'] = read.to_doc if read
128
- end
126
+
127
+ add_read_preference(sel, server)
129
128
 
130
129
  if server.features.sessions_enabled?
131
130
  apply_cluster_time!(sel, server)
@@ -139,6 +138,48 @@ module Mongo
139
138
  sel
140
139
  end
141
140
 
141
+ # Adds $readPreference field to the command document.
142
+ #
143
+ # $readPreference is only sent when the server is a mongos,
144
+ # following the rules described in
145
+ # https://github.com/mongodb/specifications/blob/master/source/server-selection/server-selection.rst#passing-read-preference-to-mongos.
146
+ # The topology does not matter for figuring out whether to send
147
+ # $readPreference since the decision is always made based on
148
+ # server type.
149
+ #
150
+ # $readPreference is sent to OP_MSG-grokking replica set members.
151
+ #
152
+ # @param [ Hash ] sel Existing command document which will be mutated.
153
+ # @param [ Server ] server The server that the command is to be sent to.
154
+ def add_read_preference(sel, server)
155
+ # https://github.com/mongodb/specifications/blob/master/source/server-selection/server-selection.rst#topology-type-single
156
+ if server.standalone?
157
+ # Read preference is never sent to standalones.
158
+ elsif server.cluster.single?
159
+ # In Single topology:
160
+ # - If no read preference is specified by the application, the driver
161
+ # adds mode: primaryPreferred.
162
+ # - If a read preference is specified by the application, the driver
163
+ # replaces the mode with primaryPreferred.
164
+ read_doc = if read
165
+ BSON::Document.new(read.to_doc)
166
+ else
167
+ BSON::Document.new
168
+ end
169
+ if [nil, 'primary'].include?(read_doc['mode'])
170
+ read_doc['mode'] = 'primaryPreferred'
171
+ end
172
+ sel['$readPreference'] = read_doc
173
+ else
174
+ # In replica sets and sharded clusters, read preference is passed
175
+ # to the server if one is specified by the application, and there
176
+ # is no default.
177
+ if read
178
+ sel['$readPreference'] = read.to_doc
179
+ end
180
+ end
181
+ end
182
+
142
183
  def apply_session_options(sel, server)
143
184
  apply_cluster_time!(sel, server)
144
185
  sel[:txnNumber] = BSON::Int64.new(txn_num) if txn_num
@@ -47,9 +47,10 @@ module Mongo
47
47
  # Deserializes vector by decoding the symbol according to its mask
48
48
  #
49
49
  # @param [ String ] buffer Buffer containing the vector to be deserialized.
50
+ # @param [ Hash ] options This method does not currently accept any options.
50
51
  #
51
52
  # @return [ Array<Symbol> ] Flags contained in the vector
52
- def deserialize(buffer)
53
+ def deserialize(buffer, options = {})
53
54
  vector = buffer.get_int32
54
55
  flags = []
55
56
  @masks.each do |flag, mask|
@@ -194,9 +194,14 @@ module Mongo
194
194
  #
195
195
  # @param [ Integer ] max_message_size The max message size.
196
196
  # @param [ IO ] io Stream containing a message
197
+ # @param [ Hash ] options
198
+ #
199
+ # @option options [ Boolean ] :deserialize_as_bson Whether to deserialize
200
+ # this message using BSON types instead of native Ruby types wherever
201
+ # possible.
197
202
  #
198
203
  # @return [ Message ] Instance of a Message class
199
- def self.deserialize(io, max_message_size = MAX_MESSAGE_SIZE, expected_response_to = nil)
204
+ def self.deserialize(io, max_message_size = MAX_MESSAGE_SIZE, expected_response_to = nil, options = {})
200
205
  length, _request_id, response_to, _op_code = deserialize_header(BSON::ByteBuffer.new(io.read(16)))
201
206
 
202
207
  # Protection from potential DOS man-in-the-middle attacks. See
@@ -216,9 +221,9 @@ module Mongo
216
221
 
217
222
  message.send(:fields).each do |field|
218
223
  if field[:multi]
219
- deserialize_array(message, buffer, field)
224
+ deserialize_array(message, buffer, field, options)
220
225
  else
221
- deserialize_field(message, buffer, field)
226
+ deserialize_field(message, buffer, field, options)
222
227
  end
223
228
  end
224
229
  if message.is_a?(Msg)
@@ -363,11 +368,16 @@ module Mongo
363
368
  # @param message [Message] Message to contain the deserialized array.
364
369
  # @param io [IO] Stream containing the array to deserialize.
365
370
  # @param field [Hash] Hash representing a field.
371
+ # @param options [ Hash ]
372
+ #
373
+ # @option options [ Boolean ] :deserialize_as_bson Whether to deserialize
374
+ # each of the elements in this array using BSON types wherever possible.
375
+ #
366
376
  # @return [Message] Message with deserialized array.
367
- def self.deserialize_array(message, io, field)
377
+ def self.deserialize_array(message, io, field, options)
368
378
  elements = []
369
379
  count = message.instance_variable_get(field[:multi])
370
- count.times { elements << field[:type].deserialize(io) }
380
+ count.times { elements << field[:type].deserialize(io, options) }
371
381
  message.instance_variable_set(field[:name], elements)
372
382
  end
373
383
 
@@ -376,11 +386,16 @@ module Mongo
376
386
  # @param message [Message] Message to contain the deserialized field.
377
387
  # @param io [IO] Stream containing the field to deserialize.
378
388
  # @param field [Hash] Hash representing a field.
389
+ # @param options [ Hash ]
390
+ #
391
+ # @option options [ Boolean ] :deserialize_as_bson Whether to deserialize
392
+ # this field using BSON types wherever possible.
393
+ #
379
394
  # @return [Message] Message with deserialized field.
380
- def self.deserialize_field(message, io, field)
395
+ def self.deserialize_field(message, io, field, options)
381
396
  message.instance_variable_set(
382
397
  field[:name],
383
- field[:type].deserialize(io)
398
+ field[:type].deserialize(io, options)
384
399
  )
385
400
  end
386
401
 
@@ -161,7 +161,7 @@ module Mongo
161
161
  end
162
162
 
163
163
  # Reverse-populates the instance variables after deserialization sets
164
- # @sections to the list of documents.
164
+ # the @sections instance variable to the list of documents.
165
165
  #
166
166
  # TODO fix deserialization so that this method is not needed.
167
167
  #
@@ -214,10 +214,7 @@ module Mongo
214
214
  if cmd.key?('$db') && !enc_cmd.key?('$db')
215
215
  enc_cmd['$db'] = cmd['$db']
216
216
  end
217
- # This will be investigtated in RUBY-2119
218
- if enc_cmd['txnNumber'].is_a?(Integer) && cmd[:txnNumber].is_a?(BSON::Int64)
219
- enc_cmd['txnNumber'] = BSON::Int64.new(enc_cmd[:txnNumber])
220
- end
217
+
221
218
  Msg.new(@flags, @options, enc_cmd)
222
219
  else
223
220
  self
@@ -57,10 +57,11 @@ module Mongo
57
57
  # Deserializes the header value from the IO stream
58
58
  #
59
59
  # @param [ String ] buffer Buffer containing the message header.
60
+ # @param [ Hash ] options This method currently accepts no options.
60
61
  #
61
62
  # @return [ Array<Fixnum> ] Array consisting of the deserialized
62
63
  # length, request id, response id, and op code.
63
- def self.deserialize(buffer)
64
+ def self.deserialize(buffer, options = {})
64
65
  buffer.get_bytes(16).unpack(HEADER_PACK)
65
66
  end
66
67
  end
@@ -123,9 +124,10 @@ module Mongo
123
124
  # Deserializes a 32-bit Fixnum from the IO stream
124
125
  #
125
126
  # @param [ String ] buffer Buffer containing the 32-bit integer
127
+ # @param [ Hash ] options This method currently accepts no options.
126
128
  #
127
129
  # @return [ Fixnum ] Deserialized Int32
128
- def self.deserialize(buffer)
130
+ def self.deserialize(buffer, options = {})
129
131
  buffer.get_int32
130
132
  end
131
133
  end
@@ -156,9 +158,10 @@ module Mongo
156
158
  # Deserializes a 64-bit Fixnum from the IO stream
157
159
  #
158
160
  # @param [ String ] buffer Buffer containing the 64-bit integer.
161
+ # @param [ Hash ] options This method currently accepts no options.
159
162
  #
160
163
  # @return [Fixnum] Deserialized Int64.
161
- def self.deserialize(buffer)
164
+ def self.deserialize(buffer, options = {})
162
165
  buffer.get_int64
163
166
  end
164
167
  end
@@ -198,19 +201,24 @@ module Mongo
198
201
  # Deserializes a section of an OP_MSG from the IO stream.
199
202
  #
200
203
  # @param [ BSON::ByteBuffer ] buffer Buffer containing the sections.
204
+ # @param [ Hash ] options
205
+ #
206
+ # @option options [ Boolean ] :deserialize_as_bson Whether to perform
207
+ # section deserialization using BSON types instead of native Ruby types
208
+ # wherever possible.
201
209
  #
202
210
  # @return [ Array<BSON::Document> ] Deserialized sections.
203
211
  #
204
212
  # @since 2.5.0
205
- def self.deserialize(buffer)
213
+ def self.deserialize(buffer, options = {})
206
214
  end_length = (@flag_bits & Msg::FLAGS.index(:checksum_present)) == 1 ? 32 : 0
207
215
  sections = []
208
216
  until buffer.length == end_length
209
217
  case byte = buffer.get_byte
210
218
  when PayloadZero::TYPE_BYTE
211
- sections << PayloadZero.deserialize(buffer)
219
+ sections << PayloadZero.deserialize(buffer, options)
212
220
  when PayloadOne::TYPE_BYTE
213
- sections += PayloadOne.deserialize(buffer)
221
+ sections += PayloadOne.deserialize(buffer, options)
214
222
  else
215
223
  raise Error::UnknownPayloadType.new(byte)
216
224
  end
@@ -260,12 +268,18 @@ module Mongo
260
268
  # Deserializes a section of payload type 0 of an OP_MSG from the IO stream.
261
269
  #
262
270
  # @param [ BSON::ByteBuffer ] buffer Buffer containing the sections.
271
+ # @param [ Hash ] options
272
+ #
273
+ # @option options [ Boolean ] :deserialize_as_bson Whether to perform
274
+ # section deserialization using BSON types instead of native Ruby types
275
+ # wherever possible.
263
276
  #
264
277
  # @return [ Array<BSON::Document> ] Deserialized section.
265
278
  #
266
279
  # @since 2.5.0
267
- def self.deserialize(buffer)
268
- BSON::Document.from_bson(buffer)
280
+ def self.deserialize(buffer, options = {})
281
+ mode = options[:deserialize_as_bson] ? :bson : nil
282
+ BSON::Document.from_bson(buffer, **{ mode: mode })
269
283
  end
270
284
  end
271
285
 
@@ -352,10 +366,16 @@ module Mongo
352
366
  # Deserializes a document from the IO stream
353
367
  #
354
368
  # @param [ String ] buffer Buffer containing the BSON encoded document.
369
+ # @param [ Hash ] options
370
+ #
371
+ # @option options [ Boolean ] :deserialize_as_bson Whether to perform
372
+ # section deserialization using BSON types instead of native Ruby types
373
+ # wherever possible.
355
374
  #
356
375
  # @return [ Hash ] The decoded BSON document.
357
- def self.deserialize(buffer)
358
- BSON::Document.from_bson(buffer)
376
+ def self.deserialize(buffer, options = {})
377
+ mode = options[:deserialize_as_bson] ? :bson : nil
378
+ BSON::Document.from_bson(buffer, **{ mode: mode })
359
379
  end
360
380
 
361
381
  # Whether there can be a size limit on this type after serialization.
@@ -389,11 +409,12 @@ module Mongo
389
409
  # Deserializes a byte from the byte buffer.
390
410
  #
391
411
  # @param [ BSON::ByteBuffer ] buffer Buffer containing the value to read.
412
+ # @param [ Hash ] options This method currently accepts no options.
392
413
  #
393
414
  # @return [ String ] The byte.
394
415
  #
395
416
  # @since 2.5.0
396
- def self.deserialize(buffer)
417
+ def self.deserialize(buffer, options = {})
397
418
  buffer.get_byte
398
419
  end
399
420
  end
@@ -144,7 +144,7 @@ module Mongo
144
144
  # @note This only retries read operations on socket errors.
145
145
  #
146
146
  # @param [ Hash ] options Options.
147
- # @param [ Proc ] block The block to execute.
147
+ # @yield Calls the provided block with no arguments
148
148
  #
149
149
  # @option options [ String ] :retry_message Message to log when retrying.
150
150
  #
@@ -400,7 +400,7 @@ module Mongo
400
400
  @auth_mechanism || (@server.features.scram_sha_1_enabled? ? :scram : :mongodb_cr)
401
401
  end
402
402
 
403
- def deliver(message, client)
403
+ def deliver(message, client, options = {})
404
404
  begin
405
405
  super
406
406
  # Important: timeout errors are not handled here
@@ -115,11 +115,16 @@ module Mongo
115
115
  # @param [ Array<Message> ] messages A one-element array containing
116
116
  # the message to dispatch.
117
117
  # @param [ Integer ] operation_id The operation id to link messages.
118
+ # @param [ Hash ] options
119
+ #
120
+ # @option options [ Boolean ] :deserialize_as_bson Whether to deserialize
121
+ # the response to this message using BSON objects in place of native
122
+ # Ruby types wherever possible.
118
123
  #
119
124
  # @return [ Protocol::Message | nil ] The reply if needed.
120
125
  #
121
126
  # @since 2.0.0
122
- def dispatch(messages, operation_id = nil, client = nil)
127
+ def dispatch(messages, operation_id = nil, client = nil, options = {})
123
128
  # The monitoring code does not correctly handle multiple messages,
124
129
  # and the driver internally does not send more than one message at
125
130
  # a time ever. Thus prohibit multiple message use for now.
@@ -127,12 +132,12 @@ module Mongo
127
132
  raise ArgumentError, 'Can only dispatch one message at a time'
128
133
  end
129
134
  message = messages.first
130
- deliver(message, client)
135
+ deliver(message, client, options)
131
136
  end
132
137
 
133
138
  private
134
139
 
135
- def deliver(message, client)
140
+ def deliver(message, client, options = {})
136
141
  if Lint.enabled? && !@socket
137
142
  raise Error::LintError, "Trying to deliver a message over a disconnected connection (to #{address})"
138
143
  end
@@ -146,7 +151,7 @@ module Mongo
146
151
  begin
147
152
  socket.write(buffer.to_s)
148
153
  result = if message.replyable?
149
- Protocol::Message.deserialize(socket, max_message_size, message.request_id)
154
+ Protocol::Message.deserialize(socket, max_message_size, message.request_id, options)
150
155
  else
151
156
  nil
152
157
  end
@@ -21,7 +21,7 @@ module Mongo
21
21
  class Populator
22
22
  include BackgroundThread
23
23
 
24
- # @param [ Server::ConnectionPool ] The connection pool.
24
+ # @param [ Server::ConnectionPool ] pool The connection pool.
25
25
  # @param [ Hash ] options The options.
26
26
  #
27
27
  # @option options [ Logger ] :logger A custom logger to use.
@@ -697,7 +697,7 @@ module Mongo
697
697
  # The exception instance should already have all of the labels set on it
698
698
  # (both client- and server-side generated ones).
699
699
  #
700
- # @param [ Error ] The exception instance to process.
700
+ # @param [ Error ] error The exception instance to process.
701
701
  #
702
702
  # @api private
703
703
  def unpin_maybe(error)
@@ -13,65 +13,94 @@
13
13
  # limitations under the License.
14
14
 
15
15
  module Mongo
16
- module SRV
16
+ module Srv
17
17
 
18
- # Polls SRV records for the URI that a cluster was created for and
19
- # updates the list of servers in the cluster when records change.
18
+ # Periodically retrieves SRV records for the cluster's SRV URI, and
19
+ # sets the cluster's server list to the SRV lookup result.
20
+ #
21
+ # If an error is encountered during SRV lookup or an SRV record is invalid
22
+ # or disallowed for security reasons, a warning is logged and monitoring
23
+ # continues.
20
24
  #
21
25
  # @api private
22
26
  class Monitor
23
27
  include Loggable
24
-
25
- MIN_RESCAN_FREQUENCY = 60
28
+ include BackgroundThread
29
+
30
+ MIN_SCAN_INTERVAL = 60
31
+
32
+ DEFAULT_TIMEOUT = 10
33
+
34
+ # Creates the SRV monitor.
35
+ #
36
+ # @param [ Cluster ] cluster The cluster.
37
+ # @param [ Hash ] options The cluster options.
38
+ #
39
+ # @option options [ Float ] :timeout The timeout to use for DNS lookups.
40
+ # @option options [ URI::SRVProtocol ] :srv_uri The SRV URI to monitor.
41
+ # @option options [ Hash ] :resolv_options For internal driver use only.
42
+ # Options to pass through to Resolv::DNS constructor for SRV lookups.
43
+ def initialize(cluster, options = nil)
44
+ options = if options
45
+ options.dup
46
+ else
47
+ {}
48
+ end
49
+ @cluster = cluster
50
+ @resolver = Srv::Resolver.new(options)
51
+ unless @srv_uri = options.delete(:srv_uri)
52
+ raise ArgumentError, 'SRV URI is required'
53
+ end
54
+ @options = options.freeze
55
+ @last_result = @srv_uri.srv_result
56
+ @stop_semaphore = Semaphore.new
57
+ end
26
58
 
27
59
  attr_reader :options
28
60
 
29
- def initialize(cluster, resolver, srv_records, options = nil)
30
- @options = options || {}
31
- @cluster = cluster
32
- @resolver = resolver
33
- @records = srv_records
34
- @no_records_found = false
35
- end
61
+ attr_reader :cluster
36
62
 
37
- def start_monitor!
38
- @thread = Thread.new do
39
- loop do
40
- sleep(rescan_frequency)
41
- scan!
42
- end
43
- end
63
+ # @return [ Srv::Result ] Last known SRV lookup result. Used for
64
+ # determining intervals between SRV lookups, which depend on SRV DNS
65
+ # records' TTL values.
66
+ attr_reader :last_result
44
67
 
68
+ def start!
69
+ super
45
70
  ObjectSpace.define_finalizer(self, self.class.finalize(@thread))
46
71
  end
47
72
 
73
+ private
74
+
75
+ def do_work
76
+ scan!
77
+ @stop_semaphore.wait(scan_interval)
78
+ end
79
+
48
80
  def scan!
49
- @old_hosts = @records.hosts
81
+ old_hosts = last_result.address_strs
50
82
 
51
83
  begin
52
- @records = @resolver.get_records(@records.hostname)
84
+ last_result = Timeout.timeout(timeout) do
85
+ @resolver.get_records(@srv_uri.query_hostname)
86
+ end
53
87
  rescue Resolv::ResolvTimeout => e
54
- log_warn("Timed out trying to resolve hostname #{@records.hostname}")
88
+ log_warn("SRV monitor: timed out trying to resolve hostname #{@srv_uri.query_hostname}: #{e.class}: #{e}")
89
+ return
90
+ rescue ::Timeout::Error
91
+ log_warn("SRV monitor: timed out trying to resolve hostname #{@srv_uri.query_hostname} (timeout=#{timeout})")
55
92
  return
56
93
  rescue Resolv::ResolvError => e
57
- log_warn("Unable to resolve hostname #{@records.hostname}")
94
+ log_warn("SRV monitor: unable to resolve hostname #{@srv_uri.query_hostname}: #{e.class}: #{e}")
58
95
  return
59
96
  end
60
97
 
61
- if @records.empty?
62
- @no_records_found = true
98
+ if last_result.empty?
99
+ log_warn("SRV monitor: hostname #{@srv_uri.query_hostname} resolved to zero records")
63
100
  return
64
101
  end
65
102
 
66
- @no_records_found = false
67
-
68
- (@old_hosts - @records.hosts).each do |host|
69
- @cluster.remove(host)
70
- end
71
-
72
- (@records.hosts - @old_hosts).each do |host|
73
- @cluster.add(host)
74
- end
103
+ @cluster.set_server_list(last_result.address_strs)
75
104
  end
76
105
 
77
106
  def self.finalize(thread)
@@ -80,17 +109,19 @@ module Mongo
80
109
  end
81
110
  end
82
111
 
83
- private
84
-
85
- def rescan_frequency
86
- if @no_records_found
87
- Server:: Monitor::HEARTBEAT_FREQUENCY
88
- elsif @records.min_ttl.nil?
89
- MIN_RESCAN_FREQUENCY
112
+ def scan_interval
113
+ if last_result.empty?
114
+ [cluster.heartbeat_interval, MIN_SCAN_INTERVAL].min
115
+ elsif last_result.min_ttl.nil?
116
+ MIN_SCAN_INTERVAL
90
117
  else
91
- [@records.min_ttl, MIN_RESCAN_FREQUENCY].max
118
+ [last_result.min_ttl, MIN_SCAN_INTERVAL].max
92
119
  end
93
120
  end
121
+
122
+ def timeout
123
+ options[:timeout] || DEFAULT_TIMEOUT
124
+ end
94
125
  end
95
126
  end
96
127
  end