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 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