semian 0.8.9 → 0.9.0
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/ext/semian/resource.c +6 -0
- data/ext/semian/resource.h +4 -0
- data/ext/semian/semian.c +1 -0
- data/lib/semian.rb +33 -4
- data/lib/semian/circuit_breaker.rb +5 -0
- data/lib/semian/instrumentable.rb +6 -0
- data/lib/semian/lru_hash.rb +160 -0
- data/lib/semian/protected_resource.rb +6 -0
- data/lib/semian/resource.rb +5 -1
- data/lib/semian/unprotected_resource.rb +6 -0
- data/lib/semian/version.rb +1 -1
- metadata +31 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 426aa6c4392e70e79cbf478827dcdccbc1a468f0f19b2a5e42b885584a389b00
|
4
|
+
data.tar.gz: df5c7c0ca8e80879c1d1d1a5cc46400c2de841e5809ea2396cc91941f222fa05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da18cc34e1c36d18904cbb584a9c7a03fd32afa976f27a6ed227b62e4ebb64065ea38a36ac951d0cade1c72b115143c3b24d6299b28de0927c78e52379205f17
|
7
|
+
data.tar.gz: 1e4dd82c16f7717022e65d4c722a28b4bd06490cbf347dfbe0f9fafd5652b615162f884a6dea04ad5ec5314986f7b0aad3abda8205cf2cc34b434416b001d0ed
|
data/ext/semian/resource.c
CHANGED
data/ext/semian/resource.h
CHANGED
data/ext/semian/semian.c
CHANGED
@@ -52,6 +52,7 @@ void Init_semian()
|
|
52
52
|
rb_define_method(cResource, "destroy", semian_resource_destroy, 0);
|
53
53
|
rb_define_method(cResource, "reset_registered_workers!", semian_resource_reset_workers, 0);
|
54
54
|
rb_define_method(cResource, "unregister_worker", semian_resource_unregister_worker, 0);
|
55
|
+
rb_define_method(cResource, "in_use?", semian_resource_in_use, 0);
|
55
56
|
|
56
57
|
id_wait_time = rb_intern("wait_time");
|
57
58
|
id_timeout = rb_intern("timeout");
|
data/lib/semian.rb
CHANGED
@@ -13,6 +13,7 @@ require 'semian/unprotected_resource'
|
|
13
13
|
require 'semian/simple_sliding_window'
|
14
14
|
require 'semian/simple_integer'
|
15
15
|
require 'semian/simple_state'
|
16
|
+
require 'semian/lru_hash'
|
16
17
|
|
17
18
|
#
|
18
19
|
# === Overview
|
@@ -89,6 +90,10 @@ module Semian
|
|
89
90
|
InternalError = Class.new(BaseError)
|
90
91
|
OpenCircuitError = Class.new(BaseError)
|
91
92
|
|
93
|
+
attr_accessor :maximum_lru_size, :minimum_lru_time
|
94
|
+
self.maximum_lru_size = 500
|
95
|
+
self.minimum_lru_time = 300
|
96
|
+
|
92
97
|
def issue_disabled_semaphores_warning
|
93
98
|
return if defined?(@warning_issued)
|
94
99
|
@warning_issued = true
|
@@ -216,7 +221,7 @@ module Semian
|
|
216
221
|
|
217
222
|
# Retrieves a hash of all registered resources.
|
218
223
|
def resources
|
219
|
-
@resources ||=
|
224
|
+
@resources ||= LRUHash.new
|
220
225
|
end
|
221
226
|
|
222
227
|
# Retrieves a hash of all registered resource consumers.
|
@@ -226,7 +231,16 @@ module Semian
|
|
226
231
|
|
227
232
|
def reset!
|
228
233
|
@consumers = {}
|
229
|
-
@resources =
|
234
|
+
@resources = LRUHash.new
|
235
|
+
end
|
236
|
+
|
237
|
+
def thread_safe?
|
238
|
+
return @thread_safe if defined?(@thread_safe)
|
239
|
+
@thread_safe = true
|
240
|
+
end
|
241
|
+
|
242
|
+
def thread_safe=(thread_safe)
|
243
|
+
@thread_safe = thread_safe
|
230
244
|
end
|
231
245
|
|
232
246
|
private
|
@@ -235,7 +249,6 @@ module Semian
|
|
235
249
|
circuit_breaker = options.fetch(:circuit_breaker, true)
|
236
250
|
return unless circuit_breaker
|
237
251
|
require_keys!([:success_threshold, :error_threshold, :error_timeout], options)
|
238
|
-
implementation = options[:thread_safety_disabled] ? ::Semian::Simple : ::Semian::ThreadSafe
|
239
252
|
|
240
253
|
exceptions = options[:exceptions] || []
|
241
254
|
CircuitBreaker.new(
|
@@ -245,10 +258,26 @@ module Semian
|
|
245
258
|
error_timeout: options[:error_timeout],
|
246
259
|
exceptions: Array(exceptions) + [::Semian::BaseError],
|
247
260
|
half_open_resource_timeout: options[:half_open_resource_timeout],
|
248
|
-
implementation: implementation,
|
261
|
+
implementation: implementation(**options),
|
249
262
|
)
|
250
263
|
end
|
251
264
|
|
265
|
+
def implementation(**options)
|
266
|
+
# thread_safety_disabled will be replaced by a global setting
|
267
|
+
# Semian is thread safe by default. It is possible
|
268
|
+
# to modify the value by using Semian.thread_safe=
|
269
|
+
unless options[:thread_safety_disabled].nil?
|
270
|
+
logger.info(
|
271
|
+
"NOTE: thread_safety_disabled will be replaced by a global setting" \
|
272
|
+
"Semian is thread safe by default. It is possible" \
|
273
|
+
"to modify the value by using Semian.thread_safe=",
|
274
|
+
)
|
275
|
+
end
|
276
|
+
|
277
|
+
thread_safe = options[:thread_safety_disabled].nil? ? Semian.thread_safe? : !options[:thread_safety_disabled]
|
278
|
+
thread_safe ? ::Semian::ThreadSafe : ::Semian::Simple
|
279
|
+
end
|
280
|
+
|
252
281
|
def create_bulkhead(name, **options)
|
253
282
|
bulkhead = options.fetch(:bulkhead, true)
|
254
283
|
return unless bulkhead
|
@@ -9,6 +9,12 @@ module Semian
|
|
9
9
|
subscribers.delete(name)
|
10
10
|
end
|
11
11
|
|
12
|
+
# Args:
|
13
|
+
# event (string)
|
14
|
+
# resource (Object)
|
15
|
+
# scope (string)
|
16
|
+
# adapter (string)
|
17
|
+
# payload (optional)
|
12
18
|
def notify(*args)
|
13
19
|
subscribers.values.each { |subscriber| subscriber.call(*args) }
|
14
20
|
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
class LRUHash
|
2
|
+
# This LRU (Least Recently Used) hash will allow
|
3
|
+
# the cleaning of resources as time goes on.
|
4
|
+
# The goal is to remove the least recently used resources
|
5
|
+
# everytime we set a new resource. A default window of
|
6
|
+
# 5 minutes will allow empty item to stay in the hash
|
7
|
+
# for a maximum of 5 minutes
|
8
|
+
extend Forwardable
|
9
|
+
def_delegators :@table, :size, :count, :empty?, :values
|
10
|
+
attr_reader :table
|
11
|
+
|
12
|
+
class NoopMutex
|
13
|
+
def synchronize(*)
|
14
|
+
yield
|
15
|
+
end
|
16
|
+
|
17
|
+
def try_lock
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def unlock
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def locked?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def owned?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def keys
|
35
|
+
@lock.synchronize { @table.keys }
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear
|
39
|
+
@lock.synchronize { @table.clear }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create an LRU hash
|
43
|
+
#
|
44
|
+
# Arguments:
|
45
|
+
# +max_size+ The maximum size of the table
|
46
|
+
# +min_time+ The minimum time a resource can live in the cache
|
47
|
+
#
|
48
|
+
# Note:
|
49
|
+
# The +min_time+ is a stronger guarantee than +max_size+. That is, if there are
|
50
|
+
# more than +max_size+ entries in the cache, but they've all been updated more
|
51
|
+
# recently than +min_time+, the garbage collection will not remove them and the
|
52
|
+
# cache can grow without bound. This usually means that you have many active
|
53
|
+
# circuits to disparate endpoints (or your circuit names are bad).
|
54
|
+
# If the max_size is 0, the garbage collection will be very aggressive and
|
55
|
+
# potentially computationally expensive.
|
56
|
+
def initialize(max_size: Semian.maximum_lru_size, min_time: Semian.minimum_lru_time)
|
57
|
+
@max_size = max_size
|
58
|
+
@min_time = min_time
|
59
|
+
@table = {}
|
60
|
+
@lock =
|
61
|
+
if Semian.thread_safe?
|
62
|
+
Mutex.new
|
63
|
+
else
|
64
|
+
NoopMutex.new
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def set(key, resource)
|
69
|
+
@lock.synchronize do
|
70
|
+
@table.delete(key)
|
71
|
+
@table[key] = resource
|
72
|
+
resource.updated_at = Time.now
|
73
|
+
end
|
74
|
+
clear_unused_resources if @table.length > @max_size
|
75
|
+
end
|
76
|
+
|
77
|
+
# This method uses the property that "Hashes enumerate their values in the
|
78
|
+
# order that the corresponding keys were inserted." Deleting a key and
|
79
|
+
# re-inserting it effectively moves it to the front of the cache.
|
80
|
+
# Update the `updated_at` field so we can use it later do decide if the
|
81
|
+
# resource is "in use".
|
82
|
+
def get(key)
|
83
|
+
@lock.synchronize do
|
84
|
+
found = @table.delete(key)
|
85
|
+
if found
|
86
|
+
@table[key] = found
|
87
|
+
found.updated_at = Time.now
|
88
|
+
end
|
89
|
+
found
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def delete(key)
|
94
|
+
@lock.synchronize do
|
95
|
+
@table.delete(key)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def []=(key, resource)
|
100
|
+
set(key, resource)
|
101
|
+
end
|
102
|
+
|
103
|
+
def [](key)
|
104
|
+
get(key)
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def clear_unused_resources
|
110
|
+
payload = {
|
111
|
+
size: @table.size,
|
112
|
+
examined: 0,
|
113
|
+
cleared: 0,
|
114
|
+
elapsed: nil,
|
115
|
+
}
|
116
|
+
timer_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
117
|
+
|
118
|
+
ran = try_synchronize do
|
119
|
+
# Clears resources that have not been used in the last 5 minutes.
|
120
|
+
|
121
|
+
stop_time = Time.now - @min_time # Don't process resources updated after this time
|
122
|
+
@table.each do |_, resource|
|
123
|
+
payload[:examined] += 1
|
124
|
+
|
125
|
+
# The update times of the resources in the LRU are monotonically increasing,
|
126
|
+
# time, so we can stop looking once we find the first resource with an
|
127
|
+
# update time after the stop_time.
|
128
|
+
break if resource.updated_at > stop_time
|
129
|
+
|
130
|
+
next if resource.in_use?
|
131
|
+
|
132
|
+
resource = @table.delete(resource.name)
|
133
|
+
if resource
|
134
|
+
payload[:cleared] += 1
|
135
|
+
resource.destroy
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
if ran
|
141
|
+
payload[:elapsed] = Process.clock_gettime(Process::CLOCK_MONOTONIC) - timer_start
|
142
|
+
Semian.notify(:lru_hash_gc, self, nil, nil, payload)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
EXCEPTION_NEVER = {Exception => :never}.freeze
|
147
|
+
EXCEPTION_IMMEDIATE = {Exception => :immediate}.freeze
|
148
|
+
private_constant :EXCEPTION_NEVER
|
149
|
+
private_constant :EXCEPTION_IMMEDIATE
|
150
|
+
|
151
|
+
def try_synchronize
|
152
|
+
Thread.handle_interrupt(EXCEPTION_NEVER) do
|
153
|
+
return false unless @lock.try_lock
|
154
|
+
Thread.handle_interrupt(EXCEPTION_IMMEDIATE) { yield }
|
155
|
+
true
|
156
|
+
ensure
|
157
|
+
@lock.unlock if @lock.owned?
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -7,11 +7,13 @@ module Semian
|
|
7
7
|
:open?, :closed?, :half_open?
|
8
8
|
|
9
9
|
attr_reader :bulkhead, :circuit_breaker, :name
|
10
|
+
attr_accessor :updated_at
|
10
11
|
|
11
12
|
def initialize(name, bulkhead, circuit_breaker)
|
12
13
|
@name = name
|
13
14
|
@bulkhead = bulkhead
|
14
15
|
@circuit_breaker = circuit_breaker
|
16
|
+
@updated_at = Time.now
|
15
17
|
end
|
16
18
|
|
17
19
|
def destroy
|
@@ -28,6 +30,10 @@ module Semian
|
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
33
|
+
def in_use?
|
34
|
+
circuit_breaker&.in_use? || bulkhead&.in_use?
|
35
|
+
end
|
36
|
+
|
31
37
|
private
|
32
38
|
|
33
39
|
def acquire_circuit_breaker(scope, adapter, resource)
|
data/lib/semian/resource.rb
CHANGED
@@ -5,7 +5,7 @@ module Semian
|
|
5
5
|
class << Semian::Resource
|
6
6
|
# Ensure that there can only be one resource of a given type
|
7
7
|
def instance(*args)
|
8
|
-
Semian.resources[args.first] ||= new(*args)
|
8
|
+
Semian.resources[args.first] ||= ProtectedResource.new(args.first, new(*args), nil)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
@@ -51,5 +51,9 @@ module Semian
|
|
51
51
|
def key
|
52
52
|
'0x00000000'
|
53
53
|
end
|
54
|
+
|
55
|
+
def in_use?
|
56
|
+
false
|
57
|
+
end
|
54
58
|
end
|
55
59
|
end
|
@@ -3,9 +3,11 @@ module Semian
|
|
3
3
|
# the semian configuration of an `Adapter` is missing or explicitly disabled
|
4
4
|
class UnprotectedResource
|
5
5
|
attr_reader :name
|
6
|
+
attr_accessor :updated_at
|
6
7
|
|
7
8
|
def initialize(name)
|
8
9
|
@name = name
|
10
|
+
@updated_at = Time.now
|
9
11
|
end
|
10
12
|
|
11
13
|
def registered_workers
|
@@ -63,5 +65,9 @@ module Semian
|
|
63
65
|
def circuit_breaker
|
64
66
|
nil
|
65
67
|
end
|
68
|
+
|
69
|
+
def in_use?
|
70
|
+
true
|
71
|
+
end
|
66
72
|
end
|
67
73
|
end
|
data/lib/semian/version.rb
CHANGED
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.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Francis
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2019-09-
|
13
|
+
date: 2019-09-04 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rake-compiler
|
@@ -166,6 +166,34 @@ dependencies:
|
|
166
166
|
- - ">="
|
167
167
|
- !ruby/object:Gem::Version
|
168
168
|
version: '0'
|
169
|
+
- !ruby/object:Gem::Dependency
|
170
|
+
name: memory_profiler
|
171
|
+
requirement: !ruby/object:Gem::Requirement
|
172
|
+
requirements:
|
173
|
+
- - ">="
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
type: :development
|
177
|
+
prerelease: false
|
178
|
+
version_requirements: !ruby/object:Gem::Requirement
|
179
|
+
requirements:
|
180
|
+
- - ">="
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: '0'
|
183
|
+
- !ruby/object:Gem::Dependency
|
184
|
+
name: benchmark-memory
|
185
|
+
requirement: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
type: :development
|
191
|
+
prerelease: false
|
192
|
+
version_requirements: !ruby/object:Gem::Requirement
|
193
|
+
requirements:
|
194
|
+
- - ">="
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
version: '0'
|
169
197
|
description: |2
|
170
198
|
A Ruby C extention that is used to control access to shared resources
|
171
199
|
across process boundaries with SysV semaphores.
|
@@ -190,6 +218,7 @@ files:
|
|
190
218
|
- lib/semian/circuit_breaker.rb
|
191
219
|
- lib/semian/grpc.rb
|
192
220
|
- lib/semian/instrumentable.rb
|
221
|
+
- lib/semian/lru_hash.rb
|
193
222
|
- lib/semian/mysql2.rb
|
194
223
|
- lib/semian/net_http.rb
|
195
224
|
- lib/semian/platform.rb
|