faulty 0.5.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8ce491fe09e0c6224c3ecc6da2825eeff601f15f7b5bbfbff065f898860fc48
4
- data.tar.gz: caf56c1bfc86511d0acb6fbd3f7507d521bcd7b8ac74b47a85ebcbd9a4fdef1b
3
+ metadata.gz: 6d954b29a9efe4360a4b3ab940afa5f4d9b51642c6244bcbc3875fb7bdf569c7
4
+ data.tar.gz: b0babca45decf10cf5b0505b301e7c89837385cd21a5727048599f2c9097549d
5
5
  SHA512:
6
- metadata.gz: 89eb97edae88432ea0e6e36a9c0b0f69d4d89075193ffd5a6eb0fe10e24c0983047289ccdb8e363619c4f83b1e80baec9decc34e4fdf029cfbef24189f244138
7
- data.tar.gz: 4cabda8638d452bcac0a6375b284b4465d7eb34ed5e4b546a7247c09816eae4480620d69f9777144bd3d3aa8abcc58ca83243037e5d7aac0635e6d01d45ff289
6
+ metadata.gz: 56c406768fe13d23cec5c56b1c6d25f278f578895c90c01731dda8f3b002f7a2ca1b06b17195cf7f86d358699b7b8fc3c9648c8aa6b0a2d30fd12f7a3a0e92e6
7
+ data.tar.gz: 0bfed6c5968d43262d4c1e02735339f6a9858a9bbebecbffcb8d9ed36a4bf2ea068fa5c6035675592eaaed3df267815c1e24b73a484f4aa421cac17ab602a805
@@ -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
- Keep in mind that when using this patch, you'll most likely want to use the
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
- closed. Payload: `circuit`
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
@@ -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
- entries = storage.entry(self, Faulty.current_time, true)
323
- status = Status.from_entries(entries, **status.to_h)
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, status: status)
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 EVENTS.include?(event)
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 EVENTS.include?(event)
21
+ return unless HONEYBADGER_EVENTS.include?(event)
14
22
 
15
- send(event, payload) if respond_to?(event, true)
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 EVENTS.include?(event)
19
+ return unless EVENT_SET.include?(event)
20
20
 
21
- send(event, payload) if respond_to?(event, true)
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, state: payload[:status].state)
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
- extra_str = extra.map { |k, v| "#{k}=#{v}" }.join(' ')
83
- logger.public_send(level, "#{msg}: #{action} #{extra_str}")
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
@@ -17,6 +17,8 @@ class Faulty
17
17
  circuit_success
18
18
  storage_failure
19
19
  ].freeze
20
+
21
+ EVENT_SET = Set.new(EVENTS)
20
22
  end
21
23
  end
22
24
 
@@ -39,7 +39,7 @@ class Faulty
39
39
  Thread.current[faulty_running_key] = true
40
40
  @faulty_circuit.run { yield }
41
41
  ensure
42
- Thread.current[faulty_running_key] = false
42
+ Thread.current[faulty_running_key] = nil
43
43
  end
44
44
  end
45
45
  end
@@ -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)
@@ -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 redis connection options to enable
12
- # circuit protection. This hash is a hash of circuit options for the
13
- # internal circuit. The hash may also have a `:instance` key, which is the
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: [::Redis::BaseConnectionError],
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
- # Reads/writes to redis are protected
53
- def io(&block)
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
- entries.each do |(time, success)|
70
- next unless time > Faulty.current_time - hash[:options].evaluation_window
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
@@ -94,7 +94,7 @@ class Faulty
94
94
  @storage.entry(circuit, time, success)
95
95
  rescue StandardError => e
96
96
  options.notifier.notify(:storage_failure, circuit: circuit, action: :entry, error: e)
97
- stub_status(circuit)
97
+ []
98
98
  end
99
99
 
100
100
  # Safely mark a circuit as open
@@ -3,6 +3,6 @@
3
3
  class Faulty
4
4
  # The current Faulty version
5
5
  def self.version
6
- Gem::Version.new('0.5.0')
6
+ Gem::Version.new('0.7.1')
7
7
  end
8
8
  end
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.5.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-05-28 00:00:00.000000000 Z
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