semian 0.25.4 → 0.26.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/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 +37 -24
- 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: 20f2ab860d074c5420db9d4a5ec675307fdf046c4ad06d3aab227273b7a3716c
|
|
4
|
+
data.tar.gz: 25e865fa1a72948765a572a20ccfbdea783c4940fd245bf18f281af8a2351f54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f8dbe833518cadd4083cb104554add1f1e0b506d02f92b8b55dbc763ff822e5e5fdf22633b3de415fde54ba56780c5ea2e2aae88bd08d7367d5fa5e10ea58d33
|
|
7
|
+
data.tar.gz: a126b289713f04a264a7a8e24e25fa1b7cd964477a22ec5f62696710c79abb9f6e73c74ef71ed77e7d05665d79ea67b53bc598d0c8b2a837eef8f8a75b608e71
|
|
@@ -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"
|
|
@@ -110,6 +111,20 @@ module Semian
|
|
|
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
|
+
@reset_mutex = Mutex.new
|
|
127
|
+
|
|
113
128
|
def issue_disabled_semaphores_warning
|
|
114
129
|
return if defined?(@warning_issued)
|
|
115
130
|
|
|
@@ -200,7 +215,7 @@ module Semian
|
|
|
200
215
|
# of who the consumer was so that we can clear the resource reference if needed.
|
|
201
216
|
consumer = args.delete(:consumer)
|
|
202
217
|
if consumer&.class&.include?(Semian::Adapter) && !args[:dynamic]
|
|
203
|
-
consumer_set = (
|
|
218
|
+
consumer_set = consumers.compute_if_absent(name) { ObjectSpace::WeakMap.new }
|
|
204
219
|
consumer_set[consumer] = true
|
|
205
220
|
end
|
|
206
221
|
self[name] || register(name, **args)
|
|
@@ -233,7 +248,7 @@ module Semian
|
|
|
233
248
|
resource = resources.delete(name)
|
|
234
249
|
if resource
|
|
235
250
|
resource.bulkhead&.unregister_worker
|
|
236
|
-
consumers_for_resource = consumers.delete(name) ||
|
|
251
|
+
consumers_for_resource = consumers.delete(name) || ObjectSpace::WeakMap.new
|
|
237
252
|
consumers_for_resource.each_key(&:clear_semian_resource)
|
|
238
253
|
end
|
|
239
254
|
end
|
|
@@ -245,29 +260,11 @@ module Semian
|
|
|
245
260
|
end
|
|
246
261
|
end
|
|
247
262
|
|
|
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
263
|
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
|
|
264
|
+
@reset_mutex.synchronize do
|
|
265
|
+
@consumers = Concurrent::Map.new
|
|
266
|
+
@resources = LRUHash.new
|
|
267
|
+
end
|
|
271
268
|
end
|
|
272
269
|
|
|
273
270
|
THREAD_BULKHEAD_DISABLED_VAR = :semian_bulkheads_disabled
|
|
@@ -285,6 +282,22 @@ module Semian
|
|
|
285
282
|
thread.thread_variable_set(THREAD_BULKHEAD_DISABLED_VAR, old_value)
|
|
286
283
|
end
|
|
287
284
|
|
|
285
|
+
def resources
|
|
286
|
+
return @resources if defined?(@resources) && @resources
|
|
287
|
+
|
|
288
|
+
@reset_mutex.synchronize do
|
|
289
|
+
@resources ||= LRUHash.new
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def consumers
|
|
294
|
+
return @consumers if defined?(@consumers) && @consumers
|
|
295
|
+
|
|
296
|
+
@reset_mutex.synchronize do
|
|
297
|
+
@consumers ||= Concurrent::Map.new
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
288
301
|
private
|
|
289
302
|
|
|
290
303
|
def create_circuit_breaker(name, **options)
|