berater 0.13.1 → 0.15.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: 1270edae5d2c019ae92b623ff4c26e588cf8d5413c8f7b6c549d3d3ec4e16766
4
- data.tar.gz: a9a659acde5a04263b1ca516a2fb1ecdd760d9133f3603b4fa7ec336c06f59df
3
+ metadata.gz: 81f3c362a1360d2b3f3b4a2829b374dd38f0f9de6809737efe7d9bb44ffbd88c
4
+ data.tar.gz: cbad555e49e5ac86d63f99486139e6df13b9da4289832b2d8cf5034bd7140018
5
5
  SHA512:
6
- metadata.gz: 07fc3784c2e6d41648f97878e3a0c62e3a089c1ef69315e88141642350180634c65a89deda082ed5933a90666c620111329756d0699d3fde5238b97d0ad37e82
7
- data.tar.gz: 6f8dc42b5b694a45c286b1d66a0b8efbf17371b3fd6bfd8936098b4155b85ae6523146fecfc6b7c46e3b0a8df1ddb03158f1fd181c9e6a1e13042232089e1a41
6
+ metadata.gz: ed26b29a67a30db075ec18ab64698b6ae76dbe7d4d8aee64c290dc449a6867566ea3c8155eb6ec6c5fe440208c19345bfc02c30adcddb4356b591f784678df99
7
+ data.tar.gz: 56143daeb1069e5d1162bea25c9892f146009168f8ef42c23cdc78774e6148602ef40364423c94e5248d2cff99793e6e5ef6a676e918ec4cf23d2686a35b5927
@@ -62,12 +62,12 @@ module Berater
62
62
  lock = limit(cost: 0)
63
63
 
64
64
  if lock.capacity == 0
65
- 1.0
65
+ 100.0
66
66
  else
67
- lock.contention.to_f / lock.capacity
67
+ lock.contention.to_f / lock.capacity * 100
68
68
  end
69
69
  rescue Berater::Overloaded
70
- 1.0
70
+ 100.0
71
71
  end
72
72
 
73
73
  def ==(other)
@@ -124,12 +124,7 @@ module Berater
124
124
  # can only call via subclass
125
125
  raise NoMethodError if self == Berater::Limiter
126
126
 
127
- if RUBY_VERSION < '3' && kwargs.empty?
128
- # avoid ruby 2 problems with empty hashes
129
- super(*args)
130
- else
131
- super
132
- end
127
+ super
133
128
  end
134
129
 
135
130
  def cache_key(key)
@@ -6,7 +6,9 @@ module Berater
6
6
  end
7
7
 
8
8
  def call(limiter, **)
9
- tracer&.trace('Berater') do |span|
9
+ return yield unless tracer
10
+
11
+ tracer.trace('Berater') do |span|
10
12
  begin
11
13
  lock = yield
12
14
  rescue Exception => error
@@ -32,7 +34,7 @@ module Berater
32
34
  private
33
35
 
34
36
  def tracer
35
- @tracer || (defined?(Datadog::Tracing) && Datadog::Tracing) || (defined?(Datadog) && Datadog.tracer)
37
+ @tracer || (defined?(Datadog) && Datadog::Tracing)
36
38
  end
37
39
  end
38
40
  end
@@ -0,0 +1,28 @@
1
+ module Berater
2
+ module Mutex
3
+ def self.included(base)
4
+ # add class methods
5
+ base.instance_eval do
6
+ def synchronize(subkey = nil, **opts, &block)
7
+ key = [ 'Mutex', name&.delete(':') || object_id, subkey ].compact.join(':')
8
+
9
+ Berater::ConcurrencyLimiter(key, 1, **mutex_options.merge(opts)) do
10
+ yield if block_given?
11
+ end
12
+ end
13
+
14
+ def mutex_options(**kwargs)
15
+ (@mutex_options ||= {}).update(kwargs)
16
+ end
17
+ end
18
+ end
19
+
20
+ def synchronize(...)
21
+ self.class.synchronize(...)
22
+ end
23
+
24
+ def self.extend_object(base)
25
+ included(base)
26
+ end
27
+ end
28
+ end
@@ -14,7 +14,7 @@ module Berater
14
14
  if res.is_a? Berater::Limiter
15
15
  # eg. expect { Berater.new(...) }.to be_overloaded
16
16
  @limiter = res
17
- @limiter.utilization >= 1
17
+ @limiter.utilization >= 100
18
18
  else
19
19
  # eg. expect { Berater(...) }.to be_overloaded
20
20
  # eg. expect { limiter.limit }.to be_overloaded
@@ -23,7 +23,7 @@ module Berater
23
23
  when Berater::Limiter
24
24
  # eg. expect(Berater.new(...)).to be_overloaded
25
25
  @limiter = obj
26
- @limiter.utilization >= 1
26
+ @limiter.utilization >= 100
27
27
  end
28
28
  rescue Berater::Overloaded
29
29
  true
@@ -1,3 +1,3 @@
1
1
  module Berater
2
- VERSION = "0.13.1"
2
+ VERSION = "0.15.0"
3
3
  end
data/lib/berater.rb CHANGED
@@ -3,11 +3,13 @@ require 'berater/limiter_set'
3
3
  require 'berater/lock'
4
4
  require 'berater/lua_script'
5
5
  require 'berater/middleware'
6
+ require 'berater/mutex'
6
7
  require 'berater/utils'
7
8
  require 'berater/version'
8
9
  require 'meddleware'
9
10
 
10
11
  module Berater
12
+ include Meddleware
11
13
  extend self
12
14
 
13
15
  class Overloaded < StandardError; end
@@ -22,12 +24,6 @@ module Berater
22
24
  @limiters ||= LimiterSet.new
23
25
  end
24
26
 
25
- def middleware(&block)
26
- (@middleware ||= Meddleware.new).tap do
27
- @middleware.instance_eval(&block) if block_given?
28
- end
29
- end
30
-
31
27
  def new(key, capacity, **opts)
32
28
  args = []
33
29
 
@@ -255,19 +255,19 @@ describe Berater::ConcurrencyLimiter do
255
255
  let(:limiter) { described_class.new(:key, 10, timeout: 30) }
256
256
 
257
257
  it 'works' do
258
- expect(limiter.utilization).to be 0.0
258
+ expect(limiter.utilization).to eq 0
259
259
 
260
260
  2.times { limiter.limit }
261
- expect(limiter.utilization).to be 0.2
261
+ expect(limiter.utilization).to eq 20
262
262
 
263
263
  Timecop.freeze(15)
264
264
 
265
265
  8.times { limiter.limit }
266
- expect(limiter.utilization).to be 1.0
266
+ expect(limiter.utilization).to eq 100
267
267
 
268
268
  Timecop.freeze(15)
269
269
 
270
- expect(limiter.utilization).to be 0.8
270
+ expect(limiter.utilization).to eq 80
271
271
  end
272
272
  end
273
273
 
@@ -65,6 +65,16 @@ describe Berater::Middleware::Trace do
65
65
  }.to raise_error(IOError)
66
66
  end
67
67
  end
68
+
69
+ context 'when tracer is not defined' do
70
+ before do
71
+ allow(instance).to receive(:tracer).and_return(nil)
72
+ end
73
+
74
+ it 'still yields' do
75
+ expect {|b| instance.call(limiter, &b) }.to yield_control
76
+ end
77
+ end
68
78
  end
69
79
 
70
80
  context 'when used as middleware' do
@@ -0,0 +1,145 @@
1
+ describe Berater::Mutex do
2
+ let(:klass) do
3
+ Class.new do
4
+ include Berater::Mutex
5
+ end
6
+ end
7
+
8
+ describe 'synchronize' do
9
+ it { expect(klass).to respond_to(:synchronize) }
10
+ it { expect(klass.new).to respond_to(:synchronize) }
11
+
12
+ it { expect { |block| klass.synchronize(&block) }.to yield_control }
13
+
14
+ it 'returns the blocks value' do
15
+ res = klass.synchronize { 123 }
16
+ expect(res).to be 123
17
+ end
18
+
19
+ it 'still works and returns nil without a block' do
20
+ expect(klass.synchronize).to be nil
21
+ end
22
+
23
+ it 'does not allow simultaneous calls' do
24
+ expect {
25
+ klass.synchronize do
26
+ klass.synchronize
27
+ end
28
+ }.to be_overloaded
29
+ end
30
+
31
+ it 'allows simultaneous calls with different sub-keys' do
32
+ expect {
33
+ klass.synchronize(:a) do
34
+ klass.synchronize(:b)
35
+ end
36
+ }.not_to raise_error
37
+ end
38
+
39
+ it 'allows consecutive calls' do
40
+ expect {
41
+ 3.times { klass.synchronize }
42
+ }.not_to raise_error
43
+ end
44
+
45
+ describe 'the instance method' do
46
+ it 'is a pass through to the class method' do
47
+ expect(klass).to receive(:synchronize)
48
+ klass.new.synchronize
49
+ end
50
+
51
+ it 'works with arguments' do
52
+ key = 'key'
53
+ opts = { timeout: 1 }
54
+ block = ->{}
55
+
56
+ expect(klass).to receive(:synchronize) do |this_key, **these_opts, &this_block|
57
+ expect(this_key).to be key
58
+ expect(these_opts).to eq opts
59
+ expect(this_block).to be block
60
+ end
61
+
62
+ klass.new.synchronize(key, **opts, &block)
63
+ end
64
+ end
65
+ end
66
+
67
+ describe '.mutex_options' do
68
+ subject { klass.mutex_options }
69
+
70
+ it { expect(klass).to respond_to(:mutex_options) }
71
+ it { is_expected.to be_a Hash }
72
+ it { is_expected.to be_empty }
73
+ it { expect(klass.new).not_to respond_to(:mutex_options) }
74
+
75
+ context 'when mutex_options are set' do
76
+ let(:klass) do
77
+ Class.new do
78
+ include Berater::Mutex
79
+
80
+ mutex_options timeout: 1
81
+ end
82
+ end
83
+
84
+ it { is_expected.to eq(timeout: 1) }
85
+
86
+ it 'uses mutex_options during synchronize' do
87
+ expect(Berater::ConcurrencyLimiter).to receive(:new).and_wrap_original do |original, *args, **kwargs|
88
+ expect(kwargs).to eq(subject)
89
+ original.call(*args, **kwargs)
90
+ end
91
+
92
+ klass.synchronize
93
+ end
94
+ end
95
+ end
96
+
97
+ describe 'when extended rather than included' do
98
+ let(:klass) do
99
+ Class.new do
100
+ extend Berater::Mutex
101
+ end
102
+ end
103
+
104
+ it { expect(klass).to respond_to(:synchronize) }
105
+ it { expect(klass).to respond_to(:mutex_options) }
106
+
107
+ it { expect(klass.new).not_to respond_to(:synchronize) }
108
+ it { expect(klass.new).not_to respond_to(:mutex_options) }
109
+ end
110
+
111
+ describe 'when used in a counter' do
112
+ subject(:counter) { klass.new }
113
+
114
+ let(:klass) do
115
+ class Counter
116
+ include Berater::Mutex
117
+
118
+ @@count = 0
119
+ @@counts = {}
120
+
121
+ def incr
122
+ synchronize { @@count += 1 }
123
+ end
124
+
125
+ def incr_key(key)
126
+ synchronize(key) do
127
+ @@counts[key] ||= 0
128
+ @@counts[key] += 1
129
+ end
130
+ end
131
+ end
132
+ Counter
133
+ end
134
+
135
+ it { expect(counter.incr).to eq 1 }
136
+ it { expect(counter.incr_key(:a)).to eq 1 }
137
+
138
+ it 'separates keys' do
139
+ res = 3.times.map { counter.incr_key(:a) }
140
+ expect(res).to eq [ 1, 2, 3 ]
141
+
142
+ expect(counter.incr_key(:b)).to eq 1
143
+ end
144
+ end
145
+ end
@@ -237,17 +237,17 @@ describe Berater::RateLimiter do
237
237
  let(:limiter) { described_class.new(:key, 10, :minute) }
238
238
 
239
239
  it do
240
- expect(limiter.utilization).to be 0.0
240
+ expect(limiter.utilization).to eq 0
241
241
 
242
242
  2.times { limiter.limit }
243
- expect(limiter.utilization).to be 0.2
243
+ expect(limiter.utilization).to eq 20
244
244
 
245
245
  8.times { limiter.limit }
246
- expect(limiter.utilization).to be 1.0
246
+ expect(limiter.utilization).to eq 100
247
247
 
248
248
  Timecop.freeze(30)
249
249
 
250
- expect(limiter.utilization).to be 0.5
250
+ expect(limiter.utilization).to eq 50
251
251
  end
252
252
  end
253
253
 
@@ -60,13 +60,13 @@ describe Berater::StaticLimiter do
60
60
  let(:limiter) { described_class.new(:key, 10) }
61
61
 
62
62
  it do
63
- expect(limiter.utilization).to be 0.0
63
+ expect(limiter.utilization).to eq 0
64
64
 
65
65
  2.times { limiter.limit }
66
- expect(limiter.utilization).to be 0.2
66
+ expect(limiter.utilization).to eq 20
67
67
 
68
68
  8.times { limiter.limit }
69
- expect(limiter.utilization).to be 1.0
69
+ expect(limiter.utilization).to eq 100
70
70
  end
71
71
  end
72
72
 
@@ -171,4 +171,13 @@ describe Berater::TestMode do
171
171
  it_behaves_like 'it supports test_mode'
172
172
  end
173
173
 
174
+ describe 'Mutex' do
175
+ let(:klass) { Class.new { include Berater::Mutex } }
176
+
177
+ context 'when test_mode = :fail' do
178
+ before { Berater.test_mode = :fail }
179
+
180
+ it { expect { klass.synchronize }.to be_overloaded }
181
+ end
182
+ end
174
183
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: berater
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.1
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Pepper
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-13 00:00:00.000000000 Z
11
+ date: 2023-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: meddleware
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0.2'
19
+ version: '0.3'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0.2'
26
+ version: '0.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: redis
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -66,34 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: codecov
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: ddtrace
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
73
  - - ">="
88
74
  - !ruby/object:Gem::Version
89
- version: '0'
75
+ version: '1'
90
76
  type: :development
91
77
  prerelease: false
92
78
  version_requirements: !ruby/object:Gem::Requirement
93
79
  requirements:
94
80
  - - ">="
95
81
  - !ruby/object:Gem::Version
96
- version: '0'
82
+ version: '1'
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: dogstatsd-ruby
99
85
  requirement: !ruby/object:Gem::Requirement
@@ -108,20 +94,6 @@ dependencies:
108
94
  - - ">="
109
95
  - !ruby/object:Gem::Version
110
96
  version: '4.3'
111
- - !ruby/object:Gem::Dependency
112
- name: rake
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
97
  - !ruby/object:Gem::Dependency
126
98
  name: rspec
127
99
  requirement: !ruby/object:Gem::Requirement
@@ -183,6 +155,7 @@ files:
183
155
  - lib/berater/middleware/load_shedder.rb
184
156
  - lib/berater/middleware/statsd.rb
185
157
  - lib/berater/middleware/trace.rb
158
+ - lib/berater/mutex.rb
186
159
  - lib/berater/rate_limiter.rb
187
160
  - lib/berater/rspec.rb
188
161
  - lib/berater/rspec/matchers.rb
@@ -205,6 +178,7 @@ files:
205
178
  - spec/middleware/statsd_spec.rb
206
179
  - spec/middleware/trace_spec.rb
207
180
  - spec/middleware_spec.rb
181
+ - spec/mutex_spec.rb
208
182
  - spec/rate_limiter_spec.rb
209
183
  - spec/riddle_spec.rb
210
184
  - spec/static_limiter_spec.rb
@@ -223,35 +197,36 @@ required_ruby_version: !ruby/object:Gem::Requirement
223
197
  requirements:
224
198
  - - ">="
225
199
  - !ruby/object:Gem::Version
226
- version: '0'
200
+ version: '3'
227
201
  required_rubygems_version: !ruby/object:Gem::Requirement
228
202
  requirements:
229
203
  - - ">="
230
204
  - !ruby/object:Gem::Version
231
205
  version: '0'
232
206
  requirements: []
233
- rubygems_version: 3.1.6
207
+ rubygems_version: 3.3.7
234
208
  signing_key:
235
209
  specification_version: 4
236
210
  summary: Berater
237
211
  test_files:
238
- - spec/rate_limiter_spec.rb
212
+ - spec/berater_spec.rb
213
+ - spec/concurrency_limiter_spec.rb
214
+ - spec/dsl_refinement_spec.rb
215
+ - spec/dsl_spec.rb
216
+ - spec/inhibitor_spec.rb
217
+ - spec/limiter_set_spec.rb
218
+ - spec/limiter_spec.rb
219
+ - spec/lua_script_spec.rb
220
+ - spec/matchers_spec.rb
221
+ - spec/middleware/fail_open_spec.rb
239
222
  - spec/middleware/load_shedder_spec.rb
240
223
  - spec/middleware/statsd_spec.rb
241
224
  - spec/middleware/trace_spec.rb
242
- - spec/middleware/fail_open_spec.rb
243
- - spec/matchers_spec.rb
244
- - spec/dsl_refinement_spec.rb
245
- - spec/test_mode_spec.rb
246
225
  - spec/middleware_spec.rb
247
- - spec/dsl_spec.rb
248
- - spec/lua_script_spec.rb
249
- - spec/concurrency_limiter_spec.rb
226
+ - spec/mutex_spec.rb
227
+ - spec/rate_limiter_spec.rb
250
228
  - spec/riddle_spec.rb
251
- - spec/limiter_set_spec.rb
252
- - spec/utils_spec.rb
253
- - spec/berater_spec.rb
254
- - spec/limiter_spec.rb
255
229
  - spec/static_limiter_spec.rb
256
- - spec/inhibitor_spec.rb
230
+ - spec/test_mode_spec.rb
257
231
  - spec/unlimiter_spec.rb
232
+ - spec/utils_spec.rb