Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only failsafe on transient database errors #203

Merged
merged 1 commit into from
Sep 3, 2024
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ end

Solid Cache supports these options in addition to the standard `ActiveSupport::Cache::Store` options:

- `error_handler` - a Proc to call to handle any `ActiveRecord::ActiveRecordError`s that are raises (default: log errors as warnings)
- `error_handler` - a Proc to call to handle any transient database errors that are raised (default: log errors as warnings)
- `expiry_batch_size` - the batch size to use when deleting old records (default: `100`)
- `expiry_method` - what expiry method to use `thread` or `job` (default: `thread`)
- `expiry_queue` - which queue to add expiry jobs to (default: `default`)
Expand Down
12 changes: 11 additions & 1 deletion lib/solid_cache/store/failsafe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
module SolidCache
class Store
module Failsafe
TRANSIENT_ACTIVE_RECORD_ERRORS = [
ActiveRecord::AdapterTimeout,
ActiveRecord::ConnectionNotEstablished,
ActiveRecord::ConnectionTimeoutError,
ActiveRecord::Deadlocked,
ActiveRecord::LockWaitTimeout,
ActiveRecord::QueryCanceled,
ActiveRecord::StatementTimeout
]

DEFAULT_ERROR_HANDLER = ->(method:, returning:, exception:) do
if Store.logger
Store.logger.error { "SolidCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
Expand All @@ -20,7 +30,7 @@ def initialize(options = {})

def failsafe(method, returning: nil)
yield
rescue ActiveRecord::ActiveRecordError => error
rescue *TRANSIENT_ACTIVE_RECORD_ERRORS => error
ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
error_handler&.call(method: method, exception: error, returning: returning)
returning
Expand Down
24 changes: 12 additions & 12 deletions test/unit/behaviors/failure_raising_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def test_fetch_read_failure_raises
key = SecureRandom.uuid
@cache.write(key, SecureRandom.alphanumeric)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.fetch(key)
end
Expand All @@ -17,7 +17,7 @@ def test_fetch_with_block_read_failure_raises
value = SecureRandom.alphanumeric
@cache.write(key, value)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.fetch(key) { SecureRandom.alphanumeric }
end
Expand All @@ -30,7 +30,7 @@ def test_read_failure_raises
key = SecureRandom.uuid
@cache.write(key, SecureRandom.alphanumeric)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.read(key)
end
Expand All @@ -45,23 +45,23 @@ def test_read_multi_failure_raises
other_key => SecureRandom.alphanumeric
)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.read_multi(key, other_key)
end
end
end

def test_write_failure_raises
assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.write(SecureRandom.uuid, SecureRandom.alphanumeric)
end
end
end

def test_write_multi_failure_raises
assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.write_multi(
SecureRandom.uuid => SecureRandom.alphanumeric,
Expand All @@ -79,7 +79,7 @@ def test_fetch_multi_failure_raises
other_key => SecureRandom.alphanumeric
)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.fetch_multi(key, other_key) { |k| "unavailable" }
end
Expand All @@ -90,7 +90,7 @@ def test_delete_failure_raises
key = SecureRandom.uuid
@cache.write(key, SecureRandom.alphanumeric)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.delete(key)
end
Expand All @@ -101,7 +101,7 @@ def test_exist_failure_raises
key = SecureRandom.uuid
@cache.write(key, SecureRandom.alphanumeric)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.exist?(key)
end
Expand All @@ -112,7 +112,7 @@ def test_increment_failure_raises
key = SecureRandom.uuid
@cache.write(key, 1, raw: true)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.increment(key)
end
Expand All @@ -123,15 +123,15 @@ def test_decrement_failure_raises
key = SecureRandom.uuid
@cache.write(key, 1, raw: true)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.decrement(key)
end
end
end

def test_clear_failure_returns_nil
assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.clear
end
Expand Down
24 changes: 12 additions & 12 deletions test/unit/behaviors_rails_7_2/failure_raising_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def test_fetch_read_failure_raises
key = SecureRandom.uuid
@cache.write(key, SecureRandom.alphanumeric)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.fetch(key)
end
Expand All @@ -17,7 +17,7 @@ def test_fetch_with_block_read_failure_raises
value = SecureRandom.alphanumeric
@cache.write(key, value)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.fetch(key) { SecureRandom.alphanumeric }
end
Expand All @@ -30,7 +30,7 @@ def test_read_failure_raises
key = SecureRandom.uuid
@cache.write(key, SecureRandom.alphanumeric)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.read(key)
end
Expand All @@ -45,23 +45,23 @@ def test_read_multi_failure_raises
other_key => SecureRandom.alphanumeric
)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.read_multi(key, other_key)
end
end
end

def test_write_failure_raises
assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.write(SecureRandom.uuid, SecureRandom.alphanumeric)
end
end
end

def test_write_multi_failure_raises
assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.write_multi(
SecureRandom.uuid => SecureRandom.alphanumeric,
Expand All @@ -79,7 +79,7 @@ def test_fetch_multi_failure_raises
other_key => SecureRandom.alphanumeric
)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.fetch_multi(key, other_key) { |k| "unavailable" }
end
Expand All @@ -90,7 +90,7 @@ def test_delete_failure_raises
key = SecureRandom.uuid
@cache.write(key, SecureRandom.alphanumeric)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.delete(key)
end
Expand All @@ -101,7 +101,7 @@ def test_exist_failure_raises
key = SecureRandom.uuid
@cache.write(key, SecureRandom.alphanumeric)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.exist?(key)
end
Expand All @@ -112,7 +112,7 @@ def test_increment_failure_raises
key = SecureRandom.uuid
@cache.write(key, 1, raw: true)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.increment(key)
end
Expand All @@ -123,15 +123,15 @@ def test_decrement_failure_raises
key = SecureRandom.uuid
@cache.write(key, 1, raw: true)

assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.decrement(key)
end
end
end

def test_clear_failure_returns_nil
assert_raise ActiveRecord::ActiveRecordError do
assert_raise ActiveRecord::StatementTimeout do
emulating_unavailability do |cache|
cache.clear
end
Expand Down
4 changes: 2 additions & 2 deletions test/unit/execution_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def test_active_record_instrumention_expiry
end

def test_no_connections_uninstrumented
ActiveRecord::ConnectionAdapters::ConnectionPool.any_instance.stubs(connection).raises(ActiveRecord::StatementInvalid)
ActiveRecord::ConnectionAdapters::ConnectionPool.any_instance.stubs(connection).raises(ActiveRecord::StatementTimeout)

cache = lookup_store(expires_in: 60, active_record_instrumentation: false)

Expand All @@ -120,7 +120,7 @@ def test_no_connections_uninstrumented
end

def test_no_connections_instrumented
ActiveRecord::ConnectionAdapters::ConnectionPool.any_instance.stubs(connection).raises(ActiveRecord::StatementInvalid)
ActiveRecord::ConnectionAdapters::ConnectionPool.any_instance.stubs(connection).raises(ActiveRecord::StatementTimeout)

cache = lookup_store(expires_in: 60)

Expand Down
12 changes: 6 additions & 6 deletions test/unit/solid_cache_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ class SolidCacheFailsafeTest < ActiveSupport::TestCase
def emulating_unavailability
wait_for_background_tasks(@cache)
stub_matcher = ActiveRecord::Base.connection.class.any_instance
stub_matcher.stubs(:exec_query).raises(ActiveRecord::StatementInvalid)
stub_matcher.stubs(:internal_exec_query).raises(ActiveRecord::StatementInvalid)
stub_matcher.stubs(:exec_delete).raises(ActiveRecord::StatementInvalid)
stub_matcher.stubs(:exec_query).raises(ActiveRecord::StatementTimeout)
stub_matcher.stubs(:internal_exec_query).raises(ActiveRecord::StatementTimeout)
stub_matcher.stubs(:exec_delete).raises(ActiveRecord::StatementTimeout)
yield lookup_store(namespace: @namespace)
ensure
stub_matcher.unstub(:exec_query)
Expand All @@ -112,9 +112,9 @@ class SolidCacheRaisingTest < ActiveSupport::TestCase
def emulating_unavailability
wait_for_background_tasks(@cache)
stub_matcher = ActiveRecord::Base.connection.class.any_instance
stub_matcher.stubs(:exec_query).raises(ActiveRecord::StatementInvalid)
stub_matcher.stubs(:internal_exec_query).raises(ActiveRecord::StatementInvalid)
stub_matcher.stubs(:exec_delete).raises(ActiveRecord::StatementInvalid)
stub_matcher.stubs(:exec_query).raises(ActiveRecord::StatementTimeout)
stub_matcher.stubs(:internal_exec_query).raises(ActiveRecord::StatementTimeout)
stub_matcher.stubs(:exec_delete).raises(ActiveRecord::StatementTimeout)
yield lookup_store(namespace: @namespace,
error_handler: ->(method:, returning:, exception:) { raise exception })
ensure
Expand Down