Class: Mongo::Retryable::WriteWorker Private

Inherits:
BaseWorker show all
Defined in:
lib/mongo/retryable/write_worker.rb

Overview

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

Implements the logic around retrying write operations.

Since:

  • 2.19.0

Instance Attribute Summary

Attributes inherited from BaseWorker

#retryable

Instance Method Summary collapse

Methods inherited from BaseWorker

#initialize

Constructor Details

This class inherits a constructor from Mongo::Retryable::BaseWorker

Instance Method Details

#nro_write_with_retry(_write_concern, context:) {|connection, txn_num, context| ... } ⇒ 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:

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

    The write concern.

  • context (Context)

    The context for the operation.

Yield Parameters:

  • connection (Connection)

    The connection through which the write should be sent.

  • txn_num (nil)

    nil as transaction number.

  • context (Operation::Context)

    The operation context.

Since:

  • 2.19.0



105
106
107
108
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
140
141
142
143
144
145
146
147
148
149
# File 'lib/mongo/retryable/write_worker.rb', line 105

def nro_write_with_retry(_write_concern, context:, &block)
  session = context.session
  server = select_server(cluster, ServerSelector.primary, session)
  options = session&.client&.options || {}

  if options[:retry_writes]
    error_count = 0
    error_to_raise = nil
    begin
      server.with_connection(connection_global_id: context.connection_global_id) do |connection|
        yield connection, nil, context
      end
    rescue Error::TimeoutError
      raise
    rescue *retryable_exceptions, Error::PoolError, Error::OperationFailure::Family => e
      if retryable_overload_error?(e)
        error_count += 1
        error_to_raise ||= e
        unless e.respond_to?(:label?) && e.label?('NoWritesPerformed')
          error_to_raise = e
        end
        delay = retry_policy.backoff_delay(error_count)
        raise error_to_raise unless retry_policy.should_retry_overload?(error_count, delay, context: context)

        log_retry(e, message: 'Write retry (overload backoff)')
        sleep(delay)
        begin
          server = select_server(
            cluster, ServerSelector.primary, session, server,
            error: e, timeout: context.remaining_timeout_sec
          )
        rescue Error, Error::AuthError => select_err
          error_to_raise.add_note("later retry failed: #{select_err.class}: #{select_err}")
          raise error_to_raise
        end
        retry
      else
        e.add_note('retries disabled')
        raise e
      end
    end
  else
    legacy_write_with_retry(server, context: context, &block)
  end
end

#retry_write_allowed?(session, write_concern) ⇒ true | false

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.

Queries whether the session and write concern support retrying writes.

Parameters:

Returns:

  • (true | false)

    Whether write retries are allowed or not.

Since:

  • 2.19.0



159
160
161
162
163
# File 'lib/mongo/retryable/write_worker.rb', line 159

def retry_write_allowed?(session, write_concern)
  return false unless session&.retry_writes?

  write_concern.nil? || WriteConcern.get(write_concern).acknowledged?
end

#write_with_retry(write_concern, context:, ending_transaction: false, &block) {|connection, txn_num, context| ... } ⇒ 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:

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

  • context (Context)

    The context for the operation.

  • block (Proc)

    The block to execute.

Yield Parameters:

  • connection (Connection)

    The connection through which the write should be sent.

  • txn_num (Integer)

    Transaction number (NOT the ACID kind).

  • context (Operation::Context)

    The operation context.

Returns:

  • (Result)

    The result of the operation.

Since:

  • 2.1.0



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/mongo/retryable/write_worker.rb', line 63

def write_with_retry(write_concern, context:, ending_transaction: false, &block)
  session = context.session

  ensure_valid_state!(ending_transaction, session)

  unless ending_transaction || retry_write_allowed?(session, write_concern)
    return legacy_write_with_retry(nil, context: context, &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,
    timeout: context.remaining_timeout_sec
  )

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

  modern_write_with_retry(session, server, context, &block)
end