gcra 1.0.3 → 1.2.1

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
- 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.