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 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