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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/README.md +117 -1489
  4. data/docs/compatibility.md +74 -0
  5. data/docs/concurrency.md +151 -0
  6. data/docs/data-structures.md +313 -0
  7. data/docs/installation.md +63 -0
  8. data/docs/integrations.md +124 -0
  9. data/docs/introduction.md +266 -0
  10. data/docs/scheduling.md +176 -0
  11. data/docs/sharing.md +211 -0
  12. data/docs/shims.md +144 -0
  13. data/docs/utilities.md +93 -0
  14. data/docs/variants-and-scopes.md +92 -0
  15. data/lib/farce/abstract/flag.rb +6 -0
  16. data/lib/farce/backports.rb +274 -0
  17. data/lib/farce/engine/jruby/fiber_scheduler.rb +3 -0
  18. data/lib/farce/engine/jruby/scheduler_synchronization.rb +87 -0
  19. data/lib/farce/engine/ruby/3.4/null_port.rb +21 -0
  20. data/lib/farce/engine/ruby/3.4/patch.rb +67 -0
  21. data/lib/farce/engine/ruby/3.4/port.rb +13 -12
  22. data/lib/farce/engine/ruby/3.4/ractor_methods.rb +0 -1
  23. data/lib/farce/engine/ruby/4.0/null_port.rb +36 -0
  24. data/lib/farce/engine/ruby/4.0/port.rb +1 -2
  25. data/lib/farce/engine/ruby/4.1/null_port.rb +5 -0
  26. data/lib/farce/engine/ruby.rb +7 -0
  27. data/lib/farce/engine/shared/null_port.rb +18 -0
  28. data/lib/farce/engine/shared/port.rb +41 -3
  29. data/lib/farce/engine/shared/queue.rb +4 -4
  30. data/lib/farce/engine/shared/ractor_methods.rb +2 -2
  31. data/lib/farce/engine/shared/ractor_selector.rb +73 -0
  32. data/lib/farce/extension.rb +150 -0
  33. data/lib/farce/integrations/ractor_tmvar.rb +4 -1
  34. data/lib/farce/internal/fake_ractor.rb +10 -2
  35. data/lib/farce/internal/scheduler_lifecycle.rb +7 -4
  36. data/lib/farce/null_port.rb +12 -0
  37. data/lib/farce/system.rb +10 -2
  38. data/lib/farce/version.rb +1 -1
  39. data/lib/farce.rb +1 -1
  40. 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)
@@ -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