Skip to content

Commit

Permalink
Translate Trilogy syscall errors as conn failed
Browse files Browse the repository at this point in the history
At GitHub we get a fair number of Trilogy `ETIMEDOUT` errors (for known
reasons that we might be able to improve somewhat, but I doubt we'll
make them go away entirely). These are very much retryable network
errors, so it'd be handy if these `ETIMEDOUT` errors were translated to
`ConnectionFailed` instead of `StatementInvalid`, making them
`retryable_connection_error`s.

https://github.com/rails/rails/blob/ed2bc92b82ddc111150cdf48bb646fd97b3baacb/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L1077

We're already translating `ECONNRESET` (via matching on the error
message) and `EPIPE` to `ConnectionFailed`. Rather than adding another
case, this commit treats all of the Trilogy `SystemCallError` subclasses
as `ConnectionFailed`.

This requires bumping trilogy 2.7 so we can get
trilogy-libraries/trilogy#143
  • Loading branch information
composerinteralia committed Jan 23, 2024
1 parent 776626f commit 6616770
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 8 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ platforms :ruby, :windows do
group :db do
gem "pg", "~> 1.3"
gem "mysql2", "~> 0.5"
gem "trilogy", ">= 2.5.0"
gem "trilogy", ">= 2.7.0"
end
end

Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ GEM
timeout (0.4.1)
tomlrb (2.0.3)
trailblazer-option (0.1.2)
trilogy (2.6.0)
trilogy (2.7.0)
turbo-rails (1.5.0)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
Expand Down Expand Up @@ -654,7 +654,7 @@ DEPENDENCIES
syntax_tree (= 6.1.1)
tailwindcss-rails
terser (>= 1.1.4)
trilogy (>= 2.5.0)
trilogy (>= 2.7.0)
turbo-rails
tzinfo-data
useragent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require "active_record/connection_adapters/abstract_mysql_adapter"

gem "trilogy", "~> 2.4"
gem "trilogy", "~> 2.7"
require "trilogy"

require "active_record/connection_adapters/trilogy/database_statements"
Expand Down Expand Up @@ -209,10 +209,11 @@ def translate_exception(exception, message:, sql:, binds:)
end

case exception
when Errno::EPIPE, SocketError, IOError
when SocketError, IOError
return ConnectionFailed.new(message, connection_pool: @pool)
when ::Trilogy::Error
if /Connection reset by peer|TRILOGY_CLOSED_CONNECTION|TRILOGY_INVALID_SEQUENCE_ID|TRILOGY_UNEXPECTED_PACKET/.match?(exception.message)
if /TRILOGY_CLOSED_CONNECTION|TRILOGY_INVALID_SEQUENCE_ID|TRILOGY_UNEXPECTED_PACKET/.match?(exception.message) ||
exception.is_a?(SystemCallError)
return ConnectionFailed.new(message, connection_pool: @pool)
end
end
Expand Down
32 changes: 32 additions & 0 deletions activerecord/test/cases/adapters/trilogy/trilogy_adapter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,38 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase
assert_includes error.message, "/var/invalid.sock"
end

test "EPIPE raises ActiveRecord::ConnectionFailed" do
assert_raises(ActiveRecord::ConnectionFailed) do
@conn.raw_connection.stub(:query, -> (*) { raise Trilogy::SyscallError::EPIPE }) do
@conn.execute("SELECT 1")
end
end
end

test "ETIMEDOUT raises ActiveRecord::ConnectionFailed" do
assert_raises(ActiveRecord::ConnectionFailed) do
@conn.raw_connection.stub(:query, -> (*) { raise Trilogy::SyscallError::ETIMEDOUT }) do
@conn.execute("SELECT 1")
end
end
end

test "ECONNREFUSED raises ActiveRecord::ConnectionFailed" do
assert_raises(ActiveRecord::ConnectionFailed) do
@conn.raw_connection.stub(:query, -> (*) { raise Trilogy::SyscallError::ECONNREFUSED }) do
@conn.execute("SELECT 1")
end
end
end

test "ECONNRESET raises ActiveRecord::ConnectionFailed" do
assert_raises(ActiveRecord::ConnectionFailed) do
@conn.raw_connection.stub(:query, -> (*) { raise Trilogy::SyscallError::ECONNRESET }) do
@conn.execute("SELECT 1")
end
end
end

# Create a temporary subscription to verify notification is sent.
# Optionally verify the notification payload includes expected types.
def assert_notification(notification, expected_payload = {}, &block)
Expand Down
2 changes: 1 addition & 1 deletion railties/lib/rails/generators/database.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def initialize(*)
def gem_for_database(database = options[:database])
case database
when "mysql" then ["mysql2", ["~> 0.5"]]
when "trilogy" then ["trilogy", ["~> 2.4"]]
when "trilogy" then ["trilogy", ["~> 2.7"]]
when "postgresql" then ["pg", ["~> 1.1"]]
when "sqlite3" then ["sqlite3", ["~> 1.4"]]
when "oracle" then ["activerecord-oracle_enhanced-adapter", nil]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class ChangeGeneratorTest < Rails::Generators::TestCase

assert_file("Gemfile") do |content|
assert_match "# Use trilogy as the database for Active Record", content
assert_match 'gem "trilogy", "~> 2.4"', content
assert_match 'gem "trilogy", "~> 2.7"', content
end

assert_file("Dockerfile") do |content|
Expand Down

0 comments on commit 6616770

Please sign in to comment.