Module: Mongo::Retryable

Overview

Defines basic behavior around retrying operations.

Since:

  • 2.1.0

Instance Method Summary collapse

Instance Method Details

#legacy_write_with_retry(server = nil, session = nil) {|server| ... } ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Implements legacy write retrying functionality by yielding to the passed block one or more times.

This method is used for operations which are not supported by modern retryable writes, such as delete_many and update_many.

Parameters:

  • server (Server) (defaults to: nil)

    The server which should be used for the operation. If not provided, the current primary will be retrieved from the cluster.

  • session (nil | Session) (defaults to: nil)

    Optional session to use with the operation.

Yield Parameters:

  • server (Server)

    The server to which the write should be sent.

Since:

  • 2.1.0



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'build/ruby-driver-master/lib/mongo/retryable.rb', line 295

def legacy_write_with_retry(server = nil, session = nil)
  # This is the pre-session retry logic, and is not subject to
  # current retryable write specifications.
  # In particular it does not retry on SocketError and SocketTimeoutError.
  attempt = 0
  begin
    attempt += 1
    server ||= select_server(cluster, ServerSelector.primary, session)
    yield server
  rescue Error::OperationFailure => e
    e.add_note('legacy retry')
    e.add_note("attempt #{attempt}")
    server = nil
    if attempt > client.max_write_retries
      raise e
    end
    if e.label?('RetryableWriteError')
      log_retry(e, message: 'Legacy write retry')
      cluster.scan!(false)
      retry
    else
      raise e
    end
  end
end

#nro_write_with_retry(session, write_concern) {|server| ... } ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Retryable writes wrapper for operations not supporting modern retryable writes.

If the driver is configured to use modern retryable writes, this method yields to the passed block exactly once, thus not retrying any writes.

If the driver is configured to use legacy retryable writes, this method delegates to legacy_write_with_retry which performs write retries using legacy logic.

Parameters:

  • session (nil | Session)

    Optional session to use with the operation.

  • write_concern (nil | Hash | WriteConcern::Base)

    The write concern.

Yield Parameters:

  • server (Server)

    The server to which the write should be sent.

Since:

  • 2.1.0



267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'build/ruby-driver-master/lib/mongo/retryable.rb', line 267

def nro_write_with_retry(session, write_concern, &block)
  if session && session.client.options[:retry_writes]
    server = select_server(cluster, ServerSelector.primary, session)
    begin
      yield server
    rescue Error::SocketError, Error::SocketTimeoutError, Error::OperationFailure => e
      e.add_note('retries disabled')
      raise e
    end
  else
    legacy_write_with_retry(nil, session, &block)
  end
end

#read_with_one_retry(options = nil) { ... } ⇒ Result

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

This only retries read operations on socket errors.

Execute a read operation with a single retry on network errors.

This method is used by the driver for some of the internal housekeeping operations. Application-requested reads should use read_with_retry rather than this method.

Examples:

Execute the read.

read_with_one_retry do
  ...
end

Parameters:

  • options (Hash) (defaults to: nil)

    Options.

Options Hash (options):

  • :retry_message (String)

    Message to log when retrying.

Yields:

  • Calls the provided block with no arguments

Returns:

  • (Result)

    The result of the operation.

Since:

  • 2.2.6



164
165
166
167
168
169
170
# File 'build/ruby-driver-master/lib/mongo/retryable.rb', line 164

def read_with_one_retry(options = nil)
  yield
rescue Error::SocketError, Error::SocketTimeoutError => e
  retry_message = options && options[:retry_message]
  log_retry(e, message: retry_message)
  yield
end

#read_with_retry(session = nil, server_selector = nil, &block) ⇒ Result

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Execute a read operation with retrying.

This method performs server selection for the specified server selector and yields to the provided block, which should execute the initial query operation and return its result. The block will be passed the server selected for the operation. If the block raises an exception, and this exception corresponds to a read retryable error, and read retries are enabled for the client, this method will perform server selection again and yield to the block again (with potentially a different server). If the block returns successfully, the result of the block is returned.

If modern retry reads are on (which is the default), the initial read operation will be retried once. If legacy retry reads are on, the initial read operation will be retried zero or more times depending on the :max_read_retries client setting, the default for which is 1. To disable read retries, turn off modern read retries by setting retry_reads: false and set :max_read_retries to 0 on the client.

Examples:

Execute the read.

read_with_retry(session, server_selector) do |server|
  ...
end

Parameters:

  • session (Mongo::Session) (defaults to: nil)

    The session that the operation is being run on.

  • server_selector (Mongo::ServerSelector::Selectable) (defaults to: nil)

    Server selector for the operation.

  • block (Proc)

    The block to execute.

Returns:

  • (Result)

    The result of the operation.

Since:

  • 2.1.0



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'build/ruby-driver-master/lib/mongo/retryable.rb', line 109

def read_with_retry(session = nil, server_selector = nil, &block)
  if session.nil? && server_selector.nil?
    # Older versions of Mongoid call read_with_retry without arguments.
    # This is already not correct in a MongoDB 3.6+ environment with
    # sessions. For compatibility we emulate the legacy driver behavior
    # here but upgrading Mongoid is strongly recommended.
    unless $_mongo_read_with_retry_warned
      $_mongo_read_with_retry_warned = true
      Logger.logger.warn("Legacy read_with_retry invocation - please update the application and/or its dependencies")
    end
    # Since we don't have a session, we cannot use the modern read retries.
    # And we need to select a server but we don't have a server selector.
    # Use PrimaryPreferred which will work as long as there is a data
    # bearing node in the cluster; the block may select a different server
    # which is fine.
    server_selector = ServerSelector.get(mode: :primary_preferred)
    legacy_read_with_retry(nil, server_selector, &block)
  elsif session && session.retry_reads?
    modern_read_with_retry(session, server_selector, &block)
  elsif client.max_read_retries > 0
    legacy_read_with_retry(session, server_selector, &block)
  else
    server = select_server(cluster, server_selector, session)
    begin
      yield server
    rescue Error::SocketError, Error::SocketTimeoutError, Error::OperationFailure => e
      e.add_note('retries disabled')
      raise e
    end
  end
end

#read_with_retry_cursor(session, server_selector, view, &block) ⇒ Cursor

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Execute a read operation returning a cursor with retrying.

This method performs server selection for the specified server selector and yields to the provided block, which should execute the initial query operation and return its result. The block will be passed the server selected for the operation. If the block raises an exception, and this exception corresponds to a read retryable error, and read retries are enabled for the client, this method will perform server selection again and yield to the block again (with potentially a different server). If the block returns successfully, the result of the block (which should be a Mongo::Operation::Result) is used to construct a Mongo::Cursor object for the result set. The cursor is then returned.

If modern retry reads are on (which is the default), the initial read operation will be retried once. If legacy retry reads are on, the initial read operation will be retried zero or more times depending on the :max_read_retries client setting, the default for which is 1. To disable read retries, turn off modern read retries by setting retry_reads: false and set :max_read_retries to 0 on the client.

Examples:

Execute a read returning a cursor.

cursor = read_with_retry_cursor(session, server_selector, view) do |server|
  # return a Mongo::Operation::Result
  ...
end

Parameters:

  • session (Mongo::Session)

    The session that the operation is being run on.

  • server_selector (Mongo::ServerSelector::Selectable)

    Server selector for the operation.

  • view (CollectionView)

    The CollectionView defining the query.

  • block (Proc)

    The block to execute.

Returns:

  • (Cursor)

    The cursor for the result set.

Since:

  • 2.1.0



62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'build/ruby-driver-master/lib/mongo/retryable.rb', line 62

def read_with_retry_cursor(session, server_selector, view, &block)
  read_with_retry(session, server_selector) do |server|
    result = yield server

    # RUBY-2367: This will be updated to allow the query cache to
    # cache cursors with multi-batch results.
    if QueryCache.enabled? && !view.collection.system_collection?
      CachingCursor.new(view, result, server, session: session)
    else
      Cursor.new(view, result, server, session: session)
    end
  end
end

#write_with_retry(session, write_concern, ending_transaction = false, &block) {|server, txn_num| ... } ⇒ Result

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

This only retries operations on not master failures, since it is the only case we can be sure a partial write did not already occur.

Implements write retrying functionality by yielding to the passed block one or more times.

If the session is provided (hence, the deployment supports sessions), and modern retry writes are enabled on the client, the modern retry logic is invoked. Otherwise the legacy retry logic is invoked.

If ending_transaction parameter is true, indicating that a transaction is being committed or aborted, the operation is executed exactly once. Note that, since transactions require sessions, this method will raise ArgumentError if ending_transaction is true and session is nil.

Examples:

Execute the write.

write_with_retry do
  ...
end

Parameters:

  • session (nil | Session)

    Optional session to use with the operation.

  • write_concern (nil | Hash | WriteConcern::Base)

    The write concern.

  • ending_transaction (true | false) (defaults to: false)

    True if the write operation is abortTransaction or commitTransaction, false otherwise.

  • block (Proc)

    The block to execute.

Yield Parameters:

  • server (Server)

    The server to which the write should be sent.

  • txn_num (Integer)

    Transaction number (NOT the ACID kind).

Returns:

  • (Result)

    The result of the operation.

Since:

  • 2.1.0



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'build/ruby-driver-master/lib/mongo/retryable.rb', line 206

def write_with_retry(session, write_concern, ending_transaction = false, &block)
  if ending_transaction && !session
    raise ArgumentError, 'Cannot end a transaction without a session'
  end

  unless ending_transaction || retry_write_allowed?(session, write_concern)
    return legacy_write_with_retry(nil, session, &block)
  end

  # If we are here, session is not nil. A session being nil would have
  # failed retry_write_allowed? check.

  server = select_server(cluster, ServerSelector.primary, session)

  unless ending_transaction || server.retry_writes?
    return legacy_write_with_retry(server, session, &block)
  end

  txn_num = if session.in_transaction?
    session.txn_num
  else
    session.next_txn_num
  end
  begin
    yield(server, txn_num, false)
  rescue Error::SocketError, Error::SocketTimeoutError => e
    e.add_note('modern retry')
    e.add_note("attempt 1")
    if !e.label?('RetryableWriteError')
      raise e
    end
    retry_write(e, session, txn_num, &block)
  rescue Error::OperationFailure => e
    e.add_note('modern retry')
    e.add_note("attempt 1")
    if e.unsupported_retryable_write?
      raise_unsupported_error(e)
    elsif !e.label?('RetryableWriteError')
      raise e
    end

    retry_write(e, session, txn_num, &block)
  end
end