mongo 2.13.0.beta1 → 2.13.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -5
  4. data/Rakefile +15 -9
  5. data/lib/mongo.rb +4 -2
  6. data/lib/mongo/auth/aws/request.rb +4 -2
  7. data/lib/mongo/bulk_write.rb +1 -0
  8. data/lib/mongo/client.rb +143 -21
  9. data/lib/mongo/cluster.rb +53 -17
  10. data/lib/mongo/cluster/sdam_flow.rb +13 -10
  11. data/lib/mongo/cluster/topology/replica_set_no_primary.rb +3 -2
  12. data/lib/mongo/cluster/topology/sharded.rb +1 -1
  13. data/lib/mongo/cluster/topology/single.rb +1 -1
  14. data/lib/mongo/collection.rb +17 -13
  15. data/lib/mongo/collection/view/readable.rb +3 -1
  16. data/lib/mongo/collection/view/writable.rb +41 -5
  17. data/lib/mongo/database.rb +31 -4
  18. data/lib/mongo/database/view.rb +19 -4
  19. data/lib/mongo/distinguishing_semaphore.rb +55 -0
  20. data/lib/mongo/error.rb +1 -0
  21. data/lib/mongo/error/invalid_session.rb +2 -1
  22. data/lib/mongo/error/operation_failure.rb +6 -0
  23. data/lib/mongo/error/sessions_not_supported.rb +35 -0
  24. data/lib/mongo/event/base.rb +6 -0
  25. data/lib/mongo/grid/file.rb +5 -0
  26. data/lib/mongo/grid/file/chunk.rb +2 -0
  27. data/lib/mongo/grid/fs_bucket.rb +15 -13
  28. data/lib/mongo/grid/stream/write.rb +9 -3
  29. data/lib/mongo/monitoring.rb +38 -0
  30. data/lib/mongo/monitoring/command_log_subscriber.rb +10 -2
  31. data/lib/mongo/monitoring/event/command_failed.rb +11 -0
  32. data/lib/mongo/monitoring/event/command_started.rb +37 -2
  33. data/lib/mongo/monitoring/event/command_succeeded.rb +11 -0
  34. data/lib/mongo/monitoring/event/server_closed.rb +1 -1
  35. data/lib/mongo/monitoring/event/server_description_changed.rb +27 -4
  36. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +9 -2
  37. data/lib/mongo/monitoring/event/server_heartbeat_started.rb +9 -2
  38. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +9 -2
  39. data/lib/mongo/monitoring/event/server_opening.rb +1 -1
  40. data/lib/mongo/monitoring/event/topology_changed.rb +1 -1
  41. data/lib/mongo/monitoring/event/topology_closed.rb +1 -1
  42. data/lib/mongo/monitoring/event/topology_opening.rb +1 -1
  43. data/lib/mongo/monitoring/publishable.rb +6 -3
  44. data/lib/mongo/monitoring/server_description_changed_log_subscriber.rb +9 -1
  45. data/lib/mongo/monitoring/topology_changed_log_subscriber.rb +1 -1
  46. data/lib/mongo/protocol/message.rb +36 -8
  47. data/lib/mongo/protocol/msg.rb +14 -0
  48. data/lib/mongo/protocol/serializers.rb +5 -2
  49. data/lib/mongo/server.rb +10 -3
  50. data/lib/mongo/server/connection.rb +4 -4
  51. data/lib/mongo/server/connection_base.rb +3 -1
  52. data/lib/mongo/server/description.rb +5 -0
  53. data/lib/mongo/server/monitor.rb +76 -44
  54. data/lib/mongo/server/monitor/connection.rb +55 -7
  55. data/lib/mongo/server/pending_connection.rb +14 -4
  56. data/lib/mongo/server/push_monitor.rb +173 -0
  57. data/{spec/runners/transactions/context.rb → lib/mongo/server/push_monitor/connection.rb} +9 -14
  58. data/lib/mongo/server_selector.rb +0 -1
  59. data/lib/mongo/server_selector/base.rb +579 -1
  60. data/lib/mongo/server_selector/nearest.rb +1 -6
  61. data/lib/mongo/server_selector/primary.rb +1 -6
  62. data/lib/mongo/server_selector/primary_preferred.rb +7 -10
  63. data/lib/mongo/server_selector/secondary.rb +1 -6
  64. data/lib/mongo/server_selector/secondary_preferred.rb +1 -7
  65. data/lib/mongo/session.rb +2 -0
  66. data/lib/mongo/socket.rb +20 -8
  67. data/lib/mongo/socket/ssl.rb +1 -1
  68. data/lib/mongo/socket/tcp.rb +1 -1
  69. data/lib/mongo/topology_version.rb +9 -0
  70. data/lib/mongo/utils.rb +62 -0
  71. data/lib/mongo/version.rb +1 -1
  72. data/spec/README.aws-auth.md +2 -2
  73. data/spec/integration/awaited_ismaster_spec.rb +28 -0
  74. data/spec/integration/change_stream_examples_spec.rb +6 -2
  75. data/spec/integration/check_clean_slate_spec.rb +16 -0
  76. data/spec/integration/client_construction_spec.rb +1 -0
  77. data/spec/integration/connect_single_rs_name_spec.rb +5 -2
  78. data/spec/integration/connection_spec.rb +7 -4
  79. data/spec/integration/crud_spec.rb +4 -4
  80. data/spec/integration/docs_examples_spec.rb +6 -0
  81. data/spec/integration/grid_fs_bucket_spec.rb +48 -0
  82. data/spec/integration/heartbeat_events_spec.rb +4 -23
  83. data/spec/integration/read_concern_spec.rb +1 -1
  84. data/spec/integration/retryable_errors_spec.rb +1 -1
  85. data/spec/integration/retryable_writes/shared/performs_legacy_retries.rb +2 -2
  86. data/spec/integration/retryable_writes/shared/performs_modern_retries.rb +3 -3
  87. data/spec/integration/retryable_writes/shared/performs_no_retries.rb +2 -2
  88. data/spec/integration/sdam_error_handling_spec.rb +37 -15
  89. data/spec/integration/sdam_events_spec.rb +77 -6
  90. data/spec/integration/sdam_prose_spec.rb +64 -0
  91. data/spec/integration/server_monitor_spec.rb +25 -1
  92. data/spec/integration/size_limit_spec.rb +7 -3
  93. data/spec/integration/size_limit_spec.rb~12e1e9c4f... RUBY-2242 Fix zlib compression (#2021) +98 -0
  94. data/spec/integration/ssl_uri_options_spec.rb +2 -2
  95. data/spec/integration/zlib_compression_spec.rb +25 -0
  96. data/spec/lite_spec_helper.rb +12 -5
  97. data/spec/mongo/auth/aws/request_spec.rb +76 -0
  98. data/spec/mongo/auth/scram_spec.rb +1 -1
  99. data/spec/mongo/client_construction_spec.rb +207 -0
  100. data/spec/mongo/client_spec.rb +38 -3
  101. data/spec/mongo/cluster/topology/replica_set_spec.rb +52 -9
  102. data/spec/mongo/cluster/topology/single_spec.rb +4 -2
  103. data/spec/mongo/cluster_spec.rb +34 -35
  104. data/spec/mongo/collection/view/change_stream_resume_spec.rb +6 -6
  105. data/spec/mongo/collection_spec.rb +500 -0
  106. data/spec/mongo/database_spec.rb +245 -8
  107. data/spec/mongo/distinguishing_semaphore_spec.rb +63 -0
  108. data/spec/mongo/error/operation_failure_spec.rb +40 -0
  109. data/spec/mongo/index/view_spec.rb +2 -2
  110. data/spec/mongo/monitoring/event/server_description_changed_spec.rb +1 -4
  111. data/spec/mongo/protocol/msg_spec.rb +10 -0
  112. data/spec/mongo/semaphore_spec.rb +51 -0
  113. data/spec/mongo/server/connection_auth_spec.rb +2 -2
  114. data/spec/mongo/server_selector/nearest_spec.rb +23 -23
  115. data/spec/mongo/server_selector/primary_preferred_spec.rb +26 -26
  116. data/spec/mongo/server_selector/primary_spec.rb +9 -9
  117. data/spec/mongo/server_selector/secondary_preferred_spec.rb +22 -22
  118. data/spec/mongo/server_selector/secondary_spec.rb +18 -18
  119. data/spec/mongo/server_selector_spec.rb +4 -4
  120. data/spec/mongo/session_spec.rb +35 -0
  121. data/spec/runners/change_streams/test.rb +2 -2
  122. data/spec/runners/cmap.rb +1 -1
  123. data/spec/runners/command_monitoring.rb +3 -34
  124. data/spec/runners/crud/context.rb +9 -5
  125. data/spec/runners/crud/operation.rb +59 -27
  126. data/spec/runners/crud/spec.rb +0 -8
  127. data/spec/runners/crud/test.rb +1 -1
  128. data/spec/runners/sdam.rb +2 -2
  129. data/spec/runners/server_selection.rb +242 -28
  130. data/spec/runners/transactions.rb +12 -12
  131. data/spec/runners/transactions/operation.rb +151 -25
  132. data/spec/runners/transactions/test.rb +60 -16
  133. data/spec/spec_tests/command_monitoring_spec.rb +22 -12
  134. data/spec/spec_tests/crud_spec.rb +1 -1
  135. data/spec/spec_tests/data/change_streams/change-streams-errors.yml +4 -8
  136. data/spec/spec_tests/data/change_streams/change-streams-resume-whitelist.yml +66 -0
  137. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/MaxStalenessTooSmall.yml +15 -0
  138. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/NoKnownServers.yml +4 -3
  139. data/spec/spec_tests/data/max_staleness/Unknown/SmallMaxStaleness.yml +1 -0
  140. data/spec/spec_tests/data/sdam_integration/cancel-server-check.yml +96 -0
  141. data/spec/spec_tests/data/sdam_integration/connectTimeoutMS.yml +88 -0
  142. data/spec/spec_tests/data/sdam_integration/find-network-error.yml +83 -0
  143. data/spec/spec_tests/data/sdam_integration/find-shutdown-error.yml +116 -0
  144. data/spec/spec_tests/data/sdam_integration/insert-network-error.yml +86 -0
  145. data/spec/spec_tests/data/sdam_integration/insert-shutdown-error.yml +115 -0
  146. data/spec/spec_tests/data/sdam_integration/isMaster-command-error.yml +168 -0
  147. data/spec/spec_tests/data/sdam_integration/isMaster-network-error.yml +162 -0
  148. data/spec/spec_tests/data/sdam_integration/isMaster-timeout.yml +229 -0
  149. data/spec/spec_tests/data/sdam_integration/rediscover-quickly-after-step-down.yml +87 -0
  150. data/spec/spec_tests/max_staleness_spec.rb +4 -142
  151. data/spec/spec_tests/retryable_reads_spec.rb +2 -2
  152. data/spec/spec_tests/sdam_integration_spec.rb +13 -0
  153. data/spec/spec_tests/sdam_monitoring_spec.rb +1 -2
  154. data/spec/spec_tests/server_selection_spec.rb +4 -116
  155. data/spec/stress/cleanup_spec.rb +17 -2
  156. data/spec/stress/connection_pool_stress_spec.rb +10 -8
  157. data/spec/support/child_process_helper.rb +78 -0
  158. data/spec/support/client_registry.rb +1 -0
  159. data/spec/support/cluster_config.rb +4 -0
  160. data/spec/support/event_subscriber.rb +123 -33
  161. data/spec/support/keyword_struct.rb +26 -0
  162. data/spec/support/shared/server_selector.rb +13 -1
  163. data/spec/support/spec_config.rb +38 -13
  164. data/spec/support/spec_organizer.rb +129 -0
  165. data/spec/support/spec_setup.rb +1 -1
  166. data/spec/support/utils.rb +46 -0
  167. metadata +992 -942
  168. metadata.gz.sig +0 -0
  169. data/lib/mongo/server_selector/selectable.rb +0 -560
  170. data/spec/runners/sdam_monitoring.rb +0 -89
@@ -36,6 +36,11 @@ module Mongo
36
36
  # Alias of error for SDAM spec compliance.
37
37
  alias :failure :error
38
38
 
39
+ # @return [ true | false ] Whether the heartbeat was awaited.
40
+ def awaited?
41
+ @awaited
42
+ end
43
+
39
44
  # Create the event.
40
45
  #
41
46
  # @example Create the event.
@@ -43,13 +48,15 @@ module Mongo
43
48
  #
44
49
  # @param [ Address ] address The server address.
45
50
  # @param [ Float ] round_trip_time Duration of ismaster call in seconds.
51
+ # @param [ true | false ] awaited Whether the heartbeat was awaited.
46
52
  #
47
53
  # @since 2.7.0
48
54
  # @api private
49
- def initialize(address, round_trip_time, error)
55
+ def initialize(address, round_trip_time, error, awaited: false)
50
56
  @address = address
51
57
  @round_trip_time = round_trip_time
52
58
  @error = error
59
+ @awaited = !!awaited
53
60
  end
54
61
 
55
62
  # Returns a concise yet useful summary of the event.
@@ -61,7 +68,7 @@ module Mongo
61
68
  # @since 2.7.0
62
69
  # @api experimental
63
70
  def summary
64
- "#<#{self.class.name.sub(/^Mongo::Monitoring::Event::/, '')}" +
71
+ "#<#{short_class_name}" +
65
72
  " address=#{address}" +
66
73
  " error=#{error.inspect}>"
67
74
  end
@@ -24,17 +24,24 @@ module Mongo
24
24
  # @return [ Address ] address The server address.
25
25
  attr_reader :address
26
26
 
27
+ # @return [ true | false ] Whether the heartbeat was awaited.
28
+ def awaited?
29
+ @awaited
30
+ end
31
+
27
32
  # Create the event.
28
33
  #
29
34
  # @example Create the event.
30
35
  # ServerHeartbeatStarted.new(address)
31
36
  #
32
37
  # @param [ Address ] address The server address.
38
+ # @param [ true | false ] awaited Whether the heartbeat was awaited.
33
39
  #
34
40
  # @since 2.7.0
35
41
  # @api private
36
- def initialize(address)
42
+ def initialize(address, awaited: false)
37
43
  @address = address
44
+ @awaited = !!awaited
38
45
  end
39
46
 
40
47
  # Returns a concise yet useful summary of the event.
@@ -46,7 +53,7 @@ module Mongo
46
53
  # @since 2.7.0
47
54
  # @api experimental
48
55
  def summary
49
- "#<#{self.class.name.sub(/^Mongo::Monitoring::Event::/, '')}" +
56
+ "#<#{short_class_name}" +
50
57
  " address=#{address}>"
51
58
  end
52
59
  end
@@ -30,6 +30,11 @@ module Mongo
30
30
  # Alias of round_trip_time.
31
31
  alias :duration :round_trip_time
32
32
 
33
+ # @return [ true | false ] Whether the heartbeat was awaited.
34
+ def awaited?
35
+ @awaited
36
+ end
37
+
33
38
  # Create the event.
34
39
  #
35
40
  # @example Create the event.
@@ -37,12 +42,14 @@ module Mongo
37
42
  #
38
43
  # @param [ Address ] address The server address.
39
44
  # @param [ Float ] round_trip_time Duration of ismaster call in seconds.
45
+ # @param [ true | false ] awaited Whether the heartbeat was awaited.
40
46
  #
41
47
  # @since 2.7.0
42
48
  # @api private
43
- def initialize(address, round_trip_time)
49
+ def initialize(address, round_trip_time, awaited: false)
44
50
  @address = address
45
51
  @round_trip_time = round_trip_time
52
+ @awaited = !!awaited
46
53
  end
47
54
 
48
55
  # Returns a concise yet useful summary of the event.
@@ -54,7 +61,7 @@ module Mongo
54
61
  # @since 2.7.0
55
62
  # @api experimental
56
63
  def summary
57
- "#<#{self.class.name.sub(/^Mongo::Monitoring::Event::/, '')}" +
64
+ "#<#{short_class_name}" +
58
65
  " address=#{address}>"
59
66
  end
60
67
  end
@@ -50,7 +50,7 @@ module Mongo
50
50
  # @since 2.7.0
51
51
  # @api experimental
52
52
  def summary
53
- "#<#{self.class.name.sub(/^Mongo::Monitoring::Event::/, '')}" +
53
+ "#<#{short_class_name}" +
54
54
  " address=#{address} topology=#{topology.summary}>"
55
55
  end
56
56
  end
@@ -50,7 +50,7 @@ module Mongo
50
50
  # @since 2.7.0
51
51
  # @api experimental
52
52
  def summary
53
- "#<#{self.class.name.sub(/^Mongo::Monitoring::Event::/, '')}" +
53
+ "#<#{short_class_name}" +
54
54
  " prev=#{previous_topology.summary}" +
55
55
  " new=#{new_topology.summary}>"
56
56
  end
@@ -45,7 +45,7 @@ module Mongo
45
45
  # @since 2.7.0
46
46
  # @api experimental
47
47
  def summary
48
- "#<#{self.class.name.sub(/^Mongo::Monitoring::Event::/, '')}" +
48
+ "#<#{short_class_name}" +
49
49
  " topology=#{topology.summary}>"
50
50
  end
51
51
  end
@@ -45,7 +45,7 @@ module Mongo
45
45
  # @since 2.7.0
46
46
  # @api experimental
47
47
  def summary
48
- "#<#{self.class.name.sub(/^Mongo::Monitoring::Event::/, '')}" +
48
+ "#<#{short_class_name}" +
49
49
  " topology=#{topology.summary}>"
50
50
  end
51
51
  end
@@ -32,7 +32,7 @@ module Mongo
32
32
  def publish_sdam_event(topic, event)
33
33
  return unless monitoring?
34
34
 
35
- log_debug("EVENT: #{event.summary}")
35
+ #log_debug("EVENT: #{event.summary}")
36
36
  monitoring.succeeded(topic, event)
37
37
  end
38
38
 
@@ -45,13 +45,16 @@ module Mongo
45
45
  private
46
46
 
47
47
  def command_started(address, operation_id, payload,
48
- socket_object_id: nil, connection_id: nil, connection_generation: nil
48
+ socket_object_id: nil, connection_id: nil, connection_generation: nil,
49
+ server_connection_id: nil
49
50
  )
50
51
  monitoring.started(
51
52
  Monitoring::COMMAND,
52
53
  Event::CommandStarted.generate(address, operation_id, payload,
53
54
  socket_object_id: socket_object_id, connection_id: connection_id,
54
- connection_generation: connection_generation)
55
+ connection_generation: connection_generation,
56
+ server_connection_id: server_connection_id,
57
+ )
55
58
  )
56
59
  end
57
60
 
@@ -25,9 +25,17 @@ module Mongo
25
25
  def log_event(event)
26
26
  log_debug(
27
27
  "Server description for #{event.address} changed from " +
28
- "'#{event.previous_description.server_type}' to '#{event.new_description.server_type}'."
28
+ "'#{event.previous_description.server_type}' to '#{event.new_description.server_type}'#{awaited_indicator(event)}."
29
29
  )
30
30
  end
31
+
32
+ def awaited_indicator(event)
33
+ if event.awaited?
34
+ ' [awaited]'
35
+ else
36
+ ''
37
+ end
38
+ end
31
39
  end
32
40
  end
33
41
  end
@@ -23,7 +23,7 @@ module Mongo
23
23
  private
24
24
 
25
25
  def log_event(event)
26
- if event.previous_topology != event.new_topology
26
+ if event.previous_topology.class != event.new_topology.class
27
27
  log_debug(
28
28
  "Topology type '#{event.previous_topology.display_name}' changed to " +
29
29
  "type '#{event.new_topology.display_name}'."
@@ -199,10 +199,33 @@ module Mongo
199
199
  # @option options [ Boolean ] :deserialize_as_bson Whether to deserialize
200
200
  # this message using BSON types instead of native Ruby types wherever
201
201
  # possible.
202
+ # @option options [ Numeric ] :socket_timeout The timeout to use for
203
+ # each read operation.
202
204
  #
203
205
  # @return [ Message ] Instance of a Message class
204
- def self.deserialize(io, max_message_size = MAX_MESSAGE_SIZE, expected_response_to = nil, options = {})
205
- length, _request_id, response_to, _op_code = deserialize_header(BSON::ByteBuffer.new(io.read(16)))
206
+ #
207
+ # @api private
208
+ def self.deserialize(io,
209
+ max_message_size = MAX_MESSAGE_SIZE,
210
+ expected_response_to = nil,
211
+ options = {}
212
+ )
213
+ # io is usually a Mongo::Socket instance, which supports the
214
+ # timeout option. For compatibility with whoever might call this
215
+ # method with some other IO-like object, pass options only when they
216
+ # are not empty.
217
+ read_options = {}
218
+ if timeout = options[:socket_timeout]
219
+ read_options[:timeout] = timeout
220
+ end
221
+
222
+ if read_options.empty?
223
+ chunk = io.read(16)
224
+ else
225
+ chunk = io.read(16, **read_options)
226
+ end
227
+ buf = BSON::ByteBuffer.new(chunk)
228
+ length, _request_id, response_to, _op_code = deserialize_header(buf)
206
229
 
207
230
  # Protection from potential DOS man-in-the-middle attacks. See
208
231
  # DRIVERS-276.
@@ -216,14 +239,19 @@ module Mongo
216
239
  raise Error::UnexpectedResponse.new(expected_response_to, response_to)
217
240
  end
218
241
 
219
- message = Registry.get(_op_code).allocate
220
- buffer = BSON::ByteBuffer.new(io.read(length - 16))
242
+ if read_options.empty?
243
+ chunk = io.read(length - 16)
244
+ else
245
+ chunk = io.read(length - 16, **read_options)
246
+ end
247
+ buf = BSON::ByteBuffer.new(chunk)
221
248
 
249
+ message = Registry.get(_op_code).allocate
222
250
  message.send(:fields).each do |field|
223
251
  if field[:multi]
224
- deserialize_array(message, buffer, field, options)
252
+ deserialize_array(message, buf, field, options)
225
253
  else
226
- deserialize_field(message, buffer, field, options)
254
+ deserialize_field(message, buf, field, options)
227
255
  end
228
256
  end
229
257
  if message.is_a?(Msg)
@@ -374,7 +402,7 @@ module Mongo
374
402
  # each of the elements in this array using BSON types wherever possible.
375
403
  #
376
404
  # @return [Message] Message with deserialized array.
377
- def self.deserialize_array(message, io, field, options)
405
+ def self.deserialize_array(message, io, field, options = {})
378
406
  elements = []
379
407
  count = message.instance_variable_get(field[:multi])
380
408
  count.times { elements << field[:type].deserialize(io, options) }
@@ -392,7 +420,7 @@ module Mongo
392
420
  # this field using BSON types wherever possible.
393
421
  #
394
422
  # @return [Message] Message with deserialized field.
395
- def self.deserialize_field(message, io, field, options)
423
+ def self.deserialize_field(message, io, field, options = {})
396
424
  message.instance_variable_set(
397
425
  field[:name],
398
426
  field[:type].deserialize(io, options)
@@ -60,6 +60,13 @@ module Mongo
60
60
  #
61
61
  # @since 2.5.0
62
62
  def initialize(flags, options, main_document, *sequences)
63
+ if flags
64
+ flags.each do |flag|
65
+ unless KNOWN_FLAGS.key?(flag)
66
+ raise ArgumentError, "Unknown flag: #{flag.inspect}"
67
+ end
68
+ end
69
+ end
63
70
  @flags = flags || []
64
71
  @options = options
65
72
  unless main_document.is_a?(Hash)
@@ -313,10 +320,17 @@ module Mongo
313
320
  # @since 2.5.0
314
321
  OP_CODE = 2013
315
322
 
323
+ KNOWN_FLAGS = {
324
+ checksum_present: true,
325
+ more_to_come: true,
326
+ exhaust_allowed: true,
327
+ }
328
+
316
329
  # Available flags for a OP_MSG message.
317
330
  FLAGS = Array.new(16).tap do |arr|
318
331
  arr[0] = :checksum_present
319
332
  arr[1] = :more_to_come
333
+ arr[16] = :exhaust_allowed
320
334
  end.freeze
321
335
 
322
336
  # @!attribute
@@ -440,12 +440,15 @@ module Mongo
440
440
  # Deserializes bytes from the byte buffer.
441
441
  #
442
442
  # @param [ BSON::ByteBuffer ] buffer Buffer containing the value to read.
443
- # @param [ Integer ] num_bytes Number of bytes to read.
443
+ # @param [ Hash ] options The method options.
444
+ #
445
+ # @option options [ Integer ] num_bytes Number of bytes to read.
444
446
  #
445
447
  # @return [ String ] The bytes.
446
448
  #
447
449
  # @since 2.5.0
448
- def self.deserialize(buffer, num_bytes = nil)
450
+ def self.deserialize(buffer, options = {})
451
+ num_bytes = options[:num_bytes]
449
452
  buffer.get_bytes(num_bytes || buffer.length)
450
453
  end
451
454
  end
@@ -64,7 +64,7 @@ module Mongo
64
64
  @connection_id_gen = Class.new do
65
65
  include Id
66
66
  end
67
- @scan_semaphore = Semaphore.new
67
+ @scan_semaphore = DistinguishingSemaphore.new
68
68
  @round_trip_time_averager = RoundTripTimeAverager.new
69
69
  @description = Description.new(address, {})
70
70
  @last_scan = nil
@@ -432,7 +432,7 @@ module Mongo
432
432
  def handle_handshake_failure!
433
433
  yield
434
434
  rescue Mongo::Error::SocketError, Mongo::Error::SocketTimeoutError => e
435
- unknown!(generation: e.generation)
435
+ unknown!(generation: e.generation, stop_push_monitor: true)
436
436
  raise
437
437
  end
438
438
 
@@ -455,7 +455,7 @@ module Mongo
455
455
  raise
456
456
  rescue Mongo::Error::SocketError => e
457
457
  # non-timeout network error
458
- unknown!(generation: e.generation)
458
+ unknown!(generation: e.generation, stop_push_monitor: true)
459
459
  raise
460
460
  rescue Auth::Unauthorized
461
461
  # auth error, keep server description and topology as they are
@@ -503,6 +503,8 @@ module Mongo
503
503
  # on 4.2+ servers).
504
504
  # @option options [ TopologyVersion ] :topology_version Topology version
505
505
  # of the error response that is causing the server to be marked unknown.
506
+ # @option options [ true | false ] :stop_push_monitor Whether to stop
507
+ # the PushMonitor associated with the server, if any.
506
508
  #
507
509
  # @since 2.4.0, SDAM events are sent as of version 2.7.0
508
510
  def unknown!(options = {})
@@ -516,6 +518,10 @@ module Mongo
516
518
  return
517
519
  end
518
520
 
521
+ if options[:stop_push_monitor]
522
+ monitor&.stop_push_monitor!
523
+ end
524
+
519
525
  # SDAM flow will update description on the server without in-place
520
526
  # mutations and invoke SDAM transitions as needed.
521
527
  config = {}
@@ -562,3 +568,4 @@ require 'mongo/server/context'
562
568
  require 'mongo/server/description'
563
569
  require 'mongo/server/monitor'
564
570
  require 'mongo/server/round_trip_time_averager'
571
+ require 'mongo/server/push_monitor'
@@ -163,7 +163,7 @@ module Mongo
163
163
  unless @socket
164
164
  # When @socket is assigned, the socket should have handshaken and
165
165
  # authenticated and be usable.
166
- @socket, @description = do_connect
166
+ @socket, @description, @compressor = do_connect
167
167
 
168
168
  publish_cmap_event(
169
169
  Monitoring::Event::Cmap::ConnectionReady.new(address, id)
@@ -194,7 +194,7 @@ module Mongo
194
194
  raise
195
195
  end
196
196
 
197
- [socket, pending_connection.description]
197
+ [socket, pending_connection.description, pending_connection.compressor]
198
198
  end
199
199
 
200
200
  # Disconnect the connection.
@@ -220,7 +220,7 @@ module Mongo
220
220
  @auth_mechanism = nil
221
221
  @last_checkin = nil
222
222
  if socket
223
- socket.close
223
+ socket.close rescue nil
224
224
  @socket = nil
225
225
  end
226
226
  @closed = true
@@ -306,7 +306,7 @@ module Mongo
306
306
  yield
307
307
  rescue Error::SocketError => e
308
308
  @error = e
309
- @server.unknown!(generation: e.generation)
309
+ @server.unknown!(generation: e.generation, stop_push_monitor: true)
310
310
  raise
311
311
  rescue Error::SocketTimeoutError => e
312
312
  @error = e
@@ -156,7 +156,9 @@ module Mongo
156
156
  operation_id = Monitoring.next_operation_id
157
157
  command_started(address, operation_id, message.payload,
158
158
  socket_object_id: socket.object_id, connection_id: id,
159
- connection_generation: generation)
159
+ connection_generation: generation,
160
+ server_connection_id: description.server_connection_id,
161
+ )
160
162
  start = Time.now
161
163
  result = nil
162
164
  begin
@@ -755,6 +755,11 @@ module Mongo
755
755
  # @since 2.7.0
756
756
  attr_reader :last_update_time
757
757
 
758
+ # @api experimental
759
+ def server_connection_id
760
+ config['connectionId']
761
+ end
762
+
758
763
  # Check equality of two descriptions.
759
764
  #
760
765
  # @example Check description equality.
@@ -77,7 +77,9 @@ module Mongo
77
77
  @monitoring = monitoring
78
78
  @options = options.freeze
79
79
  @mutex = Mutex.new
80
+ @sdam_mutex = Mutex.new
80
81
  @next_earliest_scan = @next_wanted_scan = Time.now
82
+ @update_mutex = Mutex.new
81
83
  end
82
84
 
83
85
  # @return [ Server ] server The server that this monitor is monitoring.
@@ -109,6 +111,14 @@ module Mongo
109
111
  # @return [ Monitoring ] monitoring The monitoring.
110
112
  attr_reader :monitoring
111
113
 
114
+ # @return [ Server::PushMonitor | nil ] The push monitor, if one is being
115
+ # used.
116
+ def push_monitor
117
+ @update_mutex.synchronize do
118
+ @push_monitor
119
+ end
120
+ end
121
+
112
122
  # Runs the server monitor. Refreshing happens on a separate thread per
113
123
  # server.
114
124
  #
@@ -120,9 +130,19 @@ module Mongo
120
130
  # @since 2.0.0
121
131
  def do_work
122
132
  scan!
123
- delta = @next_wanted_scan - Time.now
124
- if delta > 0
125
- server.scan_semaphore.wait(delta)
133
+ # @next_wanted_scan may be updated by the push monitor.
134
+ # However we need to check for termination flag so that the monitor
135
+ # thread exits when requested.
136
+ loop do
137
+ delta = @next_wanted_scan - Time.now
138
+ if delta > 0
139
+ signaled = server.scan_semaphore.wait(delta)
140
+ if signaled || @stop_requested
141
+ break
142
+ end
143
+ else
144
+ break
145
+ end
126
146
  end
127
147
  end
128
148
 
@@ -133,6 +153,8 @@ module Mongo
133
153
  #
134
154
  # @api public for backwards compatibility only
135
155
  def stop!
156
+ stop_push_monitor!
157
+
136
158
  # Forward super's return value
137
159
  super.tap do
138
160
  # Important: disconnect should happen after the background thread
@@ -141,6 +163,15 @@ module Mongo
141
163
  end
142
164
  end
143
165
 
166
+ def stop_push_monitor!
167
+ @update_mutex.synchronize do
168
+ if @push_monitor
169
+ @push_monitor.stop!
170
+ @push_monitor = nil
171
+ end
172
+ end
173
+ end
174
+
144
175
  # Perform a check of the server with throttling, and update
145
176
  # the server's description and average round trip time.
146
177
  #
@@ -168,19 +199,27 @@ module Mongo
168
199
 
169
200
  result = do_scan
170
201
 
202
+ run_sdam_flow(result)
203
+ end
204
+ end
205
+
206
+ def run_sdam_flow(result, awaited: false)
207
+ @sdam_mutex.synchronize do
171
208
  old_description = server.description
172
209
 
173
210
  new_description = Description.new(server.address, result,
174
211
  server.round_trip_time_averager.average_round_trip_time)
175
212
 
176
- server.cluster.run_sdam_flow(server.description, new_description)
213
+ server.cluster.run_sdam_flow(server.description, new_description, awaited: awaited)
177
214
 
178
215
  server.description.tap do |new_description|
179
- if new_description.unknown? && !old_description.unknown?
180
- @next_earliest_scan = @next_wanted_scan = Time.now
181
- else
182
- @next_earliest_scan = Time.now + MIN_SCAN_INTERVAL
183
- @next_wanted_scan = Time.now + heartbeat_interval
216
+ unless awaited
217
+ if new_description.unknown? && !old_description.unknown?
218
+ @next_earliest_scan = @next_wanted_scan = Time.now
219
+ else
220
+ @next_earliest_scan = Time.now + MIN_SCAN_INTERVAL
221
+ @next_wanted_scan = Time.now + heartbeat_interval
222
+ end
184
223
  end
185
224
  end
186
225
  end
@@ -209,44 +248,19 @@ module Mongo
209
248
  end
210
249
 
211
250
  def do_scan
212
- if monitoring.monitoring?
213
- monitoring.started(
214
- Monitoring::SERVER_HEARTBEAT,
215
- Monitoring::Event::ServerHeartbeatStarted.new(server.address)
216
- )
217
- end
218
-
219
- # The duration we publish in heartbeat succeeded/failed events is
220
- # the time spent on the entire heartbeat. This could include time
221
- # to connect the socket (including TLS handshake), not just time
222
- # spent on ismaster call itself.
223
- # The spec at https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring-monitoring.rst
224
- # requires that the duration exposed here start from "sending the
225
- # message" (ismaster). This requirement does not make sense if,
226
- # for example, we were never able to connect to the server at all
227
- # and thus ismaster was never sent.
228
- start_time = Time.now
229
-
230
251
  begin
231
- result = ismaster
232
- rescue => exc
233
- log_warn("Error running ismaster on #{server.address}: #{exc.class}: #{exc}:\n#{exc.backtrace[0..5].join("\n")}")
234
- if monitoring.monitoring?
235
- monitoring.failed(
236
- Monitoring::SERVER_HEARTBEAT,
237
- Monitoring::Event::ServerHeartbeatFailed.new(server.address, Time.now-start_time, exc)
238
- )
239
- end
240
- result = {}
241
- else
242
- if monitoring.monitoring?
243
- monitoring.succeeded(
244
- Monitoring::SERVER_HEARTBEAT,
245
- Monitoring::Event::ServerHeartbeatSucceeded.new(server.address, Time.now-start_time)
246
- )
252
+ monitoring.publish_heartbeat(server) do
253
+ ismaster
247
254
  end
255
+ rescue => exc
256
+ msg = "Error running ismaster on #{server.address}"
257
+ Utils.warn_monitor_exception(msg, exc,
258
+ logger: options[:logger],
259
+ log_prefix: options[:log_prefix],
260
+ bg_error_backtrace: options[:bg_error_backtrace],
261
+ )
262
+ {}
248
263
  end
249
- result
250
264
  end
251
265
 
252
266
  def ismaster
@@ -274,6 +288,24 @@ module Mongo
274
288
  connection.handshake!
275
289
  end
276
290
  @connection = connection
291
+ if result['topologyVersion']
292
+ # Successful response, server 4.4+
293
+ @update_mutex.synchronize do
294
+ @push_monitor ||= PushMonitor.new(
295
+ self,
296
+ TopologyVersion.new(result['topologyVersion']),
297
+ monitoring,
298
+ **Utils.shallow_symbolize_keys(options.merge(
299
+ socket_timeout: heartbeat_interval + connection.socket_timeout,
300
+ )),
301
+ )
302
+ end
303
+ push_monitor.run!
304
+ else
305
+ # Failed response or pre-4.4 server
306
+ stop_push_monitor!
307
+ end
308
+ result
277
309
  end
278
310
  result
279
311
  end