specwrk-store-redis_adapter 0.1.0 → 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: 7dfd5dfaecffd662d5eac13f71983a4f1bbafc4b05ef20140a7606d9204d6fd1
4
- data.tar.gz: 76623ce696c958f0be9661a7eb2c5455eb384f9c62a1bad4fb24d4182c58bbdf
3
+ metadata.gz: d6da1ca92a3507c6251032708246c2c75490b189e24427d4541fcba13c9f1931
4
+ data.tar.gz: b16e5dec0899ac70e6469289a1313ab390127e0bf488cbb53a2e42afcde863d7
5
5
  SHA512:
6
- metadata.gz: f061f52007d34735b291a5df219412f3a735506b07d0196087dd7218d909d717f409ddefd79fe612fb6c0fa92f0687854912d136360a2803b17c224ab0528420
7
- data.tar.gz: 0b6155b3ee87e11d591352b57fe3cc4d6d7d41358bfb14a6f3bc4dcecbed341a59067f5b1acd5d14e6a7398defcc98fa03b175c2bab76380333b9ab894fa457c
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,14 +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"
7
8
  require "specwrk/store/serializer"
9
+ require "specwrk/store/redis_adapter/version"
8
10
  require "redis-client"
9
11
 
10
12
  module Specwrk
11
13
  class Store
12
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
+
13
34
  @connection_pools = {}
14
35
  @mutex = Mutex.new
15
36
 
@@ -19,23 +40,19 @@ module Specwrk
19
40
  Thread.current[:connection] = connection
20
41
 
21
42
  id = SecureRandom.uuid
22
- queue = "specwrk-lock-#{key}"
23
- connection.pipelined do |pipeline|
24
- pipeline.call("RPUSH", queue, id)
25
- pipeline.call("EXPIRE", queue, 10) # only set if no expireat already
26
- end
43
+ lock_key = "specwrk-lock-#{key}"
44
+ locked = false
27
45
 
28
- # wait for our id to be first in line or the queue to expire
29
- 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
30
48
 
31
49
  yield
32
50
  ensure
33
- connection.pipelined do |pipeline|
34
- pipeline.call("LPOP", queue)
35
- 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
36
55
  end
37
-
38
- Thread.current[:connection] = nil
39
56
  end
40
57
  end
41
58
 
@@ -52,6 +69,23 @@ module Specwrk
52
69
  def reset_connections!
53
70
  @connection_pools.clear
54
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
55
89
  end
56
90
 
57
91
  def [](key)
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.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Westendorf
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.19.1
18
+ version: 0.19.4
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.19.1
25
+ version: 0.19.4
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: redis-client
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -76,9 +76,11 @@ files:
76
76
  - CHANGELOG.md
77
77
  - LICENSE
78
78
  - README.md
79
+ - RELEASING.md
79
80
  - Rakefile
80
81
  - docker-compose.yml
81
82
  - lib/specwrk/store/redis_adapter.rb
83
+ - lib/specwrk/store/redis_adapter/version.rb
82
84
  homepage: https://github.com/danielwestendorf/specwrk-store-redis_adapter
83
85
  licenses:
84
86
  - GPL-3.0-or-later
@@ -100,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
102
  - !ruby/object:Gem::Version
101
103
  version: '0'
102
104
  requirements: []
103
- rubygems_version: 3.6.9
105
+ rubygems_version: 4.0.10
104
106
  specification_version: 4
105
107
  summary: Redis adapater for Specwrk, a parallel RSpec test runner
106
108
  test_files: []