module Mongo::Retryable

Defines basic behaviour around retrying operations.

@since 2.1.0

Public Instance Methods

read_with_one_retry() { || ... } click to toggle source

Execute a read operation with a single retry.

@api private

@example Execute the read.

read_with_one_retry do
  ...
end

@note This only retries read operations on socket errors.

@param [ Proc ] block The block to execute.

@return [ Result ] The result of the operation.

@since 2.2.6

# File lib/mongo/retryable.rb, line 78
def read_with_one_retry
  yield
rescue Error::SocketError, Error::SocketTimeoutError
  yield
end
read_with_retry() { || ... } click to toggle source

Execute a read operation with a retry.

@api private

@example Execute the read.

read_with_retry do
  ...
end

@note This only retries read operations on socket errors.

@param [ Proc ] block The block to execute.

@yieldparam [ Server ] server The server to which the write should be sent.

@return [ Result ] The result of the operation.

@since 2.1.0

# File lib/mongo/retryable.rb, line 40
def read_with_retry
  attempt = 0
  begin
    attempt += 1
    yield
  rescue Error::SocketError, Error::SocketTimeoutError => e
    raise(e) if attempt > cluster.max_read_retries
    log_retry(e)
    cluster.scan!
    retry
  rescue Error::OperationFailure => e
    if cluster.sharded? && e.retryable?
      raise(e) if attempt > cluster.max_read_retries
      log_retry(e)
      sleep(cluster.read_retry_interval)
      retry
    else
      raise e
    end
  end
end
write_with_retry(session, write_concern) { |server, txn_num| ... } click to toggle source

Execute a write operation with a retry.

@api private

@example Execute the write.

write_with_retry do
  ...
end

@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.

@param [ Proc ] block The block to execute.

@return [ Result ] The result of the operation.

@since 2.1.0

# File lib/mongo/retryable.rb, line 101
def write_with_retry(session, write_concern, &block)
  unless retry_write_allowed?(session, write_concern)
    return legacy_write_with_retry(&block)
  end

  server = cluster.next_primary
  unless server.retry_writes?
    return legacy_write_with_retry(server, &block)
  end

  begin
    txn_num = session.next_txn_num
    yield(server, txn_num)
  rescue Error::SocketError, Error::SocketTimeoutError => e
    retry_write(e, txn_num, &block)
  rescue Error::OperationFailure => e
    raise e unless e.write_retryable?
    retry_write(e, txn_num, &block)
  end
end

Private Instance Methods

legacy_write_with_retry(server = nil) { |server || next_primary| ... } click to toggle source
# File lib/mongo/retryable.rb, line 146
def legacy_write_with_retry(server = nil)
  attempt = 0
  begin
    attempt += 1
    yield(server || cluster.next_primary)
  rescue Error::OperationFailure => e
    server = nil
    raise(e) if attempt > Cluster::MAX_WRITE_RETRIES
    if e.write_retryable?
      log_retry(e)
      cluster.scan!
      retry
    else
      raise(e)
    end
  end
end
log_retry(e) click to toggle source

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

# File lib/mongo/retryable.rb, line 165
def log_retry(e)
  Logger.logger.warn "Retry due to: #{e.class.name} #{e.message}"
end
retry_write(original_error, txn_num) { |server, txn_num| ... } click to toggle source
# File lib/mongo/retryable.rb, line 129
def retry_write(original_error, txn_num, &block)
  cluster.scan!
  server = cluster.next_primary
  raise original_error unless (server.retry_writes? && txn_num)
  log_retry(original_error)
  yield(server, txn_num)
rescue Error::SocketError, Error::SocketTimeoutError => e
  cluster.scan!
  raise e
rescue Error::OperationFailure => e
  raise original_error unless e.write_retryable?
  cluster.scan!
  raise e
rescue
  raise original_error
end
retry_write_allowed?(session, write_concern) click to toggle source
# File lib/mongo/retryable.rb, line 124
def retry_write_allowed?(session, write_concern)
  session && session.retry_writes? &&
      (write_concern.nil? || write_concern.acknowledged?)
end