throttle_machines 0.1.0 → 0.1.1

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: 21546f9777cfd269ba2461dd3a3200173591defc6f20effb84fa8b7fb254353c
4
- data.tar.gz: 4f82a87423e163ba277c5707b65dc122b1f0ba047ed97c29b5a877c14af92d94
3
+ metadata.gz: 1c498f9f097c1eac7384d573ea5e90b04e3850d6b236aae5a0f5b9bb0cdf29ae
4
+ data.tar.gz: 515fae0aad7083290b12cec368148e021ff9312fb026892fb4574fd89a1a0f6c
5
5
  SHA512:
6
- metadata.gz: 61d639a39194c9d504871e2159a30ab89671d3f42f333d1f283ecc7a7fb1c238d948a5dfa3c336a6c05fec5e3065613d00610027fbdcf6af0d3df5277dc02a2a
7
- data.tar.gz: b844e1d3ce3949c273e996972770f7d5f1aa1d3a5729da02d54651915bd826b327dd2826e96d4c09ac6ac921e720b437494ed3eac4d2c7b8db776e6f50244d3a
6
+ metadata.gz: ec8ac810c8696a93a44bcf305245eef336ce338418b82a5236e73fa919afda7125cee2ac391c0230419f136525ee1104723dc3a81e42517c4c0a48ebd6878d76
7
+ data.tar.gz: 3908d0866286abc0556b79160c8543da2b542ab3cfb55b2c2d636fc69bb0393196848f920714e89e4926d7e4450ae5de33329d0774ae4a0f8f59860bbbaad620
@@ -5,7 +5,7 @@ module ThrottleMachines
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- if respond_to?(:helper_method)
8
+ if respond_to?(:helper_method) # No available in API Mode
9
9
  helper_method :rate_limited?
10
10
  helper_method :rate_limit_remaining
11
11
  end
@@ -15,10 +15,8 @@ module ThrottleMachines
15
15
 
16
16
  initializer 'throttle_machines.configure_defaults' do |_app|
17
17
  ThrottleMachines.configure do |config|
18
- # Use Redis if available in Rails cache
19
- if defined?(Redis) && Rails.cache.respond_to?(:redis)
20
- config.store = ThrottleMachines::Stores::Redis.new(Rails.cache.redis)
21
- end
18
+ # Use Rails.cache , user can override
19
+ config.store = Rails.cache
22
20
  end
23
21
  end
24
22
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThrottleMachines
4
+ # Hedged request with circuit breaker integration
5
+ class HedgedBreaker
6
+ def initialize(breakers, delay: 0.05)
7
+ @breakers = Array(breakers)
8
+ @hedged = HedgedRequest.new(
9
+ delay: delay,
10
+ max_attempts: @breakers.size
11
+ )
12
+ end
13
+
14
+ def run(&)
15
+ @hedged.run do |attempt|
16
+ breaker = @breakers[attempt]
17
+ next if breaker.nil?
18
+
19
+ breaker.call(&)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -105,26 +105,6 @@ module ThrottleMachines
105
105
  end
106
106
  end
107
107
 
108
- # Hedged request with circuit breaker integration
109
- class HedgedBreaker
110
- def initialize(breakers, delay: 0.05)
111
- @breakers = Array(breakers)
112
- @hedged = HedgedRequest.new(
113
- delay: delay,
114
- max_attempts: @breakers.size
115
- )
116
- end
117
-
118
- def run(&)
119
- @hedged.run do |attempt|
120
- breaker = @breakers[attempt]
121
- next if breaker.nil?
122
-
123
- breaker.call(&)
124
- end
125
- end
126
- end
127
-
128
108
  # Convenience method
129
109
  def self.hedged_request(**, &)
130
110
  hedged = HedgedRequest.new(**)
@@ -12,11 +12,7 @@ module ThrottleMachines
12
12
  end
13
13
 
14
14
  def backend
15
- @backend ||= if defined?(ActiveSupport::Notifications)
16
- ActiveSupport::Notifications
17
- else
18
- NullBackend.new
19
- end
15
+ @backend ||= ActiveSupport::Notifications
20
16
  end
21
17
 
22
18
  def instrument(event_name, payload = {}, &)
@@ -35,9 +35,11 @@ module ThrottleMachines
35
35
  @configuration.instance_eval(&) if block
36
36
  end
37
37
 
38
+ # rubocop:disable Rails/Delegate -- Ruby 3.4 compatibility issue with delegate
38
39
  def reset!
39
40
  ThrottleMachines.reset!
40
41
  end
42
+ # rubocop:enable Rails/Delegate
41
43
 
42
44
  def clear!
43
45
  @configuration = Configuration.new
@@ -54,7 +56,7 @@ module ThrottleMachines
54
56
 
55
57
  # Set defaults
56
58
  @enabled = true
57
- @notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
59
+ @notifier = ActiveSupport::Notifications
58
60
  @configuration = Configuration.new
59
61
 
60
62
  def initialize(app)
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
3
  require 'concurrent'
5
4
 
6
5
  module ThrottleMachines
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module ThrottleMachines
6
4
  module Storage
7
5
  class Null < Base
@@ -0,0 +1,22 @@
1
+ local key = KEYS[1]
2
+ local emission_interval = tonumber(ARGV[1])
3
+ local delay_tolerance = tonumber(ARGV[2])
4
+ local ttl = tonumber(ARGV[3])
5
+ local now = tonumber(ARGV[4])
6
+
7
+ local tat = redis.call('GET', key)
8
+ if not tat then
9
+ tat = 0
10
+ else
11
+ tat = tonumber(tat)
12
+ end
13
+
14
+ tat = math.max(tat, now)
15
+ local allow = (tat - now) <= delay_tolerance
16
+
17
+ if allow then
18
+ local new_tat = tat + emission_interval
19
+ redis.call('SET', key, new_tat, 'EX', ttl)
20
+ end
21
+
22
+ return { allow and 1 or 0, tat }
@@ -0,0 +1,23 @@
1
+ local data = redis.call('HGETALL', KEYS[1])
2
+ if #data == 0 then
3
+ return {}
4
+ end
5
+
6
+ local state = {}
7
+ for i = 1, #data, 2 do
8
+ state[data[i]] = data[i + 1]
9
+ end
10
+
11
+ -- Auto-transition from open to half-open if timeout passed
12
+ if state['state'] == 'open' and state['opens_at'] then
13
+ local now = tonumber(ARGV[1])
14
+ local opens_at = tonumber(state['opens_at'])
15
+
16
+ if now >= opens_at then
17
+ redis.call('HSET', KEYS[1], 'state', 'half_open', 'half_open_attempts', '0')
18
+ state['state'] = 'half_open'
19
+ state['half_open_attempts'] = '0'
20
+ end
21
+ end
22
+
23
+ return state
@@ -0,0 +1,9 @@
1
+ local count = redis.call('INCRBY', KEYS[1], ARGV[1])
2
+ local ttl = redis.call('TTL', KEYS[1])
3
+
4
+ -- Set expiry if key is new (ttl == -2) or has no TTL (ttl == -1)
5
+ if ttl <= 0 then
6
+ redis.call('EXPIRE', KEYS[1], ARGV[2])
7
+ end
8
+
9
+ return count
@@ -0,0 +1,16 @@
1
+ local key = KEYS[1]
2
+ local emission_interval = tonumber(ARGV[1])
3
+ local delay_tolerance = tonumber(ARGV[2])
4
+ local now = tonumber(ARGV[3])
5
+
6
+ local tat = redis.call('GET', key)
7
+ if not tat then
8
+ tat = 0
9
+ else
10
+ tat = tonumber(tat)
11
+ end
12
+
13
+ tat = math.max(tat, now)
14
+ local allow = (tat - now) <= delay_tolerance
15
+
16
+ return { allow and 1 or 0, tat }
@@ -0,0 +1,18 @@
1
+ local key = KEYS[1]
2
+ local capacity = tonumber(ARGV[1])
3
+ local refill_rate = tonumber(ARGV[2])
4
+ local now = tonumber(ARGV[3])
5
+
6
+ local bucket = redis.call('HMGET', key, 'tokens', 'last_refill')
7
+ local tokens = tonumber(bucket[1]) or capacity
8
+ local last_refill = tonumber(bucket[2]) or now
9
+
10
+ -- Calculate tokens without modifying
11
+ local elapsed = now - last_refill
12
+ local tokens_to_add = elapsed * refill_rate
13
+ tokens = math.min(tokens + tokens_to_add, capacity)
14
+
15
+ local allow = tokens >= 1
16
+ local tokens_after = allow and (tokens - 1) or 0
17
+
18
+ return { allow and 1 or 0, tokens_after }
@@ -0,0 +1,24 @@
1
+ local state = redis.call('HGET', KEYS[1], 'state') or 'closed'
2
+ local now = ARGV[3]
3
+ local timeout = tonumber(ARGV[2])
4
+
5
+ if state == 'half_open' then
6
+ -- Failure in half-open state, just re-open the circuit
7
+ redis.call('HMSET', KEYS[1],
8
+ 'state', 'open',
9
+ 'opens_at', tonumber(now) + timeout,
10
+ 'last_failure', now
11
+ )
12
+ else -- state is 'closed' or nil
13
+ local failures = redis.call('HINCRBY', KEYS[1], 'failures', 1)
14
+ redis.call('HSET', KEYS[1], 'last_failure', now)
15
+
16
+ if failures >= tonumber(ARGV[1]) then
17
+ redis.call('HMSET', KEYS[1],
18
+ 'state', 'open',
19
+ 'opens_at', tonumber(now) + timeout
20
+ )
21
+ end
22
+ end
23
+
24
+ redis.call('EXPIRE', KEYS[1], timeout * 2)
@@ -0,0 +1,16 @@
1
+ local state = redis.call('HGET', KEYS[1], 'state')
2
+
3
+ if state == 'half_open' then
4
+ -- Increment half-open attempts and potentially close the circuit
5
+ local attempts = redis.call('HINCRBY', KEYS[1], 'half_open_attempts', 1)
6
+
7
+ if attempts >= tonumber(ARGV[1]) then
8
+ redis.call('DEL', KEYS[1])
9
+ end
10
+ elseif state == 'closed' then
11
+ -- Reset failure count on success in closed state
12
+ local failures = redis.call('HGET', KEYS[1], 'failures')
13
+ if failures and tonumber(failures) > 0 then
14
+ redis.call('HSET', KEYS[1], 'failures', 0)
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ local key = KEYS[1]
2
+ local capacity = tonumber(ARGV[1])
3
+ local refill_rate = tonumber(ARGV[2])
4
+ local ttl = tonumber(ARGV[3])
5
+ local now = tonumber(ARGV[4])
6
+
7
+ local bucket = redis.call('HMGET', key, 'tokens', 'last_refill')
8
+ local tokens = tonumber(bucket[1]) or capacity
9
+ local last_refill = tonumber(bucket[2]) or now
10
+
11
+ -- Refill tokens
12
+ local elapsed = now - last_refill
13
+ local tokens_to_add = elapsed * refill_rate
14
+ tokens = math.min(tokens + tokens_to_add, capacity)
15
+
16
+ local allow = tokens >= 1
17
+ if allow then
18
+ tokens = tokens - 1
19
+ redis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)
20
+ redis.call('EXPIRE', key, ttl)
21
+ end
22
+
23
+ return { allow and 1 or 0, tokens }
@@ -1,100 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module ThrottleMachines
6
4
  module Storage
7
5
  class Redis < Base
8
- GCRA_SCRIPT = <<~LUA
9
- local key = KEYS[1]
10
- local emission_interval = tonumber(ARGV[1])
11
- local delay_tolerance = tonumber(ARGV[2])
12
- local ttl = tonumber(ARGV[3])
13
- local now = tonumber(ARGV[4])
14
-
15
- local tat = redis.call('GET', key)
16
- if not tat then
17
- tat = 0
18
- else
19
- tat = tonumber(tat)
20
- end
21
-
22
- tat = math.max(tat, now)
23
- local allow = (tat - now) <= delay_tolerance
24
-
25
- if allow then
26
- local new_tat = tat + emission_interval
27
- redis.call('SET', key, new_tat, 'EX', ttl)
28
- end
29
-
30
- return { allow and 1 or 0, tat }
31
- LUA
32
-
33
- TOKEN_BUCKET_SCRIPT = <<~LUA
34
- local key = KEYS[1]
35
- local capacity = tonumber(ARGV[1])
36
- local refill_rate = tonumber(ARGV[2])
37
- local ttl = tonumber(ARGV[3])
38
- local now = tonumber(ARGV[4])
39
-
40
- local bucket = redis.call('HMGET', key, 'tokens', 'last_refill')
41
- local tokens = tonumber(bucket[1]) or capacity
42
- local last_refill = tonumber(bucket[2]) or now
43
-
44
- -- Refill tokens
45
- local elapsed = now - last_refill
46
- local tokens_to_add = elapsed * refill_rate
47
- tokens = math.min(tokens + tokens_to_add, capacity)
48
-
49
- local allow = tokens >= 1
50
- if allow then
51
- tokens = tokens - 1
52
- redis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)
53
- redis.call('EXPIRE', key, ttl)
54
- end
55
-
56
- return { allow and 1 or 0, tokens }
57
- LUA
58
-
59
- PEEK_GCRA_SCRIPT = <<~LUA
60
- local key = KEYS[1]
61
- local emission_interval = tonumber(ARGV[1])
62
- local delay_tolerance = tonumber(ARGV[2])
63
- local now = tonumber(ARGV[3])
64
-
65
- local tat = redis.call('GET', key)
66
- if not tat then
67
- tat = 0
68
- else
69
- tat = tonumber(tat)
70
- end
71
-
72
- tat = math.max(tat, now)
73
- local allow = (tat - now) <= delay_tolerance
74
-
75
- return { allow and 1 or 0, tat }
76
- LUA
77
-
78
- PEEK_TOKEN_BUCKET_SCRIPT = <<~LUA
79
- local key = KEYS[1]
80
- local capacity = tonumber(ARGV[1])
81
- local refill_rate = tonumber(ARGV[2])
82
- local now = tonumber(ARGV[3])
83
-
84
- local bucket = redis.call('HMGET', key, 'tokens', 'last_refill')
85
- local tokens = tonumber(bucket[1]) or capacity
86
- local last_refill = tonumber(bucket[2]) or now
87
-
88
- -- Calculate tokens without modifying
89
- local elapsed = now - last_refill
90
- local tokens_to_add = elapsed * refill_rate
91
- tokens = math.min(tokens + tokens_to_add, capacity)
92
-
93
- local allow = tokens >= 1
94
- local tokens_after = allow and (tokens - 1) or 0
95
-
96
- return { allow and 1 or 0, tokens_after }
97
- LUA
6
+ # Load Lua scripts from files
7
+ LUA_SCRIPTS_DIR = File.expand_path('redis', __dir__)
8
+
9
+ GCRA_SCRIPT = File.read(File.join(LUA_SCRIPTS_DIR, 'gcra.lua'))
10
+ TOKEN_BUCKET_SCRIPT = File.read(File.join(LUA_SCRIPTS_DIR, 'token_bucket.lua'))
11
+ PEEK_GCRA_SCRIPT = File.read(File.join(LUA_SCRIPTS_DIR, 'peek_gcra.lua'))
12
+ PEEK_TOKEN_BUCKET_SCRIPT = File.read(File.join(LUA_SCRIPTS_DIR, 'peek_token_bucket.lua'))
13
+ INCREMENT_COUNTER_SCRIPT = File.read(File.join(LUA_SCRIPTS_DIR, 'increment_counter.lua'))
14
+ GET_BREAKER_STATE_SCRIPT = File.read(File.join(LUA_SCRIPTS_DIR, 'get_breaker_state.lua'))
15
+ RECORD_BREAKER_SUCCESS_SCRIPT = File.read(File.join(LUA_SCRIPTS_DIR, 'record_breaker_success.lua'))
16
+ RECORD_BREAKER_FAILURE_SCRIPT = File.read(File.join(LUA_SCRIPTS_DIR, 'record_breaker_failure.lua'))
98
17
 
99
18
  def initialize(options = {})
100
19
  super
@@ -117,17 +36,7 @@ module ThrottleMachines
117
36
 
118
37
  # Use Lua script for atomic increment with TTL
119
38
  with_redis do |redis|
120
- redis.eval(<<~LUA, keys: [window_key], argv: [amount, window.to_i])
121
- local count = redis.call('INCRBY', KEYS[1], ARGV[1])
122
- local ttl = redis.call('TTL', KEYS[1])
123
-
124
- -- Set expiry if key is new (ttl == -2) or has no TTL (ttl == -1)
125
- if ttl <= 0 then
126
- redis.call('EXPIRE', KEYS[1], ARGV[2])
127
- end
128
-
129
- return count
130
- LUA
39
+ redis.eval(INCREMENT_COUNTER_SCRIPT, keys: [window_key], argv: [amount, window.to_i])
131
40
  end
132
41
  end
133
42
 
@@ -262,31 +171,7 @@ module ThrottleMachines
262
171
 
263
172
  # Use Lua script for atomic read and potential state transition
264
173
  result = with_redis do |redis|
265
- redis.eval(<<~LUA, keys: [breaker_key], argv: [current_time])
266
- local data = redis.call('HGETALL', KEYS[1])
267
- if #data == 0 then
268
- return {}
269
- end
270
-
271
- local state = {}
272
- for i = 1, #data, 2 do
273
- state[data[i]] = data[i + 1]
274
- end
275
-
276
- -- Auto-transition from open to half-open if timeout passed
277
- if state['state'] == 'open' and state['opens_at'] then
278
- local now = tonumber(ARGV[1])
279
- local opens_at = tonumber(state['opens_at'])
280
- #{' '}
281
- if now >= opens_at then
282
- redis.call('HSET', KEYS[1], 'state', 'half_open', 'half_open_attempts', '0')
283
- state['state'] = 'half_open'
284
- state['half_open_attempts'] = '0'
285
- end
286
- end
287
-
288
- return state
289
- LUA
174
+ redis.eval(GET_BREAKER_STATE_SCRIPT, keys: [breaker_key], argv: [current_time])
290
175
  end
291
176
 
292
177
  return { state: :closed, failures: 0, last_failure: nil } if result.empty?
@@ -309,24 +194,7 @@ module ThrottleMachines
309
194
 
310
195
  # Use Lua script for atomic success recording
311
196
  with_redis do |redis|
312
- redis.eval(<<~LUA, keys: [breaker_key], argv: [half_open_requests])
313
- local state = redis.call('HGET', KEYS[1], 'state')
314
-
315
- if state == 'half_open' then
316
- -- Increment half-open attempts and potentially close the circuit
317
- local attempts = redis.call('HINCRBY', KEYS[1], 'half_open_attempts', 1)
318
- #{' '}
319
- if attempts >= tonumber(ARGV[1]) then
320
- redis.call('DEL', KEYS[1])
321
- end
322
- elseif state == 'closed' then
323
- -- Reset failure count on success in closed state
324
- local failures = redis.call('HGET', KEYS[1], 'failures')
325
- if failures and tonumber(failures) > 0 then
326
- redis.call('HSET', KEYS[1], 'failures', 0)
327
- end
328
- end
329
- LUA
197
+ redis.eval(RECORD_BREAKER_SUCCESS_SCRIPT, keys: [breaker_key], argv: [half_open_requests])
330
198
  end
331
199
  end
332
200
 
@@ -336,32 +204,7 @@ module ThrottleMachines
336
204
 
337
205
  # Use Lua script for atomic failure recording
338
206
  with_redis do |redis|
339
- redis.eval(<<~LUA, keys: [breaker_key], argv: [threshold, timeout, now])
340
- local state = redis.call('HGET', KEYS[1], 'state') or 'closed'
341
- local now = ARGV[3]
342
- local timeout = tonumber(ARGV[2])
343
-
344
- if state == 'half_open' then
345
- -- Failure in half-open state, just re-open the circuit
346
- redis.call('HMSET', KEYS[1],
347
- 'state', 'open',
348
- 'opens_at', tonumber(now) + timeout,
349
- 'last_failure', now
350
- )
351
- else -- state is 'closed' or nil
352
- local failures = redis.call('HINCRBY', KEYS[1], 'failures', 1)
353
- redis.call('HSET', KEYS[1], 'last_failure', now)
354
- #{' '}
355
- if failures >= tonumber(ARGV[1]) then
356
- redis.call('HMSET', KEYS[1],
357
- 'state', 'open',
358
- 'opens_at', tonumber(now) + timeout
359
- )
360
- end
361
- end
362
-
363
- redis.call('EXPIRE', KEYS[1], timeout * 2)
364
- LUA
207
+ redis.eval(RECORD_BREAKER_FAILURE_SCRIPT, keys: [breaker_key], argv: [threshold, timeout, now])
365
208
  end
366
209
 
367
210
  get_breaker_state(key)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ThrottleMachines
4
- VERSION = '0.1.0'
4
+ VERSION = '0.1.1'
5
5
  end
@@ -14,9 +14,6 @@ loader = Zeitwerk::Loader.for_gem
14
14
  loader.ignore("#{__dir__}/throttle_machines/engine.rb") unless defined?(Rails::Engine)
15
15
  loader.setup
16
16
 
17
- # Load conditional dependencies manually
18
- require_relative 'throttle_machines/storage/redis' if defined?(Redis)
19
-
20
17
  module ThrottleMachines
21
18
  include ActiveSupport::Configurable
22
19
 
@@ -132,7 +129,6 @@ module ThrottleMachines
132
129
  # Auto-configure with defaults
133
130
  configure
134
131
 
135
- # Backward compatibility aliases for error classes (defined after module setup)
136
- CircuitOpenError = BreakerMachines::CircuitOpenError if defined?(BreakerMachines::CircuitOpenError)
137
- RetryExhaustedError = ChronoMachines::MaxRetriesExceededError if defined?(ChronoMachines::MaxRetriesExceededError)
132
+ CircuitOpenError = BreakerMachines::CircuitOpenError
133
+ RetryExhaustedError = ChronoMachines::MaxRetriesExceededError
138
134
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: throttle_machines
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -101,16 +101,16 @@ executables: []
101
101
  extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
- - MIT-LICENSE
104
+ - LICENSE
105
105
  - README.md
106
106
  - Rakefile
107
107
  - lib/throttle_machines.rb
108
108
  - lib/throttle_machines/async_limiter.rb
109
- - lib/throttle_machines/clock.rb
110
109
  - lib/throttle_machines/control.rb
111
110
  - lib/throttle_machines/controller_helpers.rb
112
111
  - lib/throttle_machines/dependency_error.rb
113
112
  - lib/throttle_machines/engine.rb
113
+ - lib/throttle_machines/hedged_breaker.rb
114
114
  - lib/throttle_machines/hedged_request.rb
115
115
  - lib/throttle_machines/instrumentation.rb
116
116
  - lib/throttle_machines/limiter.rb
@@ -128,6 +128,14 @@ files:
128
128
  - lib/throttle_machines/storage/memory.rb
129
129
  - lib/throttle_machines/storage/null.rb
130
130
  - lib/throttle_machines/storage/redis.rb
131
+ - lib/throttle_machines/storage/redis/gcra.lua
132
+ - lib/throttle_machines/storage/redis/get_breaker_state.lua
133
+ - lib/throttle_machines/storage/redis/increment_counter.lua
134
+ - lib/throttle_machines/storage/redis/peek_gcra.lua
135
+ - lib/throttle_machines/storage/redis/peek_token_bucket.lua
136
+ - lib/throttle_machines/storage/redis/record_breaker_failure.lua
137
+ - lib/throttle_machines/storage/redis/record_breaker_success.lua
138
+ - lib/throttle_machines/storage/redis/token_bucket.lua
131
139
  - lib/throttle_machines/throttled_error.rb
132
140
  - lib/throttle_machines/version.rb
133
141
  homepage: https://github.com/seuros/throttle_machines
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ThrottleMachines
4
- # TestClock for time manipulation in tests
5
- # This overrides the global monotonic_time method for testing
6
- class TestClock
7
- attr_accessor :current_time
8
-
9
- def initialize(start_time = Time.now.to_f)
10
- @current_time = start_time
11
-
12
- # Override the global monotonic_time method
13
- ThrottleMachines.singleton_class.define_method(:monotonic_time) do
14
- @current_time
15
- end
16
- end
17
-
18
- def now
19
- @current_time
20
- end
21
-
22
- def monotonic
23
- @current_time
24
- end
25
-
26
- def advance(seconds)
27
- @current_time += seconds
28
- end
29
-
30
- def travel_to(time)
31
- @current_time = time.to_f
32
- end
33
-
34
- def reset
35
- # Restore the original monotonic_time method
36
- ThrottleMachines.singleton_class.define_method(:monotonic_time) do
37
- BreakerMachines.monotonic_time
38
- end
39
- end
40
- end
41
- end
File without changes