vpndetection 5.4.2 → 5.4.3
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/vpndetection/client.rb +130 -10
- data/lib/vpndetection/version.rb +1 -1
- 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: ba80a879812af8dc9d6cdefdb305d97fe38281550cad1b2c9bd76ab31b416bf2
|
|
4
|
+
data.tar.gz: 602fb1b3b602fa0ec12ca8d465dcda92e22d87610714ca31ec735033a83b61f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 340fbe8a1a6265f5dec5d5e93fecd2c63c171af83699fdc073ac98410d83e2ec4924c2b358c4a30785f3aeebb8b606cbefa0eeba0c54f402fda60a03dda1f843
|
|
7
|
+
data.tar.gz: eae9ca20b51fbb6d12060add25b281625c5096e9b54fd7ef1b596a8c54c476589fc09662c117c4eeb879556482fe7caa9056c0b81581af98d739079df544d557
|
data/lib/vpndetection/client.rb
CHANGED
|
@@ -49,6 +49,9 @@ module VPNDetection
|
|
|
49
49
|
Transport::Config.new(api_key: api_key, base_url: base_url, timeout: timeout),
|
|
50
50
|
)
|
|
51
51
|
@cache = cache ? Cache.new(max_size: cache_max_size, ttl: cache_ttl) : nil
|
|
52
|
+
# The addresses with a request in flight, each a Flight its waiters block on.
|
|
53
|
+
@flights = {}
|
|
54
|
+
@flights_lock = Mutex.new
|
|
52
55
|
@concurrency = concurrency
|
|
53
56
|
@retries = retries
|
|
54
57
|
@database = DatabaseApi.new(@transport, retries: retries)
|
|
@@ -78,14 +81,26 @@ module VPNDetection
|
|
|
78
81
|
Transport.checked_timeout(timeout) unless timeout.nil?
|
|
79
82
|
return Bogon.result(ip) if Bogon.bogon?(ip)
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
loop do
|
|
85
|
+
hit = @cache&.get(ip)
|
|
86
|
+
return hit unless hit.nil?
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
flight, leader = board(ip)
|
|
89
|
+
return fetch(ip, retries, timeout) if flight.nil?
|
|
90
|
+
|
|
91
|
+
if leader
|
|
92
|
+
# Read once more now the address is boarded: a leader that landed
|
|
93
|
+
# between the miss above and boarding has already cached its answer.
|
|
94
|
+
hit = @cache.get(ip)
|
|
95
|
+
return unboard(ip, flight, :served, hit) unless hit.nil?
|
|
96
|
+
|
|
97
|
+
return lead(ip, flight) { fetch(ip, retries, timeout) }
|
|
98
|
+
end
|
|
99
|
+
state, value = flight.wait
|
|
100
|
+
return value if state == :served
|
|
101
|
+
raise value if state == :failed
|
|
102
|
+
# :abandoned - its leader never finished, so this caller asks again.
|
|
86
103
|
end
|
|
87
|
-
@cache&.set(ip, result)
|
|
88
|
-
result
|
|
89
104
|
end
|
|
90
105
|
|
|
91
106
|
# Classify the address this client is calling from.
|
|
@@ -160,9 +175,27 @@ module VPNDetection
|
|
|
160
175
|
hit = Bogon.bogon?(ip) ? Bogon.result(ip) : @cache&.get(ip)
|
|
161
176
|
hit.nil? ? pending << ip : answers[ip] = hit
|
|
162
177
|
end
|
|
163
|
-
|
|
164
|
-
|
|
178
|
+
# Boarded before any chunk is built, so a lookup arriving meanwhile waits
|
|
179
|
+
# for this batch; an address already in flight is not sent again.
|
|
180
|
+
boarded = {}
|
|
181
|
+
joined = {}
|
|
182
|
+
pending.each do |ip|
|
|
183
|
+
flight, leader = board(ip)
|
|
184
|
+
if leader == false
|
|
185
|
+
joined[ip] = flight
|
|
186
|
+
else
|
|
187
|
+
boarded[ip] = flight
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
begin
|
|
191
|
+
unless boarded.empty?
|
|
192
|
+
chunks = boarded.keys.each_slice(BATCH_MAX).to_a
|
|
193
|
+
run_batch(chunks, answers, limit, retries || @retries, timeout, boarded)
|
|
194
|
+
end
|
|
195
|
+
ensure
|
|
196
|
+
boarded.each { |ip, flight| unboard(ip, flight, :abandoned) unless flight.nil? }
|
|
165
197
|
end
|
|
198
|
+
joined.each { |ip, flight| answers[ip] = joined_answer(ip, flight, retries, timeout) }
|
|
166
199
|
|
|
167
200
|
# Reinstated in input order: a hydra settles in completion order, and a
|
|
168
201
|
# caller iterating the hash should see what they passed in.
|
|
@@ -171,11 +204,83 @@ module VPNDetection
|
|
|
171
204
|
|
|
172
205
|
private
|
|
173
206
|
|
|
207
|
+
# Concurrent misses for one address share ONE request (docs/sdk/contract.md,
|
|
208
|
+
# UMAN-4645): 5.4.2 sent one per calling thread. The first miss boards the
|
|
209
|
+
# address and every miss after it, a batch's included, waits on its Flight.
|
|
210
|
+
class Flight
|
|
211
|
+
def initialize
|
|
212
|
+
@lock = Mutex.new
|
|
213
|
+
@landed = ConditionVariable.new
|
|
214
|
+
@state = :pending
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Settles the flight once, for every waiter: :served with a Result,
|
|
218
|
+
# :failed with an Error, or :abandoned when its leader never finished.
|
|
219
|
+
def land(state, value = nil)
|
|
220
|
+
@lock.synchronize do
|
|
221
|
+
next unless @state == :pending
|
|
222
|
+
|
|
223
|
+
@state = state
|
|
224
|
+
@value = value
|
|
225
|
+
@landed.broadcast
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Blocks until the flight lands, then [state, value].
|
|
230
|
+
def wait
|
|
231
|
+
@lock.synchronize do
|
|
232
|
+
@landed.wait(@lock) while @state == :pending
|
|
233
|
+
[@state, @value]
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# [flight, true] when this caller leads the request for `ip`, [flight,
|
|
239
|
+
# false] when one is already in flight, and nil without a cache: every
|
|
240
|
+
# lookup is then served, so nothing is shared.
|
|
241
|
+
def board(ip)
|
|
242
|
+
return nil if @cache.nil?
|
|
243
|
+
|
|
244
|
+
@flights_lock.synchronize do
|
|
245
|
+
existing = @flights[ip]
|
|
246
|
+
existing ? [existing, false] : [@flights[ip] = Flight.new, true]
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Takes `ip` off the board and lands its flight. Returns `value`.
|
|
251
|
+
def unboard(ip, flight, state, value = nil)
|
|
252
|
+
@flights_lock.synchronize { @flights.delete(ip) if @flights[ip].equal?(flight) }
|
|
253
|
+
flight.land(state, value)
|
|
254
|
+
value
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Runs the request a flight is waiting on, under the options of the call
|
|
258
|
+
# that leads it. A served answer is cached before it lands; a failure
|
|
259
|
+
# reaches every waiter and is cached for none; and a leader that never
|
|
260
|
+
# finishes - a killed thread, a Timeout.timeout - abandons the flight, so
|
|
261
|
+
# its waiters ask again rather than failing with it.
|
|
262
|
+
def lead(ip, flight)
|
|
263
|
+
unboard(ip, flight, :served, yield)
|
|
264
|
+
rescue Error => e
|
|
265
|
+
unboard(ip, flight, :failed, e)
|
|
266
|
+
raise
|
|
267
|
+
ensure
|
|
268
|
+
unboard(ip, flight, :abandoned)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def fetch(ip, retries, timeout)
|
|
272
|
+
result = Retries.with_retries(retries || @retries) do
|
|
273
|
+
Transport.lookup_result(@transport.lookup_request(ip, timeout: timeout).run)
|
|
274
|
+
end
|
|
275
|
+
@cache&.set(ip, result)
|
|
276
|
+
result
|
|
277
|
+
end
|
|
278
|
+
|
|
174
279
|
# One hydra per call, sized for THIS call. Reusing an instance-level hydra
|
|
175
280
|
# would silently cap a per-call concurrency at the client's setting, and
|
|
176
281
|
# would not be safe to drive from two threads either. Each request is one
|
|
177
282
|
# chunk of up to 1000 addresses.
|
|
178
|
-
def run_batch(chunks, answers, concurrency, retries, timeout)
|
|
283
|
+
def run_batch(chunks, answers, concurrency, retries, timeout, boarded)
|
|
179
284
|
hydra = Typhoeus::Hydra.new(max_concurrency: concurrency)
|
|
180
285
|
attempts = Hash.new(0)
|
|
181
286
|
|
|
@@ -183,7 +288,11 @@ module VPNDetection
|
|
|
183
288
|
request = @transport.batch_request(chunk, timeout: timeout)
|
|
184
289
|
request.on_complete do |response|
|
|
185
290
|
outcome = settle(chunk, response, attempts, retries, enqueue)
|
|
186
|
-
|
|
291
|
+
outcome&.each do |ip, answer|
|
|
292
|
+
answers[ip] = answer
|
|
293
|
+
flight = boarded[ip]
|
|
294
|
+
unboard(ip, flight, answer.is_a?(Result) ? :served : :failed, answer) unless flight.nil?
|
|
295
|
+
end
|
|
187
296
|
end
|
|
188
297
|
hydra.queue(request)
|
|
189
298
|
end
|
|
@@ -214,6 +323,17 @@ module VPNDetection
|
|
|
214
323
|
nil
|
|
215
324
|
end
|
|
216
325
|
|
|
326
|
+
# What a batch takes for an address another call was fetching: that call's
|
|
327
|
+
# answer or error, or, when its leader never finished, its own lookup.
|
|
328
|
+
def joined_answer(ip, flight, retries, timeout)
|
|
329
|
+
state, value = flight.wait
|
|
330
|
+
return value unless state == :abandoned
|
|
331
|
+
|
|
332
|
+
lookup(ip, retries: retries, timeout: timeout)
|
|
333
|
+
rescue Error => e
|
|
334
|
+
e
|
|
335
|
+
end
|
|
336
|
+
|
|
217
337
|
# Every address lands in exactly one of `results` and `errors`; an address in
|
|
218
338
|
# neither is the server breaking its own contract, and is reported as such
|
|
219
339
|
# rather than lost.
|
data/lib/vpndetection/version.rb
CHANGED