Skip to content

Commit e3deb75

Browse files
authored
Merge pull request rails#49811 from composerinteralia/verify-only-when-connection-exercised
Prevent marking broken connections as verified
2 parents 939fd3b + a180823 commit e3deb75

File tree

7 files changed

+57
-10
lines changed

7 files changed

+57
-10
lines changed

activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,11 @@ def reconnect_can_restore_state?
977977
# If +allow_retry+ is true, a connection-related exception will
978978
# cause an automatic reconnect and re-run of the block, up to
979979
# the connection's configured +connection_retries+ setting
980-
# and the configured +retry_deadline+ limit.
980+
# and the configured +retry_deadline+ limit. (Note that when
981+
# +allow_retry+ is true, it's possible to return without having marked
982+
# the connection as verified. If the block is guaranteed to exercise the
983+
# connection, consider calling `verified!` to avoid needless
984+
# verification queries in subsequent calls.)
981985
#
982986
# If +materialize_transactions+ is false, the block will be run without
983987
# ensuring virtual transactions have been materialized in the DB
@@ -1028,9 +1032,7 @@ def with_raw_connection(allow_retry: false, materialize_transactions: true)
10281032
end
10291033

10301034
begin
1031-
result = yield @raw_connection
1032-
@verified = true
1033-
result
1035+
yield @raw_connection
10341036
rescue => original_exception
10351037
translated_exception = translate_exception_class(original_exception, nil, nil)
10361038
invalidate_transaction(translated_exception)
@@ -1065,6 +1067,13 @@ def with_raw_connection(allow_retry: false, materialize_transactions: true)
10651067
end
10661068
end
10671069

1070+
# Mark the connection as verified. Call this inside a
1071+
# `with_raw_connection` block only when the block is guaranteed to
1072+
# exercise the raw connection.
1073+
def verified!
1074+
@verified = true
1075+
end
1076+
10681077
def retryable_connection_error?(exception)
10691078
exception.is_a?(ConnectionNotEstablished) || exception.is_a?(ConnectionFailed)
10701079
end

activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transac
102102
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
103103
sync_timezone_changes(conn)
104104
result = conn.query(sql)
105+
verified!
105106
handle_warnings(sql)
106107
result
107108
end
@@ -130,6 +131,8 @@ def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
130131
result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
131132
stmt.execute(*type_casted_binds)
132133
end
134+
verified!
135+
result
133136
rescue ::Mysql2::Error => e
134137
if cache_stmt
135138
@statements.delete(sql)

activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ def query(sql, name = nil) # :nodoc:
1616

1717
log(sql, name) do
1818
with_raw_connection do |conn|
19-
conn.async_exec(sql).map_types!(@type_map_for_results).values
19+
result = conn.async_exec(sql).map_types!(@type_map_for_results).values
20+
verified!
21+
result
2022
end
2123
end
2224
end
@@ -51,6 +53,7 @@ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transac
5153
log(sql, name, async: async) do
5254
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
5355
result = conn.async_exec(sql)
56+
verified!
5457
handle_warnings(result)
5558
result
5659
end

activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,9 @@ def exec_no_cache(sql, name, binds, async:, allow_retry:, materialize_transactio
893893
type_casted_binds = type_casted_binds(binds)
894894
log(sql, name, binds, type_casted_binds, async: async) do
895895
with_raw_connection do |conn|
896-
conn.exec_params(sql, type_casted_binds)
896+
result = conn.exec_params(sql, type_casted_binds)
897+
verified!
898+
result
897899
end
898900
end
899901
end
@@ -908,7 +910,9 @@ def exec_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:
908910
type_casted_binds = type_casted_binds(binds)
909911

910912
log(sql, name, binds, type_casted_binds, stmt_key, async: async) do
911-
conn.exec_prepared(stmt_key, type_casted_binds)
913+
result = conn.exec_prepared(stmt_key, type_casted_binds)
914+
verified!
915+
result
912916
end
913917
end
914918
rescue ActiveRecord::StatementInvalid => e

activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: fals
5050
stmt.bind_params(type_casted_binds)
5151
records = stmt.to_a
5252
end
53+
verified!
5354

5455
build_result(columns: cols, rows: records)
5556
end
@@ -76,7 +77,9 @@ def begin_isolated_db_transaction(isolation) # :nodoc:
7677
def begin_db_transaction # :nodoc:
7778
log("begin transaction", "TRANSACTION") do
7879
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
79-
conn.transaction
80+
result = conn.transaction
81+
verified!
82+
result
8083
end
8184
end
8285
end
@@ -112,7 +115,9 @@ def high_precision_current_timestamp
112115
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: false)
113116
log(sql, name, async: async) do
114117
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
115-
conn.execute(sql)
118+
result = conn.execute(sql)
119+
verified!
120+
result
116121
end
117122
end
118123
end
@@ -133,7 +138,9 @@ def execute_batch(statements, name = nil)
133138

134139
log(sql, name) do
135140
with_raw_connection do |conn|
136-
conn.execute_batch2(sql)
141+
result = conn.execute_batch2(sql)
142+
verified!
143+
result
137144
end
138145
end
139146
end

activerecord/lib/active_record/connection_adapters/trilogy/database_statements.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transac
4747
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
4848
sync_timezone_changes(conn)
4949
result = conn.query(sql)
50+
verified!
5051
handle_warnings(sql)
5152
result
5253
end

activerecord/test/cases/adapter_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,26 @@ def teardown
612612
assert_predicate @connection, :active?
613613
end
614614

615+
test "quoting a string on a 'clean' failed connection will not prevent reconnecting" do
616+
remote_disconnect @connection
617+
618+
@connection.clean! # this simulates a fresh checkout from the pool
619+
620+
# Clean did not verify / fix the connection
621+
assert_not_predicate @connection, :active?
622+
623+
# Quote string will not verify a broken connection (although it may
624+
# reconnect in some cases)
625+
Post.connection.quote_string("")
626+
627+
# Because the connection hasn't been verified since checkout,
628+
# and the query cannot safely be retried, the connection will be
629+
# verified before querying.
630+
Post.delete_all
631+
632+
assert_predicate @connection, :active?
633+
end
634+
615635
test "querying after a failed query restores and succeeds" do
616636
Post.first # Connection verified (and prepared statement pool populated if enabled)
617637

0 commit comments

Comments
 (0)