mongo 2.13.0.beta1 → 2.13.0.rc1

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.
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