Lock the monitor, ensure we’re properly connected and transactions are
materialized, and then yield the underlying raw connection object.
If allow_retry is true, a connection-related exception will cause
an automatic reconnect
and re-run of the block, up to the connection’s configured connection_retries
setting and the configured retry_deadline
limit. (Note that when allow_retry is true, it’s possible to
return without having marked the connection as verified. If the block is
guaranteed to exercise the connection, consider calling `verified!` to
avoid needless verification queries in subsequent calls.)
If materialize_transactions is false, the block will be run
without ensuring virtual transactions have been materialized in the DB
server’s state. The active transaction will also remain clean (if it is
not already dirty), meaning it’s able to be restored by reconnecting and
opening an equivalent-depth set of new
transactions. This should only be used by transaction control methods, and
internal transaction-agnostic queries.
It’s not the primary use case, so not something to optimize for, but note
that this method does need to be re-entrant:
materialize_transactions will re-enter if it has work to do, and
the yield block can also do so under some circumstances.
In the latter case, we really ought to guarantee the inner call will not reconnect
(which would interfere with the still-yielded connection in the outer
block), but we currently provide no special enforcement there.
# File activerecord/lib/active_record/connection_adapters/abstract_adapter.rb, line 999
def with_raw_connection(allow_retry: false, materialize_transactions: true)
@lock.synchronize do
connect! if @raw_connection.nil? && reconnect_can_restore_state?
self.materialize_transactions if materialize_transactions
retries_available = allow_retry ? connection_retries : 0
deadline = retry_deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) + retry_deadline
reconnectable = reconnect_can_restore_state?
if @verified
# Cool, we're confident the connection's ready to use. (Note this might have
# become true during the above #materialize_transactions.)
elsif reconnectable
if allow_retry
# Not sure about the connection yet, but if anything goes wrong we can
# just reconnect and re-run our query
else
# We can reconnect if needed, but we don't trust the upcoming query to be
# safely re-runnable: let's verify the connection to be sure
verify!
end
else
# We don't know whether the connection is okay, but it also doesn't matter:
# we wouldn't be able to reconnect anyway. We're just going to run our query
# and hope for the best.
end
begin
yield @raw_connection
rescue => original_exception
translated_exception = translate_exception_class(original_exception, nil, nil)
invalidate_transaction(translated_exception)
retry_deadline_exceeded = deadline && deadline < Process.clock_gettime(Process::CLOCK_MONOTONIC)
if !retry_deadline_exceeded && retries_available > 0
retries_available -= 1
if retryable_query_error?(translated_exception)
backoff(connection_retries - retries_available)
retry
elsif reconnectable && retryable_connection_error?(translated_exception)
reconnect!(restore_transactions: true)
# Only allowed to reconnect once, because reconnect! has its own retry
# loop
reconnectable = false
retry
end
end
unless retryable_query_error?(translated_exception)
# Barring a known-retryable error inside the query (regardless of
# whether we were in a _position_ to retry it), we should infer that
# there's likely a real problem with the connection.
@verified = false
end
raise translated_exception
ensure
dirty_current_transaction if materialize_transactions
end
end
end