Skip to content

Commit e978484

Browse files
comandeo-mongop
andauthored
RUBY-3035 Reuse client in auto encrypter if unlimited pool (#2552)
* RUBY-3035 Reuse client in auto encrypter if unlimited pool * 3035 * 3035 * 3035 * 3035 * 3035 * close newly made clients in auto encrypter if its initialization fails * add note * add normal cleanup for newly created clients * fix pool sizes, add tests * use mrss helper for event retrieval with earlier assertions on existence of the needed events * fix command monitoring tests * collapse back into #with * fix stress test * update mrss * remove max pool size setting * redo the tests Co-authored-by: Oleg Pudeyev <[email protected]>
1 parent bad7fdc commit e978484

File tree

8 files changed

+347
-198
lines changed

8 files changed

+347
-198
lines changed

lib/mongo/client.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,7 @@ def with(new_options = nil)
767767
# We can't use the same cluster if some options that would affect it
768768
# have changed.
769769
if cluster_modifying?(opts)
770-
Cluster.create(client)
770+
Cluster.create(client, monitoring: opts[:monitoring])
771771
end
772772
end
773773
end

lib/mongo/cluster.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,20 +280,22 @@ def initialize(seeds, monitoring, options = Options::Redacted.new)
280280
# Create a cluster for the provided client, for use when we don't want the
281281
# client's original cluster instance to be the same.
282282
#
283-
# @api private
284-
#
285283
# @example Create a cluster for the client.
286284
# Cluster.create(client)
287285
#
288286
# @param [ Client ] client The client to create on.
287+
# @param [ Monitoring | nil ] monitoring. The monitoring instance to use
288+
# with the new cluster. If nil, a new instance of Monitoring will be
289+
# created.
289290
#
290291
# @return [ Cluster ] The cluster.
291292
#
292293
# @since 2.0.0
293-
def self.create(client)
294+
# @api private
295+
def self.create(client, monitoring: nil)
294296
cluster = Cluster.new(
295297
client.cluster.addresses.map(&:to_s),
296-
Monitoring.new,
298+
monitoring || Monitoring.new,
297299
client.cluster_options,
298300
)
299301
client.instance_variable_set(:@cluster, cluster)

lib/mongo/crypt/auto_encrypter.rb

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class AutoEncrypter
8585
# @raise [ ArgumentError ] If required options are missing or incorrectly
8686
# formatted.
8787
def initialize(options)
88+
# Note that this call may eventually, via other method invocations,
89+
# create additional clients which have to be cleaned up.
8890
@options = set_default_options(options).freeze
8991

9092
@crypt_handle = Crypt::Handle.new(
@@ -123,6 +125,30 @@ def initialize(options)
123125
end
124126
raise
125127
end
128+
rescue
129+
if @key_vault_client && @key_vault_client != options[:client] &&
130+
@key_vault_client.cluster != options[:client].cluster
131+
then
132+
begin
133+
@key_vault_client.close
134+
rescue => e
135+
log_warn("Error closing key vault client in auto encrypter's constructor: #{e.class}: #{e}")
136+
# Drop this exception so that the original exception is raised
137+
end
138+
end
139+
140+
if @metadata_client && @metadata_client != options[:client] &&
141+
@metadata_client.cluster != options[:client].cluster
142+
then
143+
begin
144+
@metadata_client.close
145+
rescue => e
146+
log_warn("Error closing metadata client in auto encrypter's constructor: #{e.class}: #{e}")
147+
# Drop this exception so that the original exception is raised
148+
end
149+
end
150+
151+
raise
126152
end
127153

128154
# Whether this encrypter should perform encryption (returns false if
@@ -168,6 +194,18 @@ def decrypt(command)
168194
def close
169195
@mongocryptd_client.close if @mongocryptd_client
170196

197+
if @key_vault_client && @key_vault_client != options[:client] &&
198+
@key_vault_client.cluster != options[:client].cluster
199+
then
200+
@key_vault_client.close
201+
end
202+
203+
if @metadata_client && @metadata_client != options[:client] &&
204+
@metadata_client.cluster != options[:client].cluster
205+
then
206+
@metadata_client.close
207+
end
208+
171209
true
172210
end
173211

@@ -210,26 +248,16 @@ def set_or_create_clients(options)
210248
client = options[:client]
211249
@key_vault_client = if options[:key_vault_client]
212250
options[:key_vault_client]
213-
# https://jira.mongodb.org/browse/RUBY-3010
214-
# https://jira.mongodb.org/browse/RUBY-3011
215-
# Specification requires to use existing client when connection pool
216-
# size is unlimited (0). Ruby driver does not support unlimited pool
217-
# size.
218-
# elsif client.options[:max_pool_size] == 0
219-
# client
251+
elsif client.options[:max_pool_size] == 0
252+
client
220253
else
221254
internal_client(client)
222255
end
223256

224257
@metadata_client = if options[:bypass_auto_encryption]
225258
nil
226-
# Specification requires to use existing client when connection pool
227-
# size is unlimited (0). Ruby driver does not support unlimited pool
228-
# size.
229-
# https://jira.mongodb.org/browse/RUBY-3010
230-
# https://jira.mongodb.org/browse/RUBY-3011
231-
# elsif client.options[:max_pool_size] == 0
232-
# client
259+
elsif client.options[:max_pool_size] == 0
260+
client
233261
else
234262
internal_client(client)
235263
end
@@ -246,12 +274,8 @@ def set_or_create_clients(options)
246274
def internal_client(client)
247275
@internal_client ||= client.with(
248276
auto_encryption_options: nil,
249-
# Specification requires that the internal client's connection pool
250-
# size is unlimited (0). Ruby driver does not support unlimited pool
251-
# size.
252-
# https://jira.mongodb.org/browse/RUBY-3010
253-
# https://jira.mongodb.org/browse/RUBY-3011
254-
# max_pool_size: 0
277+
min_pool_size: 0,
278+
monitoring: client.send(:monitoring),
255279
)
256280
end
257281
end

spec/integration/client_construction_spec.rb

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,16 +191,82 @@
191191
context 'with default key vault client' do
192192
let(:key_vault_client) { nil }
193193

194-
it 'creates a working key vault client' do
195-
key_vault_client = client.encrypter.key_vault_client
194+
shared_examples 'creates a working key vault client' do
195+
it 'creates a working key vault client' do
196+
key_vault_client = client.encrypter.key_vault_client
196197

197-
result = key_vault_client[:test].insert_one(test: 1)
198-
expect(result).to be_ok
198+
result = key_vault_client[:test].insert_one(test: 1)
199+
expect(result).to be_ok
200+
end
199201
end
200202

201-
it 'creates a key vault client with the same cluster as the existing client' do
202-
key_vault_client = client.encrypter.key_vault_client
203-
expect(key_vault_client.cluster).to eq(client.cluster)
203+
context 'when top-level max pool size is not 0' do
204+
include_examples 'creates a working key vault client'
205+
206+
shared_examples 'limited connection pool' do
207+
it 'creates a key vault client with a different cluster than the existing client' do
208+
key_vault_client = client.encrypter.key_vault_client
209+
expect(key_vault_client.cluster).not_to eq(client.cluster)
210+
end
211+
212+
# min pool size for the key vault client can be greater than 0
213+
# when the key vault client is the same as the top-level client.
214+
# This is OK because we aren't making any more connections for FLE,
215+
# the minimum was requested by application for its own needs.
216+
it 'uses min pool size 0 for key vault client' do
217+
key_vault_client = client.encrypter.key_vault_client
218+
key_vault_client.options[:min_pool_size].should be 0
219+
end
220+
end
221+
222+
context 'when top-level max pool size is not specified' do
223+
before do
224+
client.options[:max_pool_size].should be nil
225+
end
226+
227+
include_examples 'limited connection pool'
228+
229+
it 'uses unspecified max pool size for key vault client' do
230+
key_vault_client = client.encrypter.key_vault_client
231+
key_vault_client.options[:max_pool_size].should be nil
232+
end
233+
end
234+
235+
context 'when top-level max pool size is specified' do
236+
let(:options) do
237+
{
238+
auto_encryption_options: auto_encryption_options,
239+
max_pool_size: 42,
240+
}
241+
end
242+
243+
include_examples 'limited connection pool'
244+
245+
it 'uses the same max pool size for key vault client' do
246+
key_vault_client = client.encrypter.key_vault_client
247+
key_vault_client.options[:max_pool_size].should be 42
248+
end
249+
end
250+
end
251+
252+
context 'when top-level max pool size is 0' do
253+
let(:options) do
254+
{
255+
auto_encryption_options: auto_encryption_options,
256+
max_pool_size: 0,
257+
}
258+
end
259+
260+
before do
261+
client.options[:max_pool_size].should be 0
262+
end
263+
264+
include_examples 'creates a working key vault client'
265+
266+
it 'creates a key vault client with the same cluster as the existing client' do
267+
key_vault_client = client.encrypter.key_vault_client
268+
expect(key_vault_client.cluster).to eq(client.cluster)
269+
end
204270
end
205271
end
206272
end

0 commit comments

Comments
 (0)