throttle_machines 0.2.4-x86_64-linux → 0.3.0-x86_64-linux

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: b903b7c1264b244a780a3730bd09d7bdab11db1f28961910980d93d7722ff92e
4
- data.tar.gz: d6229cb0f861acea1f76e99a8ec4bcd0c3217f10745467f6411c5383519fe84b
3
+ metadata.gz: 6930fcc4385b64ab5aaa9fd9ebbaa1b001fd8a55872e84eb70b94fbeb028f9bd
4
+ data.tar.gz: a3bdbd07e82ada0f96f3e402c0c4a9e59daeabc592f750dcca1e4e6af8d4bbc8
5
5
  SHA512:
6
- metadata.gz: f35c2f3f16752f64e34105a201f5854b157a0f5092972acb47310802400502474d3d74ca52852056b77932c442af3d76ab0edec0becdd0142a07d4c17a11159e
7
- data.tar.gz: 7c20314309fda5045cc7109b03b79dcdac901cb1ce9e83011cf59f6f1906c24f3fd9e6db7b86aff3091deebd21fdbd02118db08d9bce43d7a3f5be6c06111d0c
6
+ metadata.gz: 1a408d9c1aedf8b8ae9ec3aaa212c707dc1526907f401b8c1118e0b040eb4fc161cc17b205be3f098864c02364535103ccead629f58a9d3deef1fe32f240f1d3
7
+ data.tar.gz: 859411fc180acee7b70865e9f95f20b4de8fd14ba3f1e1c48b35d612ce2455152265c76335aa722b84ee0065aaf62ca95af029b7213a14a2bf591ae5b6f76797
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'digest/sha1'
4
+
3
5
  module ThrottleMachines
4
6
  module Storage
5
7
  class Redis < Base
@@ -13,17 +15,18 @@ module ThrottleMachines
13
15
  INCREMENT_COUNTER_SCRIPT = File.read(File.join(LUA_SCRIPTS_DIR, 'increment_counter.lua'))
14
16
  # Breaker scripts removed: breaker state is owned by BreakerMachines
15
17
 
18
+ # Redis caches scripts by the SHA1 of their source, so the SHAs are known
19
+ # without a SCRIPT LOAD round trip.
20
+ GCRA_SHA = Digest::SHA1.hexdigest(GCRA_SCRIPT)
21
+ TOKEN_BUCKET_SHA = Digest::SHA1.hexdigest(TOKEN_BUCKET_SCRIPT)
22
+ PEEK_GCRA_SHA = Digest::SHA1.hexdigest(PEEK_GCRA_SCRIPT)
23
+ PEEK_TOKEN_BUCKET_SHA = Digest::SHA1.hexdigest(PEEK_TOKEN_BUCKET_SCRIPT)
24
+
16
25
  def initialize(options = {})
17
26
  super
18
27
  @redis = options[:redis] || options[:client] || options[:pool]
19
28
  @prefix = options[:prefix] || 'throttle:'
20
29
 
21
- # Cache scripts to avoid repeated script loads
22
- @gcra_sha = nil
23
- @token_bucket_sha = nil
24
- @peek_gcra_sha = nil
25
- @peek_token_bucket_sha = nil
26
-
27
30
  # Validate Redis connection
28
31
  validate_redis_connection!
29
32
  end
@@ -56,15 +59,9 @@ module ThrottleMachines
56
59
 
57
60
  # GCRA operations (atomic via Lua)
58
61
  def check_gcra_limit(key, emission_interval, delay_tolerance, ttl)
59
- ensure_gcra_script_loaded!
60
-
61
- result = with_redis do |redis|
62
- redis.evalsha(
63
- @gcra_sha,
64
- keys: [prefixed(key)],
65
- argv: [emission_interval, delay_tolerance, ttl, current_time]
66
- )
67
- end
62
+ result = run_script(GCRA_SCRIPT, GCRA_SHA,
63
+ keys: [prefixed(key)],
64
+ argv: [emission_interval, delay_tolerance, ttl, current_time])
68
65
 
69
66
  allowed = result[0] == 1
70
67
  tat = result[1]
@@ -75,24 +72,13 @@ module ThrottleMachines
75
72
  retry_after: allowed ? 0 : (tat - now - delay_tolerance),
76
73
  tat: tat
77
74
  }
78
- rescue ::Redis::CommandError => e
79
- raise unless e.message.include?('NOSCRIPT')
80
-
81
- @gcra_sha = nil
82
- retry
83
75
  end
84
76
 
85
77
  # Token bucket operations (atomic via Lua)
86
78
  def check_token_bucket(key, capacity, refill_rate, ttl)
87
- ensure_token_bucket_script_loaded!
88
-
89
- result = with_redis do |redis|
90
- redis.evalsha(
91
- @token_bucket_sha,
92
- keys: [prefixed(key)],
93
- argv: [capacity, refill_rate, ttl, current_time]
94
- )
95
- end
79
+ result = run_script(TOKEN_BUCKET_SCRIPT, TOKEN_BUCKET_SHA,
80
+ keys: [prefixed(key)],
81
+ argv: [capacity, refill_rate, ttl, current_time])
96
82
 
97
83
  allowed = result[0] == 1
98
84
  tokens = result[1]
@@ -102,24 +88,13 @@ module ThrottleMachines
102
88
  retry_after: allowed ? 0 : (1 - tokens) / refill_rate,
103
89
  tokens_remaining: tokens.floor
104
90
  }
105
- rescue ::Redis::CommandError => e
106
- raise unless e.message.include?('NOSCRIPT')
107
-
108
- @token_bucket_sha = nil
109
- retry
110
91
  end
111
92
 
112
93
  # Peek methods for non-consuming checks
113
94
  def peek_gcra_limit(key, emission_interval, delay_tolerance)
114
- ensure_peek_gcra_script_loaded!
115
-
116
- result = with_redis do |redis|
117
- redis.evalsha(
118
- @peek_gcra_sha,
119
- keys: [prefixed(key)],
120
- argv: [emission_interval, delay_tolerance, current_time]
121
- )
122
- end
95
+ result = run_script(PEEK_GCRA_SCRIPT, PEEK_GCRA_SHA,
96
+ keys: [prefixed(key)],
97
+ argv: [emission_interval, delay_tolerance, current_time])
123
98
 
124
99
  allowed = result[0] == 1
125
100
  tat = result[1]
@@ -130,23 +105,12 @@ module ThrottleMachines
130
105
  retry_after: allowed ? 0 : (tat - now - delay_tolerance),
131
106
  tat: tat
132
107
  }
133
- rescue ::Redis::CommandError => e
134
- raise unless e.message.include?('NOSCRIPT')
135
-
136
- @peek_gcra_sha = nil
137
- retry
138
108
  end
139
109
 
140
110
  def peek_token_bucket(key, capacity, refill_rate)
141
- ensure_peek_token_bucket_script_loaded!
142
-
143
- result = with_redis do |redis|
144
- redis.evalsha(
145
- @peek_token_bucket_sha,
146
- keys: [prefixed(key)],
147
- argv: [capacity, refill_rate, current_time]
148
- )
149
- end
111
+ result = run_script(PEEK_TOKEN_BUCKET_SCRIPT, PEEK_TOKEN_BUCKET_SHA,
112
+ keys: [prefixed(key)],
113
+ argv: [capacity, refill_rate, current_time])
150
114
 
151
115
  allowed = result[0] == 1
152
116
  tokens_remaining = result[1]
@@ -156,11 +120,6 @@ module ThrottleMachines
156
120
  retry_after: allowed ? 0 : (1 - tokens_remaining) / refill_rate,
157
121
  tokens_remaining: tokens_remaining.floor
158
122
  }
159
- rescue ::Redis::CommandError => e
160
- raise unless e.message.include?('NOSCRIPT')
161
-
162
- @peek_token_bucket_sha = nil
163
- retry
164
123
  end
165
124
 
166
125
  # No circuit breaker operations: breaker state is owned by BreakerMachines
@@ -192,20 +151,17 @@ module ThrottleMachines
192
151
  "#{@prefix}#{key}"
193
152
  end
194
153
 
195
- def ensure_gcra_script_loaded!
196
- @ensure_gcra_script_loaded ||= with_redis { |r| r.script(:load, GCRA_SCRIPT) }
197
- end
198
-
199
- def ensure_token_bucket_script_loaded!
200
- @ensure_token_bucket_script_loaded ||= with_redis { |r| r.script(:load, TOKEN_BUCKET_SCRIPT) }
201
- end
202
-
203
- def ensure_peek_gcra_script_loaded!
204
- @ensure_peek_gcra_script_loaded ||= with_redis { |r| r.script(:load, PEEK_GCRA_SCRIPT) }
205
- end
154
+ # EVALSHA by the precomputed SHA. A server without the script cached (a
155
+ # restart, SCRIPT FLUSH, failover) answers NOSCRIPT; EVAL then runs the
156
+ # source once and caches it for later calls. Any other error propagates.
157
+ def run_script(script, sha, keys:, argv:)
158
+ with_redis do |redis|
159
+ redis.evalsha(sha, keys: keys, argv: argv)
160
+ rescue ::Redis::CommandError => e
161
+ raise unless e.message.include?('NOSCRIPT')
206
162
 
207
- def ensure_peek_token_bucket_script_loaded!
208
- @ensure_peek_token_bucket_script_loaded ||= with_redis { |r| r.script(:load, PEEK_TOKEN_BUCKET_SCRIPT) }
163
+ redis.eval(script, keys: keys, argv: argv)
164
+ end
209
165
  end
210
166
 
211
167
  def validate_redis_connection!
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ThrottleMachines
4
- VERSION = '0.2.4'
4
+ VERSION = '0.3.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: throttle_machines
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Abdelkader Boudih
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-10 00:00:00.000000000 Z
11
+ date: 2026-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 8.0.4
19
+ version: '8.1'
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: 8.0.4
26
+ version: '8.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: concurrent-ruby
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -72,42 +72,42 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.19'
75
+ version: '0.20'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0.19'
82
+ version: '0.20'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: chrono_machines
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0.9'
89
+ version: '0.10'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0.9'
96
+ version: '0.10'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: minitest
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '5.16'
103
+ version: '6.0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '5.16'
110
+ version: '6.0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rake
113
113
  requirement: !ruby/object:Gem::Requirement