mongo 2.19.3 → 2.20.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +37 -1
- data/Rakefile +81 -172
- data/lib/mongo/cluster/topology/base.rb +16 -0
- data/lib/mongo/cluster.rb +27 -1
- data/lib/mongo/collection/view/iterable.rb +1 -0
- data/lib/mongo/collection.rb +4 -2
- data/lib/mongo/config.rb +2 -2
- data/lib/mongo/error/transactions_not_supported.rb +34 -0
- data/lib/mongo/error.rb +1 -0
- data/lib/mongo/grid/fs_bucket.rb +6 -0
- data/lib/mongo/monitoring/event/secure.rb +1 -1
- data/lib/mongo/operation/shared/executable.rb +43 -27
- data/lib/mongo/operation/shared/response_handling.rb +23 -25
- data/lib/mongo/retryable/base_worker.rb +28 -3
- data/lib/mongo/retryable/read_worker.rb +16 -14
- data/lib/mongo/retryable/write_worker.rb +11 -8
- data/lib/mongo/retryable.rb +2 -2
- data/lib/mongo/server/app_metadata/environment.rb +64 -9
- data/lib/mongo/server/app_metadata.rb +5 -4
- data/lib/mongo/server/description/features.rb +1 -0
- data/lib/mongo/server/pending_connection.rb +19 -6
- data/lib/mongo/server_selector/base.rb +32 -6
- data/lib/mongo/session/server_session/dirtyable.rb +52 -0
- data/lib/mongo/session/server_session.rb +3 -0
- data/lib/mongo/session/session_pool.rb +12 -18
- data/lib/mongo/session.rb +32 -0
- data/lib/mongo/socket/ssl.rb +22 -1
- data/lib/mongo/uri.rb +0 -4
- data/lib/mongo/version.rb +1 -5
- data/mongo.gemspec +9 -18
- data/spec/atlas/atlas_connectivity_spec.rb +4 -4
- data/spec/faas/ruby-sam-app/Gemfile +9 -0
- data/spec/faas/ruby-sam-app/mongodb/Gemfile +4 -0
- data/spec/faas/ruby-sam-app/mongodb/app.rb +149 -0
- data/spec/faas/ruby-sam-app/template.yaml +48 -0
- data/spec/integration/client_side_encryption/corpus_spec.rb +10 -2
- data/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb +3 -0
- data/spec/integration/retryable_reads_errors_spec.rb +196 -31
- data/spec/integration/retryable_writes_errors_spec.rb +156 -0
- data/spec/integration/sdam_error_handling_spec.rb +2 -0
- data/spec/lite_spec_helper.rb +0 -10
- data/spec/mongo/cluster_spec.rb +36 -0
- data/spec/mongo/collection/view/aggregation_spec.rb +6 -1
- data/spec/mongo/collection/view/explainable_spec.rb +2 -0
- data/spec/mongo/collection_crud_spec.rb +2 -1
- data/spec/mongo/operation/insert_spec.rb +1 -1
- data/spec/mongo/retryable/write_worker_spec.rb +39 -0
- data/spec/mongo/server/app_metadata/environment_spec.rb +135 -0
- data/spec/mongo/server/app_metadata_spec.rb +12 -2
- data/spec/mongo/server/connection_spec.rb +26 -0
- data/spec/mongo/session/session_pool_spec.rb +1 -16
- data/spec/mongo/session_transaction_spec.rb +15 -0
- data/spec/mongo/uri_spec.rb +0 -9
- data/spec/runners/crud/test.rb +0 -8
- data/spec/runners/crud.rb +1 -1
- data/spec/runners/transactions/test.rb +12 -3
- data/spec/runners/unified/assertions.rb +16 -3
- data/spec/runners/unified/crud_operations.rb +12 -0
- data/spec/runners/unified/support_operations.rb +3 -5
- data/spec/runners/unified/test.rb +8 -1
- data/spec/spec_tests/data/client_side_encryption/explain.yml +2 -2
- data/spec/spec_tests/data/client_side_encryption/fle2v2-BypassQueryAnalysis.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Compact.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-DecryptExistingData.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Delete.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-jsonSchema.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFieldsMap-defaults.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-FindOneAndUpdate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Indexed.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Unindexed.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-MissingKey.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-NoEncryption.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Correctness.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Correctness.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-Update.yml +1 -0
- data/spec/spec_tests/data/client_side_encryption/fle2v2-validatorAndPartialFieldExpression.yml +2 -1
- data/spec/spec_tests/data/connection_string/invalid-uris.yml +0 -10
- data/spec/spec_tests/data/connection_string/valid-options.yml +13 -0
- data/spec/spec_tests/data/crud_unified/aggregate-write-readPreference.yml +2 -0
- data/spec/spec_tests/data/crud_unified/db-aggregate-write-readPreference.yml +2 -0
- data/spec/spec_tests/data/crud_unified/find-test-all-options.yml +348 -0
- data/spec/spec_tests/data/index_management/createSearchIndex.yml +5 -3
- data/spec/spec_tests/data/index_management/createSearchIndexes.yml +7 -4
- data/spec/spec_tests/data/index_management/dropSearchIndex.yml +2 -1
- data/spec/spec_tests/data/index_management/listSearchIndexes.yml +13 -7
- data/spec/spec_tests/data/index_management/updateSearchIndex.yml +2 -1
- data/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml +3 -6
- data/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml +3 -6
- data/spec/spec_tests/data/run_command_unified/runCommand.yml +319 -0
- data/spec/spec_tests/data/sessions_unified/driver-sessions-dirty-session-errors.yml +351 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +1 -1
- data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +7 -7
- data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +3 -4
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +1 -1
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +1 -1
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +3 -3
- data/spec/spec_tests/run_command_unified_spec.rb +13 -0
- data/spec/spec_tests/sdam_unified_spec.rb +2 -0
- data/spec/spec_tests/transactions_unified_spec.rb +2 -1
- data/spec/support/certificates/atlas-ocsp-ca.crt +89 -77
- data/spec/support/certificates/atlas-ocsp.crt +117 -122
- data/spec/support/certificates/retrieve-atlas-cert +1 -1
- data/spec/support/constraints.rb +6 -0
- data/spec/support/ocsp +1 -1
- data/spec/support/recording_logger.rb +27 -0
- metadata +1245 -1298
- checksums.yaml.gz.sig +0 -0
- data/spec/shared/LICENSE +0 -20
- data/spec/shared/bin/get-mongodb-download-url +0 -17
- data/spec/shared/bin/s3-copy +0 -45
- data/spec/shared/bin/s3-upload +0 -69
- data/spec/shared/lib/mrss/child_process_helper.rb +0 -80
- data/spec/shared/lib/mrss/cluster_config.rb +0 -231
- data/spec/shared/lib/mrss/constraints.rb +0 -378
- data/spec/shared/lib/mrss/docker_runner.rb +0 -295
- data/spec/shared/lib/mrss/eg_config_utils.rb +0 -51
- data/spec/shared/lib/mrss/event_subscriber.rb +0 -210
- data/spec/shared/lib/mrss/lite_constraints.rb +0 -238
- data/spec/shared/lib/mrss/server_version_registry.rb +0 -113
- data/spec/shared/lib/mrss/session_registry.rb +0 -69
- data/spec/shared/lib/mrss/session_registry_legacy.rb +0 -60
- data/spec/shared/lib/mrss/spec_organizer.rb +0 -179
- data/spec/shared/lib/mrss/utils.rb +0 -37
- data/spec/shared/share/Dockerfile.erb +0 -330
- data/spec/shared/share/haproxy-1.conf +0 -16
- data/spec/shared/share/haproxy-2.conf +0 -17
- data/spec/shared/shlib/config.sh +0 -27
- data/spec/shared/shlib/distro.sh +0 -74
- data/spec/shared/shlib/server.sh +0 -416
- data/spec/shared/shlib/set_env.sh +0 -169
- data/spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml +0 -49
- data/spec/support/faas/app/aws_lambda/mongodb/Gemfile.lock +0 -19
- data.tar.gz.sig +0 -3
- metadata.gz.sig +0 -0
@@ -50,35 +50,33 @@ module Mongo
|
|
50
50
|
# the operation is performed.
|
51
51
|
# @param [ Mongo::Operation::Context ] context The operation context.
|
52
52
|
def add_error_labels(connection, context)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
53
|
+
yield
|
54
|
+
rescue Mongo::Error::SocketError => e
|
55
|
+
if context.in_transaction? && !context.committing_transaction?
|
56
|
+
e.add_label('TransientTransactionError')
|
57
|
+
end
|
58
|
+
if context.committing_transaction?
|
59
|
+
e.add_label('UnknownTransactionCommitResult')
|
60
|
+
end
|
62
61
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
62
|
+
maybe_add_retryable_write_error_label!(e, connection, context)
|
63
|
+
|
64
|
+
raise e
|
65
|
+
rescue Mongo::Error::SocketTimeoutError => e
|
66
|
+
maybe_add_retryable_write_error_label!(e, connection, context)
|
67
|
+
raise e
|
68
|
+
rescue Mongo::Error::OperationFailure => e
|
69
|
+
if context.committing_transaction?
|
70
|
+
if e.write_retryable? || e.wtimeout? || (e.write_concern_error? &&
|
71
|
+
!Session::UNLABELED_WRITE_CONCERN_CODES.include?(e.write_concern_error_code)
|
72
|
+
) || e.max_time_ms_expired?
|
73
|
+
e.add_label('UnknownTransactionCommitResult')
|
76
74
|
end
|
75
|
+
end
|
77
76
|
|
78
|
-
|
77
|
+
maybe_add_retryable_write_error_label!(e, connection, context)
|
79
78
|
|
80
|
-
|
81
|
-
end
|
79
|
+
raise e
|
82
80
|
end
|
83
81
|
|
84
82
|
# Unpins the session and/or the connection if the yielded to block
|
@@ -49,7 +49,8 @@ module Mongo
|
|
49
49
|
|
50
50
|
private
|
51
51
|
|
52
|
-
# Indicate which exception classes that are generally retryable
|
52
|
+
# Indicate which exception classes that are generally retryable
|
53
|
+
# when using modern retries mechanism.
|
53
54
|
#
|
54
55
|
# @return [ Array<Mongo:Error> ] Array of exception classes that are
|
55
56
|
# considered retryable.
|
@@ -58,18 +59,42 @@ module Mongo
|
|
58
59
|
Error::ConnectionPerished,
|
59
60
|
Error::ServerNotUsable,
|
60
61
|
Error::SocketError,
|
61
|
-
Error::SocketTimeoutError
|
62
|
+
Error::SocketTimeoutError,
|
62
63
|
].freeze
|
63
64
|
end
|
64
65
|
|
66
|
+
# Indicate which exception classes that are generally retryable
|
67
|
+
# when using legacy retries mechanism.
|
68
|
+
#
|
69
|
+
# @return [ Array<Mongo:Error> ] Array of exception classes that are
|
70
|
+
# considered retryable.
|
71
|
+
def legacy_retryable_exceptions
|
72
|
+
[
|
73
|
+
Error::ConnectionPerished,
|
74
|
+
Error::ServerNotUsable,
|
75
|
+
Error::SocketError,
|
76
|
+
Error::SocketTimeoutError,
|
77
|
+
Error::PoolClearedError,
|
78
|
+
Error::PoolPausedError,
|
79
|
+
].freeze
|
80
|
+
end
|
81
|
+
|
82
|
+
|
65
83
|
# Tests to see if the given exception instance is of a type that can
|
66
|
-
# be retried.
|
84
|
+
# be retried with modern retry mechanism.
|
67
85
|
#
|
68
86
|
# @return [ true | false ] true if the exception is retryable.
|
69
87
|
def is_retryable_exception?(e)
|
70
88
|
retryable_exceptions.any? { |klass| klass === e }
|
71
89
|
end
|
72
90
|
|
91
|
+
# Tests to see if the given exception instance is of a type that can
|
92
|
+
# be retried with legacy retry mechanism.
|
93
|
+
#
|
94
|
+
# @return [ true | false ] true if the exception is retryable.
|
95
|
+
def is_legacy_retryable_exception?(e)
|
96
|
+
legacy_retryable_exceptions.any? { |klass| klass === e }
|
97
|
+
end
|
73
98
|
# Logs the given deprecation warning the first time it is called for a
|
74
99
|
# given key; after that, it does nothing when given the same key.
|
75
100
|
def deprecation_warning(key, warning)
|
@@ -190,14 +190,15 @@ module Mongo
|
|
190
190
|
#
|
191
191
|
# @return [ Result ] The result of the operation.
|
192
192
|
def modern_read_with_retry(session, server_selector, &block)
|
193
|
-
|
193
|
+
server = select_server(cluster, server_selector, session)
|
194
|
+
yield server
|
194
195
|
rescue *retryable_exceptions, Error::OperationFailure, Auth::Unauthorized, Error::PoolError => e
|
195
196
|
e.add_notes('modern retry', 'attempt 1')
|
196
197
|
raise e if session.in_transaction?
|
197
198
|
raise e if !is_retryable_exception?(e) && !e.write_retryable?
|
198
|
-
retry_read(e, session, server_selector, &block)
|
199
|
+
retry_read(e, session, server_selector, failed_server: server, &block)
|
199
200
|
end
|
200
|
-
|
201
|
+
|
201
202
|
# Attempts to do a "legacy" read with retry. The operation will be
|
202
203
|
# attempted multiple times, up to the client's `max_read_retries`
|
203
204
|
# setting.
|
@@ -212,17 +213,18 @@ module Mongo
|
|
212
213
|
def legacy_read_with_retry(session, server_selector, &block)
|
213
214
|
attempt = attempt ? attempt + 1 : 1
|
214
215
|
yield select_server(cluster, server_selector, session)
|
215
|
-
rescue *
|
216
|
+
rescue *legacy_retryable_exceptions, Error::OperationFailure => e
|
216
217
|
e.add_notes('legacy retry', "attempt #{attempt}")
|
217
|
-
|
218
|
-
if
|
218
|
+
|
219
|
+
if is_legacy_retryable_exception?(e)
|
220
|
+
|
219
221
|
raise e if attempt > client.max_read_retries || session&.in_transaction?
|
220
222
|
elsif e.retryable? && !session&.in_transaction?
|
221
223
|
raise e if attempt > client.max_read_retries
|
222
224
|
else
|
223
225
|
raise e
|
224
226
|
end
|
225
|
-
|
227
|
+
|
226
228
|
log_retry(e, message: 'Legacy read retry')
|
227
229
|
sleep(client.read_retry_interval) unless is_retryable_exception?(e)
|
228
230
|
retry
|
@@ -257,19 +259,21 @@ module Mongo
|
|
257
259
|
# being run on.
|
258
260
|
# @param [ Mongo::ServerSelector::Selectable ] server_selector Server
|
259
261
|
# selector for the operation.
|
262
|
+
# @param [ Mongo::Server ] failed_server The server on which the original
|
263
|
+
# operation failed.
|
260
264
|
# @param [ Proc ] block The block to execute.
|
261
|
-
#
|
265
|
+
#
|
262
266
|
# @return [ Result ] The result of the operation.
|
263
|
-
def retry_read(original_error, session, server_selector, &block)
|
267
|
+
def retry_read(original_error, session, server_selector, failed_server: nil, &block)
|
264
268
|
begin
|
265
|
-
server = select_server(cluster, server_selector, session)
|
269
|
+
server = select_server(cluster, server_selector, session, failed_server)
|
266
270
|
rescue Error, Error::AuthError => e
|
267
271
|
original_error.add_note("later retry failed: #{e.class}: #{e}")
|
268
272
|
raise original_error
|
269
273
|
end
|
270
|
-
|
274
|
+
|
271
275
|
log_retry(original_error, message: 'Read retry')
|
272
|
-
|
276
|
+
|
273
277
|
begin
|
274
278
|
yield server, true
|
275
279
|
rescue *retryable_exceptions => e
|
@@ -289,8 +293,6 @@ module Mongo
|
|
289
293
|
raise original_error
|
290
294
|
end
|
291
295
|
end
|
292
|
-
|
293
296
|
end
|
294
|
-
|
295
297
|
end
|
296
298
|
end
|
@@ -103,8 +103,9 @@ module Mongo
|
|
103
103
|
def nro_write_with_retry(write_concern, context:, &block)
|
104
104
|
session = context.session
|
105
105
|
server = select_server(cluster, ServerSelector.primary, session)
|
106
|
-
|
107
|
-
|
106
|
+
options = session&.client&.options || {}
|
107
|
+
|
108
|
+
if options[:retry_writes]
|
108
109
|
begin
|
109
110
|
server.with_connection(connection_global_id: context.connection_global_id) do |connection|
|
110
111
|
yield connection, nil, context
|
@@ -218,7 +219,7 @@ module Mongo
|
|
218
219
|
def modern_write_with_retry(session, server, context, &block)
|
219
220
|
txn_num = nil
|
220
221
|
connection_succeeded = false
|
221
|
-
|
222
|
+
|
222
223
|
server.with_connection(connection_global_id: context.connection_global_id) do |connection|
|
223
224
|
connection_succeeded = true
|
224
225
|
|
@@ -240,7 +241,7 @@ module Mongo
|
|
240
241
|
|
241
242
|
# Context#with creates a new context, which is not necessary here
|
242
243
|
# but the API is less prone to misuse this way.
|
243
|
-
retry_write(e, txn_num, context: context.with(is_retry: true), &block)
|
244
|
+
retry_write(e, txn_num, context: context.with(is_retry: true), failed_server: server, &block)
|
244
245
|
end
|
245
246
|
|
246
247
|
# Called after a failed write, this will retry the write no more than
|
@@ -250,9 +251,11 @@ module Mongo
|
|
250
251
|
# retry.
|
251
252
|
# @param [ Number ] txn_num The transaction number.
|
252
253
|
# @param [ Operation::Context ] context The context for the operation.
|
254
|
+
# @param [ Mongo::Server ] failed_server The server on which the original
|
255
|
+
# operation failed.
|
253
256
|
#
|
254
257
|
# @return [ Result ] The result of the operation.
|
255
|
-
def retry_write(original_error, txn_num, context:, &block)
|
258
|
+
def retry_write(original_error, txn_num, context:, failed_server: nil, &block)
|
256
259
|
session = context.session
|
257
260
|
|
258
261
|
# We do not request a scan of the cluster here, because error handling
|
@@ -260,8 +263,8 @@ module Mongo
|
|
260
263
|
# server description and/or topology as necessary (specifically,
|
261
264
|
# a socket error or a not master error should have marked the respective
|
262
265
|
# server unknown). Here we just need to wait for server selection.
|
263
|
-
server = select_server(cluster, ServerSelector.primary, session)
|
264
|
-
|
266
|
+
server = select_server(cluster, ServerSelector.primary, session, failed_server)
|
267
|
+
|
265
268
|
unless server.retry_writes?
|
266
269
|
# Do not need to add "modern retry" here, it should already be on
|
267
270
|
# the first exception.
|
@@ -275,7 +278,7 @@ module Mongo
|
|
275
278
|
# special marker class to bypass the ordinarily applicable rescues.
|
276
279
|
raise Error::RaiseOriginalError
|
277
280
|
end
|
278
|
-
|
281
|
+
|
279
282
|
log_retry(original_error, message: 'Write retry')
|
280
283
|
server.with_connection(connection_global_id: context.connection_global_id) do |connection|
|
281
284
|
yield(connection, txn_num, context)
|
data/lib/mongo/retryable.rb
CHANGED
@@ -46,8 +46,8 @@ module Mongo
|
|
46
46
|
# @api private
|
47
47
|
#
|
48
48
|
# @return [ Mongo::Server ] A server matching the server preference.
|
49
|
-
def select_server(cluster, server_selector, session)
|
50
|
-
server_selector.select_server(cluster, nil, session)
|
49
|
+
def select_server(cluster, server_selector, session, failed_server = nil)
|
50
|
+
server_selector.select_server(cluster, nil, session, deprioritized: [failed_server].compact)
|
51
51
|
end
|
52
52
|
|
53
53
|
# Returns the read worker for handling retryable reads.
|
@@ -18,9 +18,12 @@ module Mongo
|
|
18
18
|
class Server
|
19
19
|
class AppMetadata
|
20
20
|
# Implements the logic from the handshake spec, for deducing and
|
21
|
-
# reporting the current
|
21
|
+
# reporting the current environment in which the program is
|
22
22
|
# executing.
|
23
23
|
#
|
24
|
+
# This includes FaaS environment checks, as well as checks for the
|
25
|
+
# presence of a container (Docker) and/or orchestrator (Kubernetes).
|
26
|
+
#
|
24
27
|
# @api private
|
25
28
|
class Environment
|
26
29
|
# Error class for reporting that too many discriminators were found
|
@@ -39,6 +42,10 @@ module Mongo
|
|
39
42
|
# Error class for reporting that the value for a field is too long.
|
40
43
|
class ValueTooLong < Mongo::Error; end
|
41
44
|
|
45
|
+
# The name and location of the .dockerenv file that will signal the
|
46
|
+
# presence of Docker.
|
47
|
+
DOCKERENV_PATH = '/.dockerenv'
|
48
|
+
|
42
49
|
# This value is not explicitly specified in the spec, only implied to be
|
43
50
|
# less than 512.
|
44
51
|
MAXIMUM_VALUE_LENGTH = 500
|
@@ -102,9 +109,11 @@ module Mongo
|
|
102
109
|
# if the environment contains invalid or contradictory state, it will
|
103
110
|
# be initialized with {{name}} set to {{nil}}.
|
104
111
|
def initialize
|
112
|
+
@fields = {}
|
105
113
|
@error = nil
|
106
114
|
@name = detect_environment
|
107
|
-
|
115
|
+
populate_faas_fields
|
116
|
+
detect_container
|
108
117
|
rescue TooManyEnvironments => e
|
109
118
|
self.error = "too many environments detected: #{e.message}"
|
110
119
|
rescue MissingVariable => e
|
@@ -115,6 +124,23 @@ module Mongo
|
|
115
124
|
self.error = "value for #{e.message} is too long"
|
116
125
|
end
|
117
126
|
|
127
|
+
# Queries the detected container information.
|
128
|
+
#
|
129
|
+
# @return [ Hash | nil ] the detected container information, or
|
130
|
+
# nil if no container was detected.
|
131
|
+
def container
|
132
|
+
fields[:container]
|
133
|
+
end
|
134
|
+
|
135
|
+
# Queries whether any environment information was able to be
|
136
|
+
# detected.
|
137
|
+
#
|
138
|
+
# @return [ true | false ] if any environment information was
|
139
|
+
# detected.
|
140
|
+
def present?
|
141
|
+
@name || fields.any?
|
142
|
+
end
|
143
|
+
|
118
144
|
# Queries whether the current environment is a valid FaaS environment.
|
119
145
|
#
|
120
146
|
# @return [ true | false ] whether the environment is a FaaS
|
@@ -159,14 +185,11 @@ module Mongo
|
|
159
185
|
@name == 'vercel'
|
160
186
|
end
|
161
187
|
|
162
|
-
# Compiles the detected environment information into a Hash.
|
163
|
-
# always include a {{name}} key, but may include other keys as well,
|
164
|
-
# depending on the detected FaaS environment. (See the handshake
|
165
|
-
# spec for details.)
|
188
|
+
# Compiles the detected environment information into a Hash.
|
166
189
|
#
|
167
190
|
# @return [ Hash ] the detected environment information.
|
168
191
|
def to_h
|
169
|
-
fields.merge(name: name)
|
192
|
+
name ? fields.merge(name: name) : fields
|
170
193
|
end
|
171
194
|
|
172
195
|
private
|
@@ -192,6 +215,38 @@ module Mongo
|
|
192
215
|
names.first
|
193
216
|
end
|
194
217
|
|
218
|
+
# Looks for the presence of a container. Currently can detect
|
219
|
+
# Docker (by the existence of a .dockerenv file in the root
|
220
|
+
# directory) and Kubernetes (by the existence of the KUBERNETES_SERVICE_HOST
|
221
|
+
# environment variable).
|
222
|
+
def detect_container
|
223
|
+
runtime = docker_present? && 'docker'
|
224
|
+
orchestrator = kubernetes_present? && 'kubernetes'
|
225
|
+
|
226
|
+
return unless runtime || orchestrator
|
227
|
+
|
228
|
+
fields[:container] = {}
|
229
|
+
fields[:container][:runtime] = runtime if runtime
|
230
|
+
fields[:container][:orchestrator] = orchestrator if orchestrator
|
231
|
+
end
|
232
|
+
|
233
|
+
# Checks for the existence of a .dockerenv in the root directory.
|
234
|
+
def docker_present?
|
235
|
+
File.exist?(dockerenv_path)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Implementing this as a method so that it can be mocked in tests, to
|
239
|
+
# test the presence or absence of Docker.
|
240
|
+
def dockerenv_path
|
241
|
+
DOCKERENV_PATH
|
242
|
+
end
|
243
|
+
|
244
|
+
# Checks for the presence of a non-empty KUBERNETES_SERVICE_HOST
|
245
|
+
# environment variable.
|
246
|
+
def kubernetes_present?
|
247
|
+
!ENV['KUBERNETES_SERVICE_HOST'].to_s.empty?
|
248
|
+
end
|
249
|
+
|
195
250
|
# Determines whether the named environment variable exists, and (if
|
196
251
|
# a pattern has been declared for that descriminator) whether the
|
197
252
|
# pattern matches the value of the variable.
|
@@ -212,10 +267,10 @@ module Mongo
|
|
212
267
|
# Extracts environment information from the current environment
|
213
268
|
# variables, based on the detected FaaS environment. Populates the
|
214
269
|
# {{@fields}} instance variable.
|
215
|
-
def
|
270
|
+
def populate_faas_fields
|
216
271
|
return unless name
|
217
272
|
|
218
|
-
|
273
|
+
FIELDS[name].each_with_object(@fields) do |(var, defn), fields|
|
219
274
|
fields[defn[:field]] = extract_field(var, defn)
|
220
275
|
end
|
221
276
|
end
|
@@ -187,13 +187,14 @@ module Mongo
|
|
187
187
|
}
|
188
188
|
end
|
189
189
|
|
190
|
-
# Returns the environment doc describing the current
|
190
|
+
# Returns the environment doc describing the current execution
|
191
|
+
# environment.
|
191
192
|
#
|
192
|
-
# @return [ Hash | nil ] the environment doc (or nil if
|
193
|
-
#
|
193
|
+
# @return [ Hash | nil ] the environment doc (or nil if no relevant
|
194
|
+
# environment info was detected)
|
194
195
|
def env_doc
|
195
196
|
env = Environment.new
|
196
|
-
env.
|
197
|
+
env.present? ? env.to_h : nil
|
197
198
|
end
|
198
199
|
|
199
200
|
def type
|
@@ -48,6 +48,7 @@ module Mongo
|
|
48
48
|
# provided by the client during findAndModify operations, requiring the
|
49
49
|
# driver to raise client-side errors when those options are provided.
|
50
50
|
find_and_modify_option_validation: 8,
|
51
|
+
sharded_transactions: 8,
|
51
52
|
transactions: 7,
|
52
53
|
scram_sha_256: 7,
|
53
54
|
array_filters: 6,
|
@@ -110,6 +110,24 @@ module Mongo
|
|
110
110
|
|
111
111
|
private
|
112
112
|
|
113
|
+
# Sends the hello command to the server, then receive and deserialize
|
114
|
+
# the response.
|
115
|
+
#
|
116
|
+
# This method is extracted to be mocked in the tests.
|
117
|
+
#
|
118
|
+
# @param [ Protocol::Message ] Command that should be sent to a server
|
119
|
+
# for handshake purposes.
|
120
|
+
#
|
121
|
+
# @return [ Mongo::Protocol::Reply ] Deserialized server response.
|
122
|
+
def get_handshake_response(hello_command)
|
123
|
+
@server.round_trip_time_averager.measure do
|
124
|
+
add_server_diagnostics do
|
125
|
+
socket.write(hello_command.serialize.to_s)
|
126
|
+
Protocol::Message.deserialize(socket, Protocol::Message::MAX_MESSAGE_SIZE)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
113
131
|
# @param [ BSON::Document | nil ] speculative_auth_doc The document to
|
114
132
|
# provide in speculativeAuthenticate field of handshake command.
|
115
133
|
#
|
@@ -131,12 +149,7 @@ module Mongo
|
|
131
149
|
doc = nil
|
132
150
|
@server.handle_handshake_failure! do
|
133
151
|
begin
|
134
|
-
response =
|
135
|
-
add_server_diagnostics do
|
136
|
-
socket.write(hello_command.serialize.to_s)
|
137
|
-
Protocol::Message.deserialize(socket, Protocol::Message::MAX_MESSAGE_SIZE)
|
138
|
-
end
|
139
|
-
end
|
152
|
+
response = get_handshake_response(hello_command)
|
140
153
|
result = Operation::Result.new([response])
|
141
154
|
result.validate!
|
142
155
|
doc = result.documents.first
|
@@ -164,6 +164,10 @@ module Mongo
|
|
164
164
|
# for mongos pinning. Added in version 2.10.0.
|
165
165
|
# @param [ true | false ] write_aggregation Whether we need a server that
|
166
166
|
# supports writing aggregations (e.g. with $merge/$out) on secondaries.
|
167
|
+
# @param [ Array<Server> ] deprioritized A list of servers that should
|
168
|
+
# be selected from only if no other servers are available. This is
|
169
|
+
# used to avoid selecting the same server twice in a row when
|
170
|
+
# retrying a command.
|
167
171
|
#
|
168
172
|
# @return [ Mongo::Server ] A server matching the server preference.
|
169
173
|
#
|
@@ -174,8 +178,8 @@ module Mongo
|
|
174
178
|
# lint mode is enabled.
|
175
179
|
#
|
176
180
|
# @since 2.0.0
|
177
|
-
def select_server(cluster, ping = nil, session = nil, write_aggregation: false)
|
178
|
-
select_server_impl(cluster, ping, session, write_aggregation).tap do |server|
|
181
|
+
def select_server(cluster, ping = nil, session = nil, write_aggregation: false, deprioritized: [])
|
182
|
+
select_server_impl(cluster, ping, session, write_aggregation, deprioritized).tap do |server|
|
179
183
|
if Lint.enabled? && !server.pool.ready?
|
180
184
|
raise Error::LintError, 'Server selector returning a server with a pool which is not ready'
|
181
185
|
end
|
@@ -183,7 +187,7 @@ module Mongo
|
|
183
187
|
end
|
184
188
|
|
185
189
|
# Parameters and return values are the same as for select_server.
|
186
|
-
private def select_server_impl(cluster, ping, session, write_aggregation)
|
190
|
+
private def select_server_impl(cluster, ping, session, write_aggregation, deprioritized)
|
187
191
|
if cluster.topology.is_a?(Cluster::Topology::LoadBalanced)
|
188
192
|
return cluster.servers.first
|
189
193
|
end
|
@@ -266,7 +270,7 @@ module Mongo
|
|
266
270
|
end
|
267
271
|
end
|
268
272
|
|
269
|
-
server = try_select_server(cluster, write_aggregation: write_aggregation)
|
273
|
+
server = try_select_server(cluster, write_aggregation: write_aggregation, deprioritized: deprioritized)
|
270
274
|
|
271
275
|
if server
|
272
276
|
unless cluster.topology.compatible?
|
@@ -321,11 +325,15 @@ module Mongo
|
|
321
325
|
# an eligible server.
|
322
326
|
# @param [ true | false ] write_aggregation Whether we need a server that
|
323
327
|
# supports writing aggregations (e.g. with $merge/$out) on secondaries.
|
328
|
+
# @param [ Array<Server> ] deprioritized A list of servers that should
|
329
|
+
# be selected from only if no other servers are available. This is
|
330
|
+
# used to avoid selecting the same server twice in a row when
|
331
|
+
# retrying a command.
|
324
332
|
#
|
325
333
|
# @return [ Server | nil ] A suitable server, if one exists.
|
326
334
|
#
|
327
335
|
# @api private
|
328
|
-
def try_select_server(cluster, write_aggregation: false)
|
336
|
+
def try_select_server(cluster, write_aggregation: false, deprioritized: [])
|
329
337
|
servers = if write_aggregation && cluster.replica_set?
|
330
338
|
# 1. Check if ALL servers in cluster support secondary writes.
|
331
339
|
is_write_supported = cluster.servers.reduce(true) do |res, server|
|
@@ -347,7 +355,7 @@ module Mongo
|
|
347
355
|
# by the selector (e.g. for secondary preferred, the first
|
348
356
|
# server may be a secondary and the second server may be primary)
|
349
357
|
# and we should take the first server here respecting the order
|
350
|
-
server = servers
|
358
|
+
server = suitable_server(servers, deprioritized)
|
351
359
|
|
352
360
|
if server
|
353
361
|
if Lint.enabled?
|
@@ -418,6 +426,24 @@ module Mongo
|
|
418
426
|
|
419
427
|
private
|
420
428
|
|
429
|
+
# Returns a server from the list of servers that is suitable for
|
430
|
+
# executing the operation.
|
431
|
+
#
|
432
|
+
# @param [ Array<Server> ] servers The candidate servers.
|
433
|
+
# @param [ Array<Server> ] deprioritized A list of servers that should
|
434
|
+
# be selected from only if no other servers are available.
|
435
|
+
#
|
436
|
+
# @return [ Server | nil ] The suitable server or nil if no suitable
|
437
|
+
# server is available.
|
438
|
+
def suitable_server(servers, deprioritized)
|
439
|
+
preferred = servers - deprioritized
|
440
|
+
if preferred.empty?
|
441
|
+
servers.first
|
442
|
+
else
|
443
|
+
preferred.first
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
421
447
|
# Convert this server preference definition into a format appropriate
|
422
448
|
# for sending to a MongoDB server (i.e., as a command field).
|
423
449
|
#
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (C) 2024 MongoDB Inc.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module Mongo
|
18
|
+
class Session
|
19
|
+
class ServerSession
|
20
|
+
# Functionality for manipulating and querying a session's
|
21
|
+
# "dirty" state, per the last paragraph at
|
22
|
+
# https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst#server-session-pool
|
23
|
+
#
|
24
|
+
# If a driver has a server session pool and a network error is
|
25
|
+
# encountered when executing any command with a ClientSession, the
|
26
|
+
# driver MUST mark the associated ServerSession as dirty. Dirty server
|
27
|
+
# sessions are discarded when returned to the server session pool. It is
|
28
|
+
# valid for a dirty session to be used for subsequent commands (e.g. an
|
29
|
+
# implicit retry attempt, a later command in a bulk write, or a later
|
30
|
+
# operation on an explicit session), however, it MUST remain dirty for
|
31
|
+
# the remainder of its lifetime regardless if later commands succeed.
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
module Dirtyable
|
35
|
+
# Query whether the server session has been marked dirty or not.
|
36
|
+
#
|
37
|
+
# @return [ true | false ] the server session's dirty state
|
38
|
+
def dirty?
|
39
|
+
@dirty
|
40
|
+
end
|
41
|
+
|
42
|
+
# Mark the server session as dirty (the default) or clean.
|
43
|
+
#
|
44
|
+
# @param [ true | false ] mark whether the mark the server session
|
45
|
+
# dirty or not.
|
46
|
+
def dirty!(mark = true)
|
47
|
+
@dirty = mark
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -15,6 +15,8 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
|
18
|
+
require 'mongo/session/server_session/dirtyable'
|
19
|
+
|
18
20
|
module Mongo
|
19
21
|
|
20
22
|
class Session
|
@@ -25,6 +27,7 @@ module Mongo
|
|
25
27
|
#
|
26
28
|
# @since 2.5.0
|
27
29
|
class ServerSession
|
30
|
+
include Dirtyable
|
28
31
|
|
29
32
|
# Regex for removing dashes from the UUID string.
|
30
33
|
#
|
@@ -25,21 +25,6 @@ module Mongo
|
|
25
25
|
#
|
26
26
|
# @since 2.5.0
|
27
27
|
class SessionPool
|
28
|
-
|
29
|
-
# Create a SessionPool.
|
30
|
-
#
|
31
|
-
# @example
|
32
|
-
# SessionPool.create(cluster)
|
33
|
-
#
|
34
|
-
# @param [ Mongo::Cluster ] cluster The cluster that will be associated with this
|
35
|
-
# session pool.
|
36
|
-
#
|
37
|
-
# @since 2.5.0
|
38
|
-
def self.create(cluster)
|
39
|
-
pool = new(cluster)
|
40
|
-
cluster.instance_variable_set(:@session_pool, pool)
|
41
|
-
end
|
42
|
-
|
43
28
|
# Initialize a SessionPool.
|
44
29
|
#
|
45
30
|
# @example
|
@@ -105,9 +90,7 @@ module Mongo
|
|
105
90
|
|
106
91
|
@mutex.synchronize do
|
107
92
|
prune!
|
108
|
-
|
109
|
-
@queue.unshift(session)
|
110
|
-
end
|
93
|
+
@queue.unshift(session) if return_to_queue?(session)
|
111
94
|
end
|
112
95
|
end
|
113
96
|
|
@@ -136,6 +119,17 @@ module Mongo
|
|
136
119
|
|
137
120
|
private
|
138
121
|
|
122
|
+
# Query whether the given session is okay to return to the
|
123
|
+
# pool's queue.
|
124
|
+
#
|
125
|
+
# @param [ Session::ServerSession ] session the session to query
|
126
|
+
#
|
127
|
+
# @return [ true | false ] whether to return the session to the
|
128
|
+
# queue.
|
129
|
+
def return_to_queue?(session)
|
130
|
+
!session.dirty? && !about_to_expire?(session)
|
131
|
+
end
|
132
|
+
|
139
133
|
def about_to_expire?(session)
|
140
134
|
if session.nil?
|
141
135
|
raise ArgumentError, 'session cannot be nil'
|