ractor-tmvar 0.2.0 → 0.4.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: 61f2c5af774133ccaca92fd2ab9d5926d1faf7d229d60f62c7928e82c8e06cef
4
- data.tar.gz: d5e176ee4886ca2f5feea7f20c9b3989e33778b3a433e7f550e281b43c9bfb72
3
+ metadata.gz: 6f3245eebf48796294d345ac5d97677c3f536347f87a204555223781cec1a87d
4
+ data.tar.gz: 3bf7ca844e8556db49d981b2d50c6e00867927f70348d59242220b66d0e20a59
5
5
  SHA512:
6
- metadata.gz: 605a25ac148a512f7d2a8c18d98e6c73cae522fb69963e7a9b5357b4165efeb044a8b0e425a2172f2ad81278a1a632a434bfeb4d22a8047e4cd44e99630a49c9
7
- data.tar.gz: 9523eb78bbef48de3b809df3a442eaa2be9df55573e2ea1e0d4a7b3692977a062ba36ca6de18182800dea1c46b0120aa50e28316d21136816af09fc4d65e5522
6
+ metadata.gz: b30e17fdb2e91c2aa60e83edf47098926a017a63a192a409545116ef7ec605ab2203361f00862de5e8f2e443058dc3c7111b69a77bf4373d9f718e74faff5c1c
7
+ data.tar.gz: 567905c4e3ae0b351a0ecaaa69b97a5de09d77e7047da0e07cee7e45818251097e2e12bf32e3ebf173bfff48ccf3dada4ba40c29ad153a87da2176028f098058
@@ -0,0 +1,54 @@
1
+ name: Build and attest gem
2
+
3
+ on:
4
+ workflow_call:
5
+
6
+ permissions:
7
+ contents: read
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ contents: read
14
+ id-token: write
15
+ attestations: write
16
+ steps:
17
+ - name: Check out tagged source
18
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
19
+ with:
20
+ ref: ${{ github.sha }}
21
+ persist-credentials: false
22
+
23
+ - name: Set up Ruby
24
+ uses: ruby/setup-ruby@e81a8fa391b11a595c1b7ed84177cc0fe02faf83 # v1
25
+ with:
26
+ ruby-version: '4.0'
27
+ bundler-cache: true
28
+
29
+ - name: Check release tag matches gem version
30
+ run: |
31
+ ruby -r ./lib/ractor/tmvar/version -e '
32
+ expected = "refs/tags/v#{Ractor::TMVar::VERSION}"
33
+ abort "Expected #{expected}, got #{ENV.fetch("GITHUB_REF")}" unless ENV.fetch("GITHUB_REF") == expected
34
+ '
35
+
36
+ - name: Run tests and lint
37
+ run: bundle exec rake
38
+
39
+ - name: Build gem
40
+ run: |
41
+ mkdir -p pkg
42
+ gem build ractor-tmvar.gemspec --output "pkg/ractor-tmvar-${GITHUB_REF_NAME#v}.gem"
43
+
44
+ - name: Attest gem provenance
45
+ uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4
46
+ with:
47
+ subject-path: pkg/*.gem
48
+
49
+ - name: Save gem
50
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
51
+ with:
52
+ name: ractor-tmvar-${{ github.ref_name }}
53
+ path: pkg/*.gem
54
+ if-no-files-found: error
@@ -6,7 +6,7 @@ jobs:
6
6
  fail-fast: false
7
7
  matrix:
8
8
  os: [ubuntu]
9
- ruby: [head, debug]
9
+ ruby: [head, debug, '4.0']
10
10
 
11
11
  runs-on: ${{ matrix.os }}-latest
12
12
  continue-on-error: ${{ matrix.ruby == 'head' || matrix.ruby == 'debug' }}
@@ -0,0 +1,17 @@
1
+ name: Build release gem
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ build:
13
+ permissions:
14
+ contents: read
15
+ id-token: write
16
+ attestations: write
17
+ uses: ./.github/workflows/build-gem.yml
data/.gitignore CHANGED
@@ -6,3 +6,7 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /vendor/
10
+ .pi
11
+ .env
12
+ .env.local
data/.rubocop.yml CHANGED
@@ -1,5 +1,7 @@
1
+ plugins:
2
+ - rubocop-rake
3
+
1
4
  AllCops:
2
- TargetRubyVersion: 2.7
3
5
  NewCops: enable
4
6
 
5
7
  Style/StringLiterals:
@@ -17,3 +19,7 @@ Metrics/ClassLength:
17
19
  Enabled: true
18
20
  Exclude:
19
21
  - "test/**/*"
22
+
23
+ Naming/PredicateMethod:
24
+ AllowedMethods:
25
+ - try_put
data/Gemfile CHANGED
@@ -9,4 +9,6 @@ gem "rake", "~> 13.0"
9
9
 
10
10
  gem "test-unit", "~> 3.0"
11
11
 
12
- gem "rubocop", "~> 0.80"
12
+ gem "rubocop", "~> 1.91"
13
+
14
+ gem "rubocop-rake", "~> 0.7"
data/Gemfile.lock CHANGED
@@ -1,46 +1,85 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ractor-tmvar (0.2.0)
5
- ractor-tvar (~> 0.3.0)
4
+ ractor-tmvar (0.4.0)
5
+ ractor-sharing (~> 0.3.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- ast (2.4.1)
11
- parallel (1.20.1)
12
- parser (2.7.2.0)
10
+ ast (2.4.3)
11
+ json (3.0.2)
12
+ language_server-protocol (3.17.0.6)
13
+ lint_roller (1.1.0)
14
+ parallel (1.28.0)
15
+ parser (3.3.12.0)
13
16
  ast (~> 2.4.1)
14
- power_assert (1.2.0)
15
- ractor-tvar (0.3.0)
16
- rainbow (3.0.0)
17
- rake (13.0.1)
18
- regexp_parser (2.0.0)
19
- rexml (3.2.4)
20
- rubocop (0.93.1)
21
- parallel (~> 1.10)
22
- parser (>= 2.7.1.5)
17
+ racc
18
+ power_assert (3.1.0)
19
+ prism (1.9.0)
20
+ racc (1.8.1)
21
+ ractor-sharing (0.3.0)
22
+ rainbow (3.1.1)
23
+ rake (13.4.2)
24
+ regexp_parser (2.13.1)
25
+ rubocop (1.91.0)
26
+ json (>= 2.3)
27
+ language_server-protocol (~> 3.17.0.2)
28
+ lint_roller (~> 1.1.0)
29
+ parallel (>= 1.10)
30
+ parser (>= 3.3.0.2)
23
31
  rainbow (>= 2.2.2, < 4.0)
24
- regexp_parser (>= 1.8)
25
- rexml
26
- rubocop-ast (>= 0.6.0)
32
+ regexp_parser (>= 2.9.3, < 3.0)
33
+ rubocop-ast (>= 1.49.0, < 2.0)
27
34
  ruby-progressbar (~> 1.7)
28
- unicode-display_width (>= 1.4.0, < 2.0)
29
- rubocop-ast (1.2.0)
30
- parser (>= 2.7.1.5)
31
- ruby-progressbar (1.10.1)
32
- test-unit (3.3.7)
35
+ unicode-display_width (>= 2.4.0, < 4.0)
36
+ rubocop-ast (1.50.0)
37
+ parser (>= 3.3.7.2)
38
+ prism (~> 1.7)
39
+ rubocop-rake (0.7.1)
40
+ lint_roller (~> 1.1)
41
+ rubocop (>= 1.72.1)
42
+ ruby-progressbar (1.13.0)
43
+ test-unit (3.7.8)
33
44
  power_assert
34
- unicode-display_width (1.7.0)
45
+ unicode-display_width (3.3.0)
46
+ unicode-emoji (~> 4.3)
47
+ unicode-emoji (4.3.0)
35
48
 
36
49
  PLATFORMS
37
50
  ruby
51
+ x86_64-linux
38
52
 
39
53
  DEPENDENCIES
40
54
  ractor-tmvar!
41
55
  rake (~> 13.0)
42
- rubocop (~> 0.80)
56
+ rubocop (~> 1.91)
57
+ rubocop-rake (~> 0.7)
43
58
  test-unit (~> 3.0)
44
59
 
60
+ CHECKSUMS
61
+ ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
62
+ bundler (4.0.16) sha256=d6ca5dd440c24f9abce9844cf44cc8e18c6a553de65a47efb4544137af92c47d
63
+ json (3.0.2) sha256=8e6d7e7b11384c21230430cef90b71f14849a34a1f4452796670f7c981bd19df
64
+ language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
65
+ lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
66
+ parallel (1.28.0) sha256=33e6de1484baf2524792d178b0913fc8eb94c628d6cfe45599ad4458c638c970
67
+ parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
68
+ power_assert (3.1.0) sha256=fefe98ed5d3140628138f3f9f9194d6ece01f1320e69700d3c928adca7518251
69
+ prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
70
+ racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
71
+ ractor-sharing (0.3.0) sha256=9adb2f899e4e38f477a2d3c02368e8ff40be96b119172312253338ee1e22f63f
72
+ ractor-tmvar (0.4.0)
73
+ rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
74
+ rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
75
+ regexp_parser (2.13.1) sha256=5aedb6b7c35688f51e86eef17a15374dfd287c83f4ca644b2c5f3ec50a33b44b
76
+ rubocop (1.91.0) sha256=9c82b7bf391c5d7e3798c5b9996e22a1fe3bd7468e351dfdeb96140c058296d0
77
+ rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db
78
+ rubocop-rake (0.7.1) sha256=3797f2b6810c3e9df7376c26d5f44f3475eda59eb1adc38e6f62ecf027cbae4d
79
+ ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
80
+ test-unit (3.7.8) sha256=689d1ca53f4d46f678b4e5d68d8e4294f87fc5e8b9238cc4de7c5727e8d65943
81
+ unicode-display_width (3.3.0) sha256=4b7aa66a4b11db50f6f7e98411215cd0dd10eaecb665d36eec5bcacb3fa0b613
82
+ unicode-emoji (4.3.0) sha256=11c02fa73290378c066bb0562cd4c87d8e1b706fbbe0059ca12746a4244de8ce
83
+
45
84
  BUNDLED WITH
46
- 2.2.0.rc.2
85
+ 4.0.16
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Ractor::TMVar
2
2
 
3
- TMVar for Ractor inspired by [Haskell's TMVar](https://hackage.haskell.org/package/stm-2.5.0.0/docs/Control-Concurrent-STM-TMVar.html) based on [Ractor::TVar](https://github.com/ko1/ractor-tvar).
3
+ TMVar for Ractor inspired by [Haskell's TMVar](https://hackage.haskell.org/package/stm-2.5.0.0/docs/Control-Concurrent-STM-TMVar.html) based on [Ractor::TVar](https://github.com/ko1/ractor-sharing/blob/main/docs/tvar.md).
4
4
 
5
5
  ## Installation
6
6
 
@@ -20,11 +20,8 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- Please see [Ractor::TVar](https://github.com/ko1/ractor-tvar) before using this.
24
- Now we have only `value` and `value=` operations.
25
-
26
23
  ```ruby
27
- require 'ractor-tmvar'
24
+ require 'ractor/tmvar'
28
25
 
29
26
  tv1 = Ractor::TMVar.new(0)
30
27
  tv2 = Ractor::TMVar.new(0)
@@ -33,27 +30,91 @@ rs = 100.times.map do
33
30
  value1 = nil
34
31
  value2 = nil
35
32
  Ractor.atomically do
36
- # take value similar to takeTMVar in Haskell
37
- value1 = v1.value
38
- value2 = v2.value
33
+ # take value like takeTMVar in Haskell
34
+ value1 = v1.take
35
+ value2 = v2.take
39
36
  end
40
37
  Ractor.atomically do
41
- # set value similar to putTMVar in Haskell
42
- v2.value = value2 + 2
43
- v1.value = value1 + 1
38
+ # set value like putTMVar in Haskell
39
+ v2.put(value2 + 2)
40
+ v1.put(value1 + 1)
44
41
  end
45
42
  end
46
43
  end
47
- rs.each(&:take)
48
- assert_equal 100, tv1.value
49
- assert_equal 200, tv2.value
44
+ rs.each(&:value)
45
+
46
+ tv1.read #=> 100
47
+ tv2.read #=> 200
48
+ ```
49
+
50
+ `Ractor::TMVar.new` creates an empty variable, while `Ractor::TMVar.new(value)` or
51
+ `Ractor::TMVar.new_filled(value)` creates a variable already containing `value`.
52
+ `nil` can be stored as a normal value:
53
+
54
+ ```ruby
55
+ tv = Ractor::TMVar.new_filled(nil)
56
+ tv.empty? #=> false
57
+ tv.read #=> nil
58
+ ```
59
+
60
+ ### Transactional operations
61
+
62
+ `take`, `put`, `swap`, `try_take`, and `try_put` must be called inside `Ractor.atomically`. `read` and
63
+ `empty?` can be called outside of a transaction.
64
+
65
+ ```ruby
66
+ tv = Ractor::TMVar.new
67
+ Ractor.atomically { tv.put(42) }
68
+ Ractor.atomically { tv.take } #=> 42
69
+ ```
70
+
71
+ ### Non-blocking try operations
72
+
73
+ `try_take` and `try_read` never retry. Without a block, they return the stored
74
+ value, or `nil` when empty. With a block, when the variable is
75
+ empty they return `nil` without calling the block. When it is full they yield the
76
+ stored value and return the block's result, so a stored `nil` can be told apart
77
+ from an empty variable. `try_take` empties the variable before yielding;
78
+ `try_read` leaves it full.
79
+
80
+ ```ruby
81
+ tv = Ractor::TMVar.new(5)
82
+
83
+ tv.try_read { |v| v * 2 } #=> 10
84
+ tv.read #=> 5
85
+
86
+ Ractor.atomically { tv.try_take { |v| v * 2 } } #=> 10
87
+ tv.empty? #=> true
88
+
89
+ empty = Ractor::TMVar.new
90
+ Ractor.atomically { empty.try_take { |v| [v] } } #=> nil (block not called)
91
+
92
+ filled_with_nil = Ractor::TMVar.new_filled(nil)
93
+ filled_with_nil.try_read { |v| [v] } #=> [nil]
94
+ ```
95
+
96
+ `try_put` returns `true` when it stored the value and `false` when the variable
97
+ was already full.
98
+
99
+ ```ruby
100
+ tv = Ractor::TMVar.new
101
+ Ractor.atomically { tv.try_put(1) } #=> true
102
+ Ractor.atomically { tv.try_put(2) } #=> false
50
103
  ```
51
104
 
52
105
  ## Development
53
106
 
54
107
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
55
108
 
56
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
109
+ To install this gem onto your local machine, run `bundle exec rake install`.
110
+
111
+ ## Verifying gem provenance
112
+
113
+ Verify a downloaded gem's build provenance attestation with the GitHub CLI:
114
+
115
+ ```sh
116
+ gh attestation verify ractor-tmvar-0.3.0.gem -R yoshitsugu/ractor-tmvar
117
+ ```
57
118
 
58
119
  ## Contributing
59
120
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  class Ractor
4
4
  class TMVar
5
- VERSION = "0.2.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
data/lib/ractor/tmvar.rb CHANGED
@@ -1,126 +1,229 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "ractor/tvar"
4
+ require_relative "tmvar/version"
4
5
 
5
6
  class Ractor
6
- # TMVar for Ractor inspired by Haskell's TMVar based on Ractor::TVar.
7
+ # Transactional MVar-like variable for Ractor, inspired by Haskell's
8
+ # +Control.Concurrent.STM.TMVar+ and built on Ractor::TVar.
9
+ #
10
+ # A TMVar is either empty or full. When full it holds a single value.
11
+ # Reading and writing operations are transactional through {Ractor.atomically}.
12
+ #
13
+ # The implementation stores +nil+ when empty and a single-element array +[value]+
14
+ # when full, so any Ruby object (including +nil+) can be stored without colliding
15
+ # with the empty state.
7
16
  class TMVar
8
17
  #
9
- # represents "empty" value for TMVar
18
+ # Create a new TMVar.
10
19
  #
11
- EMPTY = :RACTOR_TMVAR_EMPTY
20
+ # @overload initialize
21
+ # Creates an empty TMVar (Haskell's +newEmptyTMVar+).
22
+ # @overload initialize(value)
23
+ # Creates a TMVar filled with +value+ (Haskell's +newTMVar+).
24
+ # @param [Object] value value to store. With ractor-sharing 0.3, the value is
25
+ # automatically made Ractor-shareable and the passed object is frozen.
26
+ # This behavior is provided by the upstream Ractor::TVar, not a TMVar guarantee.
27
+ #
28
+ # @raise [ArgumentError] if more than one argument is given.
29
+ #
30
+ def initialize(*args)
31
+ raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..1)" if args.size > 1
32
+
33
+ @tvar = Ractor::TVar.new(args.empty? ? nil : [args[0]])
34
+ end
12
35
 
13
36
  #
14
- # initialize TMVar
37
+ # Create a new TMVar explicitly filled with +value+.
15
38
  #
16
- # @param [Object] value Value to set TVar. It needs to be shareable.
39
+ # This is equivalent to +TMVar.new(value)+ but makes the intent clear when the
40
+ # stored value might be +nil+.
41
+ #
42
+ # @param [Object] value value to store. With ractor-sharing 0.3, the value is
43
+ # automatically made Ractor-shareable and the passed object is frozen.
44
+ # This behavior is provided by the upstream Ractor::TVar, not a TMVar guarantee.
17
45
  #
18
46
  # @return [TMVar]
19
47
  #
20
- def initialize(value = nil)
21
- @tvar = Ractor::TVar.new(value)
48
+ def self.new_filled(value)
49
+ new(value)
22
50
  end
23
51
 
24
52
  #
25
- # get the value and leave the value to "empty"
26
- # If the value is already "empty", it will retry the transaction.
27
- # @note You need to wrap it by Ractor.atomically even if you only call +take+
28
- # because +TVar#value=+ needs atomically.
53
+ # Take the value, leaving the TMVar empty.
54
+ #
55
+ # If the TMVar is empty, the transaction is retried.
56
+ #
57
+ # @note Must be called inside {Ractor.atomically}.
29
58
  #
30
- # @return [Object] value of internal TVar.
59
+ # @return [Object] the stored value.
31
60
  #
32
61
  def take
33
- v = @tvar.value
34
- raise Ractor::RetryTransaction if v == EMPTY
62
+ cell = @tvar.value
63
+ raise Ractor::RetryTransaction if cell.nil?
35
64
 
36
- @tvar.value = EMPTY
37
- v
65
+ @tvar.value = nil
66
+ cell[0]
38
67
  end
39
68
 
40
69
  #
41
- # try to get the value.
42
- # If the value is "empty", it returns nil.
70
+ # Try to take the value without retrying.
43
71
  #
44
- # @return [Object] value of internal TVar, only if exists.
72
+ # If the TMVar is empty, returns +nil+ without calling the block.
73
+ # If the TMVar is full, empties it, yields the value and returns the block's result.
74
+ # Without a block, returns the stored value unchanged.
75
+ # Because the block is called only when full, a stored +nil+ can be told apart
76
+ # from an empty TMVar.
45
77
  #
46
- def try_take
47
- v = @tvar.value
48
- return nil if v == EMPTY
78
+ # @note Must be called inside {Ractor.atomically}.
79
+ #
80
+ # @overload try_take
81
+ # @return [Object, nil] the stored value, or +nil+ if empty.
82
+ # @overload try_take { |value| ... }
83
+ # @yieldparam value [Object] the stored value, including +nil+.
84
+ # @yieldreturn [Object] the value to return from +try_take+.
85
+ # @return [Object, nil] the block's result, or +nil+ if empty.
86
+ #
87
+ # @example Take a value without a block
88
+ # tv = Ractor::TMVar.new(5)
89
+ # Ractor.atomically { tv.try_take } #=> 5
90
+ # tv.empty? #=> true
91
+ #
92
+ # @example Distinguish a stored nil from an empty TMVar
93
+ # tv = Ractor::TMVar.new(nil)
94
+ # Ractor.atomically { tv.try_take { |value| [value] } } #=> [nil]
95
+ # Ractor.atomically { tv.try_take { |value| [value] } } #=> nil
96
+ #
97
+ def try_take(&block)
98
+ cell = @tvar.value
99
+ return nil if cell.nil?
49
100
 
50
- @tvar.value = EMPTY
51
- v
101
+ @tvar.value = nil
102
+ (block || method(:identity)).call(cell[0])
52
103
  end
53
104
 
54
105
  #
55
- # get the value like +take+.
56
- # The difference between +take+ and +read+ is +take+ leaves the value blank,
57
- # but +read+ not change the value to blank.
106
+ # Read the value, leaving the TMVar full.
58
107
  #
59
- # @return [Object] value of internal TVar.
108
+ # If the TMVar is empty, the transaction is retried.
109
+ # It can also be called outside {Ractor.atomically}.
110
+ #
111
+ # @return [Object] the stored value.
60
112
  #
61
113
  def read
62
- v = @tvar.value
63
- raise Ractor::RetryTransaction if v == EMPTY
114
+ cell = @tvar.value
115
+ raise Ractor::RetryTransaction if cell.nil?
64
116
 
65
- v
117
+ cell[0]
66
118
  end
67
119
 
68
120
  #
69
- # read the value like +read+ but it does not retry.
121
+ # Try to read the value without retrying.
122
+ #
123
+ # If the TMVar is empty, returns +nil+ without calling the block.
124
+ # If the TMVar is full, yields the value and returns the block's result.
125
+ # Without a block, returns the stored value unchanged.
126
+ # The TMVar remains full.
127
+ #
128
+ # @note Can be called outside {Ractor.atomically}.
70
129
  #
71
- # @return [Object] value of internal TVar.
130
+ # @overload try_read
131
+ # @return [Object, nil] the stored value, or +nil+ if empty.
132
+ # @overload try_read { |value| ... }
133
+ # @yieldparam value [Object] the stored value, including +nil+.
134
+ # @yieldreturn [Object] the value to return from +try_read+.
135
+ # @return [Object, nil] the block's result, or +nil+ if empty.
72
136
  #
73
- def try_read
74
- v = @tvar.value
75
- v == EMPTY ? nil : v
137
+ # @example Read or transform a value without removing it
138
+ # tv = Ractor::TMVar.new(5)
139
+ # tv.try_read #=> 5
140
+ # tv.try_read { |value| value * 2 } #=> 10
141
+ # tv.read #=> 5
142
+ #
143
+ # @example Distinguish a stored nil from an empty TMVar
144
+ # Ractor::TMVar.new(nil).try_read { |value| [value] } #=> [nil]
145
+ # Ractor::TMVar.new.try_read { |value| [value] } #=> nil
146
+ #
147
+ def try_read(&block)
148
+ cell = @tvar.value
149
+ return nil if cell.nil?
150
+
151
+ (block || method(:identity)).call(cell[0])
76
152
  end
77
153
 
78
154
  #
79
- # write the given value.
80
- # If the current value is not "empty", it retries the transaction.
155
+ # Put a value into the TMVar.
156
+ #
157
+ # If the TMVar is already full, the transaction is retried.
81
158
  #
82
- # @param [Object] new_value neet to be shareable
159
+ # @note Must be called inside {Ractor.atomically}.
160
+ #
161
+ # @param [Object] new_value value to store. With ractor-sharing 0.3, the value is
162
+ # automatically made Ractor-shareable and the passed object is frozen.
163
+ # This behavior is provided by the upstream Ractor::TVar, not a TMVar guarantee.
164
+ #
165
+ # @return [Object] +new_value+.
83
166
  #
84
167
  def put(new_value)
85
- raise Ractor::RetryTransaction if @tvar.value != EMPTY
168
+ raise Ractor::RetryTransaction unless @tvar.value.nil?
86
169
 
87
- @tvar.value = new_value
170
+ @tvar.value = [new_value]
171
+ new_value
88
172
  end
89
173
 
90
174
  #
91
- # try to put value to TVar's value
92
- # If the value is not "empty", it will not retry and only return false.
93
- # If it succeed to put, it returns true.
175
+ # Try to put a value into the TMVar without retrying.
176
+ #
177
+ # @note Must be called inside {Ractor.atomically}.
94
178
  #
95
- # @param [Object] new_value neet to be shareable
179
+ # @param [Object] new_value value to store. With ractor-sharing 0.3, the value is
180
+ # automatically made Ractor-shareable and the passed object is frozen.
181
+ # This behavior is provided by the upstream Ractor::TVar, not a TMVar guarantee.
182
+ #
183
+ # @return [Boolean] +true+ if the value was stored, +false+ if the TMVar was full.
96
184
  #
97
185
  def try_put(new_value)
98
- return false if @tvar.value != EMPTY
186
+ return false unless @tvar.value.nil?
99
187
 
100
- @tvar.value = new_value
188
+ @tvar.value = [new_value]
101
189
  true
102
190
  end
103
191
 
104
192
  #
105
- # return the value is "empty" or not.
193
+ # Return whether the TMVar is currently empty.
194
+ # It can also be called outside {Ractor.atomically}.
106
195
  #
107
- # @param [Boolean]
196
+ # @return [Boolean]
108
197
  #
109
198
  def empty?
110
- @tvar.value == EMPTY
199
+ @tvar.value.nil?
111
200
  end
112
201
 
113
202
  #
114
- # get the the value like +get+, and replace the value to the given value if the current value is not "empty"
203
+ # Replace the stored value and return the previous value.
204
+ #
205
+ # If the TMVar is empty, the transaction is retried.
206
+ #
207
+ # @note Must be called inside {Ractor.atomically}.
208
+ #
209
+ # @param [Object] new_value value to store. With ractor-sharing 0.3, the value is
210
+ # automatically made Ractor-shareable and the passed object is frozen.
211
+ # This behavior is provided by the upstream Ractor::TVar, not a TMVar guarantee.
115
212
  #
116
- # @param [Object] new_value neet to be shareable
213
+ # @return [Object] the previous value.
117
214
  #
118
- def swap(new)
119
- v = @tvar.value
120
- raise Ractor::RetryTransaction if v == EMPTY
215
+ def swap(new_value)
216
+ cell = @tvar.value
217
+ raise Ractor::RetryTransaction if cell.nil?
218
+
219
+ @tvar.value = [new_value]
220
+ cell[0]
221
+ end
222
+
223
+ private
121
224
 
122
- @tvar.value = new
123
- v
225
+ def identity(value)
226
+ value
124
227
  end
125
228
  end
126
229
  end
data/ractor-tmvar.gemspec CHANGED
@@ -12,13 +12,14 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "Introduce Ractor::TMVar based on Ractor::TVar"
13
13
  spec.homepage = "https://github.com/yoshitsugu/ractor-tmvar"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = Gem::Requirement.new(">= 2.7.2")
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 4.0")
16
16
 
17
17
  # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
18
18
 
19
19
  spec.metadata["homepage_uri"] = spec.homepage
20
20
  spec.metadata["source_code_uri"] = spec.homepage
21
21
  spec.metadata["changelog_uri"] = spec.homepage
22
+ spec.metadata["rubygems_mfa_required"] = "true"
22
23
 
23
24
  # Specify which files should be added to the gem when it is released.
24
25
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -29,5 +30,5 @@ Gem::Specification.new do |spec|
29
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
31
  spec.require_paths = ["lib"]
31
32
 
32
- spec.add_runtime_dependency "ractor-tvar", "~> 0.3.0"
33
+ spec.add_dependency "ractor-sharing", "~> 0.3.0"
33
34
  end
metadata CHANGED
@@ -1,17 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ractor-tmvar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yoshitsugu
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2020-12-01 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: ractor-tvar
13
+ name: ractor-sharing
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - "~>"
@@ -31,7 +30,9 @@ executables: []
31
30
  extensions: []
32
31
  extra_rdoc_files: []
33
32
  files:
33
+ - ".github/workflows/build-gem.yml"
34
34
  - ".github/workflows/main.yml"
35
+ - ".github/workflows/release.yml"
35
36
  - ".gitignore"
36
37
  - ".rubocop.yml"
37
38
  - Gemfile
@@ -51,7 +52,7 @@ metadata:
51
52
  homepage_uri: https://github.com/yoshitsugu/ractor-tmvar
52
53
  source_code_uri: https://github.com/yoshitsugu/ractor-tmvar
53
54
  changelog_uri: https://github.com/yoshitsugu/ractor-tmvar
54
- post_install_message:
55
+ rubygems_mfa_required: 'true'
55
56
  rdoc_options: []
56
57
  require_paths:
57
58
  - lib
@@ -59,15 +60,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
59
60
  requirements:
60
61
  - - ">="
61
62
  - !ruby/object:Gem::Version
62
- version: 2.7.2
63
+ version: '4.0'
63
64
  required_rubygems_version: !ruby/object:Gem::Requirement
64
65
  requirements:
65
66
  - - ">="
66
67
  - !ruby/object:Gem::Version
67
68
  version: '0'
68
69
  requirements: []
69
- rubygems_version: 3.2.0.rc.2
70
- signing_key:
70
+ rubygems_version: 4.0.20
71
71
  specification_version: 4
72
72
  summary: Ractor::TMVar
73
73
  test_files: []