gcra 1.0.3 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0398e02c46f08f3f248e36db5d87bc49c2185531
4
- data.tar.gz: 47aee2e95b7267165702aa9350c6b71225b7e8f4
2
+ SHA256:
3
+ metadata.gz: 4523f1a5f4d9e01c3363bbfd24a1d50f3933799a3ee8ac2774a82de49687344e
4
+ data.tar.gz: 963f7aaa9d67b39920bedc148d7b5c89d7acc7ec6a538fef58b7f845804e2f05
5
5
  SHA512:
6
- metadata.gz: 2755f80c4957e77ba5073df77c658263682dbbc92730bb7d4231010cdd379550167cad27a83bf321545c9879a45d7ee992219581e7885eabf1c403774bdbc316
7
- data.tar.gz: eb8b0e85d5141fcdf949b428ff930902286c7267d12dd3ae66a3297b58a211d86db03bb8c73cf104ae39cca9e194af1ba093c82c0e2a7a4a65e12ec37153d908
6
+ metadata.gz: 7b049fed03339f68e9ef24e6c4d715b28a894a990deb3e50d4f9bedc4af621f7440300efe41b519158b982806df9b3495972de367e39ae8c599095a0e42466e8
7
+ data.tar.gz: 6f65016b71ad0fce7b47e1f5340e04023ba800304404e1d7eb1dcedb6a183e4dfb0550a58a0cf9b326ef5218f06763e6d12b8b11d79055bda19815a669ef867e
@@ -97,5 +97,30 @@ module GCRA
97
97
  "Failed to store updated rate limit data for key '#{key}' after #{MAX_ATTEMPTS} attempts"
98
98
  )
99
99
  end
100
+
101
+ # Overwrite the stored value for key to that of a bucket that has
102
+ # just overflowed, ignoring any existing stored data.
103
+ def mark_overflowed(key)
104
+ key = key.to_s unless key.is_a?(String)
105
+ i = 0
106
+ while i < MAX_ATTEMPTS
107
+ tat_from_store, now = @store.get_with_time(key)
108
+ new_value = now + @delay_variation_tolerance
109
+ ttl = @delay_variation_tolerance
110
+ updated = if tat_from_store.nil?
111
+ @store.set_if_not_exists_with_ttl(key, new_value, ttl)
112
+ else
113
+ @store.compare_and_set_with_ttl(key, tat_from_store, new_value, ttl)
114
+ end
115
+ if updated
116
+ return true
117
+ end
118
+ i += 1
119
+ end
120
+
121
+ raise StoreUpdateFailed.new(
122
+ "Failed to store updated rate limit data for key '#{key}' after #{MAX_ATTEMPTS} attempts"
123
+ )
124
+ end
100
125
  end
101
126
  end
@@ -12,20 +12,30 @@ module GCRA
12
12
  redis.call('psetex', KEYS[1], ARGV[3], ARGV[2])
13
13
  return 1
14
14
  EOF
15
+
16
+ # Digest::SHA1.hexdigest(CAS_SCRIPT)
17
+ CAS_SHA = "89118e702230c0d65969c5fc557a6e942a2f4d31".freeze
15
18
  CAS_SCRIPT_MISSING_KEY_RESPONSE = 'key does not exist'.freeze
19
+ SCRIPT_NOT_IN_CACHE_RESPONSE = 'NOSCRIPT No matching script. Please use EVAL.'.freeze
20
+
21
+ CONNECTED_TO_READONLY = "READONLY You can't write against a read only slave.".freeze
16
22
 
17
- def initialize(redis, key_prefix)
23
+ def initialize(redis, key_prefix, options = {})
18
24
  @redis = redis
19
25
  @key_prefix = key_prefix
26
+
27
+ @reconnect_on_readonly = options[:reconnect_on_readonly] || false
20
28
  end
21
29
 
22
30
  # Returns the value of the key or nil, if it isn't in the store.
23
31
  # Also returns the time from the Redis server, with microsecond precision.
24
32
  def get_with_time(key)
25
- time_response = @redis.time # returns tuple (seconds since epoch, microseconds)
33
+ time_response, value = @redis.pipelined do |pipeline|
34
+ pipeline.time # returns tuple (seconds since epoch, microseconds)
35
+ pipeline.get(@key_prefix + key)
36
+ end
26
37
  # Convert tuple to nanoseconds
27
38
  time = (time_response[0] * 1_000_000 + time_response[1]) * 1_000
28
- value = @redis.get(@key_prefix + key)
29
39
  if value != nil
30
40
  value = value.to_i
31
41
  end
@@ -34,17 +44,21 @@ module GCRA
34
44
  end
35
45
 
36
46
  # Set the value of key only if it is not already set. Return whether the value was set.
37
- # Also set the key's expiration (ttl, in seconds). The operations are not performed atomically.
47
+ # Also set the key's expiration (ttl, in seconds).
38
48
  def set_if_not_exists_with_ttl(key, value, ttl_nano)
39
49
  full_key = @key_prefix + key
40
- did_set = @redis.setnx(full_key, value)
41
-
42
- if did_set
50
+ retried = false
51
+ begin
43
52
  ttl_milli = calculate_ttl_milli(ttl_nano)
44
- @redis.pexpire(full_key, ttl_milli)
53
+ @redis.set(full_key, value, nx: true, px: ttl_milli)
54
+ rescue Redis::CommandError => e
55
+ if e.message == CONNECTED_TO_READONLY && @reconnect_on_readonly && !retried
56
+ @redis.client.reconnect
57
+ retried = true
58
+ retry
59
+ end
60
+ raise
45
61
  end
46
-
47
- return did_set
48
62
  end
49
63
 
50
64
  # Atomically compare the value at key to the old value. If it matches, set it to the new value
@@ -52,12 +66,21 @@ module GCRA
52
66
  # return false with no error. If the swap succeeds, update the ttl for the key atomically.
53
67
  def compare_and_set_with_ttl(key, old_value, new_value, ttl_nano)
54
68
  full_key = @key_prefix + key
69
+ retried = false
55
70
  begin
56
71
  ttl_milli = calculate_ttl_milli(ttl_nano)
57
- swapped = @redis.eval(CAS_SCRIPT, keys: [full_key], argv: [old_value, new_value, ttl_milli])
72
+ swapped = @redis.evalsha(CAS_SHA, keys: [full_key], argv: [old_value, new_value, ttl_milli])
58
73
  rescue Redis::CommandError => e
59
74
  if e.message == CAS_SCRIPT_MISSING_KEY_RESPONSE
60
75
  return false
76
+ elsif e.message == SCRIPT_NOT_IN_CACHE_RESPONSE && !retried
77
+ @redis.script('load', CAS_SCRIPT)
78
+ retried = true
79
+ retry
80
+ elsif e.message == CONNECTED_TO_READONLY && @reconnect_on_readonly && !retried
81
+ @redis.client.reconnect
82
+ retried = true
83
+ retry
61
84
  end
62
85
  raise
63
86
  end
data/lib/gcra/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module GCRA
2
- VERSION = '1.0.3'.freeze
2
+ VERSION = '1.2.1'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gcra
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Frister
8
- autorequire:
8
+ - Tobias Schoknecht
9
+ autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2018-05-16 00:00:00.000000000 Z
12
+ date: 2022-02-12 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: rspec
@@ -40,7 +41,7 @@ dependencies:
40
41
  version: '3.3'
41
42
  description: GCRA implementation for rate limiting
42
43
  email:
43
- - michael.frister@barzahlen.de
44
+ - tobias.schoknecht@viafintech.com
44
45
  executables: []
45
46
  extensions: []
46
47
  extra_rdoc_files: []
@@ -48,11 +49,11 @@ files:
48
49
  - lib/gcra/rate_limiter.rb
49
50
  - lib/gcra/redis_store.rb
50
51
  - lib/gcra/version.rb
51
- homepage: https://github.com/Barzahlen/gcra
52
+ homepage: https://github.com/viafintech/gcra-ruby
52
53
  licenses:
53
54
  - MIT
54
55
  metadata: {}
55
- post_install_message:
56
+ post_install_message:
56
57
  rdoc_options: []
57
58
  require_paths:
58
59
  - lib
@@ -67,9 +68,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
68
  - !ruby/object:Gem::Version
68
69
  version: '0'
69
70
  requirements: []
70
- rubyforge_project:
71
- rubygems_version: 2.4.8
72
- signing_key:
71
+ rubygems_version: 3.0.6
72
+ signing_key:
73
73
  specification_version: 4
74
74
  summary: Ruby implementation of a generic cell rate algorithm (GCRA), ported from
75
75
  the Go implementation throttled.