Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ jobs:
ruby: head
- activerecord: 8.0
ruby: 3.1
- activerecord: 8.0
ruby: head
- activerecord: head
ruby: 3.1
- activerecord: head
ruby: head

env:
BUNDLE_GEMFILE: "${{ github.workspace }}/gemfiles/activerecord_${{ matrix.activerecord }}.gemfile"
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Unreleased

# 4.5.2 (Jun, 2026)
* Use concrete Trilogy connection exception classes in retry configuration.
* Use a retriable-compatible effectively unbounded max elapsed time value.

# 4.5.1 (Jul, 2025)
* Create update before insert trigger

Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
lhm-shopify (4.5.1)
lhm-shopify (4.5.2)
retriable (>= 3.0.0)

GEM
Expand Down
2 changes: 1 addition & 1 deletion gemfiles/activerecord_7.2.gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ..
specs:
lhm-shopify (4.5.1)
lhm-shopify (4.5.2)
retriable (>= 3.0.0)

GEM
Expand Down
2 changes: 1 addition & 1 deletion gemfiles/activerecord_8.0.gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ..
specs:
lhm-shopify (4.5.1)
lhm-shopify (4.5.2)
retriable (>= 3.0.0)

GEM
Expand Down
2 changes: 1 addition & 1 deletion gemfiles/activerecord_head.gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ GIT
PATH
remote: ..
specs:
lhm-shopify (4.5.1)
lhm-shopify (4.5.2)
retriable (>= 3.0.0)

GEM
Expand Down
6 changes: 5 additions & 1 deletion lib/lhm/invoker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ def run
end

def triggers_still_exist?(conn, entangler)
triggers = conn.select_values("SHOW TRIGGERS LIKE '%#{migrator.origin.name}'").select { |name| name =~ /^lhmt/ }
triggers = conn.select_values(
"SHOW TRIGGERS LIKE '%#{migrator.origin.name}'",
should_retry: true,
log_prefix: "Invoker"
).select { |name| name =~ /^lhmt/ }
triggers.sort == entangler.expected_triggers.sort
end

Expand Down
18 changes: 15 additions & 3 deletions lib/lhm/sql_retry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def default_retry_config
base_interval: 1, # the initial interval in seconds between tries.
tries: 20, # Number of attempts to make at running your code block (includes initial attempt).
rand_factor: 0, # percentage to randomize the next retry interval time
max_elapsed_time: Float::INFINITY, # max total time in seconds that code is allowed to keep being retried
max_elapsed_time: Float::MAX, # max total time in seconds that code is allowed to keep being retried
on_retry: Proc.new do |exception, try_number, total_elapsed_time, next_interval|
log_with_prefix("#{exception.class}: '#{exception.message}' - #{try_number} tries in #{total_elapsed_time} seconds and #{next_interval} seconds until the next try.", :error)
end
Expand Down Expand Up @@ -198,15 +198,27 @@ def retriable_trilogy_errors
/connection is locked to hostgroup/,
/The MySQL server is running with the --read-only option so it cannot execute this statement/,
],
Trilogy::ConnectionError => nil,
Trilogy::TimeoutError => nil,
}

retriable_trilogy_connection_error_classes.each do |error_class|
errors[error_class] = nil
end

if ActiveRecord::VERSION::STRING >= "7.1"
errors[ActiveRecord::ConnectionFailed] = nil
end

errors
end

def retriable_trilogy_connection_error_classes

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Learning question — the old setup used the ConnectionError category, which covered everything under it automatically. Now that they're listed out one by one, if trilogy adds a new connection error type later, would we have to remember to add it here too?

errors = []
errors << Trilogy::BaseConnectionError if defined?(Trilogy::BaseConnectionError)
errors << Trilogy::SSLError if defined?(Trilogy::SSLError)
errors << Trilogy::ConnectionClosed if defined?(Trilogy::ConnectionClosed)
errors.concat(Trilogy::SyscallError::ERRORS.values) if defined?(Trilogy::SyscallError::ERRORS)

errors.uniq.select { |error_class| error_class.is_a?(Class) && error_class <= Exception }
end
end
end
2 changes: 1 addition & 1 deletion lib/lhm/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# Schmidt

module Lhm
VERSION = '4.5.1'
VERSION = '4.5.2'
end
11 changes: 8 additions & 3 deletions spec/integration/lhm_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -646,11 +646,13 @@
50.times { |n| execute("insert into users set reference = '#{ n }'") }

insert = Thread.new do
connection = new_mysql_connection
10.times do |n|
connect_master!
execute("insert into users set reference = '#{ 100 + n }'")
connection.query("insert into users set reference = '#{ 100 + n }'")
sleep(0.17)
end
ensure
connection&.close
end
sleep 2

Expand All @@ -670,10 +672,13 @@
50.times { |n| execute("insert into users set reference = '#{ n }'") }

delete = Thread.new do
connection = new_mysql_connection
10.times do |n|
execute("delete from users where reference = '#{ n }'")
connection.query("delete from users where reference = '#{ n }'")
sleep(0.17)
end
ensure
connection&.close
end
sleep 2

Expand Down
35 changes: 35 additions & 0 deletions spec/unit/sql_retry_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require 'minitest/autorun'
require 'minitest/spec'
require 'active_record'
require 'lhm/sql_retry'
require 'trilogy'

describe Lhm::SqlRetry do
describe '#default_retry_config' do
it 'uses an effectively unbounded max elapsed time value compatible with retriable validation' do
retry_config = Lhm::SqlRetry.new(nil).send(:default_retry_config)

assert_equal Float::MAX, retry_config[:max_elapsed_time]
end
end

describe '#retriable_trilogy_errors' do
it 'only uses exception classes as retriable keys' do
retry_errors = Lhm::SqlRetry.new(nil).send(:retriable_trilogy_errors)

retry_errors.each_key do |error_class|
assert error_class.is_a?(Class), "#{error_class.inspect} is not a class"
assert error_class <= Exception, "#{error_class.inspect} is not an exception class"
end
end

it 'matches trilogy connection error classes without using the ConnectionError module' do
retry_errors = Lhm::SqlRetry.new(nil).send(:retriable_trilogy_errors)

refute_includes retry_errors.keys, Trilogy::ConnectionError
assert_includes retry_errors.keys, Trilogy::BaseConnectionError
assert_includes retry_errors.keys, Trilogy::ConnectionClosed
assert_includes retry_errors.keys, Trilogy::SSLError
end
end
end
Loading