faulty 0.5.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +7 -0
- data/CHANGELOG.md +25 -0
- data/Gemfile +3 -0
- data/README.md +68 -6
- data/bin/benchmark +52 -0
- data/faulty.gemspec +0 -1
- data/lib/faulty/circuit.rb +3 -15
- data/lib/faulty/events/callback_listener.rb +1 -1
- data/lib/faulty/events/honeybadger_listener.rb +10 -2
- data/lib/faulty/events/log_listener.rb +9 -5
- data/lib/faulty/events.rb +2 -0
- data/lib/faulty/patch/base.rb +1 -1
- data/lib/faulty/patch/mysql2.rb +81 -0
- data/lib/faulty/patch/redis.rb +43 -10
- data/lib/faulty/status.rb +9 -2
- data/lib/faulty/storage/fault_tolerant_proxy.rb +1 -1
- data/lib/faulty/version.rb +1 -1
- metadata +4 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d954b29a9efe4360a4b3ab940afa5f4d9b51642c6244bcbc3875fb7bdf569c7
|
4
|
+
data.tar.gz: b0babca45decf10cf5b0505b301e7c89837385cd21a5727048599f2c9097549d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 56c406768fe13d23cec5c56b1c6d25f278f578895c90c01731dda8f3b002f7a2ca1b06b17195cf7f86d358699b7b8fc3c9648c8aa6b0a2d30fd12f7a3a0e92e6
|
7
|
+
data.tar.gz: 0bfed6c5968d43262d4c1e02735339f6a9858a9bbebecbffcb8d9ed36a4bf2ea068fa5c6035675592eaaed3df267815c1e24b73a484f4aa421cac17ab602a805
|
data/.github/workflows/ci.yml
CHANGED
@@ -25,12 +25,19 @@ jobs:
|
|
25
25
|
steps:
|
26
26
|
- uses: actions/checkout@v2
|
27
27
|
- uses: ruby/setup-ruby@v1
|
28
|
+
env:
|
29
|
+
REDIS_VERSION: ${{ matrix.redis }}
|
28
30
|
with:
|
29
31
|
ruby-version: ${{ matrix.ruby }}
|
30
32
|
bundler-cache: true
|
31
33
|
- run: bundle exec rubocop
|
32
34
|
if: matrix.ruby == '2.7'
|
35
|
+
- name: start MySQL
|
36
|
+
run: sudo /etc/init.d/mysql start
|
33
37
|
- run: bundle exec rspec --format doc
|
38
|
+
env:
|
39
|
+
MYSQL_USER: root
|
40
|
+
MYSQL_PASSWORD: root
|
34
41
|
- name: Run codacy-coverage-reporter
|
35
42
|
uses: codacy/codacy-coverage-reporter-action@master
|
36
43
|
with:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,28 @@
|
|
1
|
+
## Release v0.7.1
|
2
|
+
|
3
|
+
- Fix success event crash in log listener #37 justinhoward
|
4
|
+
|
5
|
+
## Release v0.7.0
|
6
|
+
|
7
|
+
* Add initial benchmarks and performance improvements #36 justinhoward
|
8
|
+
|
9
|
+
### Breaking Changes
|
10
|
+
|
11
|
+
The `circuit_success` event no longer contains the status value. Computing this
|
12
|
+
value was causing performance problems.
|
13
|
+
|
14
|
+
## Release v0.6.0
|
15
|
+
|
16
|
+
* docs, use correct state in description for skipped event #27 senny
|
17
|
+
* Fix CI to set REDIS_VERSION correctly #31 justinhoward
|
18
|
+
* Fix a potential memory leak in patches #32 justinhoward
|
19
|
+
* Capture an error for BUSY redis backend when patched #30 justinhoward
|
20
|
+
* Add a patch for mysql2 #28 justinhoward
|
21
|
+
|
22
|
+
## Release v0.5.1
|
23
|
+
|
24
|
+
* Fix Storage::FaultTolerantProxy to return empty history on entries fail #26 justinhoward
|
25
|
+
|
1
26
|
## Release v0.5.0
|
2
27
|
|
3
28
|
* Allow creating a new Faulty instance in Faulty#register #24 justinhoward
|
data/Gemfile
CHANGED
@@ -13,7 +13,10 @@ not_jruby = %i[ruby mingw x64_mingw].freeze
|
|
13
13
|
gem 'activesupport', '>= 4.2'
|
14
14
|
gem 'bundler', '>= 1.17', '< 3'
|
15
15
|
gem 'byebug', platforms: not_jruby
|
16
|
+
gem 'honeybadger', '>= 2.0'
|
16
17
|
gem 'irb', '~> 1.0'
|
18
|
+
# Minimum of 0.5.0 for specific error classes
|
19
|
+
gem 'mysql2', '>= 0.5.0', platforms: not_jruby
|
17
20
|
gem 'redcarpet', '~> 3.5', platforms: not_jruby
|
18
21
|
gem 'rspec_junit_formatter', '~> 0.4'
|
19
22
|
gem 'simplecov', '>= 0.17.1'
|
data/README.md
CHANGED
@@ -83,6 +83,7 @@ Also see "Release It!: Design and Deploy Production-Ready Software" by
|
|
83
83
|
+ [Locking Circuits](#locking-circuits)
|
84
84
|
* [Patches](#patches)
|
85
85
|
+ [Patch::Redis](#patchredis)
|
86
|
+
+ [Patch::Mysql2](#patchmysql2)
|
86
87
|
* [Event Handling](#event-handling)
|
87
88
|
+ [CallbackListener](#callbacklistener)
|
88
89
|
+ [Other Built-in Listeners](#other-built-in-listeners)
|
@@ -948,15 +949,40 @@ Or require them in your `Gemfile`
|
|
948
949
|
gem 'faulty', require: %w[faulty faulty/patch/redis]
|
949
950
|
```
|
950
951
|
|
952
|
+
For core dependencies you'll most likely want to use the in-memory circuit
|
953
|
+
storage adapter and not the Redis storage adapter. That way if Redis fails, your
|
954
|
+
circuit storage doesn't also fail, causing cascading failures.
|
955
|
+
|
956
|
+
For example, you can use a separate Faulty instance to manage your Mysql2
|
957
|
+
circuit:
|
958
|
+
|
959
|
+
```ruby
|
960
|
+
# Setup your default config. This can use the Redis backend if you prefer
|
961
|
+
Faulty.init do |config|
|
962
|
+
# ...
|
963
|
+
end
|
964
|
+
|
965
|
+
Faulty.register(:mysql) do |config|
|
966
|
+
# Here we decide to set some circuit defaults more useful for
|
967
|
+
# frequent database calls
|
968
|
+
config.circuit_defaults = {
|
969
|
+
cool_down: 20.0,
|
970
|
+
evaluation_window: 40,
|
971
|
+
sample_threshold: 25
|
972
|
+
}
|
973
|
+
end
|
974
|
+
|
975
|
+
# Now we can use our "mysql" faulty instance when constructing a Mysql2 client
|
976
|
+
Mysql2::Client.new(host: '127.0.0.1', faulty: { instance: 'mysql2' })
|
977
|
+
```
|
978
|
+
|
951
979
|
### Patch::Redis
|
952
980
|
|
953
981
|
[`Faulty::Patch::Redis`](https://www.rubydoc.info/gems/faulty/Faulty/Patch/Redis)
|
954
982
|
protects a Redis client with an internal circuit. Pass a `:faulty` key along
|
955
983
|
with your connection options to enable the circuit breaker.
|
956
984
|
|
957
|
-
|
958
|
-
in-memory circuit storage adapter and not the Redis storage adapter. That way
|
959
|
-
if Redis fails, your circuit storage doesn't also fail.
|
985
|
+
The Redis patch supports the Redis gem versions 3 and 4.
|
960
986
|
|
961
987
|
```ruby
|
962
988
|
require 'faulty/patch/redis'
|
@@ -982,6 +1008,43 @@ redis = Redis.new(url: 'redis://localhost:6379')
|
|
982
1008
|
redis.connect # not protected by a circuit
|
983
1009
|
```
|
984
1010
|
|
1011
|
+
### Patch::Mysql2
|
1012
|
+
|
1013
|
+
[`Faulty::Patch::Mysql2`](https://www.rubydoc.info/gems/faulty/Faulty/Patch/Mysql2)
|
1014
|
+
protects a `Mysql2::Client` with an internal circuit. Pass a `:faulty` key along
|
1015
|
+
with your connection options to enable the circuit breaker.
|
1016
|
+
|
1017
|
+
Faulty supports the mysql2 gem versions 0.5 and greater.
|
1018
|
+
|
1019
|
+
Note: Although Faulty supports Ruby 2.3 in general, the Mysql2 patch is not
|
1020
|
+
fully supported on Ruby 2.3. It may work for you, but use it at your own risk.
|
1021
|
+
|
1022
|
+
```ruby
|
1023
|
+
require 'faulty/patch/mysql2'
|
1024
|
+
|
1025
|
+
mysql = Mysql2::Client.new(host: '127.0.0.1', faulty: {
|
1026
|
+
# The name for the Mysql2 circuit
|
1027
|
+
name: 'mysql2'
|
1028
|
+
|
1029
|
+
# The faulty instance to use
|
1030
|
+
# This can also be a registered faulty instance or a constant name. See API
|
1031
|
+
# docs for more details
|
1032
|
+
instance: Faulty.default
|
1033
|
+
|
1034
|
+
# By default, circuit errors will be subclasses of
|
1035
|
+
# Mysql2::Error::ConnectionError
|
1036
|
+
# To disable this behavior, set patch_errors to false and Faulty
|
1037
|
+
# will raise its default errors
|
1038
|
+
patch_errors: true
|
1039
|
+
})
|
1040
|
+
|
1041
|
+
mysql.query('SELECT * FROM users') # raises Faulty::CircuitError if connection fails
|
1042
|
+
|
1043
|
+
# If the faulty key is not given, no circuit is used
|
1044
|
+
mysql = Mysql2::Client.new(host: '127.0.0.1')
|
1045
|
+
mysql.query('SELECT * FROM users') # not protected by a circuit
|
1046
|
+
```
|
1047
|
+
|
985
1048
|
## Event Handling
|
986
1049
|
|
987
1050
|
Faulty uses an event-dispatching model to deliver notifications of internal
|
@@ -1000,9 +1063,8 @@ events. The full list of events is available from
|
|
1000
1063
|
- `circuit_reopened` - A circuit execution cause the circuit to reopen from
|
1001
1064
|
half-open. Payload: `circuit`, `error`.
|
1002
1065
|
- `circuit_skipped` - A circuit execution was skipped because the circuit is
|
1003
|
-
|
1004
|
-
- `circuit_success` - A circuit execution was successful. Payload: `circuit
|
1005
|
-
`status`
|
1066
|
+
open. Payload: `circuit`
|
1067
|
+
- `circuit_success` - A circuit execution was successful. Payload: `circuit`
|
1006
1068
|
- `storage_failure` - A storage backend raised an error. Payload `circuit` (can
|
1007
1069
|
be nil), `action`, `error`
|
1008
1070
|
|
data/bin/benchmark
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'benchmark'
|
6
|
+
require 'faulty'
|
7
|
+
|
8
|
+
n = 100_000
|
9
|
+
|
10
|
+
puts "Starting circuit benchmarks with #{n} iterations each\n\n"
|
11
|
+
|
12
|
+
Benchmark.bm(25) do |b|
|
13
|
+
in_memory = Faulty.new(listeners: [])
|
14
|
+
b.report('memory storage') do
|
15
|
+
n.times { in_memory.circuit(:memory).run { true } }
|
16
|
+
end
|
17
|
+
|
18
|
+
b.report('memory storage failures') do
|
19
|
+
n.times do
|
20
|
+
begin
|
21
|
+
in_memory.circuit(:memory_fail, sample_threshold: n + 1).run { raise 'fail' }
|
22
|
+
rescue StandardError
|
23
|
+
# Expected to raise here
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
in_memory_large = Faulty.new(listeners: [], storage: Faulty::Storage::Memory.new(max_sample_size: 1000))
|
29
|
+
b.report('large memory storage') do
|
30
|
+
n.times { in_memory_large.circuit(:memory_large).run { true } }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
n = 1_000_000
|
35
|
+
|
36
|
+
puts "\n\nStarting extra benchmarks with #{n} iterations each\n\n"
|
37
|
+
|
38
|
+
Benchmark.bm(25) do |b|
|
39
|
+
in_memory = Faulty.new(listeners: [])
|
40
|
+
|
41
|
+
log_listener = Faulty::Events::LogListener.new(Logger.new(File::NULL))
|
42
|
+
log_circuit = in_memory.circuit(:log_listener)
|
43
|
+
log_status = log_circuit.status
|
44
|
+
b.report('log listener success') do
|
45
|
+
n.times { log_listener.handle(:circuit_success, circuit: log_circuit, status: log_status) }
|
46
|
+
end
|
47
|
+
|
48
|
+
log_error = StandardError.new('test error')
|
49
|
+
b.report('log listener failure') do
|
50
|
+
n.times { log_listener.handle(:circuit_failure, error: log_error, circuit: log_circuit, status: log_status) }
|
51
|
+
end
|
52
|
+
end
|
data/faulty.gemspec
CHANGED
@@ -26,7 +26,6 @@ Gem::Specification.new do |spec|
|
|
26
26
|
# Only essential development tools and dependencies go here.
|
27
27
|
# Other non-essential development dependencies go in the Gemfile.
|
28
28
|
spec.add_development_dependency 'connection_pool', '~> 2.0'
|
29
|
-
spec.add_development_dependency 'honeybadger', '>= 2.0'
|
30
29
|
spec.add_development_dependency 'redis', '>= 3.0'
|
31
30
|
spec.add_development_dependency 'rspec', '~> 3.8'
|
32
31
|
# 0.81 is the last rubocop version with Ruby 2.3 support
|
data/lib/faulty/circuit.rb
CHANGED
@@ -319,12 +319,10 @@ class Faulty
|
|
319
319
|
|
320
320
|
# @return [Boolean] True if the circuit transitioned to closed
|
321
321
|
def success!(status)
|
322
|
-
|
323
|
-
|
324
|
-
closed = false
|
325
|
-
closed = close! if should_close?(status)
|
322
|
+
storage.entry(self, Faulty.current_time, true)
|
323
|
+
closed = close! if status.half_open?
|
326
324
|
|
327
|
-
options.notifier.notify(:circuit_success, circuit: self
|
325
|
+
options.notifier.notify(:circuit_success, circuit: self)
|
328
326
|
closed
|
329
327
|
end
|
330
328
|
|
@@ -370,16 +368,6 @@ class Faulty
|
|
370
368
|
closed
|
371
369
|
end
|
372
370
|
|
373
|
-
# Test whether we should close after a successful run
|
374
|
-
#
|
375
|
-
# Currently this is always true if the circuit is half-open, which is the
|
376
|
-
# traditional behavior for a circuit-breaker
|
377
|
-
#
|
378
|
-
# @return [Boolean] True if we should close the circuit from half-open
|
379
|
-
def should_close?(status)
|
380
|
-
status.half_open?
|
381
|
-
end
|
382
|
-
|
383
371
|
# Read from the cache if it is configured
|
384
372
|
#
|
385
373
|
# @param key The key to read from the cache
|
@@ -23,7 +23,7 @@ class Faulty
|
|
23
23
|
# @param (see ListenerInterface#handle)
|
24
24
|
# @return [void]
|
25
25
|
def handle(event, payload)
|
26
|
-
return unless
|
26
|
+
return unless EVENT_SET.include?(event)
|
27
27
|
return unless @handlers.key?(event)
|
28
28
|
|
29
29
|
@handlers[event].each do |handler|
|
@@ -8,11 +8,19 @@ class Faulty
|
|
8
8
|
#
|
9
9
|
# The honeybadger gem must be available.
|
10
10
|
class HoneybadgerListener
|
11
|
+
HONEYBADGER_EVENTS = Set[
|
12
|
+
:circuit_failure,
|
13
|
+
:circuit_opened,
|
14
|
+
:circuit_reopened,
|
15
|
+
:cache_failure,
|
16
|
+
:storage_failure
|
17
|
+
].freeze
|
18
|
+
|
11
19
|
# (see ListenerInterface#handle)
|
12
20
|
def handle(event, payload)
|
13
|
-
return unless
|
21
|
+
return unless HONEYBADGER_EVENTS.include?(event)
|
14
22
|
|
15
|
-
send(event, payload)
|
23
|
+
send(event, payload)
|
16
24
|
end
|
17
25
|
|
18
26
|
private
|
@@ -16,9 +16,9 @@ class Faulty
|
|
16
16
|
|
17
17
|
# (see ListenerInterface#handle)
|
18
18
|
def handle(event, payload)
|
19
|
-
return unless
|
19
|
+
return unless EVENT_SET.include?(event)
|
20
20
|
|
21
|
-
send(event, payload)
|
21
|
+
send(event, payload)
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
@@ -36,7 +36,7 @@ class Faulty
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def circuit_success(payload)
|
39
|
-
log(:debug, 'Circuit succeeded', payload[:circuit].name
|
39
|
+
log(:debug, 'Circuit succeeded', payload[:circuit].name)
|
40
40
|
end
|
41
41
|
|
42
42
|
def circuit_failure(payload)
|
@@ -79,8 +79,12 @@ class Faulty
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def log(level, msg, action, extra = {})
|
82
|
-
|
83
|
-
|
82
|
+
@logger.public_send(level) do
|
83
|
+
extra_str = extra.map { |k, v| "#{k}=#{v}" }.join(' ')
|
84
|
+
extra_str = " #{extra_str}" unless extra_str.empty?
|
85
|
+
|
86
|
+
"#{msg}: #{action}#{extra_str}"
|
87
|
+
end
|
84
88
|
end
|
85
89
|
end
|
86
90
|
end
|
data/lib/faulty/events.rb
CHANGED
data/lib/faulty/patch/base.rb
CHANGED
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mysql2'
|
4
|
+
|
5
|
+
if Gem::Version.new(Mysql2::VERSION) < Gem::Version.new('0.5.0')
|
6
|
+
raise NotImplementedError, 'The faulty mysql2 patch requires mysql2 0.5.0 or later'
|
7
|
+
end
|
8
|
+
|
9
|
+
class Faulty
|
10
|
+
module Patch
|
11
|
+
# Patch Mysql2 to run connections and queries in a circuit
|
12
|
+
#
|
13
|
+
# This module is not required by default
|
14
|
+
#
|
15
|
+
# Pass a `:faulty` key into your MySQL connection options to enable
|
16
|
+
# circuit protection. See {Patch.circuit_from_hash} for the available
|
17
|
+
# options.
|
18
|
+
#
|
19
|
+
# COMMIT, ROLLBACK, and RELEASE SAVEPOINT queries are intentionally not
|
20
|
+
# protected by the circuit. This is to allow open transactions to be closed
|
21
|
+
# if possible.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# require 'faulty/patch/mysql2'
|
25
|
+
#
|
26
|
+
# mysql = Mysql2::Client.new(host: '127.0.0.1', faulty: {})
|
27
|
+
# mysql.query('SELECT * FROM users') # raises Faulty::CircuitError if connection fails
|
28
|
+
#
|
29
|
+
# # If the faulty key is not given, no circuit is used
|
30
|
+
# mysql = Mysql2::Client.new(host: '127.0.0.1')
|
31
|
+
# mysql.query('SELECT * FROM users') # not protected by a circuit
|
32
|
+
#
|
33
|
+
# @see Patch.circuit_from_hash
|
34
|
+
module Mysql2
|
35
|
+
include Base
|
36
|
+
|
37
|
+
Patch.define_circuit_errors(self, ::Mysql2::Error::ConnectionError)
|
38
|
+
|
39
|
+
QUERY_WHITELIST = [
|
40
|
+
%r{\A(?:/\*.*?\*/)?\s*ROLLBACK}i,
|
41
|
+
%r{\A(?:/\*.*?\*/)?\s*COMMIT}i,
|
42
|
+
%r{\A(?:/\*.*?\*/)?\s*RELEASE\s+SAVEPOINT}i
|
43
|
+
].freeze
|
44
|
+
|
45
|
+
def initialize(opts = {})
|
46
|
+
@faulty_circuit = Patch.circuit_from_hash(
|
47
|
+
'mysql2',
|
48
|
+
opts[:faulty],
|
49
|
+
errors: [
|
50
|
+
::Mysql2::Error::ConnectionError,
|
51
|
+
::Mysql2::Error::TimeoutError
|
52
|
+
],
|
53
|
+
patched_error_module: Faulty::Patch::Mysql2
|
54
|
+
)
|
55
|
+
|
56
|
+
super
|
57
|
+
end
|
58
|
+
|
59
|
+
# Protect manual connection pings
|
60
|
+
def ping
|
61
|
+
faulty_run { super }
|
62
|
+
rescue Faulty::Patch::Mysql2::FaultyError
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
# Protect the initial connnection
|
67
|
+
def connect(*args)
|
68
|
+
faulty_run { super }
|
69
|
+
end
|
70
|
+
|
71
|
+
# Protect queries unless they are whitelisted
|
72
|
+
def query(*args)
|
73
|
+
return super if QUERY_WHITELIST.any? { |r| !r.match(args.first).nil? }
|
74
|
+
|
75
|
+
faulty_run { super }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
::Mysql2::Client.prepend(Faulty::Patch::Mysql2)
|
data/lib/faulty/patch/redis.rb
CHANGED
@@ -8,13 +8,9 @@ class Faulty
|
|
8
8
|
#
|
9
9
|
# This module is not required by default
|
10
10
|
#
|
11
|
-
# Pass a `:faulty` key into your
|
12
|
-
# circuit protection.
|
13
|
-
#
|
14
|
-
# faulty instance to create the circuit from. `Faulty.default` will be
|
15
|
-
# used if no instance is given. The `:instance` key can also reference a
|
16
|
-
# registered Faulty instance or a global constantso that it can be set
|
17
|
-
# from config files. See {Patch.circuit_from_hash}.
|
11
|
+
# Pass a `:faulty` key into your MySQL connection options to enable
|
12
|
+
# circuit protection. See {Patch.circuit_from_hash} for the available
|
13
|
+
# options.
|
18
14
|
#
|
19
15
|
# @example
|
20
16
|
# require 'faulty/patch/redis'
|
@@ -32,12 +28,18 @@ class Faulty
|
|
32
28
|
|
33
29
|
Patch.define_circuit_errors(self, ::Redis::BaseConnectionError)
|
34
30
|
|
31
|
+
class BusyError < ::Redis::CommandError
|
32
|
+
end
|
33
|
+
|
35
34
|
# Patches Redis to add the `:faulty` key
|
36
35
|
def initialize(options = {})
|
37
36
|
@faulty_circuit = Patch.circuit_from_hash(
|
38
37
|
'redis',
|
39
38
|
options[:faulty],
|
40
|
-
errors: [
|
39
|
+
errors: [
|
40
|
+
::Redis::BaseConnectionError,
|
41
|
+
BusyError
|
42
|
+
],
|
41
43
|
patched_error_module: Faulty::Patch::Redis
|
42
44
|
)
|
43
45
|
|
@@ -49,10 +51,41 @@ class Faulty
|
|
49
51
|
faulty_run { super }
|
50
52
|
end
|
51
53
|
|
52
|
-
#
|
53
|
-
def
|
54
|
+
# Protect command calls
|
55
|
+
def call(command)
|
56
|
+
faulty_run { super }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Protect command_loop calls
|
60
|
+
def call_loop(command, timeout = 0)
|
61
|
+
faulty_run { super }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Protect pipelined commands
|
65
|
+
def call_pipelined(commands)
|
54
66
|
faulty_run { super }
|
55
67
|
end
|
68
|
+
|
69
|
+
# Inject specific error classes if client is patched
|
70
|
+
#
|
71
|
+
# This method does not raise errors, it returns them
|
72
|
+
# as exception objects, so we simply modify that error if necessary and
|
73
|
+
# return it.
|
74
|
+
#
|
75
|
+
# The call* methods above will then raise that error, so we are able to
|
76
|
+
# capture it with faulty_run.
|
77
|
+
def io(&block)
|
78
|
+
return super unless @faulty_circuit
|
79
|
+
|
80
|
+
reply = super
|
81
|
+
if reply.is_a?(::Redis::CommandError)
|
82
|
+
if reply.message.start_with?('BUSY')
|
83
|
+
reply = BusyError.new(reply.message)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
reply
|
88
|
+
end
|
56
89
|
end
|
57
90
|
end
|
58
91
|
end
|
data/lib/faulty/status.rb
CHANGED
@@ -64,10 +64,17 @@ class Faulty
|
|
64
64
|
# sample_size
|
65
65
|
# @return [Status]
|
66
66
|
def self.from_entries(entries, **hash)
|
67
|
+
window_start = Faulty.current_time - hash[:options].evaluation_window
|
68
|
+
size = entries.size
|
69
|
+
i = 0
|
67
70
|
failures = 0
|
68
71
|
sample_size = 0
|
69
|
-
|
70
|
-
|
72
|
+
|
73
|
+
# This is a hot loop, and while is slightly faster than each
|
74
|
+
while i < size
|
75
|
+
time, success = entries[i]
|
76
|
+
i += 1
|
77
|
+
next unless time > window_start
|
71
78
|
|
72
79
|
sample_size += 1
|
73
80
|
failures += 1 unless success
|
data/lib/faulty/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: faulty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Howard
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '2.0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: honeybadger
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '2.0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '2.0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: redis
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -138,6 +124,7 @@ files:
|
|
138
124
|
- Gemfile
|
139
125
|
- LICENSE.txt
|
140
126
|
- README.md
|
127
|
+
- bin/benchmark
|
141
128
|
- bin/check-version
|
142
129
|
- bin/console
|
143
130
|
- bin/rspec
|
@@ -167,6 +154,7 @@ files:
|
|
167
154
|
- lib/faulty/immutable_options.rb
|
168
155
|
- lib/faulty/patch.rb
|
169
156
|
- lib/faulty/patch/base.rb
|
157
|
+
- lib/faulty/patch/mysql2.rb
|
170
158
|
- lib/faulty/patch/redis.rb
|
171
159
|
- lib/faulty/result.rb
|
172
160
|
- lib/faulty/status.rb
|