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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6daa64288b6b4677a2abc6f4e547aa72c0cd47696b1b47fd0d7a2116b5a5cc0
4
- data.tar.gz: 6fee5050c2d5d3720b22be523303bf23247ecb720361228abf7d1d15f9ed3c65
3
+ metadata.gz: 7a367c10d728005041ed53354fcd0277fb2b77165abbc908624e8f25428ac3a3
4
+ data.tar.gz: f41150be77f1125251a6440de9c3b999d23fb599f520f79829d90b243c33d339
5
5
  SHA512:
6
- metadata.gz: 7bf4baa41c3a9ccfacc8406b15f2bd85c39138593be9318f0712da906f8c816dc94b90d5097603d94b9f341fc8923b8d61854dad5f5197dfcda2dc2050a9d83c
7
- data.tar.gz: 55c9dcf87364ca17819475fbcd183f17ad8aadd3b8f32e80ce8cb3bc15a9459a02435c709f7ab057dba1eefb0c6c8e622d87acccc96dec0c493147c05fac5d12
6
+ metadata.gz: eca3ce48c96d84633d07a0612b15e23ee4e0694344ca135b19425fe6021a825c8685966ea295b02de58378af3c5e9f0e6d818229f3b3830e3b5fc7c8a9fb232c
7
+ data.tar.gz: ffa1c7ab62ec4e31a2657e9159fbb048bd439437cf5f30fa2eae0366f4aa196596f106212fba2a3739a4301952962a78baebc32882fd8fd0b2ad5bc4ab7dbf9f
@@ -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
- message = "Semian Resource #{semian_identifier}: #{error.message}"
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, "Circuit breaker is open for resource: #{@name}" unless request_allowed?
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
- subscribers[name] = block
10
+ SUBSCRIBERS[name] = block
7
11
  name
8
12
  end
9
13
 
10
14
  def unsubscribe(name)
11
- subscribers.delete(name)
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
- subscribers.values.each { |subscriber| subscriber.call(*args) }
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 < Simple::Integer
30
- def initialize(*)
31
- super
32
- @lock = Mutex.new
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 increment(*)
36
- @lock.synchronize { super }
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 Simple
7
- class SlidingWindow # :nodoc:
8
- extend Forwardable
7
+ module SlidingWindowBehavior
8
+ extend Forwardable
9
9
 
10
- def_delegators :@window, :size, :last, :empty?
11
- attr_reader :max_size
10
+ def_delegators :@window, :size, :last, :empty?
11
+ attr_reader :max_size
12
12
 
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.
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
- def initialize(max_size:)
18
- @max_size = max_size
19
- @window = []
20
- end
17
+ def reject!(&block)
18
+ @window.reject!(&block)
19
+ self
20
+ end
21
21
 
22
- def reject!(&block)
23
- @window.reject!(&block)
24
- end
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
- def push(value)
27
- resize_to(@max_size - 1) # make room
28
- @window << value
29
- self
30
- end
31
- alias_method :<<, :push
29
+ def clear
30
+ @window.clear
31
+ self
32
+ end
33
+ alias_method :destroy, :clear
32
34
 
33
- def clear
34
- @window.clear
35
- self
36
- end
37
- alias_method :destroy, :clear
35
+ private
36
+
37
+ def resize_to(size)
38
+ @window = @window.last(size) if @window.size >= size
39
+ end
40
+ end
38
41
 
39
- private
42
+ module Simple
43
+ class SlidingWindow # :nodoc:
44
+ include SlidingWindowBehavior
40
45
 
41
- def resize_to(size)
42
- @window = @window.last(size) if @window.size >= size
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 < Simple::SlidingWindow
49
- def initialize(**)
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 push(*)
66
- @lock.synchronize { super }
57
+ def initialize(max_size:)
58
+ @max_size = max_size
59
+ @window = Concurrent::Array.new
67
60
  end
68
61
  end
69
62
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Semian
4
- VERSION = "0.25.3"
4
+ VERSION = "0.25.5"
5
5
  end
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 = (consumers[name] ||= ObjectSpace::WeakMap.new)
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
- @consumers = {}
260
- @resources = LRUHash.new
261
- end
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: semian
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.25.3
4
+ version: 0.25.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Francis