semian 0.8.9 → 0.9.0

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: fe152464771999d2c44406ecfc6b9211f3288c52a0c8282175474c6a08898e03
4
- data.tar.gz: 4ba4c7224d8da1fad03de8e47793639f85e9f714e57ce04534da7d06ba1d66bf
3
+ metadata.gz: 426aa6c4392e70e79cbf478827dcdccbc1a468f0f19b2a5e42b885584a389b00
4
+ data.tar.gz: df5c7c0ca8e80879c1d1d1a5cc46400c2de841e5809ea2396cc91941f222fa05
5
5
  SHA512:
6
- metadata.gz: fe5c8eccd45220845c26fbf67e91a328ca049ca91ed3e832461928bcadcca28b42951b84dcb200e618588f2d7715f61d08110b9238d110e5258bb2876fe402f4
7
- data.tar.gz: 1b7e9d5f65abcaeca11d3d2ec6ab73f5247bf6cdb4c1e5739ac621c31144deed8d8fe9a35b5ffaec9f6ae6807551fab5bf421b34273518fbe5812768241f922e
6
+ metadata.gz: da18cc34e1c36d18904cbb584a9c7a03fd32afa976f27a6ed227b62e4ebb64065ea38a36ac951d0cade1c72b115143c3b24d6299b28de0927c78e52379205f17
7
+ data.tar.gz: 1e4dd82c16f7717022e65d4c722a28b4bd06490cbf347dfbe0f9fafd5652b615162f884a6dea04ad5ec5314986f7b0aad3abda8205cf2cc34b434416b001d0ed
@@ -240,6 +240,12 @@ semian_resource_alloc(VALUE klass)
240
240
  return obj;
241
241
  }
242
242
 
243
+ VALUE
244
+ semian_resource_in_use(VALUE self)
245
+ {
246
+ return Qtrue;
247
+ }
248
+
243
249
  static VALUE
244
250
  cleanup_semian_resource_acquire(VALUE self)
245
251
  {
@@ -126,4 +126,8 @@ semian_resource_unregister_worker(VALUE self);
126
126
  VALUE
127
127
  semian_resource_alloc(VALUE klass);
128
128
 
129
+ // Returns true if the resource is in use
130
+ VALUE
131
+ semian_resource_in_use(VALUE self);
132
+
129
133
  #endif //SEMIAN_RESOURCE_H
@@ -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");
@@ -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
@@ -78,6 +78,11 @@ module Semian
78
78
  @state.destroy
79
79
  end
80
80
 
81
+ def in_use?
82
+ return false if error_timeout_expired?
83
+ @errors.size > 0
84
+ end
85
+
81
86
  private
82
87
 
83
88
  def transition_to_close
@@ -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)
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Semian
2
- VERSION = '0.8.9'
2
+ VERSION = '0.9.0'
3
3
  end
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.8.9
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-02 00:00:00.000000000 Z
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