Skip to content

Commit ecb2c51

Browse files
RUBY-3390 Add CSOT to list_databases
1 parent 4133a25 commit ecb2c51

File tree

4 files changed

+188
-12
lines changed

4 files changed

+188
-12
lines changed

lib/mongo/client.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -937,8 +937,10 @@ def reconnect
937937
# See https://mongodb.com/docs/manual/reference/command/listDatabases/
938938
# for more information and usage.
939939
# @option opts [ Session ] :session The session to use.
940-
# @option options [ Object ] :comment A user-provided
940+
# @option opts [ Object ] :comment A user-provided
941941
# comment to attach to this command.
942+
# @option opts [ Integer | nil ] :timeout_ms Operation timeout in milliseconds.
943+
# Must a positive integer. The default value is unset which means infinite.
942944
#
943945
# @return [ Array<String> ] The names of the databases.
944946
#
@@ -958,7 +960,11 @@ def database_names(filter = {}, opts = {})
958960
#
959961
# @option opts [ true, false ] :authorized_databases A flag that determines
960962
# which databases are returned based on user privileges when access control
961-
# is enabled
963+
# is enabled.
964+
# @option opts [ Object ] :comment A user-provided
965+
# comment to attach to this command.
966+
# @option opts [ Integer | nil ] :timeout_ms Operation timeout in milliseconds.
967+
# Must a positive integer. The default value is unset which means infinite.
962968
#
963969
# See https://mongodb.com/docs/manual/reference/command/listDatabases/
964970
# for more information and usage.

lib/mongo/collection.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,10 @@ def system_collection?
12001200
name.start_with?('system.')
12011201
end
12021202

1203+
# @return [ Integer | nil ] Operation timeout that is for this database or
1204+
# for the corresponding client.
1205+
#
1206+
# @api private
12031207
def timeout_ms
12041208
options[:timeout_ms] || database.timeout_ms
12051209
end

lib/mongo/database.rb

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,10 @@ def command(operation, opts = {})
245245
#
246246
# @option opts :read [ Hash ] The read preference for this command.
247247
# @option opts :session [ Session ] The session to use for this command.
248+
# @option opts [ Object ] :comment A user-provided
249+
# comment to attach to this command.
250+
# @option opts [ Integer | nil ] :timeout_ms Operation timeout in milliseconds.
251+
# Must a positive integer. The default value is unset which means infinite.
248252
#
249253
# @return [ Hash ] The result of the command execution.
250254
# @api private
@@ -258,15 +262,20 @@ def read_command(operation, opts = {})
258262
Lint.validate_underscore_read_preference(txn_read_pref)
259263
preference = ServerSelector.get(txn_read_pref)
260264

261-
client.send(:with_session, opts) do |session|
262-
read_with_retry(session, preference) do |server|
265+
client.with_session(opts) do |session|
266+
context = Operation::Context.new(
267+
client: client,
268+
session: session,
269+
operation_timeouts: operation_timeouts(opts)
270+
)
271+
read_with_retry(session, preference, context) do |server|
263272
Operation::Command.new(
264273
selector: operation.dup,
265274
db_name: name,
266275
read: preference,
267276
session: session,
268277
comment: opts[:comment],
269-
).execute(server, context: Operation::Context.new(client: client, session: session))
278+
).execute(server, context: context)
270279
end
271280
end
272281
end
@@ -279,14 +288,16 @@ def read_command(operation, opts = {})
279288
# @param [ Hash ] options The options for the operation.
280289
#
281290
# @option options [ Session ] :session The session to use for the operation.
282-
# @option opts [ Hash ] :write_concern The write concern options.
291+
# @option options [ Hash ] :write_concern The write concern options.
292+
# @option options [ Integer | nil ] :timeout_ms Operation timeout in milliseconds.
293+
# Must a positive integer. The default value is unset which means infinite.
283294
#
284295
# @return [ Result ] The result of the command.
285296
#
286297
# @since 2.0.0
287298
def drop(options = {})
288299
operation = { :dropDatabase => 1 }
289-
client.send(:with_session, options) do |session|
300+
client.with_session(options) do |session|
290301
write_concern = if options[:write_concern]
291302
WriteConcern.get(options[:write_concern])
292303
else
@@ -297,7 +308,14 @@ def drop(options = {})
297308
db_name: name,
298309
write_concern: write_concern,
299310
session: session
300-
}).execute(next_primary(nil, session), context: Operation::Context.new(client: client, session: session))
311+
}).execute(
312+
next_primary(nil, session),
313+
context: Operation::Context.new(
314+
client: client,
315+
session: session,
316+
operation_timeouts: operation_timeouts(options)
317+
)
318+
)
301319
end
302320
end
303321

@@ -498,8 +516,27 @@ def self.create(client)
498516
client.instance_variable_set(:@database, database)
499517
end
500518

519+
# @return [ Integer | nil ] Operation timeout that is for this database or
520+
# for the corresponding client.
521+
#
522+
# @api private
501523
def timeout_ms
502524
options[:timeout_ms] || client.timeout_ms
503525
end
526+
527+
# @return [ Hash ] timeout_ms value set on the operation level (if any),
528+
# and/or timeout_ms that is set on collection/database/client level (if any).
529+
#
530+
# @api private
531+
def operation_timeouts(opts)
532+
# TODO: We should re-evaluate if we need two timeouts separately.
533+
{}.tap do |result|
534+
if opts[:timeout_ms].nil?
535+
result[:inherited_timeout_ms] = timeout_ms
536+
else
537+
result[:operation_timeout_ms] = opts.delete(:timeout_ms)
538+
end
539+
end
540+
end
504541
end
505542
end

spec/mongo/client_spec.rb

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,74 @@
561561
expect(command['comment']).to eq('comment')
562562
end
563563
end
564+
565+
context 'with timeout_ms' do
566+
# To make it easier with failCommand
567+
require_topology :single
568+
569+
before do
570+
root_authorized_client.use('admin').command({
571+
configureFailPoint: "failCommand",
572+
mode: "alwaysOn",
573+
data: {
574+
failCommands: ["listDatabases"],
575+
blockConnection: true,
576+
blockTimeMS: 100
577+
}
578+
})
579+
end
580+
581+
after do
582+
root_authorized_client.use('admin').command({
583+
configureFailPoint: "failCommand",
584+
mode: "off"
585+
})
586+
end
587+
588+
context 'when timeout_ms is set on command level' do
589+
context 'when there is not enough time' do
590+
it 'raises' do
591+
expect do
592+
monitored_client.database_names({}, timeout_ms: 50)
593+
end.to raise_error(Mongo::Error::TimeoutError)
594+
end
595+
end
596+
597+
context 'when there is enough time' do
598+
it 'does not raise' do
599+
expect do
600+
monitored_client.database_names({}, timeout_ms: 200)
601+
end.not_to raise_error
602+
end
603+
end
604+
end
605+
606+
context 'when timeout_ms is set on client level' do
607+
context 'when there is not enough time' do
608+
let(:client) do
609+
root_authorized_client.with(timeout_ms: 50)
610+
end
611+
612+
it 'raises' do
613+
expect do
614+
client.database_names({})
615+
end.to raise_error(Mongo::Error::TimeoutError)
616+
end
617+
end
618+
619+
context 'when there is enough time' do
620+
let(:client) do
621+
root_authorized_client.with(timeout_ms: 200)
622+
end
623+
624+
it 'does not raise' do
625+
expect do
626+
monitored_client.database_names({})
627+
end.not_to raise_error
628+
end
629+
end
630+
end
631+
end
564632
end
565633

566634
describe '#list_databases' do
@@ -572,8 +640,6 @@
572640
end
573641

574642
context 'when filter criteria is present' do
575-
min_server_fcv '3.6'
576-
577643
include_context 'ensure test db exists'
578644

579645
let(:result) do
@@ -591,8 +657,6 @@
591657
end
592658

593659
context 'when name_only is true' do
594-
min_server_fcv '3.6'
595-
596660
let(:command) do
597661
Utils.get_command_event(root_authorized_client, 'listDatabases') do |client|
598662
client.list_databases({}, true)
@@ -667,6 +731,71 @@
667731
expect(command['comment']).to eq('comment')
668732
end
669733
end
734+
735+
context 'with timeout_ms' do
736+
before do
737+
root_authorized_client.use('admin').command({
738+
configureFailPoint: "failCommand",
739+
mode: "alwaysOn",
740+
data: {
741+
failCommands: ["listDatabases"],
742+
blockConnection: true,
743+
blockTimeMS: 100
744+
}
745+
})
746+
end
747+
748+
after do
749+
root_authorized_client.use('admin').command({
750+
configureFailPoint: "failCommand",
751+
mode: "off"
752+
})
753+
end
754+
755+
context 'when timeout_ms is set on command level' do
756+
context 'when there is not enough time' do
757+
it 'raises' do
758+
expect do
759+
monitored_client.list_databases({}, false, timeout_ms: 50)
760+
end.to raise_error(Mongo::Error::TimeoutError)
761+
end
762+
end
763+
764+
context 'when there is enough time' do
765+
it 'does not raise' do
766+
expect do
767+
monitored_client.list_databases({}, false, timeout_ms: 200)
768+
end.not_to raise_error
769+
end
770+
end
771+
end
772+
773+
context 'when timeout_ms is set on client level' do
774+
context 'when there is not enough time' do
775+
let(:client) do
776+
root_authorized_client.with(timeout_ms: 50)
777+
end
778+
779+
it 'raises' do
780+
expect do
781+
client.list_databases({})
782+
end.to raise_error(Mongo::Error::TimeoutError)
783+
end
784+
end
785+
786+
context 'when there is enough time' do
787+
let(:client) do
788+
root_authorized_client.with(timeout_ms: 200)
789+
end
790+
791+
it 'does not raise' do
792+
expect do
793+
monitored_client.list_databases({})
794+
end.not_to raise_error
795+
end
796+
end
797+
end
798+
end
670799
end
671800

672801
describe '#list_mongo_databases' do

0 commit comments

Comments
 (0)