specwrk-store-redis_adapter 0.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e259578b3ddfd3328361f01c837fcd22ccda10f27be6d31fa4f3ba050339644
4
- data.tar.gz: 74062ea30b88f3abe7aff3c977ae9bf1f6fbcb6282a017bdd4d5195b5b80159a
3
+ metadata.gz: d6da1ca92a3507c6251032708246c2c75490b189e24427d4541fcba13c9f1931
4
+ data.tar.gz: b16e5dec0899ac70e6469289a1313ab390127e0bf488cbb53a2e42afcde863d7
5
5
  SHA512:
6
- metadata.gz: 7a6be55c8236a50d82b56c48a2d5341bc64c1f8f750ce1714a224d6188d9de403dd5502a1c8d81848b5b14f3fbddea439e26e9644cf43d5352d667fca262b12c
7
- data.tar.gz: 937702cc0caa0afb9d051e7d32a7167ef9818605203a7e388fde775d3520fef519acc6fb68911f1c0b873c3c283cedb0bbd1f6891a8625c105782d0bf54e6b4b
6
+ metadata.gz: c576a494cc6f2828973fe8c494dc880519bc70f8d0189aa0a25e3e0967b4eb86b9b808a2d8ee7acbfcfdd8ff7667aaa130c7d4cc9f3ae7c0586d28e7ac6fe510
7
+ data.tar.gz: f9ab432763045f76f9d5fbc887eacb5e42b8e141b82362a6a31b981aa9b75f7ea96430953aba893b4604cdc748a15f3c4bcf0685649fcbdc123f08b04f6df458
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2026-08-23
4
+
5
+ - Replace the Redis queue lock with atomic, token-owned Lua lock and unlock scripts cached and versioned by content SHA.
6
+ - Raise `Specwrk::Store::LockUnavailableError` immediately when a Redis lock cannot be acquired.
7
+ - Document the release process and manage versions with `gem-release`.
8
+
3
9
  ## [0.1.0] - 2025-08-24
4
10
 
5
11
  - Initial release
data/RELEASING.md ADDED
@@ -0,0 +1,95 @@
1
+ # Releasing the Specwrk Redis Store Adapter
2
+
3
+ Releases are cut from `main` in two stages:
4
+
5
+ 1. `gem bump` updates the version for the release.
6
+ 2. Bundler's `rake release` task tags the commit, pushes Git, and publishes the gem.
7
+
8
+ ## One-time setup
9
+
10
+ Install `gem-release`, which provides the `gem bump` command. It is intentionally
11
+ not a development dependency because it is only needed by maintainers cutting a
12
+ release.
13
+
14
+ ```sh
15
+ gem install gem-release
16
+ ```
17
+
18
+ Configure credentials for RubyGems:
19
+
20
+ ```sh
21
+ gem signin
22
+ ```
23
+
24
+ Pushing this gem to RubyGems requires multi-factor authentication. Run the
25
+ release from an interactive terminal and have your authenticator available.
26
+ RubyGems will prompt for an OTP code or WebAuthn verification when `rake release`
27
+ pushes the gem. Signing in stores an API key, but does not bypass the MFA check
28
+ required for each push.
29
+
30
+ ## Cut a release
31
+
32
+ Start from an up-to-date, clean `main` branch. This is important because
33
+ `rake release` pushes the current branch.
34
+
35
+ ```sh
36
+ git switch main
37
+ git pull --ff-only origin main
38
+ git status --short
39
+ ```
40
+
41
+ Bump the version without committing. Use `patch`, `minor`, `major`, or an
42
+ explicit version number:
43
+
44
+ ```sh
45
+ gem bump --version patch --no-commit
46
+ ```
47
+
48
+ This updates `lib/specwrk/store/redis_adapter/version.rb` while leaving the
49
+ release changes uncommitted. Update `CHANGELOG.md` using the new version:
50
+
51
+ 1. Add a dated section for the new version.
52
+ 2. Move the Unreleased entries into that section.
53
+ 3. Leave an empty Unreleased section for subsequent changes.
54
+
55
+ Stage the version, changelog, and any release documentation, then create the
56
+ version commit:
57
+
58
+ ```sh
59
+ git add lib/specwrk/store/redis_adapter/version.rb CHANGELOG.md RELEASING.md
60
+ git commit -m "Bump specwrk-store-redis_adapter to VERSION"
61
+ ```
62
+
63
+ Replace `VERSION` with the new version, for example `0.1.1`. Confirm that the
64
+ commit contains the version and changelog updates:
65
+
66
+ ```sh
67
+ git show --stat --oneline HEAD
68
+ git status --short
69
+ ```
70
+
71
+ Run the full checks:
72
+
73
+ ```sh
74
+ bundle exec rake
75
+ ```
76
+
77
+ Publish the release:
78
+
79
+ ```sh
80
+ bundle exec rake release
81
+ ```
82
+
83
+ The release task requires a clean tracked worktree and then:
84
+
85
+ 1. Builds `pkg/specwrk-store-redis_adapter-VERSION.gem`.
86
+ 2. Creates the annotated `vVERSION` tag.
87
+ 3. Pushes `main` and the tag to `origin`.
88
+ 4. Pushes the gem to RubyGems.org and prompts for MFA verification.
89
+
90
+ Use `gem bump` only to update the version; use `bundle exec rake release`, not
91
+ `gem release`, to publish this project.
92
+
93
+ If the RubyGems push fails after the tag was pushed, fix the authentication or
94
+ network problem and rerun `bundle exec rake release`. The task recognizes the
95
+ existing tag and retries the gem publication.
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specwrk
4
+ class Store
5
+ REDIS_ADAPTER_VERSION = "0.2.0"
6
+ end
7
+ end
@@ -2,13 +2,35 @@
2
2
 
3
3
  require "json"
4
4
  require "securerandom"
5
+ require "digest/sha1"
5
6
 
6
7
  require "specwrk/store/base_adapter"
8
+ require "specwrk/store/serializer"
9
+ require "specwrk/store/redis_adapter/version"
7
10
  require "redis-client"
8
11
 
9
12
  module Specwrk
10
13
  class Store
11
14
  class RedisAdapter < Specwrk::Store::BaseAdapter
15
+ VERSION = REDIS_ADAPTER_VERSION
16
+ LOCK_TTL_MILLISECONDS = 10_000
17
+ LOCK_SCRIPT = <<~LUA
18
+ if redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) then
19
+ return 1
20
+ end
21
+
22
+ return 0
23
+ LUA
24
+ UNLOCK_SCRIPT = <<~LUA
25
+ if redis.call("GET", KEYS[1]) == ARGV[1] then
26
+ return redis.call("DEL", KEYS[1])
27
+ end
28
+
29
+ return 0
30
+ LUA
31
+ LOCK_SCRIPT_SHA = Digest::SHA1.hexdigest(LOCK_SCRIPT)
32
+ UNLOCK_SCRIPT_SHA = Digest::SHA1.hexdigest(UNLOCK_SCRIPT)
33
+
12
34
  @connection_pools = {}
13
35
  @mutex = Mutex.new
14
36
 
@@ -18,23 +40,19 @@ module Specwrk
18
40
  Thread.current[:connection] = connection
19
41
 
20
42
  id = SecureRandom.uuid
21
- queue = "specwrk-lock-#{key}"
22
- connection.pipelined do |pipeline|
23
- pipeline.call("RPUSH", queue, id)
24
- pipeline.call("EXPIRE", queue, 10) # only set if no expireat already
25
- end
43
+ lock_key = "specwrk-lock-#{key}"
44
+ locked = false
26
45
 
27
- # wait for our id to be first in line or the queue to expire
28
- sleep(0.01 * rand) until [id, nil].include? connection.call("LINDEX", queue, 0)
46
+ locked = obtain_lock(connection, lock_key, id)
47
+ raise Specwrk::Store::LockUnavailableError unless locked
29
48
 
30
49
  yield
31
50
  ensure
32
- connection.pipelined do |pipeline|
33
- pipeline.call("LPOP", queue)
34
- pipeline.call("EXPIRE", queue, 10) # keeps the queue fresh when things are moving
51
+ begin
52
+ release_lock(connection, lock_key, id) if locked
53
+ ensure
54
+ Thread.current[:connection] = nil
35
55
  end
36
-
37
- Thread.current[:connection] = nil
38
56
  end
39
57
  end
40
58
 
@@ -51,18 +69,35 @@ module Specwrk
51
69
  def reset_connections!
52
70
  @connection_pools.clear
53
71
  end
72
+
73
+ private
74
+
75
+ def obtain_lock(connection, key, id)
76
+ eval_script(connection, LOCK_SCRIPT, LOCK_SCRIPT_SHA, key, id, LOCK_TTL_MILLISECONDS) == 1
77
+ end
78
+
79
+ def release_lock(connection, key, id)
80
+ eval_script(connection, UNLOCK_SCRIPT, UNLOCK_SCRIPT_SHA, key, id)
81
+ end
82
+
83
+ def eval_script(connection, script, sha, key, *arguments)
84
+ connection.call("EVALSHA", sha, 1, key, *arguments)
85
+ rescue RedisClient::NoScriptError
86
+ connection.call("SCRIPT", "LOAD", script)
87
+ retry
88
+ end
54
89
  end
55
90
 
56
91
  def [](key)
57
92
  with_connection do |redis|
58
93
  value = redis.call("HGET", scope, key)
59
- JSON.parse(value, symbolize_names: true) if value
94
+ self.class.serializer.load(value) if value
60
95
  end
61
96
  end
62
97
 
63
98
  def []=(key, value)
64
99
  with_connection do |redis|
65
- redis.call("HSET", scope, key, JSON.generate(value))
100
+ redis.call("HSET", scope, key, self.class.serializer.dump(value))
66
101
  end
67
102
  end
68
103
 
@@ -101,7 +136,7 @@ module Specwrk
101
136
 
102
137
  read_keys.zip(values).each do |key, value|
103
138
  next if value.nil?
104
- result[key] = JSON.parse(value, symbolize_names: true)
139
+ result[key] = self.class.serializer.load(value)
105
140
  end
106
141
 
107
142
  result
@@ -111,7 +146,7 @@ module Specwrk
111
146
  return if hash.nil? || hash.length.zero?
112
147
 
113
148
  with_connection do |redis|
114
- redis.call("HMSET", scope, *hash.flat_map { |key, value| [key, JSON.generate(value)] })
149
+ redis.call("HMSET", scope, *hash.flat_map { |key, value| [key, self.class.serializer.dump(value)] })
115
150
  end
116
151
  end
117
152
 
@@ -132,6 +167,13 @@ module Specwrk
132
167
  end
133
168
  end
134
169
  end
170
+
171
+ # override
172
+ def scope
173
+ return @scope if self.class.serializer.adapter_name == "json"
174
+
175
+ [self.class.serializer.adapter_name, @scope].join("-")
176
+ end
135
177
  end
136
178
  end
137
179
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: specwrk-store-redis_adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Westendorf
@@ -11,6 +11,20 @@ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: specwrk
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.19.4
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.19.4
26
+ - !ruby/object:Gem::Dependency
27
+ name: redis-client
14
28
  requirement: !ruby/object:Gem::Requirement
15
29
  requirements:
16
30
  - - ">="
@@ -24,13 +38,13 @@ dependencies:
24
38
  - !ruby/object:Gem::Version
25
39
  version: '0'
26
40
  - !ruby/object:Gem::Dependency
27
- name: redis-client
41
+ name: standard
28
42
  requirement: !ruby/object:Gem::Requirement
29
43
  requirements:
30
44
  - - ">="
31
45
  - !ruby/object:Gem::Version
32
46
  version: '0'
33
- type: :runtime
47
+ type: :development
34
48
  prerelease: false
35
49
  version_requirements: !ruby/object:Gem::Requirement
36
50
  requirements:
@@ -38,7 +52,7 @@ dependencies:
38
52
  - !ruby/object:Gem::Version
39
53
  version: '0'
40
54
  - !ruby/object:Gem::Dependency
41
- name: standard
55
+ name: msgpack
42
56
  requirement: !ruby/object:Gem::Requirement
43
57
  requirements:
44
58
  - - ">="
@@ -62,10 +76,11 @@ files:
62
76
  - CHANGELOG.md
63
77
  - LICENSE
64
78
  - README.md
79
+ - RELEASING.md
65
80
  - Rakefile
66
81
  - docker-compose.yml
67
82
  - lib/specwrk/store/redis_adapter.rb
68
- - specwrk-store-redis_adapter-0.0.5.gem
83
+ - lib/specwrk/store/redis_adapter/version.rb
69
84
  homepage: https://github.com/danielwestendorf/specwrk-store-redis_adapter
70
85
  licenses:
71
86
  - GPL-3.0-or-later
@@ -87,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
102
  - !ruby/object:Gem::Version
88
103
  version: '0'
89
104
  requirements: []
90
- rubygems_version: 3.6.9
105
+ rubygems_version: 4.0.10
91
106
  specification_version: 4
92
107
  summary: Redis adapater for Specwrk, a parallel RSpec test runner
93
108
  test_files: []
Binary file