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.
- checksums.yaml +4 -4
- data/README.md +15 -11
- data/Rakefile +5 -5
- data/lib/redstruct/all.rb +14 -0
- data/lib/redstruct/configuration.rb +9 -6
- data/lib/redstruct/connection_proxy.rb +123 -0
- data/lib/redstruct/counter.rb +96 -0
- data/lib/redstruct/error.rb +2 -0
- data/lib/redstruct/factory/object.rb +31 -0
- data/lib/redstruct/factory.rb +94 -55
- data/lib/redstruct/hash.rb +123 -0
- data/lib/redstruct/list.rb +315 -0
- data/lib/redstruct/lock.rb +183 -0
- data/lib/redstruct/script.rb +104 -0
- data/lib/redstruct/set.rb +155 -0
- data/lib/redstruct/sorted_set/slice.rb +124 -0
- data/lib/redstruct/sorted_set.rb +153 -0
- data/lib/redstruct/string.rb +66 -0
- data/lib/redstruct/struct.rb +87 -0
- data/lib/redstruct/utils/coercion.rb +14 -8
- data/lib/redstruct/utils/inspectable.rb +8 -4
- data/lib/redstruct/utils/iterable.rb +52 -0
- data/lib/redstruct/utils/scriptable.rb +32 -6
- data/lib/redstruct/version.rb +4 -1
- data/lib/redstruct.rb +17 -51
- data/lib/yard/defscript_handler.rb +5 -3
- data/test/redstruct/configuration_test.rb +13 -0
- data/test/redstruct/connection_proxy_test.rb +85 -0
- data/test/redstruct/counter_test.rb +108 -0
- data/test/redstruct/factory/object_test.rb +21 -0
- data/test/redstruct/factory_test.rb +136 -0
- data/test/redstruct/hash_test.rb +138 -0
- data/test/redstruct/list_test.rb +244 -0
- data/test/redstruct/lock_test.rb +108 -0
- data/test/redstruct/script_test.rb +53 -0
- data/test/redstruct/set_test.rb +219 -0
- data/test/redstruct/sorted_set/slice_test.rb +10 -0
- data/test/redstruct/sorted_set_test.rb +219 -0
- data/test/redstruct/string_test.rb +8 -0
- data/test/redstruct/struct_test.rb +61 -0
- data/test/redstruct/utils/coercion_test.rb +33 -0
- data/test/redstruct/utils/inspectable_test.rb +31 -0
- data/test/redstruct/utils/iterable_test.rb +94 -0
- data/test/redstruct/utils/scriptable_test.rb +67 -0
- data/test/redstruct_test.rb +14 -0
- data/test/test_helper.rb +77 -1
- metadata +58 -26
- data/lib/redstruct/connection.rb +0 -47
- data/lib/redstruct/factory/creation.rb +0 -95
- data/lib/redstruct/factory/deserialization.rb +0 -7
- data/lib/redstruct/hls/lock.rb +0 -175
- data/lib/redstruct/hls/queue.rb +0 -29
- data/lib/redstruct/hls.rb +0 -2
- data/lib/redstruct/types/base.rb +0 -36
- data/lib/redstruct/types/counter.rb +0 -65
- data/lib/redstruct/types/hash.rb +0 -72
- data/lib/redstruct/types/list.rb +0 -76
- data/lib/redstruct/types/script.rb +0 -56
- data/lib/redstruct/types/set.rb +0 -96
- data/lib/redstruct/types/sorted_set.rb +0 -129
- data/lib/redstruct/types/string.rb +0 -64
- data/lib/redstruct/types/struct.rb +0 -58
- data/lib/releaser/logger.rb +0 -15
- data/lib/releaser/repository.rb +0 -32
- data/lib/tasks/release.rake +0 -49
- 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.
|
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-
|
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.
|
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.
|
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.
|
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.
|
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/
|
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/
|
105
|
-
- lib/redstruct/
|
106
|
-
- lib/redstruct/
|
107
|
-
- lib/redstruct/
|
108
|
-
- lib/redstruct/
|
109
|
-
- lib/redstruct/
|
110
|
-
- lib/redstruct/
|
111
|
-
- lib/redstruct/
|
112
|
-
- lib/redstruct/
|
113
|
-
- lib/redstruct/
|
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/
|
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/
|
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
|
data/lib/redstruct/connection.rb
DELETED
@@ -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
|
data/lib/redstruct/hls/lock.rb
DELETED
@@ -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
|
data/lib/redstruct/hls/queue.rb
DELETED
@@ -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