redstruct 0.1.7 → 0.2.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -11
  3. data/Rakefile +5 -5
  4. data/lib/redstruct/all.rb +14 -0
  5. data/lib/redstruct/configuration.rb +9 -6
  6. data/lib/redstruct/connection_proxy.rb +123 -0
  7. data/lib/redstruct/counter.rb +96 -0
  8. data/lib/redstruct/error.rb +2 -0
  9. data/lib/redstruct/factory/object.rb +31 -0
  10. data/lib/redstruct/factory.rb +94 -55
  11. data/lib/redstruct/hash.rb +123 -0
  12. data/lib/redstruct/list.rb +315 -0
  13. data/lib/redstruct/lock.rb +183 -0
  14. data/lib/redstruct/script.rb +104 -0
  15. data/lib/redstruct/set.rb +155 -0
  16. data/lib/redstruct/sorted_set/slice.rb +124 -0
  17. data/lib/redstruct/sorted_set.rb +153 -0
  18. data/lib/redstruct/string.rb +66 -0
  19. data/lib/redstruct/struct.rb +87 -0
  20. data/lib/redstruct/utils/coercion.rb +14 -8
  21. data/lib/redstruct/utils/inspectable.rb +8 -4
  22. data/lib/redstruct/utils/iterable.rb +52 -0
  23. data/lib/redstruct/utils/scriptable.rb +32 -6
  24. data/lib/redstruct/version.rb +4 -1
  25. data/lib/redstruct.rb +17 -51
  26. data/lib/yard/defscript_handler.rb +5 -3
  27. data/test/redstruct/configuration_test.rb +13 -0
  28. data/test/redstruct/connection_proxy_test.rb +85 -0
  29. data/test/redstruct/counter_test.rb +108 -0
  30. data/test/redstruct/factory/object_test.rb +21 -0
  31. data/test/redstruct/factory_test.rb +136 -0
  32. data/test/redstruct/hash_test.rb +138 -0
  33. data/test/redstruct/list_test.rb +244 -0
  34. data/test/redstruct/lock_test.rb +108 -0
  35. data/test/redstruct/script_test.rb +53 -0
  36. data/test/redstruct/set_test.rb +219 -0
  37. data/test/redstruct/sorted_set/slice_test.rb +10 -0
  38. data/test/redstruct/sorted_set_test.rb +219 -0
  39. data/test/redstruct/string_test.rb +8 -0
  40. data/test/redstruct/struct_test.rb +61 -0
  41. data/test/redstruct/utils/coercion_test.rb +33 -0
  42. data/test/redstruct/utils/inspectable_test.rb +31 -0
  43. data/test/redstruct/utils/iterable_test.rb +94 -0
  44. data/test/redstruct/utils/scriptable_test.rb +67 -0
  45. data/test/redstruct_test.rb +14 -0
  46. data/test/test_helper.rb +77 -1
  47. metadata +58 -26
  48. data/lib/redstruct/connection.rb +0 -47
  49. data/lib/redstruct/factory/creation.rb +0 -95
  50. data/lib/redstruct/factory/deserialization.rb +0 -7
  51. data/lib/redstruct/hls/lock.rb +0 -175
  52. data/lib/redstruct/hls/queue.rb +0 -29
  53. data/lib/redstruct/hls.rb +0 -2
  54. data/lib/redstruct/types/base.rb +0 -36
  55. data/lib/redstruct/types/counter.rb +0 -65
  56. data/lib/redstruct/types/hash.rb +0 -72
  57. data/lib/redstruct/types/list.rb +0 -76
  58. data/lib/redstruct/types/script.rb +0 -56
  59. data/lib/redstruct/types/set.rb +0 -96
  60. data/lib/redstruct/types/sorted_set.rb +0 -129
  61. data/lib/redstruct/types/string.rb +0 -64
  62. data/lib/redstruct/types/struct.rb +0 -58
  63. data/lib/releaser/logger.rb +0 -15
  64. data/lib/releaser/repository.rb +0 -32
  65. data/lib/tasks/release.rake +0 -49
  66. data/test/redstruct/restruct_test.rb +0 -4
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'securerandom'
5
+ require 'test_helper'
6
+
7
+ module Redstruct
8
+ module Utils
9
+ class ScriptableTest < Redstruct::Test
10
+ def test_defscript
11
+ script = <<~LUA
12
+ local sum = 0
13
+ for i, key in ipairs(KEYS) do
14
+ local value = tonumber(ARGV[i])
15
+ redis.call("set", key, value)
16
+ sum = sum + value
17
+ end
18
+
19
+ return sum
20
+ LUA
21
+ script = script.strip
22
+ sha1 = Digest::SHA1.hexdigest(script)
23
+
24
+ klass = Class.new(Redstruct::Factory::Object) do
25
+ include Redstruct::Utils::Scriptable
26
+ defscript :test, script
27
+ end
28
+
29
+ assert_equal({ script: script, sha1: sha1 }, klass::SCRIPT_TEST, 'should have defined the constant properly')
30
+
31
+ factory = create_factory
32
+ keys = [factory.string(SecureRandom.hex(4)).key, factory.string(SecureRandom.hex(4)).key]
33
+ argv = [SecureRandom.random_number(10), SecureRandom.random_number(10)]
34
+ assert_equal argv.reduce(&:+), klass.new(factory: factory).test(keys: keys, argv: argv)
35
+
36
+ keys.each_with_index do |key, index|
37
+ assert_equal argv[index], factory.string(key).get.to_i, 'script should have set the value properly'
38
+ end
39
+ end
40
+
41
+ def test_const_defined
42
+ klass = Class.new { include Redstruct::Utils::Scriptable }
43
+ klass.const_set('SCRIPT_TEST', true)
44
+
45
+ stdout, = capture_subprocess_io { klass.defscript('test', 'return 0') }
46
+ assert_match(/WARN/, stdout, 'should produce a warning if the constant is already defined')
47
+ refute klass.method_defined?('test')
48
+ assert_equal true, klass::SCRIPT_TEST, 'should still be the old value'
49
+ end
50
+
51
+ def test_method_defined
52
+ klass = Class.new do
53
+ include Redstruct::Utils::Scriptable
54
+
55
+ def test
56
+ return true
57
+ end
58
+ end
59
+
60
+ stdout, = capture_subprocess_io { klass.defscript('test', 'return 0') }
61
+ assert_match(/WARN/, stdout, 'should produce a warning if the method is already defined')
62
+ refute klass.const_defined?('SCRIPT_TEST')
63
+ assert_equal true, klass.new.test, 'should still be the old value'
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class RedstructTest < Redstruct::Test
6
+ # Test singleton property of the config
7
+ def test_config
8
+ config = Redstruct.config
9
+ assert_kind_of Redstruct::Configuration, config, 'Should return an instance of the configuration class'
10
+
11
+ reference = Redstruct.config
12
+ assert_equal config, reference, 'Should be a singleton object'
13
+ end
14
+ end
data/test/test_helper.rb CHANGED
@@ -1,4 +1,80 @@
1
+ # frozen_string_literal: true
2
+
1
3
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
+ require 'securerandom'
2
5
  require 'bundler/setup'
3
- require 'redstruct'
4
6
  require 'minitest/autorun'
7
+ require 'flexmock/minitest'
8
+
9
+ ci_build = ENV['CI_BUILD'].to_i.positive?
10
+
11
+ bundler_groups = %i[default test]
12
+ bundler_groups << (ci_build ? :ci : :debug)
13
+ Bundler.require(*bundler_groups)
14
+
15
+ # Start coverage
16
+ Codacy::Reporter.start if ci_build
17
+
18
+ # Default Redstruct config
19
+ require 'redstruct/all'
20
+ Redstruct.config.default_namespace = "redstruct:test:#{SecureRandom.uuid}"
21
+ Redstruct.config.default_connection = ConnectionPool.new(size: 5, timeout: 2) do
22
+ Redis.new(host: ENV.fetch('REDIS_HOST', '127.0.0.1'), port: ENV.fetch('REDIS_PORT', 6379).to_i, db: ENV.fetch('REDIS_DB', 0).to_i)
23
+ end
24
+
25
+ # Setup cleanup hook
26
+ Minitest.after_run do
27
+ Redstruct.config.default_connection.with do |conn|
28
+ conn.flushdb
29
+ conn.script(:flush)
30
+ end
31
+ end
32
+
33
+ # Small class used to generate thread-safe sequence when creating per-test
34
+ # factories
35
+ class AtomicInteger
36
+ def initialize
37
+ @lock = Mutex.new
38
+ @current = 0
39
+ end
40
+
41
+ def incr
42
+ value = nil
43
+ @lock.synchronize do
44
+ value = @current
45
+ @current += 1
46
+ end
47
+
48
+ return value
49
+ end
50
+ end
51
+
52
+ module Redstruct
53
+ # Base class for all Redstruct tests. Configures the gem, provides a default factory, and makes sure to clean it up
54
+ # at the end
55
+ class Test < Minitest::Test
56
+ @@counter = AtomicInteger.new # rubocop: disable Style/ClassVars
57
+
58
+ parallelize_me!
59
+ make_my_diffs_pretty!
60
+
61
+ # Use this helper to create a factory that the test class will keep track of and remove at the end
62
+ def create_factory(namespace = nil)
63
+ namespace ||= "#{Redstruct.config.default_namespace}:#{@@counter.incr}"
64
+ return Redstruct::Factory.new(namespace: namespace)
65
+ end
66
+
67
+ # Helper when trying to ensure a particular redis-rb command was called
68
+ # while still calling it. This allows for testing things outside of our
69
+ # control (e.g. srandmember returning random items)
70
+ # The reason we don't simply just mock the return value is to ensure
71
+ # that tests will break if a command (e.g. srandmember) changes its return
72
+ # value
73
+ def ensure_command_called(object, command, *args, allow: true)
74
+ mock = flexmock(object.connection).should_receive(command).with(object.key, *args)
75
+ mock = mock.pass_thru if allow
76
+
77
+ return mock
78
+ end
79
+ end
80
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redstruct
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicolas Pepin-Perreault
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-12 00:00:00.000000000 Z
11
+ date: 2017-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -50,14 +50,14 @@ dependencies:
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '1.12'
53
+ version: '1.13'
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '1.12'
60
+ version: '1.13'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: rake
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -78,14 +78,14 @@ dependencies:
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '5.0'
81
+ version: '5.1'
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '5.0'
88
+ version: '5.1'
89
89
  description: Provides higher level data structures in Ruby using standard Redis commands.
90
90
  Also provides basic object mapping for pre-existing types.
91
91
  email:
@@ -97,33 +97,47 @@ files:
97
97
  - README.md
98
98
  - Rakefile
99
99
  - lib/redstruct.rb
100
+ - lib/redstruct/all.rb
100
101
  - lib/redstruct/configuration.rb
101
- - lib/redstruct/connection.rb
102
+ - lib/redstruct/connection_proxy.rb
103
+ - lib/redstruct/counter.rb
102
104
  - lib/redstruct/error.rb
103
105
  - lib/redstruct/factory.rb
104
- - lib/redstruct/factory/creation.rb
105
- - lib/redstruct/factory/deserialization.rb
106
- - lib/redstruct/hls.rb
107
- - lib/redstruct/hls/lock.rb
108
- - lib/redstruct/hls/queue.rb
109
- - lib/redstruct/types/base.rb
110
- - lib/redstruct/types/counter.rb
111
- - lib/redstruct/types/hash.rb
112
- - lib/redstruct/types/list.rb
113
- - lib/redstruct/types/script.rb
114
- - lib/redstruct/types/set.rb
115
- - lib/redstruct/types/sorted_set.rb
116
- - lib/redstruct/types/string.rb
117
- - lib/redstruct/types/struct.rb
106
+ - lib/redstruct/factory/object.rb
107
+ - lib/redstruct/hash.rb
108
+ - lib/redstruct/list.rb
109
+ - lib/redstruct/lock.rb
110
+ - lib/redstruct/script.rb
111
+ - lib/redstruct/set.rb
112
+ - lib/redstruct/sorted_set.rb
113
+ - lib/redstruct/sorted_set/slice.rb
114
+ - lib/redstruct/string.rb
115
+ - lib/redstruct/struct.rb
118
116
  - lib/redstruct/utils/coercion.rb
119
117
  - lib/redstruct/utils/inspectable.rb
118
+ - lib/redstruct/utils/iterable.rb
120
119
  - lib/redstruct/utils/scriptable.rb
121
120
  - lib/redstruct/version.rb
122
- - lib/releaser/logger.rb
123
- - lib/releaser/repository.rb
124
- - lib/tasks/release.rake
125
121
  - lib/yard/defscript_handler.rb
126
- - test/redstruct/restruct_test.rb
122
+ - test/redstruct/configuration_test.rb
123
+ - test/redstruct/connection_proxy_test.rb
124
+ - test/redstruct/counter_test.rb
125
+ - test/redstruct/factory/object_test.rb
126
+ - test/redstruct/factory_test.rb
127
+ - test/redstruct/hash_test.rb
128
+ - test/redstruct/list_test.rb
129
+ - test/redstruct/lock_test.rb
130
+ - test/redstruct/script_test.rb
131
+ - test/redstruct/set_test.rb
132
+ - test/redstruct/sorted_set/slice_test.rb
133
+ - test/redstruct/sorted_set_test.rb
134
+ - test/redstruct/string_test.rb
135
+ - test/redstruct/struct_test.rb
136
+ - test/redstruct/utils/coercion_test.rb
137
+ - test/redstruct/utils/inspectable_test.rb
138
+ - test/redstruct/utils/iterable_test.rb
139
+ - test/redstruct/utils/scriptable_test.rb
140
+ - test/redstruct_test.rb
127
141
  - test/test_helper.rb
128
142
  homepage: https://npepinpe.github.com/redstruct/
129
143
  licenses:
@@ -150,5 +164,23 @@ signing_key:
150
164
  specification_version: 4
151
165
  summary: Higher level data structures for Redis.
152
166
  test_files:
153
- - test/redstruct/restruct_test.rb
167
+ - test/redstruct/configuration_test.rb
168
+ - test/redstruct/connection_proxy_test.rb
169
+ - test/redstruct/counter_test.rb
170
+ - test/redstruct/factory/object_test.rb
171
+ - test/redstruct/factory_test.rb
172
+ - test/redstruct/hash_test.rb
173
+ - test/redstruct/list_test.rb
174
+ - test/redstruct/lock_test.rb
175
+ - test/redstruct/script_test.rb
176
+ - test/redstruct/set_test.rb
177
+ - test/redstruct/sorted_set/slice_test.rb
178
+ - test/redstruct/sorted_set_test.rb
179
+ - test/redstruct/string_test.rb
180
+ - test/redstruct/struct_test.rb
181
+ - test/redstruct/utils/coercion_test.rb
182
+ - test/redstruct/utils/inspectable_test.rb
183
+ - test/redstruct/utils/iterable_test.rb
184
+ - test/redstruct/utils/scriptable_test.rb
185
+ - test/redstruct_test.rb
154
186
  - test/test_helper.rb
@@ -1,47 +0,0 @@
1
- module Redstruct
2
- class Connection
3
- # @return [Array<Symbol>] List of methods from the Redis class that we don't want to delegate to.
4
- NON_COMMAND_METHODS = [:[], :[]=, :_eval, :_scan, :method_missing, :call, :dup, :inspect, :to_s].freeze
5
-
6
- attr_reader :pool
7
-
8
- def initialize(pool)
9
- raise(Redstruct::Error, 'Requires a ConnectionPool to proxy to') unless pool.is_a?(ConnectionPool)
10
- @pool = pool
11
- end
12
-
13
- # Executes the given block by first fixing a thread local connection from the pool,
14
- # such that all redis commands executed within the block are on the same connection.
15
- # This is necessary when doing pipelining, or multi/exec stuff
16
- # @return [Object] whatever the passed block evaluates to, nil otherwise
17
- def with
18
- result = nil
19
- @pool.with do |c|
20
- begin
21
- Thread.current[:__redstruct_connection] = c
22
- result = yield(c) if block_given?
23
- ensure
24
- Thread.current[:__redstruct_connection] = nil
25
- end
26
- end
27
-
28
- return result
29
- end
30
-
31
- # While slower on load, defining all methods that we want to pipe to one of the connections results in
32
- # faster calls at runtime, and gives us the convenience of not going through the pool.with everytime.
33
- Redis.public_instance_methods(false).each do |method|
34
- next if NON_COMMAND_METHODS.include?(method)
35
- class_eval <<~METHOD, __FILE__, __LINE__ + 1
36
- def #{method}(*args, &block)
37
- connection = Thread.current[:__redstruct_connection]
38
- if connection.nil?
39
- with { |c| c.#{method}(*args, &block) }
40
- else
41
- return connection.#{method}(*args, &block)
42
- end
43
- end
44
- METHOD
45
- end
46
- end
47
- end
@@ -1,95 +0,0 @@
1
- module Redstruct
2
- class Factory
3
- # Module to hold all the factory creation methods.
4
- module Creation
5
- # Builds a struct with the given key (namespaced) and sharing the factory connection
6
- # Building a struct is only really useful if you plan on making only basic operations,
7
- # such as delete, expire, etc. It is however recommended to always build your objects
8
- # in the same way, e.g. if it's a lock, use Factory#lock
9
- # @param [::String] key base key to use
10
- # @return [Redstruct::Types::Struct] base struct pointing to that key
11
- def struct(key, **options)
12
- return create(Redstruct::Types::Struct, key, **options)
13
- end
14
-
15
- # Builds a Redis string struct from the key
16
- # @param [::String] key base key to use
17
- # @return [Redstruct::Types::String]
18
- def string(key, **options)
19
- return create(Redstruct::Types::String, key, **options)
20
- end
21
-
22
- # Builds a Redis list struct from the key
23
- # @param [::String] key base key to use
24
- # @return [Redstruct::Types::List]
25
- def list(key, **options)
26
- return create(Redstruct::Types::List, key, **options)
27
- end
28
-
29
- # Builds a Redis set struct from the key
30
- # @param [::String] key base key to use
31
- # @return [Redstruct::Types::Set]
32
- def set(key, **options)
33
- return create(Redstruct::Types::Set, key, **options)
34
- end
35
-
36
- # Builds a Redis sorted set (zset) struct from the key
37
- # @param [::String] key base key to use
38
- # @return [Redstruct::Types::SortedSet]
39
- def sorted_set(key, **options)
40
- return create(Redstruct::Types::SortedSet, key, **options)
41
- end
42
-
43
- # Builds a Redis hash struct from the key
44
- # @param [::String] key base key to use
45
- # @return [Redstruct::Types::Hash]
46
- def hash(key, **options)
47
- return create(Redstruct::Types::Hash, key, **options)
48
- end
49
-
50
- # Builds a Redis backed lock from the key
51
- # @param [::String] key base key to use
52
- # @return [Redstruct::Hls::Lock]
53
- def lock(key, **options)
54
- return create(Redstruct::Hls::Lock, key, **options)
55
- end
56
-
57
- # Builds a Redis counter struct from the key
58
- # @param [::String] key base key to use
59
- # @return [Redstruct::Types::Counter]
60
- def counter(key, **options)
61
- return create(Redstruct::Types::Counter, key, **options)
62
- end
63
-
64
- # Builds a Redis backed queue from the key
65
- # @param [::String] key base key to use
66
- # @return [Redstruct::Hls::Queue]
67
- def queue(key)
68
- return create(Redstruct::Hls::Queue, key)
69
- end
70
-
71
- # @todo The script cache is actually based on the database you will connect to. Therefore, it might be smarter to move it to the connection used?
72
- # Caveat: if the script with the given ID exists in the cache, we don't bother updating it.
73
- # So if the script actually changed since the first call, the one sent during the first call will
74
- def script(id, script)
75
- return @script_cache.synchronize do
76
- @script_cache[id] = Redstruct::Types::Script.new(key: id, script: script, factory: self) if @script_cache[id].nil?
77
- @script_cache[id]
78
- end
79
- end
80
-
81
- # Returns a factory with an isolated namespace.
82
- # @example Given a factory `f` with namespace fact:first
83
- # f.factory('second') # => Redstruct::Factory: namespace: <"fact:first:second">, script_cache: <[]>
84
- # @return [Factory] namespaced factory
85
- def factory(namespace)
86
- return self.class.new(connection: @connection, namespace: isolate(namespace))
87
- end
88
-
89
- def create(type, key, **options)
90
- return type.new(key: isolate(key), factory: self, **options)
91
- end
92
- private :create
93
- end
94
- end
95
- end
@@ -1,7 +0,0 @@
1
- module Redstruct
2
- class Factory
3
- # Module to hold all the factory creation methods.
4
- module Deserialization
5
- end
6
- end
7
- end
@@ -1,175 +0,0 @@
1
- require 'securerandom'
2
-
3
- module Redstruct
4
- module Hls
5
- # Implementation of a simple binary lock (locked/not locked), with option to block and wait for the lock.
6
- # Uses two redis structures: a string for the lease, and a list for blocking operations.
7
- # @see #acquire
8
- # @see #release
9
- # @see #locked
10
- # @attr_reader [::String, nil] token the current token or nil
11
- # @attr_reader [Fixnum] expiry expiry of the underlying redis structures in milliseconds
12
- # @attr_reader [Fixnum, nil] timeout the timeout to wait when attempting to acquire the lock, in seconds
13
- class Lock < Redstruct::Types::Base
14
- include Redstruct::Utils::Scriptable, Redstruct::Utils::Coercion
15
-
16
- # The default expiry on the underlying redis keys, in milliseconds
17
- DEFAULT_EXPIRY = 1000
18
-
19
- # The default timeout when blocking, in seconds; a nil value means it is non-blocking
20
- DEFAULT_TIMEOUT = nil
21
-
22
- attr_reader :token, :expiry, :timeout
23
-
24
- # @param [Integer] expiry in milliseconds; to prevent infinite locking, each mutex is released after a certain expiry time
25
- # @param [Integer] timeout in seconds; if > 0, will block for this amount of time when trying to obtain the lock
26
- def initialize(expiry: DEFAULT_EXPIRY, timeout: DEFAULT_TIMEOUT, **options)
27
- super(**options)
28
-
29
- @token = nil
30
- @expiry = expiry
31
- @timeout = timeout.to_i
32
-
33
- create do |factory|
34
- @lease = factory.string('lease')
35
- @tokens = factory.list('tokens')
36
- end
37
- end
38
-
39
- # Executes the given block if the lock can be acquired
40
- # @yield Block to be executed if the lock is acquired
41
- def locked
42
- yield if acquire
43
- ensure
44
- release
45
- end
46
-
47
- # Whether or not the lock will block when attempting to acquire it
48
- # @return [Boolean]
49
- def blocking?
50
- return @timeout.positive?
51
- end
52
-
53
- # Attempts to acquire the lock. First attempts to grab the lease (a redis string).
54
- # If the current token is already the lease token, the lock is considered acquired.
55
- # If there is no current lease, then sets it to the current token.
56
- # If there is a current lease that is not the current token, then:
57
- # 1) If this not a blocking lock (see Lock#blocking?), return false
58
- # 2) If this is a blocking lock, block and wait for the next token to be pushed on the tokens list
59
- # 3) If a token was pushed, set it as our token and refresh the expiry
60
- # @return [Boolean] True if acquired, false otherwise
61
- def acquire
62
- acquired = false
63
- token = non_blocking_acquire(@token)
64
- token = blocking_acquire if token.nil? && blocking?
65
-
66
- unless token.nil?
67
- @token = token
68
- acquired = true
69
- end
70
-
71
- return acquired
72
- end
73
-
74
- # Releases the lock only if the current token is the value of the lease.
75
- # If the lock is a blocking lock (see Lock#blocking?), push the next token on the tokens list.
76
- # @return [Boolean] True if released, false otherwise
77
- def release
78
- return false if @token.nil?
79
-
80
- next_token = SecureRandom.uuid
81
- return coerce_bool(release_script(keys: [@lease.key, @tokens.key], argv: [@token, next_token, @expiry]))
82
- end
83
-
84
- def non_blocking_acquire(token = nil)
85
- token ||= generate_token
86
- return acquire_script(keys: @lease.key, argv: [token, @expiry])
87
- end
88
- private :non_blocking_acquire
89
-
90
- def blocking_acquire
91
- timeout = @timeout == Float::INFINITY ? 0 : @timeout
92
- token = @tokens.pop(timeout: timeout)
93
-
94
- # Attempt to reacquire in a non blocking way to:
95
- # 1) assert we do own the lock (edge case)
96
- # 2) touch the lock expiry
97
- token = non_blocking_acquire(token) unless token.nil?
98
-
99
- return token
100
- end
101
- private :blocking_acquire
102
-
103
- # The acquire script attempts to set the lease (keys[1]) to the given token (argv[1]), only
104
- # if it wasn't already set. It then compares to check if the value of the lease is that of the token,
105
- # and if so refreshes the expiry (argv[2]) time of the lease.
106
- # @param [Array<(::String)>] keys The lease key specifying who owns the mutex at the moment
107
- # @param [Array<(::String, Fixnum)>] argv The current token; the expiry time in milliseconds
108
- # @return [::String] Returns the token if acquired, nil otherwise.
109
- defscript :acquire_script, <<~LUA
110
- local token = ARGV[1]
111
- local expiry = tonumber(ARGV[2])
112
-
113
- redis.call('set', KEYS[1], token, 'NX')
114
- if redis.call('get', KEYS[1]) == token then
115
- redis.call('pexpire', KEYS[1], expiry)
116
- return token
117
- end
118
-
119
- return false
120
- LUA
121
-
122
- # The release script compares the given token (argv[1]) with the lease value (keys[1]); if they are the same,
123
- # then a new token (argv[2]) is set as the lease, and pushed on the tokens (keys[2]) list
124
- # for the next acquire request.
125
- # @param [Array<(::String, ::String)>] keys The lease key; the tokens list key
126
- # @param [Array<(::String, ::String, Fixnum)>] argv The current token; the next token to push; the expiry time of both keys
127
- # @return [Fixnum] 1 if released, 0 otherwise
128
- defscript :release_script, <<~LUA
129
- local currentToken = ARGV[1]
130
- local nextToken = ARGV[2]
131
- local expiry = tonumber(ARGV[3])
132
-
133
- if redis.call('get', KEYS[1]) == currentToken then
134
- redis.call('set', KEYS[1], nextToken, 'PX', expiry)
135
- redis.call('lpush', KEYS[2], nextToken)
136
- redis.call('pexpire', KEYS[2], expiry)
137
- return true
138
- end
139
-
140
- return false
141
- LUA
142
-
143
- # @!group Serialization
144
- # Returns a hash representation of the object
145
- # @see Lock#from_h
146
- # @return [Hash<Symbol, Object>] hash representation of the lock
147
- def to_h
148
- return super.merge(token: @token, expiry: @expiry, timeout: @timeout)
149
- end
150
-
151
- class << self
152
- # Builds a lock from a hash.
153
- # @see Lock#to_h
154
- # @see Factory#create_from_h
155
- # @param [Hash] hash hash generated by calling Lock#to_h. Ensure beforehand that keys are symbols.
156
- # @return [Lock]
157
- def from_h(hash, factory)
158
- hash[:factory] = factory
159
- return new(**hash)
160
- end
161
- end
162
- # @!endgroup
163
-
164
- def generate_token
165
- return SecureRandom.uuid
166
- end
167
- private :generate_token
168
-
169
- # Helper method for easy inspection
170
- def inspectable_attributes
171
- super.merge(expiry: @expiry, blocking: blocking?)
172
- end
173
- end
174
- end
175
- end
@@ -1,29 +0,0 @@
1
- module Redstruct
2
- module Hls
3
- class Queue < Redstruct::Types::List
4
- include Redstruct::Utils::Scriptable
5
-
6
- def enqueue(*elements)
7
- self.connection.rpush(@key, elements)
8
- end
9
-
10
- def dequeue(length: 1)
11
- elements = dequeue_script(keys: @key, argv: length)
12
- length == 1 ? elements.first : elements
13
- end
14
-
15
- # Dequeues up to argv[1] amount of items from the list at keys[1]
16
- # @param [Array<(::String)>] keys The key of the list to dequeue from
17
- # @param [Array<(Fixnum)>] argv The number of items to dequeue
18
- # @return [Array] An array of items dequeued or an empty array
19
- defscript :dequeue_script, <<~LUA
20
- local length = tonumber(ARGV[1])
21
- local elements = redis.call('lrange', KEYS[1], 0, length - 1)
22
- redis.call('ltrim', KEYS[1], length, -1)
23
-
24
- return elements
25
- LUA
26
- protected :dequeue_script
27
- end
28
- end
29
- end
data/lib/redstruct/hls.rb DELETED
@@ -1,2 +0,0 @@
1
- require 'redstruct/hls/lock'
2
- require 'redstruct/hls/queue'