semian 0.25.3 → 0.25.5
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/lib/semian/adapter.rb +1 -2
- data/lib/semian/circuit_breaker.rb +1 -1
- data/lib/semian/instrumentable.rb +11 -9
- data/lib/semian/simple_integer.rb +22 -6
- data/lib/semian/simple_sliding_window.rb +40 -47
- data/lib/semian/version.rb +1 -1
- data/lib/semian.rb +25 -25
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a367c10d728005041ed53354fcd0277fb2b77165abbc908624e8f25428ac3a3
|
4
|
+
data.tar.gz: f41150be77f1125251a6440de9c3b999d23fb599f520f79829d90b243c33d339
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eca3ce48c96d84633d07a0612b15e23ee4e0694344ca135b19425fe6021a825c8685966ea295b02de58378af3c5e9f0e6d818229f3b3830e3b5fc7c8a9fb232c
|
7
|
+
data.tar.gz: ffa1c7ab62ec4e31a2657e9159fbb048bd439437cf5f30fa2eae0366f4aa196596f106212fba2a3739a4301952962a78baebc32882fd8fd0b2ad5bc4ab7dbf9f
|
data/lib/semian/adapter.rb
CHANGED
@@ -49,8 +49,7 @@ module Semian
|
|
49
49
|
last_error = nil unless last_error.is_a?(Exception) # Net::HTTPServerError is not an exception
|
50
50
|
raise self.class::CircuitOpenError.new(semian_identifier, message), cause: last_error
|
51
51
|
rescue ::Semian::BaseError => error
|
52
|
-
|
53
|
-
raise self.class::ResourceBusyError.new(semian_identifier, message)
|
52
|
+
raise self.class::ResourceBusyError.new(semian_identifier, error.message)
|
54
53
|
rescue *resource_exceptions => error
|
55
54
|
error.semian_identifier = semian_identifier if error.respond_to?(:semian_identifier=)
|
56
55
|
raise
|
@@ -39,7 +39,7 @@ module Semian
|
|
39
39
|
def acquire(resource = nil, &block)
|
40
40
|
transition_to_half_open if transition_to_half_open?
|
41
41
|
|
42
|
-
raise OpenCircuitError
|
42
|
+
raise OpenCircuitError unless request_allowed?
|
43
43
|
|
44
44
|
result = nil
|
45
45
|
begin
|
@@ -1,14 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "concurrent-ruby"
|
4
|
+
|
3
5
|
module Semian
|
4
6
|
module Instrumentable
|
7
|
+
SUBSCRIBERS = Concurrent::Map.new
|
8
|
+
|
5
9
|
def subscribe(name = rand, &block)
|
6
|
-
|
10
|
+
SUBSCRIBERS[name] = block
|
7
11
|
name
|
8
12
|
end
|
9
13
|
|
10
14
|
def unsubscribe(name)
|
11
|
-
|
15
|
+
SUBSCRIBERS.delete(name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def subscribers
|
19
|
+
SUBSCRIBERS
|
12
20
|
end
|
13
21
|
|
14
22
|
# Args:
|
@@ -18,13 +26,7 @@ module Semian
|
|
18
26
|
# adapter (string)
|
19
27
|
# payload (optional)
|
20
28
|
def notify(*args)
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def subscribers
|
27
|
-
@subscribers ||= {}
|
29
|
+
SUBSCRIBERS.values.each { |subscriber| subscriber.call(*args) }
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "thread"
|
4
|
+
require "concurrent"
|
4
5
|
|
5
6
|
module Semian
|
6
7
|
module Simple
|
@@ -26,14 +27,29 @@ module Semian
|
|
26
27
|
end
|
27
28
|
|
28
29
|
module ThreadSafe
|
29
|
-
class Integer
|
30
|
-
def initialize
|
31
|
-
|
32
|
-
|
30
|
+
class Integer
|
31
|
+
def initialize
|
32
|
+
@atom = Concurrent::AtomicFixnum.new(0)
|
33
|
+
end
|
34
|
+
|
35
|
+
def value
|
36
|
+
@atom.value
|
33
37
|
end
|
34
38
|
|
35
|
-
def
|
36
|
-
@
|
39
|
+
def value=(new_value)
|
40
|
+
@atom.value = new_value
|
41
|
+
end
|
42
|
+
|
43
|
+
def increment(val = 1)
|
44
|
+
@atom.increment(val)
|
45
|
+
end
|
46
|
+
|
47
|
+
def reset
|
48
|
+
@atom.value = 0
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy
|
52
|
+
reset
|
37
53
|
end
|
38
54
|
end
|
39
55
|
end
|
@@ -1,69 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "thread"
|
4
|
+
require "concurrent"
|
4
5
|
|
5
6
|
module Semian
|
6
|
-
module
|
7
|
-
|
8
|
-
extend Forwardable
|
7
|
+
module SlidingWindowBehavior
|
8
|
+
extend Forwardable
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
def_delegators :@window, :size, :last, :empty?
|
11
|
+
attr_reader :max_size
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
# A sliding window is a structure that stores the most @max_size recent timestamps
|
14
|
+
# like this: if @max_size = 4, current time is 10, @window =[5,7,9,10].
|
15
|
+
# Another push of (11) at 11 sec would make @window [7,9,10,11], shifting off 5.
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
def reject!(&block)
|
18
|
+
@window.reject!(&block)
|
19
|
+
self
|
20
|
+
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
def push(value)
|
23
|
+
resize_to(@max_size - 1) # make room
|
24
|
+
@window << value
|
25
|
+
self
|
26
|
+
end
|
27
|
+
alias_method :<<, :push
|
25
28
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
alias_method :<<, :push
|
29
|
+
def clear
|
30
|
+
@window.clear
|
31
|
+
self
|
32
|
+
end
|
33
|
+
alias_method :destroy, :clear
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
private
|
36
|
+
|
37
|
+
def resize_to(size)
|
38
|
+
@window = @window.last(size) if @window.size >= size
|
39
|
+
end
|
40
|
+
end
|
38
41
|
|
39
|
-
|
42
|
+
module Simple
|
43
|
+
class SlidingWindow # :nodoc:
|
44
|
+
include SlidingWindowBehavior
|
40
45
|
|
41
|
-
def
|
42
|
-
@
|
46
|
+
def initialize(max_size:)
|
47
|
+
@max_size = max_size
|
48
|
+
@window = []
|
43
49
|
end
|
44
50
|
end
|
45
51
|
end
|
46
52
|
|
47
53
|
module ThreadSafe
|
48
|
-
class SlidingWindow
|
49
|
-
|
50
|
-
super
|
51
|
-
@lock = Mutex.new
|
52
|
-
end
|
53
|
-
|
54
|
-
# #size, #last, and #clear are not wrapped in a mutex. For the first two,
|
55
|
-
# the worst-case is a thread-switch at a timing where they'd receive an
|
56
|
-
# out-of-date value--which could happen with a mutex as well.
|
57
|
-
#
|
58
|
-
# As for clear, it's an all or nothing operation. Doesn't matter if we
|
59
|
-
# have the lock or not.
|
60
|
-
|
61
|
-
def reject!(*)
|
62
|
-
@lock.synchronize { super }
|
63
|
-
end
|
54
|
+
class SlidingWindow
|
55
|
+
include SlidingWindowBehavior
|
64
56
|
|
65
|
-
def
|
66
|
-
@
|
57
|
+
def initialize(max_size:)
|
58
|
+
@max_size = max_size
|
59
|
+
@window = Concurrent::Array.new
|
67
60
|
end
|
68
61
|
end
|
69
62
|
end
|
data/lib/semian/version.rb
CHANGED
data/lib/semian.rb
CHANGED
@@ -4,6 +4,7 @@ require "forwardable"
|
|
4
4
|
require "logger"
|
5
5
|
require "weakref"
|
6
6
|
require "thread"
|
7
|
+
require "concurrent-ruby"
|
7
8
|
|
8
9
|
require "semian/version"
|
9
10
|
require "semian/instrumentable"
|
@@ -103,13 +104,30 @@ module Semian
|
|
103
104
|
OpenCircuitError = Class.new(BaseError)
|
104
105
|
SemaphoreMissingError = Class.new(BaseError)
|
105
106
|
|
106
|
-
attr_accessor :maximum_lru_size, :minimum_lru_time, :default_permissions, :namespace, :default_force_config_validation
|
107
|
+
attr_accessor :maximum_lru_size, :minimum_lru_time, :default_permissions, :namespace, :default_force_config_validation, :resources, :consumers
|
107
108
|
|
108
109
|
self.maximum_lru_size = 500
|
109
110
|
self.minimum_lru_time = 300 # 300 seconds / 5 minutes
|
110
111
|
self.default_permissions = 0660
|
111
112
|
self.default_force_config_validation = false
|
112
113
|
|
114
|
+
# We only allow disabling thread-safety for parts of the code that are on the hot path.
|
115
|
+
# Since locking there could have a significant impact. Everything else is enforced thread safety
|
116
|
+
def thread_safe?
|
117
|
+
return @thread_safe if defined?(@thread_safe)
|
118
|
+
|
119
|
+
@thread_safe = true
|
120
|
+
end
|
121
|
+
|
122
|
+
def thread_safe=(thread_safe)
|
123
|
+
@thread_safe = thread_safe
|
124
|
+
end
|
125
|
+
|
126
|
+
self.resources = LRUHash.new
|
127
|
+
self.consumers = Concurrent::Map.new
|
128
|
+
|
129
|
+
@reset_mutex = Mutex.new
|
130
|
+
|
113
131
|
def issue_disabled_semaphores_warning
|
114
132
|
return if defined?(@warning_issued)
|
115
133
|
|
@@ -200,7 +218,7 @@ module Semian
|
|
200
218
|
# of who the consumer was so that we can clear the resource reference if needed.
|
201
219
|
consumer = args.delete(:consumer)
|
202
220
|
if consumer&.class&.include?(Semian::Adapter) && !args[:dynamic]
|
203
|
-
consumer_set = (
|
221
|
+
consumer_set = consumers.compute_if_absent(name) { ObjectSpace::WeakMap.new }
|
204
222
|
consumer_set[consumer] = true
|
205
223
|
end
|
206
224
|
self[name] || register(name, **args)
|
@@ -233,7 +251,7 @@ module Semian
|
|
233
251
|
resource = resources.delete(name)
|
234
252
|
if resource
|
235
253
|
resource.bulkhead&.unregister_worker
|
236
|
-
consumers_for_resource = consumers.delete(name) ||
|
254
|
+
consumers_for_resource = consumers.delete(name) || ObjectSpace::WeakMap.new
|
237
255
|
consumers_for_resource.each_key(&:clear_semian_resource)
|
238
256
|
end
|
239
257
|
end
|
@@ -245,29 +263,11 @@ module Semian
|
|
245
263
|
end
|
246
264
|
end
|
247
265
|
|
248
|
-
# Retrieves a hash of all registered resources.
|
249
|
-
def resources
|
250
|
-
@resources ||= LRUHash.new
|
251
|
-
end
|
252
|
-
|
253
|
-
# Retrieves a hash of all registered resource consumers.
|
254
|
-
def consumers
|
255
|
-
@consumers ||= {}
|
256
|
-
end
|
257
|
-
|
258
266
|
def reset!
|
259
|
-
@
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
def thread_safe?
|
264
|
-
return @thread_safe if defined?(@thread_safe)
|
265
|
-
|
266
|
-
@thread_safe = true
|
267
|
-
end
|
268
|
-
|
269
|
-
def thread_safe=(thread_safe)
|
270
|
-
@thread_safe = thread_safe
|
267
|
+
@reset_mutex.synchronize do
|
268
|
+
self.consumers = Concurrent::Map.new
|
269
|
+
self.resources = LRUHash.new
|
270
|
+
end
|
271
271
|
end
|
272
272
|
|
273
273
|
THREAD_BULKHEAD_DISABLED_VAR = :semian_bulkheads_disabled
|