specwrk-store-redis_adapter 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2774f66f6a6356247c90f8dae3cc57452a0389ed502d4713ddd5d3ccc5c3f9cb
4
+ data.tar.gz: 2ff23a77e09dab33ae4518e2fffa96af8d1893349278615542074073859d8ed2
5
+ SHA512:
6
+ metadata.gz: 785340c5eaf3af5540b4eb0ed60d95ff79ed98574bdc1734822cce33091327b99b9f27579ea233c3d9682bc471257906d6da2e9127c2f1267daf607871e75085
7
+ data.tar.gz: 156a21fb6424a180af03989bcbb3973e5825b32664ab9e1fc77c511124643c58b03f8bb8636ef00153721a458302e9b7a2fbaa09e87fe4919492b3409a343c4c
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/standardrb/standard
3
+ ruby_version: 3.1
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-08-24
4
+
5
+ - Initial release
data/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (c) Daniel Westendorf
2
+
3
+ specwrk-store-redis_adapter is an Open Source project licensed under the terms of
4
+ the LGPLv3 license. Please see <http://www.gnu.org/licenses/lgpl-3.0.html>
5
+ for license text.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Redis Store Adapter for Specwrk
2
+
3
+ TODO!
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[spec standard]
@@ -0,0 +1,6 @@
1
+ version: '3'
2
+ services:
3
+ redis:
4
+ image: redis:4.0
5
+ ports:
6
+ - "0.0.0.0:6379:6379"
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ require "specwrk/store/base_adapter"
6
+ require "redis-client"
7
+ require "redlock"
8
+
9
+ module Specwrk
10
+ class Store
11
+ class RedisAdapter < Specwrk::Store::BaseAdapter
12
+ VERSION = "0.1.0"
13
+
14
+ REDIS_KEY_DELIMITER = "||||"
15
+
16
+ @connection_pools = {}
17
+ @mutex = Mutex.new
18
+
19
+ class << self
20
+ def with_lock(uri, key)
21
+ connection_pool_for(uri).with do |connection|
22
+ client = Redlock::Client.new(
23
+ [connection],
24
+ retry_count: lock_retry_count,
25
+ retry_delay: lock_retry_delay,
26
+ retry_jitter: lock_retry_jitter
27
+ )
28
+
29
+ until (lock = client.lock("specwrk-lock-#{key}", lock_ttl))
30
+ sleep(rand(0.001..0.09))
31
+ end
32
+
33
+ yield
34
+
35
+ client.unlock(lock)
36
+ end
37
+ end
38
+
39
+ def connection_pool_for(uri)
40
+ return @connection_pools[uri] if @connection_pools.key? uri
41
+
42
+ @mutex.synchronize do
43
+ @connection_pools[uri] ||= RedisClient.config(url: uri).new_pool(
44
+ size: ENV.fetch("SPECWRK_THREAD_COUNT", "4").to_i
45
+ )
46
+ end
47
+ end
48
+
49
+ def reset_connections!
50
+ @connection_pools.clear
51
+ end
52
+
53
+ private
54
+
55
+ # In ms
56
+ def lock_ttl
57
+ ENV.fetch("SPECWRK_REDIS_ADAPTER_LOCK_TTL", "5000").to_i
58
+ end
59
+
60
+ # See https://www.rubydoc.info/gems/redlock/Redlock/Client#initialize-instance_method
61
+ def lock_retry_count
62
+ ENV.fetch("SPECWRK_REDIS_ADAPTER_LOCK_RETRY_COUNT", "3").to_i
63
+ end
64
+
65
+ def lock_retry_delay
66
+ ENV.fetch("SPECWRK_REDIS_ADAPTER_LOCK_RETRY_DELAY", "100").to_i
67
+ end
68
+
69
+ def lock_retry_jitter
70
+ ENV.fetch("SPECWRK_REDIS_ADAPTER_LOCK_RETRY_JITTER", "10").to_i
71
+ end
72
+ end
73
+
74
+ def [](key)
75
+ with_connection do |redis|
76
+ value = redis.call("GET", encode_key(key))
77
+ JSON.parse(value, symbolize_names: true) if value
78
+ end
79
+ end
80
+
81
+ def []=(key, value)
82
+ with_connection do |redis|
83
+ redis.call("SET", encode_key(key), JSON.generate(value))
84
+ end
85
+ end
86
+
87
+ def keys
88
+ [].tap do |collected|
89
+ scan_for("#{scope}#{REDIS_KEY_DELIMITER}*") { |k| collected << decode_key(k) }
90
+ end
91
+ end
92
+
93
+ def clear
94
+ delete(*keys)
95
+ end
96
+
97
+ def delete(*keys)
98
+ return if keys.length.zero?
99
+
100
+ with_connection do |redis|
101
+ redis.call("DEL", *keys.map { |key| encode_key key })
102
+ end
103
+ end
104
+
105
+ def merge!(h2)
106
+ multi_write(h2)
107
+ end
108
+
109
+ def multi_read(*read_keys)
110
+ return {} if read_keys.length.zero?
111
+
112
+ values = with_connection do |redis|
113
+ redis.call("MGET", *read_keys.map { |key| encode_key(key) })
114
+ end
115
+
116
+ result = {}
117
+
118
+ read_keys.zip(values).each do |key, value|
119
+ next if value.nil?
120
+ result[key] = JSON.parse(value, symbolize_names: true)
121
+ end
122
+
123
+ result
124
+ end
125
+
126
+ def multi_write(hash)
127
+ return if hash.nil? || hash.length.zero?
128
+
129
+ with_connection do |redis|
130
+ redis.call("MSET", *hash.flat_map { |key, value| [encode_key(key), JSON.generate(value)] })
131
+ end
132
+ end
133
+
134
+ def empty?
135
+ keys.length.zero?
136
+ end
137
+
138
+ private
139
+
140
+ def with_connection
141
+ self.class.connection_pool_for(uri).with do |connection|
142
+ yield connection
143
+ end
144
+ end
145
+
146
+ def encode_key(key)
147
+ [scope, REDIS_KEY_DELIMITER, key].join
148
+ end
149
+
150
+ def decode_key(key)
151
+ key.split(REDIS_KEY_DELIMITER).last
152
+ end
153
+
154
+ def scan_for(match)
155
+ with_connection do |redis|
156
+ cursor = "0"
157
+ loop do
158
+ cursor, batch = redis.call("SCAN", cursor, "MATCH", match, "COUNT", 5_000)
159
+ batch.each { |k| yield k }
160
+ break if cursor == "0"
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: specwrk-store-redis_adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Westendorf
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: specwrk
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: redis-client
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: redlock
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: standard
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ email:
69
+ - daniel@prowestech.com
70
+ executables: []
71
+ extensions: []
72
+ extra_rdoc_files: []
73
+ files:
74
+ - ".rspec"
75
+ - ".standard.yml"
76
+ - CHANGELOG.md
77
+ - LICENSE
78
+ - README.md
79
+ - Rakefile
80
+ - docker-compose.yml
81
+ - lib/specwrk/store/redis_adapter.rb
82
+ homepage: https://github.com/danielwestendorf/specwrk-store-redis_adapter
83
+ licenses:
84
+ - GPL-3.0-or-later
85
+ metadata:
86
+ homepage_uri: https://github.com/danielwestendorf/specwrk-store-redis_adapter
87
+ source_code_uri: https://github.com/danielwestendorf/specwrk-store-redis_adapter
88
+ changelog_uri: https://github.com/danielwestendorf/specwrk-store-redis_adapter/blob/main/CHANGELOG.md
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 3.0.0
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubygems_version: 3.6.9
104
+ specification_version: 4
105
+ summary: Redis adapater for Specwrk, a parallel RSpec test runner
106
+ test_files: []