farce 0.3.0-arm-linux-gnu → 0.4.0-arm-linux-gnu
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 +44 -0
- data/README.md +117 -1489
- data/docs/compatibility.md +74 -0
- data/docs/concurrency.md +151 -0
- data/docs/data-structures.md +313 -0
- data/docs/installation.md +63 -0
- data/docs/integrations.md +124 -0
- data/docs/introduction.md +266 -0
- data/docs/scheduling.md +176 -0
- data/docs/sharing.md +211 -0
- data/docs/shims.md +144 -0
- data/docs/utilities.md +93 -0
- data/docs/variants-and-scopes.md +92 -0
- data/lib/farce/abstract/flag.rb +6 -0
- data/lib/farce/backports.rb +274 -0
- data/lib/farce/engine/jruby/fiber_scheduler.rb +3 -0
- data/lib/farce/engine/jruby/scheduler_synchronization.rb +87 -0
- data/lib/farce/engine/ruby/3.4/null_port.rb +21 -0
- data/lib/farce/engine/ruby/3.4/patch.rb +67 -0
- data/lib/farce/engine/ruby/3.4/port.rb +13 -12
- data/lib/farce/engine/ruby/3.4/ractor_methods.rb +0 -1
- data/lib/farce/engine/ruby/4.0/null_port.rb +36 -0
- data/lib/farce/engine/ruby/4.0/port.rb +1 -2
- data/lib/farce/engine/ruby/4.1/null_port.rb +5 -0
- data/lib/farce/engine/ruby.rb +7 -0
- data/lib/farce/engine/shared/null_port.rb +18 -0
- data/lib/farce/engine/shared/port.rb +41 -3
- data/lib/farce/engine/shared/queue.rb +4 -4
- data/lib/farce/engine/shared/ractor_methods.rb +2 -2
- data/lib/farce/engine/shared/ractor_selector.rb +73 -0
- data/lib/farce/extension.rb +150 -0
- data/lib/farce/integrations/ractor_tmvar.rb +4 -1
- data/lib/farce/internal/fake_ractor.rb +10 -2
- data/lib/farce/internal/scheduler_lifecycle.rb +7 -4
- data/lib/farce/null_port.rb +12 -0
- data/lib/farce/system.rb +10 -2
- data/lib/farce/version.rb +1 -1
- data/lib/farce.rb +1 -1
- metadata +22 -1
data/docs/sharing.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
# @title Sharing Unshareable Data
|
|
3
|
+
-->
|
|
4
|
+
|
|
5
|
+
# Sharing Unshareable Data
|
|
6
|
+
|
|
7
|
+
Ruby's `Ractor::Port#send` and related methods share Ruby objects between Ractors with this logic:
|
|
8
|
+
|
|
9
|
+
1. If the object is Ractor shareable, pass it by reference.
|
|
10
|
+
2. If the object isn't Ractor shareable and the `move` option hasn't been set to `true`, pass it by value (copy it).
|
|
11
|
+
3. Otherwise move it from the sending Ractor to the receiving Ractor, invalidating any previous references.
|
|
12
|
+
|
|
13
|
+
This is a great start but has some complications:
|
|
14
|
+
* Copying can lead to a lot of data duplication, as large object trees may be copied between Ractors over and over again.
|
|
15
|
+
* Moving may invalidate nested references unexpectedly, breaking code in the sending Ractor.
|
|
16
|
+
* If you want to prevent sending unshareable objects to other Ractors, you have to manually check them every time.
|
|
17
|
+
|
|
18
|
+
Farce has mechanisms and tools to improve the situation.
|
|
19
|
+
|
|
20
|
+
## Sharing Modes
|
|
21
|
+
|
|
22
|
+
> [!TIP]
|
|
23
|
+
> Learn more in the dedicated [modes documentation](modes.md).
|
|
24
|
+
|
|
25
|
+
The [default variants](variants-and-scopes.md#variants-and-scopes) of most [data structures](data-structures.md#data-structures), as well as [`Farce::Port`](shims.md#port), implement a range of sharing modes, typically as a keyword argument for initialization as well as for methods that modify their content.
|
|
26
|
+
|
|
27
|
+
These modes are:
|
|
28
|
+
|
|
29
|
+
| Mode | What happens to non-shareable data | A typical use |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| `:smart` (default) | Applies smart rules or a dedicated proxy, raising for unsupported values. | Share supported values with less copying. |
|
|
32
|
+
| `:smart_raise` | Applies smart rules or a dedicated proxy, with a raising fallback. | Reject unsupported transfers. |
|
|
33
|
+
| `:smart_copy` | Applies smart rules or a dedicated proxy, then tries copying and Marshal. Default for `Farce::Port`. | Send values with a copying fallback. |
|
|
34
|
+
| `:smart_move` | Applies smart rules or a dedicated proxy, then tries moving. | Hand off unsupported values. |
|
|
35
|
+
| `:copy` | Transfers a copy and leaves the original usable. | Send a snapshot of a request. |
|
|
36
|
+
| `:marshal` | Serializes a snapshot and reconstructs it once per receiving Ractor. | Transfer an Unshared container.|
|
|
37
|
+
| `:move` | Transfers ownership and makes the original inaccessible. | Hand a completed batch to a consumer. |
|
|
38
|
+
| `:local` | Keeps the same object in its originating Ractor. | Pass work between local threads or fibers. |
|
|
39
|
+
| `:make_shareable` | Calls `Ractor.make_shareable` on the original. | Publish finished configuration. |
|
|
40
|
+
| `:mutable` | Copies non-shareable objects into a [`Farce::Mutable`](#mutables). | Synchronize mutations across Ractors. |
|
|
41
|
+
| `:shareable_copy` | Makes a shareable copy and leaves the original alone. | Publish a snapshot of an editable document. |
|
|
42
|
+
| `:dedup` | Deduplicates the value, then makes it shareable. May update and freeze the original. | Reuse repeated message contents. |
|
|
43
|
+
| `:proxy` | Creates a [`Farce::Proxy`](#proxies) that executes calls in the original Ractor. | Share access to a mutable object. |
|
|
44
|
+
| `:raise` | Raises `Ractor::IsolationError`. | Enforce a shareable-data boundary. |
|
|
45
|
+
|
|
46
|
+
Here's an example:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
map = Farce::Map.new(mode: :mutable)
|
|
50
|
+
map[:content] = "Hi there!"
|
|
51
|
+
|
|
52
|
+
Ractor.new(map) { it[:content] << " How are you doing?" }.join
|
|
53
|
+
|
|
54
|
+
# Hi there! How are you doing?
|
|
55
|
+
puts map[:content]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Envelopes
|
|
59
|
+
|
|
60
|
+
Farce allows you to explicitly wrap an object in an envelope. This is useful if you want to pass a value around between multiple Ractors (or multiple times between the same Ractors) without copying, wrapping, converting, or moving it every single time.
|
|
61
|
+
|
|
62
|
+
Instead, you can wrap it in an envelope, and then later explicitly retrieve the value again.
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
# We don't want to copy this over and over again.
|
|
66
|
+
big_array = 10_000.times.map { rand }
|
|
67
|
+
envelope = Farce::Envelope.new(big_array) # copied in once, copied out on demand
|
|
68
|
+
|
|
69
|
+
Ractor.new(envelope) do |envelope|
|
|
70
|
+
# we have a reference to envelope, but not a copy of its data
|
|
71
|
+
# this allows us to pass it on easily
|
|
72
|
+
queue = Farce::Queue.new(mode: :raise)
|
|
73
|
+
queue.push(envelope) # this is okay, the envelope is shareable
|
|
74
|
+
|
|
75
|
+
# We can pass the envelope by other means, like a queue!
|
|
76
|
+
Ractor.new(queue) do |queue|
|
|
77
|
+
message = queue.pop
|
|
78
|
+
# okay, let's get the copy, bypassing the outer ractor
|
|
79
|
+
copy_of_array = message.value
|
|
80
|
+
puts "Size of the array: #{copy_of_array.size}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Envelopes support `copy`, `move`, `local`, as well as a dummy envelope for wrapping shareable objects.
|
|
86
|
+
|
|
87
|
+
## Mutables
|
|
88
|
+
|
|
89
|
+
Mutables are special wrappers for Ruby objects that are Ractor-shareable when frozen but can be modified when not.
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
mutable = Farce::Mutable.new("content")
|
|
93
|
+
Ractor.new(mutable) { it << " & additional content" }.join
|
|
94
|
+
mutable # => #<Farce::Mutable[String] "content & additional content">
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
They expose a mutable API by keeping a frozen snapshot of the wrapped object, atomically unfreezing, mutating, and freezing it whenever a method otherwise throws a `FrozenError`. This means non-mutating methods have almost no additional cost, and in contrast to [proxies](#proxies), method calls do not have to be dispatched across Ractors, even when modifying the object. However, mutating methods will copy the wrapped object's content. This may be fine, but could be expensive when used repeatedly on large objects.
|
|
98
|
+
|
|
99
|
+
## Proxies
|
|
100
|
+
|
|
101
|
+
A proxy mimics the API of another object, executing method calls within the Ractor that created it. This is an easy way to have drop-in replacements for objects that cannot be shared across Ractors.
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
# Mutable arrays aren't Ractor-shareable
|
|
105
|
+
array = []
|
|
106
|
+
proxied = []
|
|
107
|
+
proxy = Farce::Proxy.new(proxied)
|
|
108
|
+
|
|
109
|
+
Farce::Ractor.new(array, proxy) do |*list|
|
|
110
|
+
list.each { it << 42 }
|
|
111
|
+
end.join
|
|
112
|
+
|
|
113
|
+
# The array got copied instead of being modified in place
|
|
114
|
+
array # => []
|
|
115
|
+
|
|
116
|
+
# The proxy didn't get copied
|
|
117
|
+
proxied # => [42]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Use this for very large objects, where copying the object would be more expensive than the Ractor-coordination overhead, or objects that can not be moved or copied between Ractors.
|
|
121
|
+
|
|
122
|
+
The downside is a dispatch to another Ractor on every method call.
|
|
123
|
+
If copying on writes is an option, consider [`Farce::Mutable`](#mutables) instead.
|
|
124
|
+
If the object may be moved across Ractors, maybe a [`Farce::Lease`](#leases) is a better option.
|
|
125
|
+
|
|
126
|
+
There is an advanced [customization API](https://rkh.github.io/farce/main/Farce/Proxy.html), which allows fine-tuning and reducing overhead.
|
|
127
|
+
|
|
128
|
+
## Mode Managers
|
|
129
|
+
|
|
130
|
+
You can use a mode manager if you want to support sharing modes for a custom object.
|
|
131
|
+
|
|
132
|
+
The mode manager might wrap objects in an envelope. It also exposes a method to unwrap objects again, which will only do so for envelopes created by the specific mode manager. That's why we were able to pass an envelope through the queue in the [example above](#envelopes) without it getting opened automatically.
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
manager = Farce::ModeManager.new(mode: :move)
|
|
136
|
+
payload = ["inside a mutable array"]
|
|
137
|
+
Ractor.shareable?(payload) # => false
|
|
138
|
+
|
|
139
|
+
shareable = manager.wrap(payload) # => #<Farce::Envelope::Move>
|
|
140
|
+
Ractor.shareable?(shareable) # => true
|
|
141
|
+
|
|
142
|
+
payload = shareable.value
|
|
143
|
+
Ractor.shareable?(payload) # => false
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Leases
|
|
147
|
+
|
|
148
|
+
You can think of a `Farce::Lease` as a reusable `move` envelope. Or [Haskell's MVar](https://hackage-content.haskell.org/package/base-4.22.0.0/docs/Control-Concurrent-MVar.html) if that's more your jam.
|
|
149
|
+
|
|
150
|
+
You can store any object in it, including non-shareable ones (if they are movable). A Ractor can check objects out (at which point they get moved into that Ractor), work with them, and move them back into the lease container. The checkout call will block while another Ractor holds the lease, serializing any operations.
|
|
151
|
+
|
|
152
|
+
This is very useful for objects that can be moved but can't be copied, especially IO-based objects like database connections.
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
file_lease = Farce::Lease.new { File.open("example.txt", "w") }
|
|
156
|
+
|
|
157
|
+
Ractor.new(file_lease) do |file_lease|
|
|
158
|
+
file_lease.checkout do |file|
|
|
159
|
+
file.puts "Written from another Ractor!"
|
|
160
|
+
end
|
|
161
|
+
end.join
|
|
162
|
+
|
|
163
|
+
# make sure we close the file
|
|
164
|
+
file_lease.checkout(&:close)
|
|
165
|
+
|
|
166
|
+
# dereference the lease so it can be garbage collected
|
|
167
|
+
file_lease = nil
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
However, this might not be enough for handling database connections, where you'd usually have a pool of connections. You can use `Farce::LeasePool` which will manage multiple objects, only blocking on `checkout` if all objects have been leased.
|
|
171
|
+
|
|
172
|
+
Moreover, it can generate these objects for you on demand (until a certain number has been reached).
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
# db connections are created on demand if there are fewer than five
|
|
176
|
+
pool = Farce::LeasePool.new(max_size: 5) { DB.connect }
|
|
177
|
+
pool.size # => 0
|
|
178
|
+
pool.max_size # => 5
|
|
179
|
+
|
|
180
|
+
pool.checkout do |db|
|
|
181
|
+
# ... do something with db ...
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Or, if you want to associate leasable values with keys, you can use a `LeaseMap`:
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
leases = Farce::LeaseMap.new { { primary: [], replica: [] } }
|
|
189
|
+
leases.checkout(:primary) { |items| items << :updated }
|
|
190
|
+
leases.checkout(:primary, &:dup) # => [:updated]
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
At first glance, this looks just like a [map](data-structures.md#maps) of `Lease` instances. And it pretty much is, except it has some nice tooling on top of it, where it can automatically check values out and back in:
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
leases = Farce::LeaseMap.new { { primary: [], replica: [] } }
|
|
197
|
+
|
|
198
|
+
leases.auto_lease do
|
|
199
|
+
leases[:primary] << :updated
|
|
200
|
+
leases[:replica] << :replicated
|
|
201
|
+
leases[:primary] << :verified # Reuses the same checkout
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Both resources are checked back in when the block exits, even on an exception.
|
|
205
|
+
leases.available?(:primary) # => true
|
|
206
|
+
leases.checkout(:primary, &:dup) # => [:updated, :verified]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Read Next
|
|
210
|
+
|
|
211
|
+
[README](../README.md#features) · [Transfer modes](modes.md) · [Data Structures](data-structures.md)
|
data/docs/shims.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
# @title Shims, Polyfills, and Extensions
|
|
3
|
+
-->
|
|
4
|
+
|
|
5
|
+
# Shims, Polyfills, and Extensions
|
|
6
|
+
|
|
7
|
+
Farce adapts Ruby APIs for use across Ractors and with Fiber schedulers. Its Ractor and Port implementations also provide fallbacks on Ruby engines without native Ractors.
|
|
8
|
+
|
|
9
|
+
## Ractor
|
|
10
|
+
|
|
11
|
+
Farce includes `Farce::Ractor`, which will either delegate to `Ractor` or supply a polyfill for it.
|
|
12
|
+
|
|
13
|
+
On platforms that don't support Ractors, a Thread-based implementation is provided that tracks Ractor-membership via Thread
|
|
14
|
+
groups.
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
ractor = Farce::Ractor.new do
|
|
18
|
+
value = receive
|
|
19
|
+
puts "Received #{value.inspect}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
ractor.send 42
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Port
|
|
26
|
+
|
|
27
|
+
Farce includes `Farce::Port`, which is either an extended subclass of `Ractor::Port` if it is available, or a polyfill if it isn't.
|
|
28
|
+
|
|
29
|
+
In addition to `Ractor::Port` it supports the following features:
|
|
30
|
+
* [Sharing modes](sharing.md#sharing-modes) both for `.new` and `#send`.
|
|
31
|
+
* Opt-in auto-local sharing: If the port belongs to the current Ractor, any object can be sent to it without copying or moving it.
|
|
32
|
+
* `#receive` supports a `timeout` option on all Ruby implementations and versions, not just CRuby 4.1+
|
|
33
|
+
* You can use `#owned?` to check if the current Ractor owns a port.
|
|
34
|
+
* `#receive` does not block a Fiber scheduler when called from a non-blocking Fiber.
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
# reject unshareable values except if they are being sent from the ractor owning the port
|
|
38
|
+
port = Farce::Port.new(mode: :raise, auto_local: true)
|
|
39
|
+
object = Object.new
|
|
40
|
+
|
|
41
|
+
port.send(object)
|
|
42
|
+
port.receive.equal?(object) # => true
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Backports
|
|
46
|
+
|
|
47
|
+
While Farce includes `Farce::Ractor` and `Farce::Port`, it does not backport any methods to the standard `Ractor` class.
|
|
48
|
+
|
|
49
|
+
Instead, backports are provided by `Farce::Backports`.
|
|
50
|
+
|
|
51
|
+
### Available Backports
|
|
52
|
+
|
|
53
|
+
#### From Ruby 4.0
|
|
54
|
+
|
|
55
|
+
The following methods are added to `Ractor` on Ruby versions prior to 4.0:
|
|
56
|
+
|
|
57
|
+
- [`default_port`](https://docs.ruby-lang.org/en/4.0/Ractor.html#method-i-default_port)
|
|
58
|
+
- [`join`](https://docs.ruby-lang.org/en/4.0/Ractor.html#method-i-join)
|
|
59
|
+
- [`monitor`](https://docs.ruby-lang.org/en/4.0/Ractor.html#method-i-monitor)
|
|
60
|
+
- [`unmonitor`](https://docs.ruby-lang.org/en/4.0/Ractor.html#method-i-unmonitor)
|
|
61
|
+
- [`value`](https://docs.ruby-lang.org/en/4.0/Ractor.html#method-i-value)
|
|
62
|
+
|
|
63
|
+
#### From Ruby 4.1
|
|
64
|
+
|
|
65
|
+
The following methods are added to `Ractor` on Ruby versions prior to 4.1:
|
|
66
|
+
|
|
67
|
+
- [`alive?`](https://docs.ruby-lang.org/en/master/Ractor.html#method-i-alive-3F)
|
|
68
|
+
|
|
69
|
+
It also extends the following methods with optional timeout support:
|
|
70
|
+
|
|
71
|
+
- [`Ractor.select`](https://docs.ruby-lang.org/en/master/Ractor.html#method-c-select)
|
|
72
|
+
- [`Ractor#receive`](https://docs.ruby-lang.org/en/master/Ractor.html#method-i-receive)
|
|
73
|
+
- [`Ractor::Port#receive`](https://docs.ruby-lang.org/en/master/Ractor/Port.html#method-i-receive)
|
|
74
|
+
|
|
75
|
+
No breaking changes are backported. This includes the updated [`monitor`](https://docs.ruby-lang.org/en/master/Ractor.html#method-i-monitor) behavior. The [polyfill](#ractor) will choose its `monitor` implementation based on `RUBY_VERSION` (so that any code checking the Ruby version will see the behavior it should).
|
|
76
|
+
|
|
77
|
+
### Without Activating Backports
|
|
78
|
+
|
|
79
|
+
You can call these methods on `Farce::Backports`, directly, passing in the Ractor instance as a first argument.
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
ractor = Farce::Ractor.new { sleep 1; raise "I'm not happy!" }
|
|
83
|
+
supervisor = Farce::Ractor.new { puts "status: #{receive}" }
|
|
84
|
+
port = Farce::Backports.default_port(supervisor)
|
|
85
|
+
|
|
86
|
+
# This will print "status: aborted" in approximately one second
|
|
87
|
+
Farce::Backports.monitor(ractor, port)
|
|
88
|
+
|
|
89
|
+
# Wait for the supervisor to receive the status
|
|
90
|
+
Farce::Backports.join(supervisor)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Using Backports via Refinements
|
|
94
|
+
|
|
95
|
+
You can activate the backports for a lexical scope (within a file, or even just within a module within a file, or starting with Ruby 4.1 for an individual block) using them as a [refinement](https://docs.ruby-lang.org/en/master/syntax/refinements_rdoc.html):
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
# only activates the backports for the current file
|
|
99
|
+
using Farce::Backports
|
|
100
|
+
|
|
101
|
+
# Refinements can't install constants, so we still need to use Farce::Ractor for JRuby/TruffleRuby
|
|
102
|
+
# If you know you're on CRuby, you can replace Farce::Ractor with Ractor
|
|
103
|
+
ractor = Farce::Ractor.new { sleep 1; raise "I'm not happy!" }
|
|
104
|
+
supervisor = Farce::Ractor.new { puts "status: #{receive}" }
|
|
105
|
+
|
|
106
|
+
# These methods now work regardless of whether you used Ractor or Farce::Ractor
|
|
107
|
+
port = supervisor.default_port
|
|
108
|
+
ractor.monitor(port)
|
|
109
|
+
supervisor.join
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Activating Backports Globally
|
|
113
|
+
|
|
114
|
+
You can activate backports globally. As these don't override built-in methods, doing so should only cause issues for code expecting these methods not to be defined based on the `RUBY_VERSION` string.
|
|
115
|
+
|
|
116
|
+
Anywhere before executing your code, like an initializer:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
Farce::Backports.install
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Now you can do this in any file:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
# After installing backports globally, Ractor will always be defined
|
|
126
|
+
ractor = Ractor.new { sleep 1; raise "I'm not happy!" }
|
|
127
|
+
supervisor = Ractor.new { puts "status: #{receive}" }
|
|
128
|
+
ractor.monitor(supervisor.default_port)
|
|
129
|
+
supervisor.join
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## WeakRef
|
|
133
|
+
|
|
134
|
+
`Farce::WeakRef` is a drop-in replacement for Ruby's [`WeakRef`](https://docs.ruby-lang.org/en/4.0/WeakRef.html), which at the time of writing cannot be used outside of the main Ractor at all.
|
|
135
|
+
|
|
136
|
+
Farce implements a version based on weak [atoms](data-structures.md#atoms). Not only can it be used on non-main Ractors, `Farce::WeakRef` instances themselves are Ractor-shareable if the value they reference is also Ractor-shareable (or has been garbage collected). Moreover, calling `Ractor.make_shareable(weak_ref)` will propagate through to the referenced value.
|
|
137
|
+
|
|
138
|
+
## Resolv
|
|
139
|
+
|
|
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
|
+
|
|
142
|
+
## Read Next
|
|
143
|
+
|
|
144
|
+
[README](../README.md#features) · [Sharing Unshareable Data](sharing.md) · [Scheduling Code](scheduling.md)
|
data/docs/utilities.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
# @title Utilities and Custom Classes
|
|
3
|
+
-->
|
|
4
|
+
|
|
5
|
+
# Utilities and Custom Classes
|
|
6
|
+
|
|
7
|
+
Farce provides helpers for converting object graphs, sharing custom classes, timing work, and coordinating access to values. Use these when adapting an existing library or building your own concurrency tools.
|
|
8
|
+
|
|
9
|
+
## Top Level Methods
|
|
10
|
+
|
|
11
|
+
* `Farce.clock` returns the monotonic clock time in seconds as a Float.
|
|
12
|
+
* `Farce.config` returns the global configuration object.
|
|
13
|
+
* `Farce.configure` allows you to configure Farce.
|
|
14
|
+
* `Farce.dedup` de-duplicates the given object based on a deduplication cache shared by all Ractors.
|
|
15
|
+
* `Farce.enfarce` turns a vanilla data structure into its Farce equivalent.
|
|
16
|
+
* `Farce.freeze_graph` recursively freezes an object graph.
|
|
17
|
+
* `Farce.in_parallel`, `Farce.on_main`, and `Farce.schedule`, see [Scheduling Code](scheduling.md#scheduling-code)
|
|
18
|
+
* `Farce.rebind` rebinds a proc or lambda while preserving its Ractor-shareability.
|
|
19
|
+
* `Farce.transaction` creates and runs a [transaction](concurrency.md#transactions).
|
|
20
|
+
|
|
21
|
+
Some examples:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
a = { a: [+"b"] }
|
|
25
|
+
b = { a: [+"b"] }
|
|
26
|
+
|
|
27
|
+
# Farce.clock
|
|
28
|
+
Farce.clock # => 0.017476999908685684
|
|
29
|
+
Farce.clock(in: 10) # => 10.017520000003278
|
|
30
|
+
|
|
31
|
+
# Farce.dedup
|
|
32
|
+
a.equal? b # => false
|
|
33
|
+
Farce.dedup(a).equal? Farce.dedup(b) # => true
|
|
34
|
+
|
|
35
|
+
# Farce.enfarce
|
|
36
|
+
Farce.enfarce(a) # => #<Farce::Map {a: #<Farce::Vector ["b"]>}>
|
|
37
|
+
Farce::Strict.enfarce(a) # => #<Farce::Strict::Map {a: #<Farce::Strict::Vector ["b"]>}>
|
|
38
|
+
|
|
39
|
+
# Farce.freeze_graph
|
|
40
|
+
Farce.freeze_graph(a)
|
|
41
|
+
|
|
42
|
+
# Farce.rebind
|
|
43
|
+
callback = ->(add) { self + add }
|
|
44
|
+
rebound = Farce.rebind(callback, self: 42)
|
|
45
|
+
rebound.call(18) # => 60
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Additional Classes
|
|
49
|
+
|
|
50
|
+
Other classes Farce provides include:
|
|
51
|
+
* `Config`: Configuration class, see [Third-Party Fiber Schedulers](scheduling.md#third-party-fiber-schedulers) example.
|
|
52
|
+
* `ClassMirror`: inheritance-aware registry for classes
|
|
53
|
+
* `Clock`: Timing functions based on a monotonic clock rather than on `Time`.
|
|
54
|
+
* `Deduper`: Create your own deduplication cache. Direct usage isn't recommended, use [`Farce.dedup`](#top-level-methods) instead.
|
|
55
|
+
* `Exchanger`: A synchronization point for two-way data swapping between Threads, Ractors, and/or Fibers. Drop-in replacement for concurrent-ruby's exchanger.
|
|
56
|
+
* `Extension`: Defines extensions for existing classes that can either be activated globally or used as refinements.
|
|
57
|
+
* `Lazy`: Lazily initialized value.
|
|
58
|
+
* `LazyRef`: A [reference](data-structures.md#references) for a lazily initialized value.
|
|
59
|
+
* `ThreadExecutor`: A [task executor](scheduling.md#scheduling-code) creating a new thread for each unit of work. Used on TruffleRuby.
|
|
60
|
+
* `Walker`: A tool for walking a Ruby object tree.
|
|
61
|
+
* `WeakValue`: A value object version of [`WeakRef`](shims.md#weakref). Allows handling references more explicitly, without automatic method delegation.
|
|
62
|
+
|
|
63
|
+
In addition, Farce includes a range of error classes not listed here. Check the [API documentation](https://rkh.github.io/farce/) or [code base](https://github.com/rkh/farce/blob/main/lib/farce/error.rb) for these.
|
|
64
|
+
|
|
65
|
+
## Shareability Mixins
|
|
66
|
+
|
|
67
|
+
Farce includes mixins to help you make your custom classes shareable:
|
|
68
|
+
|
|
69
|
+
* `Shareable` automatically marks objects as shareable (via `Ractor.make_shareable`) after initialization.
|
|
70
|
+
* `Shareable::Delegated` delegates `freeze` and `frozen?` to another object holding your object's state.
|
|
71
|
+
* `Shareable::Immutable` instances are always immutable, being frozen after initialization.
|
|
72
|
+
* `Shareable::Native` is for objects implemented in a native extension, which allows setting the frozen and shareable state separately.
|
|
73
|
+
* `Shareable::Tracked` for objects implementing frozen tracking (via an internal [flag](data-structures.md#flags)).
|
|
74
|
+
* `Shareable::Unfreezable` for objects that cannot be frozen (like [queues](data-structures.md#queues)).
|
|
75
|
+
|
|
76
|
+
And also mixins to prevent them from being shareable:
|
|
77
|
+
|
|
78
|
+
* `Unshareable`: Instances aren't shareable and cannot be made shareable. By default, they also cannot be copied or moved between Ractors.
|
|
79
|
+
* `Unshareable::Copyable`: Instances aren't shareable but may be copied to another Ractor.
|
|
80
|
+
* `Unshareable::Movable`: Instances aren't shareable but may be moved to another Ractor.
|
|
81
|
+
|
|
82
|
+
`Unshareable::Copyable` and `Unshareable::Movable` may be combined.
|
|
83
|
+
|
|
84
|
+
## Constants
|
|
85
|
+
|
|
86
|
+
* `Farce::MODES`: List of supported [sharing modes](sharing.md#sharing-modes).
|
|
87
|
+
* `Farce::NULL_PORT`: A special `Ractor::Port` that acts as a sink for messages and never closes.
|
|
88
|
+
* `Farce::SCOPES`: List of [provided scopes](variants-and-scopes.md#provided-scopes).
|
|
89
|
+
* `Farce::VERSION`: The current version.
|
|
90
|
+
|
|
91
|
+
## Read Next
|
|
92
|
+
|
|
93
|
+
[README](../README.md#features) · [Sharing Unshareable Data](sharing.md) · [Integrations](integrations.md)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
# @title Variants and Scopes
|
|
3
|
+
-->
|
|
4
|
+
|
|
5
|
+
# Variants and Scopes
|
|
6
|
+
|
|
7
|
+
Many classes, including all [data structures](data-structures.md#data-structures), implement multiple variants as separate subclasses within module namespaces.
|
|
8
|
+
|
|
9
|
+
`Farce::Map`, `Farce::Strict::Map`, `Farce::Local::Map`, and `Farce::Unshared::Map` all support the same API, except that:
|
|
10
|
+
|
|
11
|
+
* `Farce::Map` accepts an optional `mode` keyword for its initializer and many methods.
|
|
12
|
+
* `Farce::Strict::Map` will not allow any non-shareable data to be stored in it. It is slightly faster than `Farce::Map`.
|
|
13
|
+
* `Farce::Local::Map` will accept an optional `scope` keyword for its initializer, and will have different content for each [scope](#provided-scopes).
|
|
14
|
+
* `Farce::Unshared::Map` cannot be shared across Ractors, but in turn can store any Ruby object directly, including unshareable objects. It is as fast as `Farce::Strict::Map`.
|
|
15
|
+
|
|
16
|
+
## Available Variants
|
|
17
|
+
|
|
18
|
+
> [!TIP]
|
|
19
|
+
> Learn more in the dedicated [variants documentation](variants.md).
|
|
20
|
+
|
|
21
|
+
These variants are provided by Farce:
|
|
22
|
+
|
|
23
|
+
1. **Default** variants, under the `Farce` namespace:
|
|
24
|
+
* Can **store unshareable values**, usually via [modes](sharing.md#sharing-modes).
|
|
25
|
+
For maps, this is only supported for values. Keys must be shareable.
|
|
26
|
+
* Instances are **Ractor-shareable**.
|
|
27
|
+
* The data structure is **thread-safe**.
|
|
28
|
+
2. **Strict** variants, under the `Farce::Strict` namespace:
|
|
29
|
+
* **Forbid unshareable values**
|
|
30
|
+
* Instances are **Ractor-shareable**.
|
|
31
|
+
* The data structure is **thread-safe**.
|
|
32
|
+
* May provide performance benefits over the default variant.
|
|
33
|
+
3. **Local** variants, under the `Farce::Local` namespace:
|
|
34
|
+
* Will have **different content** for each [scope](#provided-scopes).
|
|
35
|
+
* Can **store unshareable values**, including unshareable map keys.
|
|
36
|
+
* Instances are **Ractor-shareable**.
|
|
37
|
+
* The data structure is **thread-safe**.
|
|
38
|
+
4. **Unshared** variants, under `Farce::Unshared` namespace:
|
|
39
|
+
* Can **store unshareable values**, including unshareable map keys.
|
|
40
|
+
* Instances are **<u>not</u> Ractor-shareable**.
|
|
41
|
+
* The data structure is **thread-safe**.
|
|
42
|
+
* May provide performance benefits over the default variant.
|
|
43
|
+
5. **Unsafe** variants, under `Farce::Unsafe` namespace:
|
|
44
|
+
* Same as Unshared, except they do **<u>not</u> guarantee thread-safety**
|
|
45
|
+
* May provide performance benefits over all other variants.
|
|
46
|
+
6. **Transaction** variants, under `Farce::Transaction` namespace:
|
|
47
|
+
* Created as mirrors of another object within a [transaction](concurrency.md#transactions).
|
|
48
|
+
* Should not be shared across transaction boundaries.
|
|
49
|
+
* Should not be initialized directly.
|
|
50
|
+
|
|
51
|
+
The following is true for all variants:
|
|
52
|
+
* Blocking operations will suspend a non-blocking fiber, but not block the underlying scheduler.
|
|
53
|
+
* The return value of `frozen?` will correctly reflect whether an instance is mutable or not.
|
|
54
|
+
|
|
55
|
+
However, keep the following in mind:
|
|
56
|
+
* Not all classes implement all variants. Check out the [full list](variants.md#classes-implementing-variants)
|
|
57
|
+
* Other classes that don't implement variants are also nested under the `Farce` namespace.
|
|
58
|
+
|
|
59
|
+
## Provided scopes
|
|
60
|
+
|
|
61
|
+
> [!TIP]
|
|
62
|
+
> Learn more in the dedicated [scopes documentation](scopes.md).
|
|
63
|
+
|
|
64
|
+
Local variants support scopes:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
map = Farce::Local::Map.new(scope: :thread)
|
|
68
|
+
map[:id] = 1
|
|
69
|
+
|
|
70
|
+
Thread.new do
|
|
71
|
+
map[:id] = 2
|
|
72
|
+
map[:id] # => 2
|
|
73
|
+
end.join
|
|
74
|
+
|
|
75
|
+
map[:id] # => 1
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The following scopes are available:
|
|
79
|
+
|
|
80
|
+
| Scope | Description |
|
|
81
|
+
| ------------------ | ------------------------------------------------------------------------------------------------ |
|
|
82
|
+
| `ractor` (default) | Content varies by [Ractor](https://docs.ruby-lang.org/en/4.0/Ractor.html) |
|
|
83
|
+
| `thread_group` | Content varies by [ThreadGroup](https://docs.ruby-lang.org/en/4.0/ThreadGroup.html) |
|
|
84
|
+
| `thread` | Content varies by [Thread](https://docs.ruby-lang.org/en/4.0/Thread.html) |
|
|
85
|
+
| `fiber_storage` | Content varies by [Fiber storage](https://docs.ruby-lang.org/en/4.0/Fiber.html#method-i-storage) |
|
|
86
|
+
| `fiber` | Content varies by [Fiber](https://docs.ruby-lang.org/en/4.0/Fiber.html) |
|
|
87
|
+
|
|
88
|
+
Fiber storage is typically inherited by a blocking fiber from the fiber creating it.
|
|
89
|
+
|
|
90
|
+
## Read Next
|
|
91
|
+
|
|
92
|
+
[README](../README.md#features) · [Variants](variants.md) · [Local scopes](scopes.md) · [Sharing Unshareable Data](sharing.md)
|
data/lib/farce/abstract/flag.rb
CHANGED
|
@@ -59,6 +59,12 @@ module Farce
|
|
|
59
59
|
include Internal::ValueSerialization
|
|
60
60
|
include Internal::Inspect
|
|
61
61
|
|
|
62
|
+
# @return [Boolean] whether the flag is set
|
|
63
|
+
def set? = value
|
|
64
|
+
|
|
65
|
+
# @return [Boolean] whether the flag is unset
|
|
66
|
+
def unset? = !value
|
|
67
|
+
|
|
62
68
|
# Wait until the flag becomes true.
|
|
63
69
|
# @param timeout [Numeric, nil] the total seconds available
|
|
64
70
|
# @yield called when the timeout expires
|