From 1e71231b66ec2ef9cd1bbe0ecb3cc5f7cde323b7 Mon Sep 17 00:00:00 2001 From: Neil Anderson Date: Thu, 11 Jun 2026 23:15:27 -0400 Subject: [PATCH 1/6] Use concrete Trilogy retry exception classes --- CHANGELOG.md | 1 + lib/lhm/sql_retry.rb | 16 ++++++++++++++-- spec/unit/sql_retry_spec.rb | 27 +++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 spec/unit/sql_retry_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 034ea0b9..0c05271b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +* Use concrete Trilogy connection exception classes in retry configuration. # 4.5.1 (Jul, 2025) * Create update before insert trigger diff --git a/lib/lhm/sql_retry.rb b/lib/lhm/sql_retry.rb index 152edc2b..193c77c0 100644 --- a/lib/lhm/sql_retry.rb +++ b/lib/lhm/sql_retry.rb @@ -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 + 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 diff --git a/spec/unit/sql_retry_spec.rb b/spec/unit/sql_retry_spec.rb new file mode 100644 index 00000000..fb2bd735 --- /dev/null +++ b/spec/unit/sql_retry_spec.rb @@ -0,0 +1,27 @@ +require 'minitest/autorun' +require 'minitest/spec' +require 'active_record' +require 'lhm/sql_retry' +require 'trilogy' + +describe Lhm::SqlRetry do + 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 From ef6916626de5b290690a8165906c82e874b676ae Mon Sep 17 00:00:00 2001 From: Neil Anderson Date: Thu, 11 Jun 2026 23:28:43 -0400 Subject: [PATCH 2/6] Retry trigger checks during LHM invocations --- .github/workflows/test.yml | 4 ++++ lib/lhm/invoker.rb | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 09977bc4..e96db0e1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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" diff --git a/lib/lhm/invoker.rb b/lib/lhm/invoker.rb index c9d3484a..6b9f5f74 100644 --- a/lib/lhm/invoker.rb +++ b/lib/lhm/invoker.rb @@ -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 From 2f72ea16fb9d57c1f53b685a5e8fdaf19f7a3ff3 Mon Sep 17 00:00:00 2001 From: Neil Anderson Date: Fri, 12 Jun 2026 00:02:40 -0400 Subject: [PATCH 3/6] Use separate connections in parallel integration tests --- spec/integration/lhm_spec.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/spec/integration/lhm_spec.rb b/spec/integration/lhm_spec.rb index 747d88e1..539e33c4 100644 --- a/spec/integration/lhm_spec.rb +++ b/spec/integration/lhm_spec.rb @@ -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 @@ -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 From f4697fc08bf72afae7710de3e2a2fa7a99d417cf Mon Sep 17 00:00:00 2001 From: Neil Anderson Date: Fri, 12 Jun 2026 01:40:12 -0400 Subject: [PATCH 4/6] Bump version to 4.5.2 --- CHANGELOG.md | 2 ++ Gemfile.lock | 2 +- gemfiles/activerecord_7.2.gemfile.lock | 2 +- gemfiles/activerecord_8.0.gemfile.lock | 2 +- gemfiles/activerecord_head.gemfile.lock | 2 +- lib/lhm/version.rb | 2 +- 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c05271b..c89b56af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ # Unreleased + +# 4.5.2 (Jun, 2026) * Use concrete Trilogy connection exception classes in retry configuration. # 4.5.1 (Jul, 2025) diff --git a/Gemfile.lock b/Gemfile.lock index 6e8cd32b..67976c0a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - lhm-shopify (4.5.1) + lhm-shopify (4.5.2) retriable (>= 3.0.0) GEM diff --git a/gemfiles/activerecord_7.2.gemfile.lock b/gemfiles/activerecord_7.2.gemfile.lock index addbd25e..57700f3f 100644 --- a/gemfiles/activerecord_7.2.gemfile.lock +++ b/gemfiles/activerecord_7.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - lhm-shopify (4.5.1) + lhm-shopify (4.5.2) retriable (>= 3.0.0) GEM diff --git a/gemfiles/activerecord_8.0.gemfile.lock b/gemfiles/activerecord_8.0.gemfile.lock index 8217011a..4b67a09a 100644 --- a/gemfiles/activerecord_8.0.gemfile.lock +++ b/gemfiles/activerecord_8.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - lhm-shopify (4.5.1) + lhm-shopify (4.5.2) retriable (>= 3.0.0) GEM diff --git a/gemfiles/activerecord_head.gemfile.lock b/gemfiles/activerecord_head.gemfile.lock index 3fb89740..4a8ea557 100644 --- a/gemfiles/activerecord_head.gemfile.lock +++ b/gemfiles/activerecord_head.gemfile.lock @@ -26,7 +26,7 @@ GIT PATH remote: .. specs: - lhm-shopify (4.5.1) + lhm-shopify (4.5.2) retriable (>= 3.0.0) GEM diff --git a/lib/lhm/version.rb b/lib/lhm/version.rb index 850d0334..1be7453c 100644 --- a/lib/lhm/version.rb +++ b/lib/lhm/version.rb @@ -2,5 +2,5 @@ # Schmidt module Lhm - VERSION = '4.5.1' + VERSION = '4.5.2' end From 5e2f4123cf23dbadc1d08635080346649fb26309 Mon Sep 17 00:00:00 2001 From: Neil Anderson Date: Fri, 12 Jun 2026 08:27:00 -0400 Subject: [PATCH 5/6] Use retriable-compatible max elapsed time --- CHANGELOG.md | 1 + lib/lhm/sql_retry.rb | 2 +- spec/unit/sql_retry_spec.rb | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c89b56af..fc365fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ # 4.5.2 (Jun, 2026) * Use concrete Trilogy connection exception classes in retry configuration. +* Use a retriable-compatible unbounded max elapsed time value. # 4.5.1 (Jul, 2025) * Create update before insert trigger diff --git a/lib/lhm/sql_retry.rb b/lib/lhm/sql_retry.rb index 193c77c0..dc5ccbdb 100644 --- a/lib/lhm/sql_retry.rb +++ b/lib/lhm/sql_retry.rb @@ -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: nil, # 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 diff --git a/spec/unit/sql_retry_spec.rb b/spec/unit/sql_retry_spec.rb index fb2bd735..ea91d030 100644 --- a/spec/unit/sql_retry_spec.rb +++ b/spec/unit/sql_retry_spec.rb @@ -5,6 +5,14 @@ require 'trilogy' describe Lhm::SqlRetry do + describe '#default_retry_config' do + it 'uses an unbounded max elapsed time value compatible with retriable validation' do + retry_config = Lhm::SqlRetry.new(nil).send(:default_retry_config) + + assert_nil 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) From 8ef59c6f47128685ec830aac9c8764e07e00686d Mon Sep 17 00:00:00 2001 From: Neil Anderson Date: Fri, 12 Jun 2026 08:41:15 -0400 Subject: [PATCH 6/6] Use finite max elapsed time for retriable compatibility --- CHANGELOG.md | 2 +- lib/lhm/sql_retry.rb | 2 +- spec/unit/sql_retry_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc365fb2..9cc02a49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 4.5.2 (Jun, 2026) * Use concrete Trilogy connection exception classes in retry configuration. -* Use a retriable-compatible unbounded max elapsed time value. +* Use a retriable-compatible effectively unbounded max elapsed time value. # 4.5.1 (Jul, 2025) * Create update before insert trigger diff --git a/lib/lhm/sql_retry.rb b/lib/lhm/sql_retry.rb index dc5ccbdb..cf73e314 100644 --- a/lib/lhm/sql_retry.rb +++ b/lib/lhm/sql_retry.rb @@ -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: nil, # 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 diff --git a/spec/unit/sql_retry_spec.rb b/spec/unit/sql_retry_spec.rb index ea91d030..d6ff5b5e 100644 --- a/spec/unit/sql_retry_spec.rb +++ b/spec/unit/sql_retry_spec.rb @@ -6,10 +6,10 @@ describe Lhm::SqlRetry do describe '#default_retry_config' do - it 'uses an unbounded max elapsed time value compatible with retriable validation' 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_nil retry_config[:max_elapsed_time] + assert_equal Float::MAX, retry_config[:max_elapsed_time] end end