faulty 0.1.2 → 0.1.3

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: e1da265b4bb2e4ae865726930ec3855a03d31c3b47c92b9ef08d7519cdb9d9d9
4
- data.tar.gz: a2fdbb6d1ccdfb7be6c68467ce45a802644ef4f9a61769e8b7387d626307facd
3
+ metadata.gz: f12337b51cbd0d484e4255078d7818354a79bf4138a5ef2b527d93915834bd9c
4
+ data.tar.gz: bbc9ec4e9e9e4707ec4d88246d77b19faead958a75b0f3e58d623fbb70d1ae59
5
5
  SHA512:
6
- metadata.gz: 2ad680693b76db3f3d420d7ded71269c8ad669c23bffa0e4ab598d8d0772955c70479d1497ea51b7aa9691b8438e06bc83ff13d4f3e3f6842a3327971dcbb4d3
7
- data.tar.gz: 2890441590276ecd72e16ee9866955b027a3e1df26907f9cc71a94f3f1161c01ea30665860bcf526fe871e84a10a7c9c728c050bbf961d8b9d670b4a6fb4bbfa
6
+ metadata.gz: 2d79bbe071f0471735795996ff7f54b0f580d445973c9c048d53bd5fac5352f0f33446e3833a39ff3cb3db88c849eb5dbb2cac253452de61d76ee467bb2e9bc9
7
+ data.tar.gz: a3266ee94d29c63a6e880461378223e95578518575e4b8d4795df028f0e4c717c0099b88d56e51a697fac99f43e1578cda92f48c937b8186003e4a56e0eeec2d
@@ -1,10 +1,15 @@
1
+ ## Release v0.1.3
2
+
3
+ * Fix bug where memory storage would delete the newest entries #5
4
+ * Add HoneybadgerListener for error reporting #4
5
+
1
6
  ## Release v0.1.2
2
7
 
3
- * Fix Storage::FaultTolerantProxy open and reopen methods
8
+ * Fix Storage::FaultTolerantProxy open and reopen methods #2
4
9
 
5
10
  ## Release v0.1.1
6
11
 
7
- * Fix a crash when Storage::FaultTolerantProxy created a status stub
12
+ * Fix a crash when Storage::FaultTolerantProxy created a status stub #1
8
13
 
9
14
  ## Release v0.1.0
10
15
 
data/README.md CHANGED
@@ -67,6 +67,51 @@ end
67
67
  For a full list of configuration options, see the
68
68
  [Global Configuration](#global-configuration) section.
69
69
 
70
+ ## What is this for?
71
+
72
+ Circuit breakers are a fault-tolerance tool for creating separation between your
73
+ application and external dependencies. For example, your application may call an
74
+ external API to send a text message:
75
+
76
+ ```ruby
77
+ TextApi.send(message)
78
+ ```
79
+
80
+ In normal operation, this API call is very fast. However what if the texting
81
+ service started hanging? Your application would quickly use up a lot of
82
+ resources waiting for requests to return from the service. You could consider
83
+ adding a timeout to your request:
84
+
85
+ ```ruby
86
+ TextApi.send(message, timeout: 5)
87
+ ```
88
+
89
+ Now your application will terminate requests after 5 seconds, but that could
90
+ still add up to a lot of resources if you call this thousands of times. Circuit
91
+ breakers solve this problem.
92
+
93
+ ```ruby
94
+ Faulty.circuit(:text_api).run do
95
+ TextApi.send(message, timeout: 5)
96
+ end
97
+ ```
98
+
99
+ Now, when the text API hangs, the first few will run and start timing out. This
100
+ will trip the circuit. After the circuit trips
101
+ (see [How it Works](#how-it-works)), calls to the text API will be paused for
102
+ the configured cool down period. Your application resources are not overwhelmed.
103
+
104
+ You are free to implement a fallback or error handling however you wish, for
105
+ example, in this case, you might add the text message to a failure queue:
106
+
107
+ ```ruby
108
+ Faulty.circuit(:text_api).run do
109
+ TextApi.send(message, timeout: 5)
110
+ rescue Faulty::CircuitError => e
111
+ FailureQueue.enqueue(message)
112
+ end
113
+ ```
114
+
70
115
  ## Basic Usage
71
116
 
72
117
  To create a circuit, call `Faulty.circuit`. This can be done as you use the
@@ -137,9 +182,9 @@ Faulty's implementation are:
137
182
  - Event-based monitoring
138
183
 
139
184
  Following the principals of the circuit-breaker pattern, the block given to
140
- `run` or `try_run` will always be executed as long as long as it never raises an
141
- error. If the block _does_ raise an error, then the circuit keeps track of the
142
- number of runs and the failure rate.
185
+ `run` or `try_run` will always be executed as long as it never raises an error.
186
+ If the block _does_ raise an error, then the circuit keeps track of the number
187
+ of runs and the failure rate.
143
188
 
144
189
  Once both thresholds are breached, the circuit is opened. Once open, the
145
190
  circuit starts the cool-down period. Any executions within that cool-down are
@@ -201,7 +246,7 @@ A circuit can be created with the following configuration options. Those options
201
246
  are only set once, synchronized across threads, and will persist in-memory until
202
247
  the process exits. If you're using [scopes](#scopes), the options are retained
203
248
  within the context of each scope. All options given after the first call to
204
- `Faulty.circuit` (or `Scope.circuit` are ignored.
249
+ `Faulty.circuit` (or `Scope.circuit`) are ignored.
205
250
 
206
251
  This is because the circuit objects themselves are internally memoized, and are
207
252
  read-only once created.
@@ -338,12 +383,17 @@ events. The full list of events is available from `Faulty::Events::EVENTS`.
338
383
  closed. Payload: `circuit`
339
384
  - `circuit_success` - A circuit execution was successful. Payload: `circuit`,
340
385
  `status`
341
- - `storage_failure` - A storage backend raised an error. Payload `circuit`,
342
- `action`, `error`
386
+ - `storage_failure` - A storage backend raised an error. Payload `circuit` (can
387
+ be nil), `action`, `error`
343
388
 
344
389
  By default events are logged using `Faulty::Events::LogListener`, but that can
345
390
  be replaced, or additional listeners can be added.
346
391
 
392
+ ### CallbackListener
393
+
394
+ The callback listener is useful for ad-hoc handling of events. You can specify
395
+ an event handler by calling a method on the callback handler by the same name.
396
+
347
397
  ```ruby
348
398
  Faulty.init do |config|
349
399
  # Replace the default listener with a custom callback listener
@@ -356,6 +406,17 @@ Faulty.init do |config|
356
406
  end
357
407
  ```
358
408
 
409
+ ### Other Built-in Listeners
410
+
411
+ In addition to the log and callback listeners, Faulty intends to implement
412
+ built-in service-specific handlers to make it easy to integrate with monitoring
413
+ and reporting software.
414
+
415
+ - `Faulty::Events::HoneybadgerListener`: Reports circuit and backend errors to
416
+ the Honeybadger error reporting service.
417
+
418
+ ### Custom Listeners
419
+
359
420
  You can implement your own listener by following the documentation in
360
421
  `Faulty::Events::ListenerInterface`. For example:
361
422
 
@@ -405,7 +466,7 @@ Faulty.init do |config|
405
466
  storage.key_prefix = 'faulty'
406
467
 
407
468
  # A string to separate the parts of the redis key
408
- storage.key_separator: ':'
469
+ storage.key_separator = ':'
409
470
 
410
471
  # The maximum number of circuit runs that will be stored
411
472
  storage.max_sample_size = 100
@@ -416,7 +477,7 @@ Faulty.init do |config|
416
477
  end
417
478
  ```
418
479
 
419
- ### Listing Circuits
480
+ ## Listing Circuits
420
481
 
421
482
  For monitoring or debugging, you may need to retrieve a list of all circuit
422
483
  names. This is possible with `Faulty.list_circuits` (or the equivalent method on
@@ -432,6 +493,36 @@ statuses = Faulty.list_circuits.map do |name|
432
493
  end
433
494
  ```
434
495
 
496
+ ## Locking Circuits
497
+
498
+ It is possible to lock a circuit open or closed. A circuit that is locked open
499
+ will never execute its block, and always raise an `Faulty::OpenCircuitError`.
500
+ This is useful in cases where you need to manually disable a dependency
501
+ entirely. If a cached value is available, that will be returned from the circuit
502
+ until it expires, even outside its refresh period.
503
+
504
+ ```ruby
505
+ Faulty.circuit(:broken_api).lock_open!
506
+ ```
507
+
508
+ A circuit that is locked closed will never trip. This is useful in cases where a
509
+ circuit is continuously tripping incorrectly. If a cached value is available, it
510
+ will have the same behavior as an unlocked circuit.
511
+
512
+ ```ruby
513
+ Faulty.circuit(:false_positive).lock_closed!
514
+ ```
515
+
516
+ To remove a lock of either type:
517
+
518
+ ```ruby
519
+ Faulty.circuit(:fixed).unlock!
520
+ ```
521
+
522
+ Locking or unlocking a circuit has no concurrency guarantees, so it's not
523
+ recommended to lock or unlock circuits from production code. Instead, locks are
524
+ intended as an emergency tool for troubleshooting and debugging.
525
+
435
526
  ## Scopes
436
527
 
437
528
  It is possible to have multiple configurations of Faulty running within the same
@@ -545,15 +636,34 @@ would be useful for other users.
545
636
  Faulty has its own opinions about how to implement a circuit breaker in Ruby,
546
637
  but there are and have been many other options:
547
638
 
548
- - [circuitbox](https://github.com/yammer/circuitbox)
549
- - [circuit_breaker-ruby](https://github.com/scripbox/circuit_breaker-ruby)
550
- - [stoplight](https://github.com/orgsync/stoplight) (currently unmaintained)
639
+ ### Currently Active
640
+
641
+ - [semian](https://github.com/Shopify/semian): A resiliency toolkit that
642
+ includes circuit breakers. It uses adapters to auto-wire circuits, and it has
643
+ only host-local storage by design.
644
+ - [circuitbox](https://github.com/yammer/circuitbox): Similar in function to
645
+ Faulty, but with a different API. It uses Moneta to abstract circuit storage
646
+ to allow any key-value store.
647
+
648
+ ### Previous Work
649
+
650
+ - [circuit_breaker-ruby](https://github.com/scripbox/circuit_breaker-ruby) (no
651
+ recent activity)
652
+ - [stoplight](https://github.com/orgsync/stoplight) (unmaintained)
551
653
  - [circuit_breaker](https://github.com/wooga/circuit_breaker) (archived)
552
654
  - [simple_circuit_breaker](https://github.com/soundcloud/simple_circuit_breaker)
553
655
  (unmaintained)
554
656
  - [breaker](https://github.com/ahawkins/breaker) (unmaintained)
555
657
  - [circuit_b](https://github.com/alg/circuit_b) (unmaintained)
556
658
 
659
+ ### Faulty's Unique Features
660
+
661
+ - Simple API but configurable for advanced users
662
+ - Pluggable storage backends (circuitbox also has this)
663
+ - Global, or local configuration with scopes
664
+ - Integrated caching support tailored for fault-tolerance
665
+ - Manually lock circuits open or closed
666
+
557
667
  [api docs]: https://www.rubydoc.info/github/ParentSquare/faulty/master
558
668
  [martin fowler]: https://www.martinfowler.com/bliki/CircuitBreaker.html
559
669
  [hystrix]: https://github.com/Netflix/Hystrix/wiki/How-it-Works
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.add_development_dependency 'bundler', '>= 1.17', '< 3'
28
28
  spec.add_development_dependency 'byebug', '~> 11.0'
29
29
  spec.add_development_dependency 'connection_pool', '~> 2.0'
30
+ spec.add_development_dependency 'honeybadger', '>= 2.0'
30
31
  spec.add_development_dependency 'irb', '~> 1.0'
31
32
  spec.add_development_dependency 'redcarpet', '~> 3.5'
32
33
  spec.add_development_dependency 'redis', '~> 3.0'
@@ -21,5 +21,6 @@ module Faulty
21
21
  end
22
22
 
23
23
  require 'faulty/events/callback_listener'
24
- require 'faulty/events/notifier'
24
+ require 'faulty/events/honeybadger_listener'
25
25
  require 'faulty/events/log_listener'
26
+ require 'faulty/events/notifier'
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faulty
4
+ module Events
5
+ # Reports circuit errors to Honeybadger
6
+ #
7
+ # https://www.honeybadger.io/
8
+ #
9
+ # The honeybadger gem must be available.
10
+ class HoneybadgerListener
11
+ # (see ListenerInterface#handle)
12
+ def handle(event, payload)
13
+ return unless EVENTS.include?(event)
14
+
15
+ send(event, payload) if respond_to?(event, true)
16
+ end
17
+
18
+ private
19
+
20
+ def circuit_failure(payload)
21
+ _circuit_error(payload)
22
+ end
23
+
24
+ def circuit_opened(payload)
25
+ _circuit_error(payload)
26
+ end
27
+
28
+ def circuit_reopened(payload)
29
+ _circuit_error(payload)
30
+ end
31
+
32
+ def cache_failure(payload)
33
+ Honeybadger.notify(payload[:error], context: {
34
+ action: payload[:action],
35
+ key: payload[:key]
36
+ })
37
+ end
38
+
39
+ def storage_failure(payload)
40
+ Honeybadger.notify(payload[:error], context: {
41
+ action: payload[:action],
42
+ circuit: payload[:circuit]&.name
43
+ })
44
+ end
45
+
46
+ def _circuit_error(payload)
47
+ Honeybadger.notify(payload[:error], context: {
48
+ circuit: payload[:circuit].name
49
+ })
50
+ end
51
+ end
52
+ end
53
+ end
@@ -11,6 +11,9 @@ module Faulty
11
11
 
12
12
  # Notify all listeners of an event
13
13
  #
14
+ # If a listener raises an error while handling an event, that error will
15
+ # be captured and written to STDERR.
16
+ #
14
17
  # @param event [Symbol] The event name
15
18
  # @param payload [Hash] A hash of event payload data. The payload keys
16
19
  # differ between events, but should be consistent across calls for a
@@ -18,7 +21,13 @@ module Faulty
18
21
  def notify(event, payload)
19
22
  raise ArgumentError, "Unknown event #{event}" unless EVENTS.include?(event)
20
23
 
21
- @listeners.each { |l| l.handle(event, payload) }
24
+ @listeners.each do |listener|
25
+ begin
26
+ listener.handle(event, payload)
27
+ rescue StandardError => e
28
+ warn "Faulty listener #{listener.class.name} crashed: #{e.message}"
29
+ end
30
+ end
22
31
  end
23
32
  end
24
33
  end
@@ -83,7 +83,7 @@ module Faulty
83
83
  memory = fetch(circuit)
84
84
  memory.runs.borrow do |runs|
85
85
  runs.push([time, success])
86
- runs.pop if runs.size > options.max_sample_size
86
+ runs.shift if runs.size > options.max_sample_size
87
87
  end
88
88
  memory.status(circuit.options)
89
89
  end
@@ -3,6 +3,6 @@
3
3
  module Faulty
4
4
  # The current Faulty version
5
5
  def self.version
6
- Gem::Version.new('0.1.2')
6
+ Gem::Version.new('0.1.3')
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.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Howard
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-28 00:00:00.000000000 Z
11
+ date: 2020-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -86,6 +86,20 @@ dependencies:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
88
  version: '2.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: honeybadger
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '2.0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '2.0'
89
103
  - !ruby/object:Gem::Dependency
90
104
  name: irb
91
105
  requirement: !ruby/object:Gem::Requirement
@@ -268,6 +282,7 @@ files:
268
282
  - lib/faulty/error.rb
269
283
  - lib/faulty/events.rb
270
284
  - lib/faulty/events/callback_listener.rb
285
+ - lib/faulty/events/honeybadger_listener.rb
271
286
  - lib/faulty/events/listener_interface.rb
272
287
  - lib/faulty/events/log_listener.rb
273
288
  - lib/faulty/events/notifier.rb