throttle_machines 0.2.4 → 0.3.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: 6450b4499ed28a6520bdc5d51a2c5bc7fcc55ba8e1f1e327def0ca9ab5c80d62
4
- data.tar.gz: af1addd1f524c9f5663f3f241ce15bfb1aa9a6b5365463f3c9d453a9e03f0b05
3
+ metadata.gz: f3b31d5fbfe9776c3fce6b2dfa90a77932536992f5a182313bb36b12f58d04af
4
+ data.tar.gz: db51e8f4c56ac00ce340debbc10202614ec68bf2d3dc64596705c529897df6fd
5
5
  SHA512:
6
- metadata.gz: 564076859ef7219e48060400a26295cffc830a74fbf82d757766753e19d2020fce390ce8b91b9daf996147689dd47fec0911037a34a0208cd160ec3d6009ac70
7
- data.tar.gz: 39590d460f59b66a700487146ea3309aa8df4d9f3b31e0248acb1b0748bc04b3a26f9a55f52f18b4beb3fca7618a8142092b4d434f45a4ce833a265d8e2c5fdb
6
+ metadata.gz: e4ffd7243f769547032d4ec7e15c7e390142e0b22f178a6eff6f651d10fd7fba31389bb80f6ff843c81ce1491528c1e70c9e57c4c7f02da6d4d71b61d0ed3bcc
7
+ data.tar.gz: 28e290c3e45461f6491e156594c4625be6b6073d8856a5448038c2a872323ecfa1532d31deb18dac35dd9b8dfa91614f6cb3df076dbaaba122e102d7efcff265
@@ -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,7 +1,7 @@
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: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 8.0.4
18
+ version: '8.1'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 8.0.4
25
+ version: '8.1'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: concurrent-ruby
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -71,42 +71,42 @@ dependencies:
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '0.19'
74
+ version: '0.20'
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '0.19'
81
+ version: '0.20'
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: chrono_machines
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '0.9'
88
+ version: '0.10'
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '0.9'
95
+ version: '0.10'
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: minitest
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '5.16'
102
+ version: '6.0'
103
103
  type: :development
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: '5.16'
109
+ version: '6.0'
110
110
  - !ruby/object:Gem::Dependency
111
111
  name: rb_sys
112
112
  requirement: !ruby/object:Gem::Requirement