gitlab-labkit 4.2.0 → 4.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/labkit/rate_limit/evaluator.rb +31 -8
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 63bc67bcb0041f919748393e6ace8e96d29bcd88e2c5b0e08b8baad8968670b1
|
|
4
|
+
data.tar.gz: 5bd9b55ece983433e125c7cd087030d740647101a48e9af1f97f1aa68ed51db4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61d298ae9cb7c584d6e9979fbdb25fc112b705a8f1c0022825192e2759356408cbe6c03caf339ae5140220e325dc90532f24775a1f76decf1eb1547269cf3aba
|
|
7
|
+
data.tar.gz: 3755c6de56d994d081210d1cae48430d9938420c6ac33932ce672f3c20eee86457742ce75966bbd2163c09807a4b4e4c72e82579f8eba262e25cb1b79204ce1f
|
|
@@ -12,6 +12,12 @@ module Labkit
|
|
|
12
12
|
CHAR_VALUE_MAX_LENGTH = 200
|
|
13
13
|
MISSING_VALUE_SENTINEL = "_unknown_"
|
|
14
14
|
|
|
15
|
+
# Carries the rule in flight out of the rule loop and into the rescue in
|
|
16
|
+
# #check/#peek, where the error is seen but the rule is out of scope.
|
|
17
|
+
# Per call, not instance state: one Evaluator serves concurrent requests.
|
|
18
|
+
# Only read while an exception unwinds; nil means no rule was in flight.
|
|
19
|
+
RuleCursor = Struct.new(:rule)
|
|
20
|
+
|
|
15
21
|
# Atomic increment-with-TTL Lua script. The whole script runs as one
|
|
16
22
|
# operation from Redis's perspective, so there is no window between
|
|
17
23
|
# the increment and EXPIRE that can leak a key without TTL.
|
|
@@ -68,12 +74,13 @@ module Labkit
|
|
|
68
74
|
end
|
|
69
75
|
|
|
70
76
|
def check(identifier, cost: 1, rule_context: nil)
|
|
71
|
-
|
|
77
|
+
cursor = RuleCursor.new
|
|
78
|
+
check_rules(identifier, cost, rule_context, cursor)
|
|
72
79
|
rescue StandardError => e
|
|
73
80
|
# Intentionally broad: fail-open applies to any unexpected error (network,
|
|
74
81
|
# timeout, OOM) not only Redis protocol errors.
|
|
75
82
|
report_error_metrics
|
|
76
|
-
log_error(e, identifier)
|
|
83
|
+
log_error(e, identifier, cursor.rule)
|
|
77
84
|
Result.error
|
|
78
85
|
end
|
|
79
86
|
|
|
@@ -81,10 +88,11 @@ module Labkit
|
|
|
81
88
|
# shape; the underlying Redis counter is not mutated and the TTL is not
|
|
82
89
|
# extended. A missing Redis key is treated as count=0 (matched, not exceeded).
|
|
83
90
|
def peek(identifier, rule_context: nil)
|
|
84
|
-
|
|
91
|
+
cursor = RuleCursor.new
|
|
92
|
+
peek_rules(identifier, rule_context, cursor)
|
|
85
93
|
rescue StandardError => e
|
|
86
94
|
report_error_metrics
|
|
87
|
-
log_error(e, identifier)
|
|
95
|
+
log_error(e, identifier, cursor.rule)
|
|
88
96
|
Result.error
|
|
89
97
|
end
|
|
90
98
|
|
|
@@ -117,11 +125,14 @@ module Labkit
|
|
|
117
125
|
# counters were incremented. No blocking verdict is lost that way - a
|
|
118
126
|
# :limit rule over its limit returns before any later rule can raise -
|
|
119
127
|
# but a rule declared after the failing one loses its chance to block.
|
|
120
|
-
# The request is counted and allowed.
|
|
121
|
-
|
|
128
|
+
# The request is counted and allowed. +cursor+ is set before the match, so
|
|
129
|
+
# a raise from the match itself (e.g. Regexp::TimeoutError) is attributed.
|
|
130
|
+
def check_rules(identifier, cost, rule_context, cursor)
|
|
122
131
|
result = Result.new
|
|
123
132
|
|
|
124
133
|
@rules.each do |rule|
|
|
134
|
+
cursor.rule = rule
|
|
135
|
+
|
|
125
136
|
next unless rule_matches?(rule, identifier)
|
|
126
137
|
|
|
127
138
|
if rule.action == :skip
|
|
@@ -141,6 +152,9 @@ module Labkit
|
|
|
141
152
|
return result if result.block?
|
|
142
153
|
end
|
|
143
154
|
|
|
155
|
+
# The loop is done, so anything raised from here on belongs to no rule.
|
|
156
|
+
cursor.rule = nil
|
|
157
|
+
|
|
144
158
|
report_unmatched_metrics unless result.matched?
|
|
145
159
|
result
|
|
146
160
|
end
|
|
@@ -152,10 +166,12 @@ module Labkit
|
|
|
152
166
|
# peek does not need the count_distinct identifier key - it reads SCARD on
|
|
153
167
|
# the rule-keyed compound key, which contains the cardinality across all
|
|
154
168
|
# members. So missing-key fail-open does not apply here.
|
|
155
|
-
def peek_rules(identifier, rule_context)
|
|
169
|
+
def peek_rules(identifier, rule_context, cursor)
|
|
156
170
|
result = Result.new
|
|
157
171
|
|
|
158
172
|
@rules.each do |rule|
|
|
173
|
+
cursor.rule = rule
|
|
174
|
+
|
|
159
175
|
next unless rule_matches?(rule, identifier)
|
|
160
176
|
|
|
161
177
|
return result.skip!(rule) if rule.action == :skip
|
|
@@ -164,6 +180,8 @@ module Labkit
|
|
|
164
180
|
return result if result.block?
|
|
165
181
|
end
|
|
166
182
|
|
|
183
|
+
cursor.rule = nil
|
|
184
|
+
|
|
167
185
|
result
|
|
168
186
|
end
|
|
169
187
|
|
|
@@ -319,9 +337,14 @@ module Labkit
|
|
|
319
337
|
end
|
|
320
338
|
end
|
|
321
339
|
|
|
322
|
-
|
|
340
|
+
# rule is nil when the error was raised with no rule in flight - before the
|
|
341
|
+
# loop reached one, or after it finished - so the field is logged as null
|
|
342
|
+
# rather than omitted, the same way identifier is. A named rule is the rule
|
|
343
|
+
# whose match or evaluation raised.
|
|
344
|
+
def log_error(error, identifier, rule = nil)
|
|
323
345
|
@logger.warn(
|
|
324
346
|
name: @name,
|
|
347
|
+
rule: rule&.name,
|
|
325
348
|
Labkit::Fields::ERROR_TYPE => "rate_limit_error",
|
|
326
349
|
Labkit::Fields::CLASS_NAME => error.class.to_s,
|
|
327
350
|
Labkit::Fields::ERROR_MESSAGE => error.message,
|