123456789_123456789_123456789_123456789_123456789_

Class: Mongo::Retryable::ReadWorker Private

Do not use. This class is for internal use only.
Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, BaseWorker, Forwardable
Instance Chain:
self, BaseWorker
Inherits: Mongo::Retryable::BaseWorker
Defined in: lib/mongo/retryable/read_worker.rb

Overview

Implements the logic around retrying read operations.

Since:

  • 2.19.0

Class Method Summary

BaseWorker - Inherited

.new

Constructs a new worker.

Instance Attribute Summary

BaseWorker - Inherited

Instance Method Summary

BaseWorker - Inherited

#deprecation_warning

Logs the given deprecation warning the first time it is called for a given key; after that, it does nothing when given the same key.

#is_legacy_retryable_exception?

Tests to see if the given exception instance is of a type that can be retried with legacy retry mechanism.

#is_retryable_exception?

Tests to see if the given exception instance is of a type that can be retried with modern retry mechanism.

#legacy_retryable_exceptions

Indicate which exception classes that are generally retryable when using legacy retries mechanism.

#log_retry

Log a warning so that any application slow down is immediately obvious.

#overload_error?

Whether the error indicates server overload.

#retry_policy

Returns the retry policy from the client.

#retryable_exceptions

Indicate which exception classes that are generally retryable when using modern retries mechanism.

#retryable_overload_error?

Whether the error is a retryable overload error.

Instance Method Details

#deprecated_legacy_read_with_retry(&block) ⇒ Result (private)

Attempts to do a legacy read_with_retry, without either a session or server_selector. This is a deprecated use-case, and a warning will be issued the first time this is invoked.

Parameters:

  • block (Proc)

    The block to execute.

Returns:

  • (Result)

    The result of the operation.

Since:

  • 2.19.0

[ GitHub ]

  
# File 'lib/mongo/retryable/read_worker.rb', line 170

def deprecated_legacy_read_with_retry(&block)
  deprecation_warning :read_with_retry,
                      'Legacy read_with_retry invocation - ' \
                      'please update the application and/or its dependencies'

  # 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)
end

#legacy_read_with_retry(session, server_selector, context = nil) ⇒ Result (private)

Attempts to do a “legacy” read with retry. The operation will be attempted multiple times, up to the client’s max_read_retries setting.

Parameters:

  • session (Mongo::Session)

    The session that the operation is being run on.

  • server_selector (Mongo::ServerSelector::Selectable)

    ::Mongo::Server selector for the operation.

  • context (Mongo::Operation::Context | nil) (defaults to: nil)

    Context for the read operation.

  • block (Proc)

    The block to execute.

Returns:

  • (Result)

    The result of the operation.

Since:

  • 2.19.0

[ GitHub ]

  
# File 'lib/mongo/retryable/read_worker.rb', line 232

def legacy_read_with_retry(session, server_selector, context = nil)
  context&.check_timeout!
  attempt = attempt ? attempt + 1 : 1
  yield select_server(cluster, server_selector, session)
rescue *legacy_retryable_exceptions, Error::OperationFailure::Family => e
  e.add_notes('legacy retry', "attempt #{attempt}")

  if is_legacy_retryable_exception?(e)
    raise e if attempt > client.max_read_retries || session&.in_transaction?
  elsif e.retryable? && !session&.in_transaction?
    raise e if attempt > client.max_read_retries
  else
    raise e
  end

  log_retry(e, message: 'Legacy read retry')
  sleep(client.read_retry_interval) unless is_retryable_exception?(e)
  retry
end

#modern_read_with_retry(session, server_selector, context, &block) ⇒ Result (private)

Attempts to do a “modern” read with retry. Only a single retry will be attempted.

Parameters:

  • session (Mongo::Session)

    The session that the operation is being run on.

  • server_selector (Mongo::ServerSelector::Selectable)

    ::Mongo::Server selector for the operation.

  • context (Mongo::Operation::Context)

    Context for the read operation.

  • block (Proc)

    The block to execute.

Returns:

  • (Result)

    The result of the operation.

Since:

  • 2.19.0

[ GitHub ]

  
# File 'lib/mongo/retryable/read_worker.rb', line 196

def modern_read_with_retry(session, server_selector, context, &block)
  server = select_server(
    cluster,
    server_selector,
    session,
    timeout: context&.remaining_timeout_sec
  )
  result = yield server
  retry_policy.record_success(is_retry: false)
  result
rescue *retryable_exceptions, Error::OperationFailure::Family, Auth::Unauthorized, Error::PoolError => e
  e.add_notes('modern retry', 'attempt 1')
  raise e if session.in_transaction? && !retryable_overload_error?(e)

  if retryable_overload_error?(e)
    overload_read_retry(e, session, server_selector, context, server, error_count: 1, &block)
  else
    raise e if !is_retryable_exception?(e) && !e.write_retryable?

    retry_read(e, session, server_selector, context: context, failed_server: server, &block)
  end
end

#overload_read_retry(last_error, session, server_selector, context, failed_server, error_count:) (private)

Retry loop for overload errors with exponential backoff. Each retry sleeps with jittered backoff, respects MAX_RETRIES, and consumes a token from the bucket when adaptive retries are enabled.

Since:

  • 2.19.0

[ GitHub ]

  
# File 'lib/mongo/retryable/read_worker.rb', line 339

def overload_read_retry(last_error, session, server_selector, context, failed_server, error_count:)
  loop do
    delay = retry_policy.backoff_delay(error_count)
    raise last_error unless retry_policy.should_retry_overload?(error_count, delay, context: context)

    log_retry(last_error, message: 'Read retry (overload backoff)')
    sleep(delay)

    begin
      server = select_server(
        cluster, server_selector, session, failed_server,
        error: last_error,
        timeout: context&.remaining_timeout_sec
      )
    rescue Error, Error::AuthError => e
      last_error.add_note("later retry failed: #{e.class}: #{e}")
      raise last_error
    end

    begin
      context&.check_timeout!
      result = yield server, true
      retry_policy.record_success(is_retry: true)
      return result
    rescue Error::TimeoutError
      raise
    rescue *retryable_exceptions, Error::OperationFailure::Family, Auth::Unauthorized, Error::PoolError => e
      error_count += 1
      e.add_notes('modern retry', "attempt #{error_count}")
      is_overload = retryable_overload_error?(e)
      raise e unless is_overload || is_retryable_exception?(e) || e.write_retryable?

      retry_policy.record_non_overload_retry_failure unless is_overload
      failed_server = server
      last_error = e
    end
  end
end

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

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

[ GitHub ]

  
# File 'lib/mongo/retryable/read_worker.rb', line 151

def read_with_one_retry(options = nil)
  yield
rescue *retryable_exceptions, Error::PoolError => e
  raise e unless e.write_retryable?

  retry_message = options && options[:retry_message]
  log_retry(e, message: retry_message)
  yield
end

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

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 | nil) (defaults to: nil)

    The session that the operation is being run on.

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

    ::Mongo::Server selector for the operation.

  • context (Mongo::Operation::Context | nil) (defaults to: nil)

    Context for the read operation.

  • block (Proc)

    The block to execute.

Returns:

  • (Result)

    The result of the operation.

Since:

  • 2.19.0

[ GitHub ]

  
# File 'lib/mongo/retryable/read_worker.rb', line 115

def read_with_retry(session = nil, server_selector = nil, context = nil, &block)
  if session.nil? && server_selector.nil?
    deprecated_legacy_read_with_retry(&block)
  elsif session&.retry_reads?
    modern_read_with_retry(session, server_selector, context, &block)
  elsif client.max_read_retries > 0
    legacy_read_with_retry(session, server_selector, context, &block)
  else
    read_without_retry(session, server_selector, &block)
  end
end

#read_with_retry_cursor(session, server_selector, view, context: nil) ⇒ Cursor

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)

    ::Mongo::Server selector for the operation.

  • view (CollectionView)

    The CollectionView defining the query.

  • context (Operation::Context | nil)

    the operation context to use with the cursor.

  • block (Proc)

    The block to execute.

Returns:

  • (Cursor)

    The cursor for the result set.

Since:

  • 2.19.0

[ GitHub ]

  
# File 'lib/mongo/retryable/read_worker.rb', line 66

def read_with_retry_cursor(session, server_selector, view, context: nil)
  read_with_retry(session, server_selector, context) 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, context: context)
    else
      Cursor.new(view, result, server, session: session, context: context)
    end
  end
end

#read_without_retry(session, server_selector) ⇒ Result (private)

Attempts to do a read without a retry; for example, when retries have been explicitly disabled.

Parameters:

  • session (Mongo::Session)

    The session that the operation is being run on.

  • server_selector (Mongo::ServerSelector::Selectable)

    ::Mongo::Server selector for the operation.

  • block (Proc)

    The block to execute.

Returns:

  • (Result)

    The result of the operation.

Since:

  • 2.19.0

[ GitHub ]

  
# File 'lib/mongo/retryable/read_worker.rb', line 262

def read_without_retry(session, server_selector)
  server = select_server(cluster, server_selector, session)

  begin
    yield server
  rescue *retryable_exceptions, Error::PoolError, Error::OperationFailure::Family => e
    e.add_note('retries disabled')
    raise e
  end
end

#retry_read(original_error, session, server_selector, context: nil, failed_server: nil, &block) ⇒ Result (private)

The retry logic of the “modern” read_with_retry implementation.

Parameters:

  • original_error (Exception)

    The original error that triggered the retry.

  • session (Mongo::Session)

    The session that the operation is being run on.

  • server_selector (Mongo::ServerSelector::Selectable)

    ::Mongo::Server selector for the operation.

  • :context (Mongo::Operation::Context | nil)

    Context for the read operation.

  • :failed_server (Mongo::Server | nil)

    The server on which the original operation failed.

  • block (Proc)

    The block to execute.

Returns:

  • (Result)

    The result of the operation.

Since:

  • 2.19.0

[ GitHub ]

  
# File 'lib/mongo/retryable/read_worker.rb', line 288

def retry_read(original_error, session, server_selector, context: nil, failed_server: nil, &block)
  server = select_server_for_retry(
    original_error, session, server_selector, context, failed_server
  )

  log_retry(original_error, message: 'Read retry')

  begin
    context&.check_timeout!
    attempt = attempt ? attempt + 1 : 2
    result = yield server, true
    retry_policy.record_success(is_retry: true)
    result
  rescue Error::TimeoutError
    raise
  rescue *retryable_exceptions => e
    e.add_notes('modern retry', "attempt #{attempt}")
    if retryable_overload_error?(e)
      return overload_read_retry(e, session, server_selector, context, server, error_count: attempt, &block)
    end

    raise e unless context&.csot?

    retry
  rescue Error::OperationFailure::Family, Error::PoolError => e
    e.add_note('modern retry')
    if retryable_overload_error?(e)
      e.add_note("attempt #{attempt}")
      return overload_read_retry(e, session, server_selector, context, server, error_count: attempt, &block)
    end
    if e.write_retryable?
      e.add_note("attempt #{attempt}")
      raise e unless context&.csot?

      retry

    else
      original_error.add_note("later retry failed: #{e.class}: #{e}")
      raise original_error
    end
  rescue Error, Error::AuthError => e
    e.add_note('modern retry')
    original_error.add_note("later retry failed: #{e.class}: #{e}")
    raise original_error
  end
end

#select_server_for_retry(original_error, session, server_selector, context, failed_server) (private)

Since:

  • 2.19.0

[ GitHub ]

  
# File 'lib/mongo/retryable/read_worker.rb', line 378

def select_server_for_retry(original_error, session, server_selector, context, failed_server)
  select_server(
    cluster,
    server_selector,
    session,
    failed_server,
    error: original_error,
    timeout: context&.remaining_timeout_sec
  )
rescue Error, Error::AuthError => e
  original_error.add_note("later retry failed: #{e.class}: #{e}")
  raise original_error
end