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 +4 -4
- data/lib/throttle_machines/storage/redis.rb +31 -75
- data/lib/throttle_machines/version.rb +1 -1
- metadata +9 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f3b31d5fbfe9776c3fce6b2dfa90a77932536992f5a182313bb36b12f58d04af
|
|
4
|
+
data.tar.gz: db51e8f4c56ac00ce340debbc10202614ec68bf2d3dc64596705c529897df6fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
208
|
-
|
|
163
|
+
redis.eval(script, keys: keys, argv: argv)
|
|
164
|
+
end
|
|
209
165
|
end
|
|
210
166
|
|
|
211
167
|
def validate_redis_connection!
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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: '
|
|
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: '
|
|
109
|
+
version: '6.0'
|
|
110
110
|
- !ruby/object:Gem::Dependency
|
|
111
111
|
name: rb_sys
|
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|