redstruct 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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'