Module: Mongo::Retryable
- Included in:
- Cluster::CursorReaper, Collection, Collection::View::Aggregation, Collection::View::MapReduce, Cursor, Database, Database::View, Index::View, Server::Connection, Server::Monitor::Connection, Session
- Defined in:
- lib/mongo/retryable.rb
Overview
Defines basic behavior around retrying operations.
Instance Method Summary collapse
-
#legacy_write_with_retry(server = nil, session = nil) {|server| ... } ⇒ Object
private
Implements legacy write retrying functionality by yielding to the passed block one or more times.
-
#nro_write_with_retry(session, write_concern) {|server| ... } ⇒ Object
private
Retryable writes wrapper for operations not supporting modern retryable writes.
-
#read_with_one_retry(options = nil) { ... } ⇒ Result
private
Execute a read operation with a single retry on network errors.
-
#read_with_retry(session = nil, server_selector = nil, &block) ⇒ Result
private
Execute a read operation with retrying.
-
#read_with_retry_cursor(session, server_selector, view, &block) ⇒ Cursor
private
Execute a read operation returning a cursor with retrying.
-
#write_with_retry(session, write_concern, ending_transaction = false, &block) {|server, txn_num| ... } ⇒ Result
private
Implements write retrying functionality by yielding to the passed block one or more times.
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.
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
# File 'lib/mongo/retryable.rb', line 285 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.write_retryable? && !(session && session.in_transaction?) 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.
257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/mongo/retryable.rb', line 257 def nro_write_with_retry(session, write_concern, &block) if session && session.client.[: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.
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.
154 155 156 157 158 159 160 |
# File 'lib/mongo/retryable.rb', line 154 def read_with_one_retry( = nil) yield rescue Error::SocketError, Error::SocketTimeoutError => e = && [:retry_message] log_retry(e, 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.
99 100 101 102 103 104 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 |
# File 'lib/mongo/retryable.rb', line 99 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.
59 60 61 62 63 64 |
# File 'lib/mongo/retryable.rb', line 59 def read_with_retry_cursor(session, server_selector, view, &block) read_with_retry(session, server_selector) do |server| result = yield server Cursor.new(view, result, server, session: session) 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.
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.
196 197 198 199 200 201 202 203 204 205 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 |
# File 'lib/mongo/retryable.rb', line 196 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 session.in_transaction? && !ending_transaction 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 (session.in_transaction? && !ending_transaction) || !e.write_retryable? raise e end retry_write(e, session, txn_num, &block) end end |