farce 0.4.1 → 0.5.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/CHANGELOG.md +80 -2
- data/README.md +3 -1
- data/docs/benchmarks.md +37 -21
- data/docs/compatibility.md +13 -0
- data/docs/data-structures.md +37 -0
- data/docs/integrations.md +1 -0
- data/docs/introduction.md +19 -0
- data/docs/modes.md +2 -0
- data/docs/scheduling.md +35 -3
- data/docs/scopes.md +3 -1
- data/docs/shims.md +53 -0
- data/docs/utilities.md +31 -0
- data/docs/variants.md +9 -0
- data/ext/farce/farce.c +9 -0
- data/ext/farce/map.c +14 -3
- data/ext/farce/priority_queue.c +46 -1
- data/ext/farce/queue.c +141 -7
- data/ext/farce/vector.c +3 -3
- data/ext/farce/weak_map.c +12 -3
- data/ext/java/org/farce/PriorityQueue.java +18 -2
- data/lib/farce/abstract/concurrent_map.rb +122 -0
- data/lib/farce/abstract/counter.rb +35 -1
- data/lib/farce/abstract/executor.rb +30 -7
- data/lib/farce/abstract/flag.rb +12 -0
- data/lib/farce/abstract/pool.rb +158 -47
- data/lib/farce/abstract/priority_queue.rb +1 -1
- data/lib/farce/abstract/publisher.rb +189 -0
- data/lib/farce/abstract/queue.rb +29 -3
- data/lib/farce/abstract/scheduler.rb +41 -14
- data/lib/farce/abstract/timer_queue.rb +3 -3
- data/lib/farce/abstract/vector.rb +3 -1
- data/lib/farce/atom.rb +9 -2
- data/lib/farce/clock.rb +12 -0
- data/lib/farce/concurrent/count_down_latch.rb +37 -0
- data/lib/farce/concurrent/cyclic_barrier.rb +109 -0
- data/lib/farce/concurrent/event.rb +60 -0
- data/lib/farce/concurrent/fiber_local_var.rb +31 -0
- data/lib/farce/concurrent/local_var.rb +65 -0
- data/lib/farce/concurrent/thread_local_var.rb +28 -0
- data/lib/farce/concurrent/tuple.rb +74 -0
- data/lib/farce/concurrent.rb +50 -0
- data/lib/farce/config.rb +112 -2
- data/lib/farce/counter.rb +0 -16
- data/lib/farce/engine/jruby/map.rb +8 -6
- data/lib/farce/engine/jruby/signal.rb +2 -2
- data/lib/farce/engine/jvm/farce.jar +0 -0
- data/lib/farce/engine/jvm/priority_queue.rb +14 -4
- data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -1
- data/lib/farce/engine/ruby/shared/ractor_selector.rb +1 -4
- data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
- data/lib/farce/engine/shared/exchanger.rb +2 -2
- data/lib/farce/engine/shared/queue.rb +43 -5
- data/lib/farce/engine/shared/ractor_selector.rb +1 -1
- data/lib/farce/engine/shared/strict_map.rb +5 -0
- data/lib/farce/engine/shared/unshared_atom.rb +1 -1
- data/lib/farce/engine/shared/unshared_vector.rb +8 -7
- data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
- data/lib/farce/engine/shared/weak_atom/base.rb +5 -10
- data/lib/farce/engine/shared/weak_map/cell.rb +1 -1
- data/lib/farce/engine/truffleruby/map.rb +8 -6
- data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
- data/lib/farce/engine/truffleruby/signal.rb +1 -1
- data/lib/farce/integrations/concurrent.rb +16 -3
- data/lib/farce/internal/blocking_priority_queue.rb +4 -9
- data/lib/farce/internal/flag_waiting.rb +1 -1
- data/lib/farce/internal/lease_initialization.rb +1 -1
- data/lib/farce/internal/lease_pool_state.rb +1 -1
- data/lib/farce/internal/lease_state.rb +2 -2
- data/lib/farce/internal/local_executor_methods.rb +3 -3
- data/lib/farce/internal/map_value_modes.rb +9 -2
- data/lib/farce/internal/pool_settings.rb +9 -0
- data/lib/farce/internal/pool_worker.rb +34 -21
- data/lib/farce/internal/reservation_waiting.rb +1 -1
- data/lib/farce/internal/scheduler_lifecycle.rb +2 -2
- data/lib/farce/internal/weak_map_value_modes.rb +5 -0
- data/lib/farce/internal.rb +8 -8
- data/lib/farce/lazy.rb +3 -0
- data/lib/farce/lazy_ref.rb +3 -0
- data/lib/farce/local/lease.rb +2 -9
- data/lib/farce/local/lease_map.rb +2 -9
- data/lib/farce/local/pool.rb +38 -5
- data/lib/farce/local/publisher.rb +30 -0
- data/lib/farce/local/scheduler.rb +18 -5
- data/lib/farce/local/vector.rb +3 -2
- data/lib/farce/memoize.rb +314 -0
- data/lib/farce/pool.rb +5 -0
- data/lib/farce/priority_queue.rb +1 -1
- data/lib/farce/publisher.rb +57 -0
- data/lib/farce/signal.rb +1 -1
- data/lib/farce/strict/publisher.rb +31 -0
- data/lib/farce/strict/vector.rb +3 -2
- data/lib/farce/subscription/definitions.rb +27 -0
- data/lib/farce/subscription.rb +121 -0
- data/lib/farce/transaction/map.rb +35 -1
- data/lib/farce/unshared/pool.rb +1 -1
- data/lib/farce/unshared/publisher.rb +25 -0
- data/lib/farce/unshared/scheduler.rb +3 -3
- data/lib/farce/unshared/vector.rb +3 -2
- data/lib/farce/vector.rb +12 -9
- data/lib/farce/version.rb +1 -1
- data/lib/farce/walker/definitions.rb +13 -2
- data/lib/farce/weak_atom.rb +5 -2
- data/lib/farce.rb +3 -1
- metadata +18 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0010e09d494cac546062e8d36c1a5f4479b31210f429ab07ec02d7fa04044418
|
|
4
|
+
data.tar.gz: d09a395c757a2ad0e90725b53a45703706a092c94f800b59b6fdd51611939697
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '019155c1fdddbbb9a26aef82fcb399cb409e7e3aa25b462961b3b8b9998ec6ec7b077ceb44e71177a9ab5c2d2952cfe21affe9b989fecc6c4c1f390b2765219a'
|
|
7
|
+
data.tar.gz: 0babf9eccef51c77c3a936322f105789c724ffb3a43412250fd781e4292e1ca4f341fdba35f9989599625b38fc8020439813c20b666160b21e557dcd37f2700b
|
data/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,87 @@
|
|
|
2
2
|
|
|
3
3
|
Farce follows [Semantic Versioning 2.0](http://semver.org/). Any public method on any public class or module documented in the README or via YARD and not declared private is part of the public API.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Version | Date | Important Changes
|
|
8
|
+
--------------------------------|------------|--------------------------------------
|
|
9
|
+
[0.5.0](#farce-050-2026-10-21) | 2026-10-21 | Publishers, memoization, pool and queue resizing, and better concurrent-ruby compatibility
|
|
10
|
+
[0.4.1](#farce-041-2026-10-18) | 2026-10-18 | Include improved io_uring support in prebuilt Linux binaries
|
|
11
|
+
[0.4.0](#farce-040-2026-10-16) | 2026-10-16 | Introduce `Farce::Backports`
|
|
12
|
+
[0.3.0](#farce-030-2026-10-13) | 2026-10-13 | Introduce `Farce::Task` and executor improvements
|
|
13
|
+
[0.2.0](#farce-020-2026-10-11) | 2026-10-11 | Improvements for flags, vectors, and timer queues, and performance
|
|
14
|
+
[0.1.0](#farce-010-2026-10-09) | 2026-10-09 | Initial release
|
|
15
|
+
|
|
16
|
+
## Release History
|
|
17
|
+
|
|
18
|
+
### Farce 0.5.0 (2026-10-21)
|
|
19
|
+
|
|
20
|
+
#### New Features
|
|
21
|
+
|
|
22
|
+
##### Publishers
|
|
23
|
+
|
|
24
|
+
`Farce::Publisher` broadcasts messages to multiple subscribers. It supports Ractors, ports, queues, atoms, counters, flags, collections, signals, other publishers, and callbacks.
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
publisher = Farce::Publisher.new
|
|
28
|
+
counter = Farce::Counter.new
|
|
29
|
+
queue = Farce::Queue.new
|
|
30
|
+
|
|
31
|
+
publisher.subscribe(counter)
|
|
32
|
+
publisher.subscribe(queue)
|
|
33
|
+
publisher.publish(:hello)
|
|
34
|
+
|
|
35
|
+
counter.value # => 1
|
|
36
|
+
queue.pop # => :hello
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
##### Memoization
|
|
40
|
+
|
|
41
|
+
Added `Farce::Memoize` for memoization with scopes, transfer modes, and weak values, including after freezing.
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
class MyClass
|
|
45
|
+
include Farce::Memoize
|
|
46
|
+
|
|
47
|
+
# Memoize #expensive_method, shared across Ractors
|
|
48
|
+
memoize def expensive_method = expensive_computation
|
|
49
|
+
|
|
50
|
+
# Memoize #method_with_unsharable_value for each Ractor separately
|
|
51
|
+
def method_with_unsharable_value = unsharable_computation
|
|
52
|
+
memoize :method_with_unsharable_value, scope: :ractor
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
##### Better `concurrent-ruby` Compatibility
|
|
57
|
+
|
|
58
|
+
* Added `Farce::Concurrent`, with replacements for `CountDownLatch`, `CyclicBarrier`, `Event`, `FiberLocalVar`, `ThreadLocalVar`, `LockLocalVar`, and `Tuple`, plus aliases for existing Farce classes.
|
|
59
|
+
* Maps now support all public API methods of `Concurrent::Map`.
|
|
60
|
+
* Added `Counter#update` to atomically replace the value using a block.
|
|
61
|
+
* Added `true?`, `false?`, `make_true`, and `make_false` to flags.
|
|
62
|
+
|
|
63
|
+
##### Queue and Pool Tuning
|
|
64
|
+
|
|
65
|
+
* Queues, priority queues, timer queues, schedulers, and pools now support resizing.
|
|
66
|
+
* Pools now support tuning after construction. Increasing the minimum starts workers immediately. Excess workers retire when idle without interrupting running tasks.
|
|
67
|
+
* Added `parallel_pool_*` configuration options for the shared CRuby pool used by `Farce.in_parallel`.
|
|
68
|
+
|
|
69
|
+
##### Miscellaneous
|
|
70
|
+
|
|
71
|
+
* Added `timeout:` to executor `schedule` and `execute` methods.
|
|
72
|
+
* Added `Farce::Clock.remaining` and refactored code to use it.
|
|
73
|
+
* Vectors now accept an integer size in their constructors, filling the initial entries with `nil`.
|
|
74
|
+
|
|
75
|
+
#### Bug Fixes
|
|
76
|
+
|
|
77
|
+
* Prevent waits from spinning when an unchanged value has non-reflexive equality, such as `Float::NAN`, or overrides `==`, `!=`, or `equal?`. Predicates receive unwrapped values.
|
|
78
|
+
* Prevent graph traversal from retrying indefinitely when a map value cannot be matched using ordinary equality.
|
|
79
|
+
* Fix vector wait timeout fallbacks on CRuby and TruffleRuby.
|
|
80
|
+
|
|
81
|
+
#### Other changes
|
|
82
|
+
|
|
83
|
+
* Added `pairing_heap` to the priority queue benchmark and refreshed the published benchmark results. It is the fastest priority queue implementation written in pure Ruby.
|
|
84
|
+
|
|
85
|
+
### Farce 0.4.1 (2026-10-18)
|
|
8
86
|
|
|
9
87
|
* Make sure prebuilt Linux binaries include liburing support.
|
|
10
88
|
* Use a Futex for efficient fiber synchronization if possible.
|
data/README.md
CHANGED
|
@@ -39,6 +39,7 @@ Use familiar Ruby APIs with coordinated access to shared state:
|
|
|
39
39
|
- **Maps, vectors, and sets** for collections, with sorted and weak-reference variants.
|
|
40
40
|
- **Atoms, counters, flags, and molecules** for individual values and structured records.
|
|
41
41
|
- **FIFO, priority, and timer queues** for passing work, applying back pressure, or delaying delivery.
|
|
42
|
+
- **Publishers and subscribers** for one-to-many communication, where a single publisher can broadcast messages to multiple subscribers.
|
|
42
43
|
|
|
43
44
|
For example, use a Vector as a shared list:
|
|
44
45
|
|
|
@@ -68,6 +69,7 @@ cache.key?(:a) # => false, the least recently used entry was evicted
|
|
|
68
69
|
- **`Farce::Port`** adds transfer modes, receive timeouts, and Fiber scheduler support.
|
|
69
70
|
- **`Farce::WeakRef` and `Farce::Resolv`** work outside the main Ractor.
|
|
70
71
|
- **`Farce::Backports`** backports newer `Ractor` and `Ractor::Port` features to older Ruby versions. Can be invoked directly, used with refinements, or installed globally.
|
|
72
|
+
- **`Farce::Concurrent`** provides Ractor-friendly replacements for some classes provided by `concurrent-ruby`.
|
|
71
73
|
|
|
72
74
|
Send a result back from a worker:
|
|
73
75
|
|
|
@@ -210,7 +212,7 @@ config.class # => Farce::Map
|
|
|
210
212
|
config[:retries].class # => Farce::Vector
|
|
211
213
|
```
|
|
212
214
|
|
|
213
|
-
Other helpers freeze object graphs, deduplicate values, and measure monotonic time. Lazy values and exchangers coordinate initialization and handoffs. Shareability mixins define how custom classes can be frozen, shared, copied, or moved.
|
|
215
|
+
Other helpers freeze object graphs, deduplicate values, offer method memoization, and measure monotonic time. Lazy values and exchangers coordinate initialization and handoffs. Shareability mixins define how custom classes can be frozen, shared, copied, or moved.
|
|
214
216
|
|
|
215
217
|
→ [Conversion helpers and extension points](docs/utilities.md).
|
|
216
218
|
|
data/docs/benchmarks.md
CHANGED
|
@@ -57,6 +57,7 @@ Also note that `Ratomic::Map` has a significant performance benefit over all oth
|
|
|
57
57
|
concurrent-ruby-ext | 1.2x slower | same-ish | 32-bit | no ractor support
|
|
58
58
|
ratomic | 1.5x slower | 1.6x slower | 64-bit | no overflow protection
|
|
59
59
|
ractor_safe | 2.1x slower | 2.1x slower | 64-bit |
|
|
60
|
+
atomic-ruby | 10x slower | 2.9x slower | any | via `Atom`
|
|
60
61
|
concurrent-ruby | 12x slower | 5x slower | 32-bit | no ractor support
|
|
61
62
|
|
|
62
63
|
Performance differences between implementations are consistent between single-threaded and multi-threaded benchmarks.
|
|
@@ -101,13 +102,14 @@ In the producer/consumer workload in `benchmark/queue.rb`, Ruby's built-in `Thre
|
|
|
101
102
|
Implementation | Performance | Notes
|
|
102
103
|
-------------------------------|-------------|-------------
|
|
103
104
|
`Thread::Queue` | Fastest | no ractor support
|
|
104
|
-
`
|
|
105
|
-
`
|
|
106
|
-
`Farce::Queue`
|
|
107
|
-
`
|
|
108
|
-
`
|
|
109
|
-
`
|
|
110
|
-
`
|
|
105
|
+
`AtomicQueue` | 1.5x slower | no ractor support
|
|
106
|
+
`Thread::SizedQueue` | 1.5x slower | no ractor support
|
|
107
|
+
`Farce::Strict::Queue` | 1.6x slower | only allows sharable objects
|
|
108
|
+
`Farce::Queue` | 2.2x slower |
|
|
109
|
+
`RactorSafe::Queue` | 2.8x slower | only allows sharable objects
|
|
110
|
+
`Ratomic::Queue` | 12x slower | breaks ractor isolation
|
|
111
|
+
`RactorQueue` | 29x slower | breaks ractor isolation
|
|
112
|
+
`Ractor::Port` (multiplexing) | 110x slower |
|
|
111
113
|
|
|
112
114
|
## Priority queue performance
|
|
113
115
|
|
|
@@ -126,14 +128,14 @@ The below numbers compare non-blocking APIs, as only Farce implements a blocking
|
|
|
126
128
|
<th>↓ Gem</th>
|
|
127
129
|
<th>Insertion order →</th>
|
|
128
130
|
<th>Random</th>
|
|
129
|
-
<th>
|
|
131
|
+
<th>Sorted</th>
|
|
130
132
|
<th>Random</th>
|
|
131
|
-
<th>
|
|
133
|
+
<th>Sorted</th>
|
|
132
134
|
</tr>
|
|
133
135
|
</thead>
|
|
134
136
|
<tbody>
|
|
135
137
|
<tr>
|
|
136
|
-
<td colspan="2">farce 0.1
|
|
138
|
+
<td colspan="2">farce 0.4.1</td>
|
|
137
139
|
<td>fastest</td>
|
|
138
140
|
<td>fastest</td>
|
|
139
141
|
<td>fastest</td>
|
|
@@ -141,43 +143,57 @@ The below numbers compare non-blocking APIs, as only Farce implements a blocking
|
|
|
141
143
|
</tr>
|
|
142
144
|
<tr>
|
|
143
145
|
<td colspan="2"><a href="https://github.com/mame/rbtree">rbtree</a> 0.4.7</td>
|
|
144
|
-
<td>1.
|
|
145
|
-
<td>
|
|
146
|
+
<td>1.6x slower</td>
|
|
147
|
+
<td>2.9x slower</td>
|
|
146
148
|
<td>–</td>
|
|
147
149
|
<td>–</td>
|
|
148
150
|
</tr>
|
|
149
151
|
<tr>
|
|
150
152
|
<td colspan="2"><a href="https://github.com/boborbt/priority_queue_cxx">priority_queue_cxx</a> 0.3.7</td>
|
|
151
|
-
<td>
|
|
152
|
-
<td>
|
|
153
|
+
<td>2.7x slower</td>
|
|
154
|
+
<td>4.0x slower</td>
|
|
153
155
|
<td>–</td>
|
|
154
156
|
<td>–</td>
|
|
155
157
|
</tr>
|
|
156
158
|
<tr>
|
|
157
|
-
<td colspan="2"><a href="https://github.com/
|
|
158
|
-
<td>
|
|
159
|
-
<td>
|
|
159
|
+
<td colspan="2"><a href="https://github.com/mhib/pairing_heap">pairing_heap</a> 3.1.2 (simple)</td>
|
|
160
|
+
<td>3.0x slower</td>
|
|
161
|
+
<td>1.3x slower</td>
|
|
162
|
+
<td>2.3x slower</td>
|
|
163
|
+
<td>1.1x slower</td>
|
|
164
|
+
</tr>
|
|
165
|
+
<tr>
|
|
166
|
+
<td colspan="2"><a href="https://github.com/socketry/io-event">io-event</a> 1.22.3</td>
|
|
167
|
+
<td>4.5x slower</td>
|
|
168
|
+
<td>9.6x slower</td>
|
|
160
169
|
<td>1.3x slower</td>
|
|
161
170
|
<td>1.7x slower</td>
|
|
162
171
|
</tr>
|
|
172
|
+
<tr>
|
|
173
|
+
<td colspan="2"><a href="https://github.com/mhib/pairing_heap">pairing_heap</a> 3.1.2</td>
|
|
174
|
+
<td>5.6x slower</td>
|
|
175
|
+
<td>4.1x slower</td>
|
|
176
|
+
<td>3.6x slower</td>
|
|
177
|
+
<td>1.9x slower</td>
|
|
178
|
+
</tr>
|
|
163
179
|
<tr>
|
|
164
180
|
<td colspan="2"><a href="https://github.com/rubyworks/pqueue">pqueue</a> 2.2.0</td>
|
|
165
|
-
<td>
|
|
166
|
-
<td>
|
|
181
|
+
<td>15.1x slower</td>
|
|
182
|
+
<td>19.7x slower</td>
|
|
167
183
|
<td>2.2x slower</td>
|
|
168
184
|
<td>1.9x slower</td>
|
|
169
185
|
</tr>
|
|
170
186
|
<tr>
|
|
171
187
|
<td colspan="2"><a href="https://github.com/matiasbattocchia/lazy-priority-queue">lazy_priority_queue</a> 0.1.1</td>
|
|
172
188
|
<td>9.9x slower</td>
|
|
173
|
-
<td>
|
|
189
|
+
<td>20x slower</td>
|
|
174
190
|
<td>3.9x slower</td>
|
|
175
191
|
<td>3.2x slower</td>
|
|
176
192
|
</tr>
|
|
177
193
|
<tr>
|
|
178
194
|
<td colspan="2"><a href="https://github.com/philiprehberger/rb-priority-queue">philiprehberger-priority_queue</a> 0.5.0</td>
|
|
179
195
|
<td>13x slower</td>
|
|
180
|
-
<td>
|
|
196
|
+
<td>48x slower</td>
|
|
181
197
|
<td>7.6x slower</td>
|
|
182
198
|
<td>11x slower</td>
|
|
183
199
|
</tr>
|
data/docs/compatibility.md
CHANGED
|
@@ -69,6 +69,19 @@ This means:
|
|
|
69
69
|
* `Kernel.instance_method(:frozen?).bind_call(object)` might report a different value from `object.frozen?`
|
|
70
70
|
* C-level checks for frozen state, such as `RB_OBJ_FROZEN`, might differ from what Ruby-level methods report.
|
|
71
71
|
|
|
72
|
+
### Fiber schedulers and Ractors don't play nice on Ruby 3.4
|
|
73
|
+
|
|
74
|
+
Fiber schedulers running in a non-main Ractor have a chance of causing a segmentation fault on x86_64 Linux with Ruby 3.4.11 and earlier.
|
|
75
|
+
|
|
76
|
+
This issue is [unrelated to Farce itself](https://gist.github.com/rkh/f330bd5c4accc9718f5ca227ae020c94) and typically only surfaces irregularly under a high number of Ractor shutdowns.
|
|
77
|
+
It does however surface from time to time when running the Farce test suite on affected Ruby versions and platforms.
|
|
78
|
+
|
|
79
|
+
Installing and loading Farce, using Farce data structures, etc, does not increase the likelihood of encountering this issue.
|
|
80
|
+
|
|
81
|
+
### GC related tests are flaky in certain environments
|
|
82
|
+
|
|
83
|
+
Garbage collection related tests may exhibit flakiness on some Ruby versions and platforms. This can lead to unexpected failures when running the test suite.
|
|
84
|
+
|
|
72
85
|
## Read Next
|
|
73
86
|
|
|
74
87
|
[README](../README.md#features) · [Installation](installation.md) · [Integrations](integrations.md)
|
data/docs/data-structures.md
CHANGED
|
@@ -22,6 +22,7 @@ Farce provides a range of data structures that are ractor-safe, mutable, and exp
|
|
|
22
22
|
- [Priority Queues](#priority-queues)
|
|
23
23
|
- [Timer Queues](#timer-queues)
|
|
24
24
|
- [Queue capacity](#queue-capacity)
|
|
25
|
+
- [Publishers](#publishers)
|
|
25
26
|
- [Read Next](#read-next)
|
|
26
27
|
|
|
27
28
|
## Maps
|
|
@@ -308,6 +309,42 @@ queue.try_push(:c) # => false
|
|
|
308
309
|
Normal queues have a default capacity of 1024. You can explicitly set the capacity to `nil` to create an unbounded queue.
|
|
309
310
|
Priority queues and timer queues do not have a default capacity, so earlier entries are not getting blocked unexpectedly.
|
|
310
311
|
|
|
312
|
+
## Publishers
|
|
313
|
+
|
|
314
|
+
Both ports and queues deliver messages exactly ones. Multiple receivers can read from the same port (if they are running inside the same ractor) or queue, but each message will only be delivered once.
|
|
315
|
+
|
|
316
|
+
A publisher allows one-to-many communication, where a single publisher can broadcast messages to multiple subscribers. Published messages are sent to enabled subscribers.
|
|
317
|
+
|
|
318
|
+
Subscribers can be:
|
|
319
|
+
|
|
320
|
+
* Ractors, ports, queues, or other publishers, using their respective message sending mechanisms.
|
|
321
|
+
* A [vector](#vectors) or [set](#sets), in which case each message will be added to the collection.
|
|
322
|
+
* A [counter](#counters), incrementing it for each message received. This avoids potential copying of the message itself.
|
|
323
|
+
* A [flag](#flags), setting it to true whenever a message is received. This avoids potential copying of the message itself.
|
|
324
|
+
* A `Farce::Concurrent::Event`, setting it for each message received.
|
|
325
|
+
* A `Farce::Concurrent::CountDownLatch`, counting down for each message received.
|
|
326
|
+
* A callback, executed for each message received.
|
|
327
|
+
|
|
328
|
+
For unshared and local publisher [variants](variants-and-scopes.md) subscribers may also include:
|
|
329
|
+
|
|
330
|
+
* Any unshared variant of the subscriber list above.
|
|
331
|
+
* Ruby's `Thread::Queue` or `Thread::SizedQueue`
|
|
332
|
+
* `Concurrent::AtomicReference`, `Concurrent::AtomicFixnum`, `Concurrent::AtomicBoolean`, `Concurrent::Event`, and `Concurrent::CountDownLatch` if the [concurrent-ruby integration](integrations.md#concurrent-ruby) is active.
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
publisher = Farce::Publisher.new
|
|
336
|
+
counter = Farce::Counter.new
|
|
337
|
+
|
|
338
|
+
publisher.subscribe do |message|
|
|
339
|
+
puts "Received: #{message}"
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
publisher.publish(:hello)
|
|
343
|
+
publisher.publish(:world)
|
|
344
|
+
|
|
345
|
+
counter.to_i # => 2
|
|
346
|
+
```
|
|
347
|
+
|
|
311
348
|
## Read Next
|
|
312
349
|
|
|
313
350
|
[README](../README.md#features) · [Sharing Unshareable Data](sharing.md) · [Concurrency](concurrency.md) · [Variants and Scopes](variants-and-scopes.md)
|
data/docs/integrations.md
CHANGED
|
@@ -82,6 +82,7 @@ The integration adds the following features if [concurrent-ruby](https://github.
|
|
|
82
82
|
|
|
83
83
|
* [Transaction support for `Concurrent::TVar`](concurrency.md#tvars)
|
|
84
84
|
* Automatic conversion of `Concurrent::Map` instances to [maps](data-structures.md#maps) via [`Farce.enfarce`](utilities.md#top-level-methods).
|
|
85
|
+
* [Subscriber support](data-structures.md#publishers) for `Concurrent::AtomicReference`, `Concurrent::AtomicFixnum`, `Concurrent::AtomicBoolean`, `Concurrent::Event`, and `Concurrent::CountDownLatch` with unshared and local publishers.
|
|
85
86
|
|
|
86
87
|
## Ractor Sharing
|
|
87
88
|
|
data/docs/introduction.md
CHANGED
|
@@ -137,6 +137,25 @@ Both the Ruby core library, and other libraries, like the very popular [concurre
|
|
|
137
137
|
<td>❌ <b>not supported</b></td>
|
|
138
138
|
<td>⚠️ CRuby only</td>
|
|
139
139
|
</tr>
|
|
140
|
+
<tr>
|
|
141
|
+
<td rowspan="3"><a href="https://github.com/joshuay03/atomic-ruby">atomic-ruby</a></td>
|
|
142
|
+
<td>AtomicQueue, AtomicThreadPool</td>
|
|
143
|
+
<td>⚠️ CRuby only</td>
|
|
144
|
+
<td>⚠️ CRuby only</td>
|
|
145
|
+
<td>❌ <b>not supported</b></td>
|
|
146
|
+
</tr>
|
|
147
|
+
<tr>
|
|
148
|
+
<td>Atom, AtomicCountDownLatch, AtomicBoolean</td>
|
|
149
|
+
<td>⚠️ CRuby only<b>¹</b></td>
|
|
150
|
+
<td>⚠️ CRuby only<b>¹</b></td>
|
|
151
|
+
<td>⚠️ CRuby only<b>¹</b></td>
|
|
152
|
+
</tr>
|
|
153
|
+
<tr>
|
|
154
|
+
<td>AtomicConditionVariable</td>
|
|
155
|
+
<td>⚠️ CRuby only</td>
|
|
156
|
+
<td>❌ <b>not supported</b></td>
|
|
157
|
+
<td>❌ <b>not supported</b></td>
|
|
158
|
+
</tr>
|
|
140
159
|
</tbody>
|
|
141
160
|
</table>
|
|
142
161
|
|
data/docs/modes.md
CHANGED
|
@@ -390,8 +390,10 @@ The same choices appear in several Farce APIs. Containers normally default to `:
|
|
|
390
390
|
| `Farce::Exchanger` | `new`, `exchange` | The value offered to a partner. |
|
|
391
391
|
| `Farce::WeakAtom` | `new`, `store`, `update`, and other replacement operations | Weakly held values. Supports only `:raise`, `:make_shareable`, and `:dedup`. Defaults to `:raise`. |
|
|
392
392
|
| `Farce::Atom` | `new`, `store`, `swap`, `update`, and other replacement operations | The stored value. |
|
|
393
|
+
| `Farce::Memoize` | `memoize`, `[]` | Memoized results with `scope: :global` (the default). With `weak: true`, supports only `:raise`, `:make_shareable`, and `:dedup`, and defaults to `:raise`. Other scopes ignore the mode. |
|
|
393
394
|
| `Farce::Lazy`, `Farce::LazyRef` | `new` | The result computed once by the factory. |
|
|
394
395
|
| `Farce::Task` | `new`, `schedule`, `try_schedule`, `in_parallel`, `on_main`, `add_argument` | Arguments and the successful result. `add_argument` overrides the mode for one argument. Scheduling can select local mode automatically. |
|
|
396
|
+
| `Farce::Publisher`, `Farce::Unshared::Publisher`, `Farce::Local::Publisher` | `subscribe` with a block | Callback arguments and queued payloads. Defaults to `:smart` for `Publisher` and `:local` for its Unshared and Local variants. |
|
|
395
397
|
| `Farce::Molecule` | `define`, `new` | Newly created field atoms. |
|
|
396
398
|
| `Farce::Vector` | `new`, `push`, `store`, `update`, and other replacement operations | Element values. |
|
|
397
399
|
| `Farce::WeakSet` | `new`, `add`, `add?` | Weakly held elements. Supports only `:raise`, `:make_shareable`, and `:dedup`. Defaults to `:raise`. |
|
data/docs/scheduling.md
CHANGED
|
@@ -43,7 +43,7 @@ sleep 1.1
|
|
|
43
43
|
|
|
44
44
|
As you can see in the [shared-state example](introduction.md#example), creating Ractors ad hoc because you want to run something in parallel is quite expensive.
|
|
45
45
|
|
|
46
|
-
Use `Farce.in_parallel` to schedule parallel work. On CRuby, it manages a shared [Ractor pool](#ractor-pools), starting workers when the current ones cannot keep up with the load, up to the number of CPU cores. It shuts unused workers down after some inactivity. On JRuby and TruffleRuby, it starts a new thread for each task.
|
|
46
|
+
Use `Farce.in_parallel` to schedule parallel work. On CRuby, it manages a shared [Ractor pool](#ractor-pools), starting workers when the current ones cannot keep up with the load, up to the number of CPU cores by default. It shuts unused workers down after some inactivity. On JRuby and TruffleRuby, it starts a new thread for each task.
|
|
47
47
|
|
|
48
48
|
```ruby
|
|
49
49
|
input = "this is my input"
|
|
@@ -52,6 +52,21 @@ Farce.in_parallel(input) { expensive_computation(it) }
|
|
|
52
52
|
|
|
53
53
|
Like most of Farce, this is completely opt-in. If you never use this feature, no pool is being set up and no extra Ractors are created.
|
|
54
54
|
|
|
55
|
+
Configure the CRuby pool before creating any scheduler:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
Farce.config do |config|
|
|
59
|
+
config.parallel_pool_min_size = 0
|
|
60
|
+
config.parallel_pool_max_size = 8
|
|
61
|
+
config.parallel_pool_max_inflight = 64
|
|
62
|
+
config.parallel_pool_grow_after = 0.005
|
|
63
|
+
config.parallel_pool_shrink_after = 30
|
|
64
|
+
config.parallel_pool_capacity = 1024
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
These are the defaults, except `parallel_pool_max_size` defaults to `nil` to use the CPU count. Set `parallel_pool_capacity` to `nil` for an unbounded queue, `parallel_pool_max_inflight` to `nil` to disable the per-worker task limit, or `parallel_pool_shrink_after` to `nil` to retain idle workers. A positive `parallel_pool_min_size` starts workers when the pool is created. These options are ignored on JRuby and TruffleRuby.
|
|
69
|
+
|
|
55
70
|
## `Farce.schedule`
|
|
56
71
|
|
|
57
72
|
`Farce.in_parallel` might not always be what you want. Maybe you are working on a library and don't want to dictate a concurrency model?
|
|
@@ -98,6 +113,23 @@ Scheduler instances are Ractor-shareable.
|
|
|
98
113
|
|
|
99
114
|
`Farce::Pool` implements the same scheduling interface, but manages a pool of Ractors. This is what [`in_parallel`](#farcein_parallel) uses under the hood.
|
|
100
115
|
|
|
116
|
+
You can change a pool's settings while it runs:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
pool = Farce::Pool.new
|
|
120
|
+
pool.max_size = 8
|
|
121
|
+
pool.min_size = 2
|
|
122
|
+
pool.max_inflight = 32
|
|
123
|
+
pool.grow_after = 0.01
|
|
124
|
+
pool.shrink_after = 10
|
|
125
|
+
pool.capacity = 2048
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Increasing `min_size` starts workers immediately. Lowering `max_size` retires excess workers as they become idle. Busy workers continue servicing queued work so running tasks can wait for tasks queued on the same pool. The size can remain above the new maximum while workers stay busy. Running tasks are never interrupted. `min_size` must not exceed `max_size`. Changes to growth and idle delays recheck queued work and restart idle waits. Setting `max_inflight` or `shrink_after` to `nil` disables that limit.
|
|
129
|
+
|
|
130
|
+
On CRuby, these settings are also available through `Farce.in_parallel`, such as `Farce.in_parallel.max_size = 8`. Its initial values come from `Farce::Config`. `Farce::Unshared::Pool` supports the same setters. Changes to `Farce::Local::Pool` affect only the current scope. New scopes retain the constructor settings.
|
|
131
|
+
|
|
132
|
+
|
|
101
133
|
```ruby
|
|
102
134
|
# At least four Ractors, up to ten. Launch a new one if a task waits longer than 100 milliseconds.
|
|
103
135
|
pool = Farce::Pool.new(min_size: 4, max_size: 10, grow_after: 0.1)
|
|
@@ -167,9 +199,9 @@ Farce.in_parallel do
|
|
|
167
199
|
end
|
|
168
200
|
```
|
|
169
201
|
|
|
170
|
-
If a string or symbol is provided (like `carbon_fiber`), it will first resolve this to a constant (i.e., `CarbonFiber`). If that constant is a class, it will use that for creating the fiber scheduler. If it is a module, it will look for a `Scheduler` constant inside it, which matches the established pattern implemented by most gems, including `farce`, `async`, `carbon_fiber`, `libev_scheduler` (use `libev` as value), `
|
|
202
|
+
If a string or symbol is provided (like `carbon_fiber`), it will first resolve this to a constant (i.e., `CarbonFiber`). If that constant is a class, it will use that for creating the fiber scheduler. If it is a module, it will look for a `Scheduler` constant inside it, which matches the established pattern implemented by most gems, including `farce`, `async`, `carbon_fiber`, `libev_scheduler` (use `libev` as value), `itsi-scheduler` (use `itsi` as value), but it also works for gems that define their scheduler class at top level, like `fiber_scheduler`.
|
|
171
203
|
|
|
172
|
-
Note that most of these are outdated and don't work with any recent Ruby version, with the notable exception of `async` and `
|
|
204
|
+
Note that most of these are outdated and don't work with any recent Ruby version, with the notable exception of `async`, `carbon_fiber`, and `itsi-scheduler`.
|
|
173
205
|
|
|
174
206
|
## Read Next
|
|
175
207
|
|
data/docs/scopes.md
CHANGED
|
@@ -238,7 +238,7 @@ Pass the needed data as task arguments when crossing an execution boundary. Use
|
|
|
238
238
|
|
|
239
239
|
## Classes that support scopes
|
|
240
240
|
|
|
241
|
-
The classes in this table accept the same five scopes. The scope determines which backing container or resource an operation uses. Each class retains its own behavior, such as queue ordering or weak references.
|
|
241
|
+
The classes and modules in this table accept the same five scopes. The scope determines which backing container or resource an operation uses. Each class retains its own behavior, such as queue ordering or weak references.
|
|
242
242
|
|
|
243
243
|
| Class | What each scope gets |
|
|
244
244
|
| --- | --- |
|
|
@@ -259,8 +259,10 @@ The classes in this table accept the same five scopes. The scope determines whic
|
|
|
259
259
|
| `Farce::Local::WeakSet` | A set that retains its elements weakly. |
|
|
260
260
|
| `Farce::Local::Vector` | An indexed collection. |
|
|
261
261
|
| `Farce::Local::Queue` | A FIFO queue with independent capacity and lifecycle. |
|
|
262
|
+
| `Farce::Local::Publisher` | An independent set of subscriptions. |
|
|
262
263
|
| `Farce::Local::PriorityQueue` | A queue ordered by priority. |
|
|
263
264
|
| `Farce::Local::TimerQueue` | A queue whose values become available at scheduled times. |
|
|
265
|
+
| `Farce::Memoize` | A cached result per instance and method. Also accepts `:global` (the default) for a shared cache or `:unshared` for a cache within one Ractor. |
|
|
264
266
|
| `Farce::Local::Lazy` | A value computed on first access in that scope. |
|
|
265
267
|
| `Farce::Local::LazyRef` | A reference that delegates to a scoped lazy value. |
|
|
266
268
|
| `Farce::Local::Lease` | An independently initialized resource to borrow. |
|
data/docs/shims.md
CHANGED
|
@@ -139,6 +139,59 @@ Farce implements a version based on weak [atoms](data-structures.md#atoms). Not
|
|
|
139
139
|
|
|
140
140
|
`Farce::Resolv` is a version of Ruby's [`Resolv`](https://docs.ruby-lang.org/en/4.0/Resolv.html), using [counters](data-structures.md#counters) instead of a global instance variable, and can therefore be used outside the main Ractor.
|
|
141
141
|
|
|
142
|
+
## Concurrent
|
|
143
|
+
|
|
144
|
+
`Farce::Concurrent` includes the following classes:
|
|
145
|
+
|
|
146
|
+
Class | Description
|
|
147
|
+
------------------|---------------------------------------------------------------
|
|
148
|
+
`Array` | Alias for `Farce::Vector`
|
|
149
|
+
`Atom` | Alias for `Farce::Atom`
|
|
150
|
+
`AtomicBoolean` | Alias for `Farce::Flag`
|
|
151
|
+
`AtomicFixnum` | Alias for `Farce::Counter`
|
|
152
|
+
`CountDownLatch` | Drop-in replacement for `concurrent-ruby`'s `CountDownLatch`
|
|
153
|
+
`CyclicBarrier` | Replacement for `concurrent-ruby`'s `CyclicBarrier`
|
|
154
|
+
`Event` | Drop-in replacement for `concurrent-ruby`'s `Event`
|
|
155
|
+
`Exchanger` | Alias for `Farce::Exchanger`
|
|
156
|
+
`FiberLocalVar` | Drop-in replacement for `concurrent-ruby`'s `FiberLocalVar`
|
|
157
|
+
`Hash` | Alias for `Farce::Map`
|
|
158
|
+
`Lock` | Alias for `Farce::Lock`
|
|
159
|
+
`LockLocalVar` | Drop-in replacement for `concurrent-ruby`'s `LockLocalVar`
|
|
160
|
+
`ReadWriteLock` | Alias for `Farce::ReadWriteLock`
|
|
161
|
+
`ThreadLocalVar` | Drop-in replacement for `concurrent-ruby`'s `ThreadLocalVar`
|
|
162
|
+
`Tuple` | Replacement for `concurrent-ruby`'s `Tuple`
|
|
163
|
+
|
|
164
|
+
The nice thing is that these are either always Ractor-shareable, or in the case of `FiberLocalVar` and `ThreadLocalVar`, are Ractor-shareable if their default values or blocks are.
|
|
165
|
+
|
|
166
|
+
For instance, you can replace threads with ractors in concurrent-ruby's own `CountDownLatch` example:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
latch = Farce::Concurrent::CountDownLatch.new(3)
|
|
170
|
+
|
|
171
|
+
waiter = Ractor.new(latch) do |latch|
|
|
172
|
+
latch.wait
|
|
173
|
+
puts "Waiter released"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
decrementer = Ractor.new(latch) do |latch|
|
|
177
|
+
sleep 1
|
|
178
|
+
latch.count_down
|
|
179
|
+
puts latch.count
|
|
180
|
+
|
|
181
|
+
sleep 1
|
|
182
|
+
latch.count_down
|
|
183
|
+
puts latch.count
|
|
184
|
+
|
|
185
|
+
sleep 1
|
|
186
|
+
latch.count_down
|
|
187
|
+
puts latch.count
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
waiter.join
|
|
191
|
+
decrementer.join
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
|
|
142
195
|
## Read Next
|
|
143
196
|
|
|
144
197
|
[README](../README.md#features) · [Sharing Unshareable Data](sharing.md) · [Scheduling Code](scheduling.md)
|
data/docs/utilities.md
CHANGED
|
@@ -81,6 +81,37 @@ And also mixins to prevent them from being shareable:
|
|
|
81
81
|
|
|
82
82
|
`Unshareable::Copyable` and `Unshareable::Movable` may be combined.
|
|
83
83
|
|
|
84
|
+
## Memoization
|
|
85
|
+
|
|
86
|
+
`Farce::Memoize` offers Ractor-friendly memoization:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
class MyClass
|
|
90
|
+
include Farce::Memoize
|
|
91
|
+
|
|
92
|
+
# Memoize #expensive_method, shared across Ractors
|
|
93
|
+
memoize def expensive_method = expensive_computation
|
|
94
|
+
|
|
95
|
+
# Memoize #method_with_unsharable_value for each Ractor separately
|
|
96
|
+
def method_with_unsharable_value = unsharable_computation
|
|
97
|
+
memoize :method_with_unsharable_value, scope: :ractor
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
# The memoized method will also be private
|
|
102
|
+
memoize def private_method = expensive_computation
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Or as a one-liner:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
class MyClass
|
|
110
|
+
# Memoize return values of #foo and #bar until they get garbage collected
|
|
111
|
+
include Farce::Memoize[:foo, :bar, weak: true]
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
84
115
|
## Constants
|
|
85
116
|
|
|
86
117
|
* `Farce::MODES`: List of supported [sharing modes](sharing.md#sharing-modes).
|
data/docs/variants.md
CHANGED
|
@@ -201,6 +201,15 @@ end
|
|
|
201
201
|
<td align="center" style="text-align: center">🔗</td> <!-- Unsafe -->
|
|
202
202
|
<td align="center" style="text-align: center">➖</td> <!-- Transaction -->
|
|
203
203
|
</tr>
|
|
204
|
+
<tr>
|
|
205
|
+
<td><tt>Publisher</tt></td>
|
|
206
|
+
<td align="center" style="text-align: center">✅</td> <!-- Farce -->
|
|
207
|
+
<td align="center" style="text-align: center">✅</td> <!-- Strict -->
|
|
208
|
+
<td align="center" style="text-align: center">✅</td> <!-- Local -->
|
|
209
|
+
<td align="center" style="text-align: center">✅</td> <!-- Unshared -->
|
|
210
|
+
<td align="center" style="text-align: center">🔗</td> <!-- Unsafe -->
|
|
211
|
+
<td align="center" style="text-align: center">➖</td> <!-- Transaction -->
|
|
212
|
+
</tr>
|
|
204
213
|
<tr>
|
|
205
214
|
<td><tt>Queue</tt></td>
|
|
206
215
|
<td align="center" style="text-align: center">✅</td> <!-- Farce -->
|
data/ext/farce/farce.c
CHANGED
|
@@ -306,6 +306,14 @@ containers_raise_key_error(VALUE receiver, VALUE key)
|
|
|
306
306
|
rb_exc_raise(exception);
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
static VALUE
|
|
310
|
+
farce_value_changed(VALUE namespace, VALUE current, VALUE expected, VALUE identity)
|
|
311
|
+
{
|
|
312
|
+
if (current == expected) return Qfalse;
|
|
313
|
+
if (RTEST(identity)) return identity;
|
|
314
|
+
return RTEST(rb_equal(current, expected)) ? Qfalse : Qtrue;
|
|
315
|
+
}
|
|
316
|
+
|
|
309
317
|
static VALUE
|
|
310
318
|
farce_futex_supported(VALUE namespace)
|
|
311
319
|
{
|
|
@@ -336,6 +344,7 @@ Init_farce(void)
|
|
|
336
344
|
VALUE mInternal = rb_const_get(mFarce, rb_intern("Internal"));
|
|
337
345
|
VALUE singleton = rb_singleton_class(mInternal);
|
|
338
346
|
eIsolationError = rb_const_get(rb_cRactor, rb_intern("IsolationError"));
|
|
347
|
+
rb_define_singleton_method(mInternal, "value_changed?", farce_value_changed, 3);
|
|
339
348
|
#if RUBY_API_VERSION_CODE == 30400
|
|
340
349
|
rb_define_singleton_method(mInternal, "lock_native_thread", farce_lock_native_thread, 0);
|
|
341
350
|
#endif
|
data/ext/farce/map.c
CHANGED
|
@@ -1417,9 +1417,15 @@ map_modify_body(VALUE opaque)
|
|
|
1417
1417
|
}
|
|
1418
1418
|
|
|
1419
1419
|
static VALUE
|
|
1420
|
-
map_modify(VALUE
|
|
1420
|
+
map_modify(int argc, VALUE *argv, VALUE self)
|
|
1421
1421
|
{
|
|
1422
1422
|
rb_check_frozen(self);
|
|
1423
|
+
VALUE key;
|
|
1424
|
+
VALUE keywords = Qnil;
|
|
1425
|
+
VALUE keyword_values[1] = {Qundef};
|
|
1426
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
1427
|
+
rb_scan_args(argc, argv, "1:", &key, &keywords);
|
|
1428
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
1423
1429
|
rb_need_block();
|
|
1424
1430
|
map_t *map = get_map(self);
|
|
1425
1431
|
key = map_prepare_key(map, key);
|
|
@@ -1432,7 +1438,12 @@ map_modify(VALUE self, VALUE key)
|
|
|
1432
1438
|
};
|
|
1433
1439
|
operation.hash = map_key_hash(map, key);
|
|
1434
1440
|
rb_check_frozen(self);
|
|
1435
|
-
|
|
1441
|
+
map_timeout_t timeout = map_parse_timeout(
|
|
1442
|
+
keyword_values[0] == Qundef ? Qnil : keyword_values[0]
|
|
1443
|
+
);
|
|
1444
|
+
if (!map_lock_for_key_with_timeout(
|
|
1445
|
+
map, key, operation.hash, &timeout, &operation.execution
|
|
1446
|
+
)) return Qfalse;
|
|
1436
1447
|
bool found;
|
|
1437
1448
|
size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
|
|
1438
1449
|
if (RB_OBJ_FROZEN(self)) {
|
|
@@ -2151,7 +2162,7 @@ map_define_methods(VALUE klass)
|
|
|
2151
2162
|
rb_define_method(klass, "store", map_store_with_timeout, -1);
|
|
2152
2163
|
rb_define_method(klass, "swap", map_swap, -1);
|
|
2153
2164
|
rb_define_method(klass, "update", map_update, -1);
|
|
2154
|
-
rb_define_method(klass, "modify", map_modify, 1);
|
|
2165
|
+
rb_define_method(klass, "modify", map_modify, -1);
|
|
2155
2166
|
rb_define_method(klass, "wait_until_changed", map_wait_until_changed, -1);
|
|
2156
2167
|
rb_define_method(klass, "wait_until_non_nil", map_wait_until_non_nil, -1);
|
|
2157
2168
|
rb_define_method(klass, "store_if_absent", map_store_if_absent, -1);
|