farce 0.1.0-arm-linux-gnu → 0.2.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 +80 -1
- data/README.md +72 -16
- data/docs/gems/dry-types.md +8 -8
- data/docs/modes.md +67 -10
- data/ext/farce/atom.c +37 -22
- data/ext/farce/bounded_map.c +17 -13
- data/ext/farce/containers.h +15 -0
- data/ext/farce/counter.c +13 -18
- data/ext/farce/exchanger.c +5 -1
- data/ext/farce/farce.c +1 -1
- data/ext/farce/flag.c +39 -3
- data/ext/farce/io.c +3 -3
- data/ext/farce/lock.c +17 -23
- data/ext/farce/map.c +88 -66
- data/ext/farce/priority_queue.c +55 -43
- data/ext/farce/queue.c +10 -4
- data/ext/farce/reactor.c +52 -25
- data/ext/farce/reactor.h +1 -1
- data/ext/farce/signal.c +5 -1
- data/ext/farce/transaction.c +20 -24
- data/ext/farce/tree_map.c +33 -18
- data/ext/farce/trie.c +81 -43
- data/ext/farce/unshareable.c +168 -19
- data/ext/farce/unshared_io_pool.h +21 -12
- data/ext/farce/unshared_signal.c +13 -2
- data/ext/farce/unshared_wait.h +3 -0
- data/ext/farce/vector.c +82 -18
- data/ext/farce/weak_map.c +75 -63
- data/lib/farce/_yard/macros.rb +18 -3
- data/lib/farce/abstract/flag.rb +73 -0
- data/lib/farce/abstract/queue.rb +4 -3
- data/lib/farce/abstract/scheduler.rb +3 -3
- data/lib/farce/abstract/set.rb +17 -5
- data/lib/farce/abstract/sorted_set.rb +2 -2
- data/lib/farce/abstract/timer_queue.rb +49 -10
- data/lib/farce/abstract/vector.rb +510 -28
- data/lib/farce/atom.rb +1 -1
- data/lib/farce/class_mirror.rb +20 -7
- data/lib/farce/config.rb +7 -1
- data/lib/farce/engine/jvm/flag.rb +15 -3
- data/lib/farce/engine/ruby/3.4/farce.so +0 -0
- data/lib/farce/engine/ruby/4.0/farce.so +0 -0
- data/lib/farce/engine/ruby/shared/require_hook.rb +18 -0
- data/lib/farce/engine/ruby.rb +7 -0
- data/lib/farce/engine/shared/unshared_vector.rb +31 -4
- data/lib/farce/engine/shared/vector.rb +8 -0
- data/lib/farce/engine/shared/weak_map/index.rb +14 -1
- data/lib/farce/engine/truffleruby/native/flag.rb +15 -3
- data/lib/farce/envelope.rb +54 -7
- data/lib/farce/exchanger.rb +1 -1
- data/lib/farce/flag.rb +2 -0
- data/lib/farce/integrations/active_support.rb +1 -1
- data/lib/farce/integrations/bson.rb +1 -1
- data/lib/farce/integrations/cbor.rb +1 -1
- data/lib/farce/integrations/concurrent.rb +3 -3
- data/lib/farce/integrations/date.rb +8 -0
- data/lib/farce/integrations/dry_types.rb +7 -4
- data/lib/farce/integrations/json.rb +2 -2
- data/lib/farce/integrations/msgpack.rb +1 -1
- data/lib/farce/integrations/oj.rb +3 -4
- data/lib/farce/integrations/psych.rb +1 -1
- data/lib/farce/integrations/ractor_sharing.rb +1 -1
- data/lib/farce/integrations/sorted_set.rb +1 -1
- data/lib/farce/integrations/weakref.rb +9 -1
- data/lib/farce/integrations/yajl.rb +3 -3
- data/lib/farce/integrations.rb +33 -23
- data/lib/farce/internal/flag_waiting.rb +30 -0
- data/lib/farce/internal/freeze.rb +8 -3
- data/lib/farce/internal/map_value_modes.rb +1 -1
- data/lib/farce/internal/scheduler_lifecycle.rb +1 -1
- data/lib/farce/lazy.rb +4 -4
- data/lib/farce/lazy_ref.rb +3 -3
- data/lib/farce/lfu_map.rb +1 -1
- data/lib/farce/local/flag.rb +4 -0
- data/lib/farce/local/scoped.rb +1 -1
- data/lib/farce/lru_map.rb +1 -1
- data/lib/farce/map.rb +2 -2
- data/lib/farce/mode_manager/smart_rule.rb +138 -0
- data/lib/farce/mode_manager.rb +12 -3
- data/lib/farce/molecule.rb +2 -2
- data/lib/farce/pool.rb +1 -1
- data/lib/farce/port.rb +4 -4
- data/lib/farce/priority_queue.rb +1 -1
- data/lib/farce/proxy/register.rb +6 -0
- data/lib/farce/proxy/supervisor.rb +1 -1
- data/lib/farce/proxy.rb +7 -2
- data/lib/farce/queue.rb +1 -1
- data/lib/farce/scheduler.rb +2 -2
- data/lib/farce/set.rb +1 -1
- data/lib/farce/sorted_set.rb +1 -1
- data/lib/farce/timer_queue.rb +20 -6
- data/lib/farce/transaction/vector.rb +43 -9
- data/lib/farce/transaction/wrapper.rb +14 -0
- data/lib/farce/tree_map.rb +1 -1
- data/lib/farce/unsafe/lfu_map.rb +2 -0
- data/lib/farce/unsafe/lru_map.rb +2 -0
- data/lib/farce/unsafe/tree_map.rb +2 -0
- data/lib/farce/unshareable.rb +8 -19
- data/lib/farce/unshared/atom.rb +3 -0
- data/lib/farce/unshared/lfu_map.rb +2 -0
- data/lib/farce/unshared/lru_map.rb +2 -0
- data/lib/farce/unshared/map.rb +3 -0
- data/lib/farce/unshared/set.rb +1 -0
- data/lib/farce/unshared/sorted_set.rb +1 -0
- data/lib/farce/unshared/tree_map.rb +5 -1
- data/lib/farce/unshared/vector.rb +3 -0
- data/lib/farce/unshared/weak_atom.rb +5 -1
- data/lib/farce/unshared/weak_key_map.rb +3 -0
- data/lib/farce/unshared/weak_map.rb +3 -0
- data/lib/farce/unshared/weak_set.rb +1 -0
- data/lib/farce/unshared/weak_value_map.rb +3 -0
- data/lib/farce/vector.rb +44 -19
- data/lib/farce/version.rb +1 -1
- data/lib/farce.rb +11 -8
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b3b75367e471eaa033e832d3dce0e50d7286b2b4f1da9873a0b5fa55f9d9ce0f
|
|
4
|
+
data.tar.gz: 5f2531148719ccd407e5e5451069b2df80e290607c8e4f2dc65300aa21246fb8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 35cbb56604adae3e1ca7105a3bd0a7c72d4d5e63126988767c4235c87a4bd4b04e3335714bb4bdcab696588c317398cfe07a5ca5bc357ddb9f4b796164283cd1
|
|
7
|
+
data.tar.gz: f8d44d695172b5994d1e75da5b7f4a850717d88356451a932ce9fb347ddb6a3ffcbe01e624ce2a8b70d9b8c632c2eb03ac18a32c105114c74f8b7c9a6e98ad89
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,85 @@ Farce follows [Semantic Versioning 2.0](http://semver.org/). Any public method o
|
|
|
4
4
|
|
|
5
5
|
## Development Releases
|
|
6
6
|
|
|
7
|
+
### Farce 0.2.0 (2026-10-11)
|
|
8
|
+
|
|
9
|
+
#### New Features
|
|
10
|
+
|
|
11
|
+
##### Waiting on Flags
|
|
12
|
+
|
|
13
|
+
You can now wait for a `Farce::Flag` or `Farce::Local::Flag` to change its value:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
flag = Farce::Flag.new
|
|
17
|
+
|
|
18
|
+
Thread.new do
|
|
19
|
+
flag.wait_until_set
|
|
20
|
+
puts "Flag has been activated!"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
flag.set
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
##### Timer Queue Entries
|
|
27
|
+
|
|
28
|
+
`TimerQueue#push` and `TimerQueue#try_push` now return a `TimerQueue::Entry` object on a successful push.
|
|
29
|
+
This allows you to cancel a scheduled value using the returned entry:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
queue = Farce::TimerQueue.new
|
|
33
|
+
entry = queue.push(:work, delay: 60)
|
|
34
|
+
entry.delete # => true
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
##### Expanded Vector Functionality
|
|
38
|
+
|
|
39
|
+
Improved Array compatibility for `Vector`, `Strict::Vector`, `Unshared::Vector`, and `Local::Vector`:
|
|
40
|
+
|
|
41
|
+
* Added `to_ary`, `delete`, and `delete_if`.
|
|
42
|
+
* Added range and start/length indexing, slice assignment, multiple-value `push` and `append`, and `pop(count)`.
|
|
43
|
+
* Added `shift`, `unshift`, `prepend`, `insert`, `delete_at`, `slice!`, `replace`, and `fill`.
|
|
44
|
+
* Added `map!`, `collect!`, `keep_if`, `select!`, `filter!`, `reject!`, `compact!`, and `uniq!`.
|
|
45
|
+
* Added `reverse!`, `rotate!`, `shuffle!`, `sort!`, `sort_by!`, and `flatten!`.
|
|
46
|
+
* Added `flatten`, `transpose`, `product`, `combination`, `repeated_combination`, `permutation`, and `repeated_permutation`.
|
|
47
|
+
* Added lexicographic `<=>`. Equality and hashing continue to use object identity.
|
|
48
|
+
|
|
49
|
+
##### Miscellaneous
|
|
50
|
+
|
|
51
|
+
* Unshareable objects may now be frozen.
|
|
52
|
+
* Trying to make an unshareable object shareable raises a better error message now (`Ractor::Error` with a descriptive message, instead of a `NoMethodError`).
|
|
53
|
+
* Added a new transfer mode: `:marshal`, which will copy objects between Ractors using Ruby's [Marshal](https://docs.ruby-lang.org/en/4.0/Marshal.html) serialization.
|
|
54
|
+
|
|
55
|
+
#### Bug Fixes
|
|
56
|
+
|
|
57
|
+
* Improve compatibility with other gems modifying `Kernel#require`, work around an issue with Ruby 4.0's runtime ractor require patch and alias methods chains not working nicely with `Module#prepend`.
|
|
58
|
+
|
|
59
|
+
#### Performance Improvements
|
|
60
|
+
|
|
61
|
+
##### Rework CRuby native extension
|
|
62
|
+
|
|
63
|
+
Incorporated the advice from ["Tips for updating Ruby C extensions for efficiently using Ruby 4 VM APIs"](https://github.com/ruby/ruby/blob/master/doc/efficient_extensions.md).
|
|
64
|
+
|
|
65
|
+
With this change, all long-living Farce objects can be compacted and moved to old GC generations. This was not the case previously for maps and vectors that had snapshots taken of them (either for iteration or for transactions). These ended up being permanently kept in young generations. A long-living map or vector would therefore cause frequent GC runs to be quite expensive.
|
|
66
|
+
|
|
67
|
+
| Operation | Before | After | Change |
|
|
68
|
+
| --------------------------------------------------- | ------- | ------- | ----------- |
|
|
69
|
+
| `Map#each` over 1024 entries | 34.1 μs | 35.2 μs | 3% slower |
|
|
70
|
+
| Transaction with a map with 16 entries | 25.2 μs | 25.3 μs | 0.4% slower |
|
|
71
|
+
| Transaction with a map with 1024 entries | 26.7 μs | 29.2 μs | 9% slower |
|
|
72
|
+
| Transaction with a vector with 10,000 entries | 26.1 μs | 29.4 μs | 13% slower |
|
|
73
|
+
| GC while keeping a vector with 10,000 entries alive | 67.6 μs | 28.6 μs | 58% faster |
|
|
74
|
+
|
|
75
|
+
Beyond these specific cases:
|
|
76
|
+
* Other iteration methods will have a similar performance impact to `Map#each` (for instance, `Map#transform_values`).
|
|
77
|
+
* Methods not involving iterations, like reading or writing a specific map entry, do not show any measurable performance impact.
|
|
78
|
+
|
|
79
|
+
Measurements were taken on CRuby 4.0.7 (arm64-darwin23) with YJIT enabled. Measurements taken against Ruby's main branch show similar results.
|
|
80
|
+
|
|
81
|
+
#### Other changes
|
|
82
|
+
|
|
83
|
+
* Avoid circular require warnings when automatically loading third-party integrations.
|
|
84
|
+
* Do not install a require hook if automatic integration loading is disabled.
|
|
85
|
+
|
|
7
86
|
### Farce 0.1.0 (2026-10-09)
|
|
8
87
|
|
|
9
|
-
|
|
88
|
+
Initial release.
|
data/README.md
CHANGED
|
@@ -133,6 +133,7 @@ puts output
|
|
|
133
133
|
- [Constants](#constants)
|
|
134
134
|
- [Compatibility and Dependencies](#compatibility-and-dependencies)
|
|
135
135
|
- [Ruby](#ruby)
|
|
136
|
+
- [Optional Dependencies](#optional-dependencies)
|
|
136
137
|
- [Similar Projects](#similar-projects)
|
|
137
138
|
- [Installation](#installation)
|
|
138
139
|
- [Globally](#globally)
|
|
@@ -415,6 +416,23 @@ list.to_a # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
|
415
416
|
list.sum # => 45
|
|
416
417
|
```
|
|
417
418
|
|
|
419
|
+
There is one important difference to arrays: Bulk mutations, such as `Vector#map!` are atomic. Either the block succeeds for all entries, or if it raises an exception then none of the changes are applied. Such mutations may also not write to the same vector from within the block.
|
|
420
|
+
|
|
421
|
+
```ruby
|
|
422
|
+
list = Farce::Vector[1, 2, 3, 4]
|
|
423
|
+
|
|
424
|
+
begin
|
|
425
|
+
list.map! do |value|
|
|
426
|
+
raise "error" if value == 2
|
|
427
|
+
value * 2
|
|
428
|
+
end
|
|
429
|
+
rescue => e
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# This would return 2 if list was an array
|
|
433
|
+
list.first # => 1
|
|
434
|
+
```
|
|
435
|
+
|
|
418
436
|
#### Sets
|
|
419
437
|
|
|
420
438
|
`Farce::Set` is to `Set` what `Farce::Map` is to `Hash`. It implements the same interface, with additional atomic operations.
|
|
@@ -536,7 +554,7 @@ end
|
|
|
536
554
|
|
|
537
555
|
#### Queues
|
|
538
556
|
|
|
539
|
-
`Farce::Queue` is
|
|
557
|
+
`Farce::Queue` is an alternative to Ruby's `Queue` and `SizedQueue`. It provides a superset of their API, offering both blocking and a non-blocking API, while implementing Ractor-sharing via [sharing modes](#sharing-modes).
|
|
540
558
|
|
|
541
559
|
```ruby
|
|
542
560
|
queue = Farce::Queue.new
|
|
@@ -569,7 +587,7 @@ queue.pop # => :b
|
|
|
569
587
|
|
|
570
588
|
##### Timer Queues
|
|
571
589
|
|
|
572
|
-
Timer queues are similar to priority queues, sorted by float values, but these values are timestamps (based on [Farce::Clock](#additional-classes), not on Ruby's Time, which isn't monotonic). Any `
|
|
590
|
+
Timer queues are similar to priority queues, sorted by float values, but these values are timestamps (based on [Farce::Clock](#additional-classes), not on Ruby's Time, which isn't monotonic). Any `pop` will block until the next timestamp has been reached.
|
|
573
591
|
|
|
574
592
|
```ruby
|
|
575
593
|
queue = Farce::TimerQueue.new
|
|
@@ -579,7 +597,16 @@ queue.push(:ok, in: 1.5)
|
|
|
579
597
|
queue.pop # => :ok
|
|
580
598
|
```
|
|
581
599
|
|
|
582
|
-
Timer queues are useful for implementing schedulers (which is what Farce uses them for under the hood)
|
|
600
|
+
Timer queues are useful for implementing schedulers (which is what Farce uses them for under the hood). Both `push` and `try_push` return a cancellation handle when they succeed. Use `entry.delete` or `queue.delete(entry)` to cancel a scheduled value. The handle exposes its `queue` and scheduled `clock` time.
|
|
601
|
+
|
|
602
|
+
```ruby
|
|
603
|
+
queue = Farce::TimerQueue.new
|
|
604
|
+
entry = queue.push(:work, in: 60)
|
|
605
|
+
|
|
606
|
+
entry.delete # => true
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
You can also delete a value if you know its timestamp:
|
|
583
610
|
|
|
584
611
|
```ruby
|
|
585
612
|
queue = Farce::TimerQueue.new
|
|
@@ -589,6 +616,9 @@ queue.push(:first, at: timestamp - 0.1)
|
|
|
589
616
|
queue.push(:second, at: timestamp)
|
|
590
617
|
queue.push(:third, at: timestamp + 0.1)
|
|
591
618
|
|
|
619
|
+
fourth = queue.push(:fourth, at: timestamp + 0.2)
|
|
620
|
+
queue.delete(fourth)
|
|
621
|
+
|
|
592
622
|
queue.delete(:second, at: timestamp)
|
|
593
623
|
|
|
594
624
|
result = []
|
|
@@ -601,7 +631,7 @@ result # => [:first, :third]
|
|
|
601
631
|
Queues support setting a maximum capacity. If the capacity has been reached it will block any pushes to create back pressure.
|
|
602
632
|
|
|
603
633
|
```ruby
|
|
604
|
-
queue = Farce::Queue.new(2)
|
|
634
|
+
queue = Farce::Queue.new(capacity: 2)
|
|
605
635
|
|
|
606
636
|
queue.try_push(:a) # => true
|
|
607
637
|
queue.try_push(:b) # => true
|
|
@@ -683,17 +713,22 @@ The [default variants](#variants-and-scopes) of most [data structures](#data-str
|
|
|
683
713
|
|
|
684
714
|
These modes are:
|
|
685
715
|
|
|
686
|
-
| Mode
|
|
687
|
-
|
|
|
688
|
-
| `:
|
|
689
|
-
| `:
|
|
690
|
-
| `:
|
|
691
|
-
| `:
|
|
692
|
-
| `:
|
|
693
|
-
| `:
|
|
694
|
-
| `:
|
|
695
|
-
| `:
|
|
696
|
-
| `:
|
|
716
|
+
| Mode | What happens to non-shareable data | A typical use |
|
|
717
|
+
| --- | --- | --- |
|
|
718
|
+
| `:smart` (default) | Applies smart rules or a dedicated proxy, raising for unsupported values. | Share supported values with less copying. |
|
|
719
|
+
| `:smart_raise` | Applies smart rules or a dedicated proxy, with a raising fallback. | Reject unsupported transfers. |
|
|
720
|
+
| `:smart_copy` | Applies smart rules or a dedicated proxy, then tries copying and Marshal. Default for `Farce::Port`. | Send values with a copying fallback. |
|
|
721
|
+
| `:smart_move` | Applies smart rules or a dedicated proxy, then tries moving. | Hand off unsupported values. |
|
|
722
|
+
| `:copy` | Transfers a copy and leaves the original usable. | Send a snapshot of a request. |
|
|
723
|
+
| `:marshal` | Serializes a snapshot and reconstructs it once per receiving Ractor. | Transfer an Unshared container.|
|
|
724
|
+
| `:move` | Transfers ownership and makes the original inaccessible. | Hand a completed batch to a consumer. |
|
|
725
|
+
| `:local` | Keeps the same object in its originating Ractor. | Pass work between local threads or fibers. |
|
|
726
|
+
| `:make_shareable` | Calls `Ractor.make_shareable` on the original. | Publish finished configuration. |
|
|
727
|
+
| `:mutable` | Copies non-shareable objects into a [`Farce::Mutable`](#mutables). | Synchronize mutations across Ractors. |
|
|
728
|
+
| `:shareable_copy` | Makes a shareable copy and leaves the original alone. | Publish a snapshot of an editable document. |
|
|
729
|
+
| `:dedup` | Deduplicates the value, then makes it shareable. May update and freeze the original. | Reuse repeated message contents. |
|
|
730
|
+
| `:proxy` | Creates a [`Farce::Proxy`](#proxies) that executes calls in the original Ractor. | Share access to a mutable object. |
|
|
731
|
+
| `:raise` | Raises `Ractor::IsolationError`. | Enforce a shareable-data boundary. |
|
|
697
732
|
|
|
698
733
|
Here's an example:
|
|
699
734
|
|
|
@@ -974,7 +1009,7 @@ list = Farce::Vector.new
|
|
|
974
1009
|
list[0] = 42
|
|
975
1010
|
|
|
976
1011
|
# Atomically replace list[0] with 256 if the value is still 42
|
|
977
|
-
list.
|
|
1012
|
+
list.compare_and_set(0, 42, 256)
|
|
978
1013
|
```
|
|
979
1014
|
|
|
980
1015
|
#### Observability and Signaling
|
|
@@ -1433,6 +1468,27 @@ Moreover:
|
|
|
1433
1468
|
* Dropping support for a CRuby version is only done in major releases.
|
|
1434
1469
|
* If support for an older CRuby version is dropped, Farce will still backport security fixes for at least as long as that CRuby version is [still receiving security fixes](https://www.ruby-lang.org/en/downloads/branches/).
|
|
1435
1470
|
|
|
1471
|
+
### Optional Dependencies
|
|
1472
|
+
|
|
1473
|
+
Farce's optional [integrations](#integrations) depend on external libraries. Automated tests only run against recent versions of these libraries, but the code is written to be compatible with as wide a range as possible.
|
|
1474
|
+
|
|
1475
|
+
Farce should be compatible with these versions:
|
|
1476
|
+
|
|
1477
|
+
| Gem | Minimum Version | Release Date |
|
|
1478
|
+
|-------------------|-----------------|--------------|
|
|
1479
|
+
| `ractor-tmvar` | 0.3.0 | 2026-10-06 |
|
|
1480
|
+
| `msgpack` | 1.7.0 | 2023-03-29 |
|
|
1481
|
+
| `concurrent-ruby` | 1.1.8 | 2021-01-20 |
|
|
1482
|
+
| `dry-types` | 1.0.0 | 2019-04-23 |
|
|
1483
|
+
| `activesupport` | 5.1.0 | 2017-04-27 |
|
|
1484
|
+
| `oj` | 2.8.1 | 2014-04-21 |
|
|
1485
|
+
| `bson` | 2.0.0 | 2013-12-02 |
|
|
1486
|
+
| `psych` | 1.1.0 | 2011-03-30 |
|
|
1487
|
+
|
|
1488
|
+
Any version of `cbor`, `json`, `ractor-sharing`, `sorted_set`, and `weakref` should be supported
|
|
1489
|
+
|
|
1490
|
+
If you want to use any of these in a version that is incompatible with Farce, you can [disable automatic integration loading](#disable-automatic-loading).
|
|
1491
|
+
|
|
1436
1492
|
### Similar Projects
|
|
1437
1493
|
|
|
1438
1494
|
* [ractor-shim](https://github.com/eregon/ractor-shim/) provides similar functionality to `Farce::Ractor`. See [the comparison document](docs/gems/ractor-shim.md) for more details.
|
data/docs/gems/dry-types.md
CHANGED
|
@@ -49,7 +49,7 @@ end
|
|
|
49
49
|
numbers = Types::IntegerVector[["1", 2]]
|
|
50
50
|
numbers.class # => Farce::Vector
|
|
51
51
|
numbers.to_a # => [1, 2]
|
|
52
|
-
numbers.mode # => :
|
|
52
|
+
numbers.mode # => :smart
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
The bare `Vector` accepts any Array members. Imported dry namespaces control
|
|
@@ -243,8 +243,7 @@ and dry-types' `Strict` namespace configure separate parts of the conversion.
|
|
|
243
243
|
| `:local` | `Farce::Local::Vector`, `Farce::Local::Map`, `Farce::Local::Set`, `Farce::Local::Counter`, `Farce::Local::Flag`, `Farce::Local::Atom` |
|
|
244
244
|
|
|
245
245
|
The integration imports only classes that Farce provides for the selected
|
|
246
|
-
variant.
|
|
247
|
-
three scalar-backed types.
|
|
246
|
+
variant.
|
|
248
247
|
|
|
249
248
|
For a single shared collection type, use `.with(mode: ...)`:
|
|
250
249
|
|
|
@@ -258,10 +257,11 @@ vector = Types::LocalValueVector[[payload]]
|
|
|
258
257
|
vector[0].equal?(payload) # => true
|
|
259
258
|
```
|
|
260
259
|
|
|
261
|
-
The supported shared modes are `:
|
|
262
|
-
`:shareable_copy`, and `:raise`.
|
|
263
|
-
|
|
264
|
-
|
|
260
|
+
The supported shared modes are `:smart`, `:smart_raise`, `:smart_copy`,
|
|
261
|
+
`:copy`, `:local`, `:make_shareable`, `:shareable_copy`, and `:raise`.
|
|
262
|
+
The default is `:smart`. They apply to collections and Atom. Counter and Flag have
|
|
263
|
+
no transfer mode. `:move` and `:smart_move` are rejected because dry-types creates
|
|
264
|
+
and examines intermediate values during coercion.
|
|
265
265
|
|
|
266
266
|
## Farce input and ownership
|
|
267
267
|
|
|
@@ -278,7 +278,7 @@ converted.to_a # => [1]
|
|
|
278
278
|
```
|
|
279
279
|
|
|
280
280
|
The explicit read establishes where transfer and ownership happen. This rule
|
|
281
|
-
also applies when the container's default mode is `:
|
|
281
|
+
also applies when the container's default mode is `:smart` because an individual
|
|
282
282
|
entry might have been inserted with `mode: :move`.
|
|
283
283
|
|
|
284
284
|
Vector materialization uses its normal snapshot. Map and set materialization
|
data/docs/modes.md
CHANGED
|
@@ -14,7 +14,9 @@ This improves over the default control via `Ractor::Port`'s `move:` option.
|
|
|
14
14
|
- [Introduction: What Ruby gives you](#introduction-what-ruby-gives-you)
|
|
15
15
|
- [What `move: true` does on `Ractor::Port`](#what-move-true-does-on-ractorport)
|
|
16
16
|
- [Farce's Modes](#farces-modes)
|
|
17
|
+
- [Smart modes: apply rules before transferring](#smart-modes-apply-rules-before-transferring)
|
|
17
18
|
- [`:copy`: keep working with the original](#copy-keep-working-with-the-original)
|
|
19
|
+
- [`:marshal`: reconstruct a serialized snapshot](#marshal-reconstruct-a-serialized-snapshot)
|
|
18
20
|
- [`:move`: hand off a finished batch](#move-hand-off-a-finished-batch)
|
|
19
21
|
- [`:local`: preserve object identity](#local-preserve-object-identity)
|
|
20
22
|
- [`:make_shareable`: publish the original](#make_shareable-publish-the-original)
|
|
@@ -76,7 +78,7 @@ port.receive # => [4, 5]
|
|
|
76
78
|
port.close
|
|
77
79
|
```
|
|
78
80
|
|
|
79
|
-
Farce extends this choice with
|
|
81
|
+
Farce extends this choice with fourteen named modes.
|
|
80
82
|
|
|
81
83
|
## Farce's Modes
|
|
82
84
|
|
|
@@ -84,7 +86,12 @@ The following modes are accepted by `Farce::Port`. They apply to non-shareable v
|
|
|
84
86
|
|
|
85
87
|
| Mode | What happens to non-shareable data | A typical use |
|
|
86
88
|
| --- | --- | --- |
|
|
87
|
-
| `:
|
|
89
|
+
| `:smart` | Applies smart rules or a dedicated proxy. Raises for unsupported values. Default for most mode-backed classes. | Share supported values with less copying. |
|
|
90
|
+
| `:smart_raise` | Applies smart rules or a dedicated proxy, with a raising fallback. | Reject unsupported transfers. |
|
|
91
|
+
| `:smart_copy` | Applies smart rules or a dedicated proxy, then tries copying and Marshal. Default for `Farce::Port`. | Send values with a copying fallback. |
|
|
92
|
+
| `:smart_move` | Applies smart rules or a dedicated proxy, then tries moving. | Hand off unsupported values. |
|
|
93
|
+
| `:copy` | Transfers a copy and leaves the original usable. | Send a snapshot of a request. |
|
|
94
|
+
| `:marshal` | Serializes a snapshot and reconstructs it once per receiving Ractor. | Transfer an Unshared container with fresh storage. |
|
|
88
95
|
| `:move` | Transfers ownership and makes the original inaccessible. | Hand a completed batch to a consumer. |
|
|
89
96
|
| `:local` | Keeps the same object in its originating Ractor. | Pass work between local threads or fibers. |
|
|
90
97
|
| `:make_shareable` | Calls `Ractor.make_shareable` on the original. | Publish finished configuration. |
|
|
@@ -94,12 +101,38 @@ The following modes are accepted by `Farce::Port`. They apply to non-shareable v
|
|
|
94
101
|
| `:proxy` | Creates a `Farce::Proxy` that executes calls in the original Ractor. | Share access to a mutable object. |
|
|
95
102
|
| `:raise` | Raises `Ractor::IsolationError`. | Enforce a shareable-data boundary. |
|
|
96
103
|
|
|
104
|
+
### Smart modes: apply rules before transferring
|
|
105
|
+
|
|
106
|
+
Most mode-backed classes default to `:smart`. `Farce::Port` defaults to `:smart_copy` so values without a suitable rule or dedicated proxy can still be copied. Classes with restricted weak-value modes keep their `:raise` default.
|
|
107
|
+
|
|
108
|
+
Smart modes pass already-shareable values through unchanged. Mutable strings become frozen, deduplicated strings without freezing the source string. Time values are made shareable in place. The Date integration does the same for Date and DateTime values.
|
|
109
|
+
|
|
110
|
+
With `:smart`, arrays and Ruby sets containing only shareable elements are made shareable in place. Symbol-keyed hashes containing only shareable values are made shareable in every smart mode. This can freeze the original, including with `:smart_copy` and `:smart_move`.
|
|
111
|
+
|
|
112
|
+
For arrays and Ruby sets, `:smart_copy` makes a shallow copy and tries to prepare each element with `:smart_raise`. If all results are directly usable, it makes that copy shareable. Otherwise, it copies the original graph, preserving aliases and cycles. If native copying raises a standard error, it tries Marshal reconstruction. If both fail, it raises the original copying error. It does not recursively insert managed envelopes into the container. Identity-based Farce sets use the prepared element for membership and reject a copying envelope fallback.
|
|
113
|
+
|
|
114
|
+
A dedicated proxy applies only to the exact registered class. Smart rules inherit through the source class hierarchy and can customize individual smart modes. Without applicable behavior, `:smart` and `:smart_raise` raise `Ractor::IsolationError`, `:smart_copy` tries copying and Marshal, and `:smart_move` tries moving.
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
class Job
|
|
118
|
+
attr_reader :id
|
|
119
|
+
def initialize(id) = @id = id
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
Farce::ModeManager::SmartRule.define(Job, :marshal)
|
|
123
|
+
queue = Farce::Queue.new
|
|
124
|
+
queue << Job.new(42)
|
|
125
|
+
queue.pop.id # => 42
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Use an explicit `:copy` mode when callers need independently mutable copies even for values that smart rules could make shareable. On JRuby and TruffleRuby, ordinary values are already shareable and pass through unchanged.
|
|
129
|
+
|
|
97
130
|
### `:copy`: keep working with the original
|
|
98
131
|
|
|
99
132
|
Copying is a good starting point for ordinary hashes, arrays, and strings. The receiver can change its copy without changing the sender's data. Shareable parts of the message can still be reused.
|
|
100
133
|
|
|
101
134
|
```ruby
|
|
102
|
-
port = Farce::Port.new
|
|
135
|
+
port = Farce::Port.new(mode: :copy)
|
|
103
136
|
request = { ids: [10, 20] }
|
|
104
137
|
port.send(request)
|
|
105
138
|
request[:ids] << 30
|
|
@@ -113,6 +146,27 @@ port.close
|
|
|
113
146
|
|
|
114
147
|
Copying is not a way to serialize every Ruby object. Some objects cannot be copied across Ractors. Choose a mode that fits the value, or send ordinary data from which the receiver can build what it needs.
|
|
115
148
|
|
|
149
|
+
### `:marshal`: reconstruct a serialized snapshot
|
|
150
|
+
|
|
151
|
+
Use `:marshal` for values with a Marshal representation, including `Farce::Unshared` containers whose backends cannot be copied directly between native Ractors. Farce calls `Marshal.dump` when preparing the value. A shareable envelope holds the frozen bytes. The first read in each Ractor calls `Marshal.load`, then caches that Ractor's reconstructed value.
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
port = Farce::Port.new(mode: :marshal)
|
|
155
|
+
source = Farce::Unshared::Map.new({ jobs: [] })
|
|
156
|
+
port.send(source)
|
|
157
|
+
source[:jobs] << :sender_change
|
|
158
|
+
|
|
159
|
+
received = port.receive
|
|
160
|
+
received[:jobs] # => []
|
|
161
|
+
received[:jobs] << :receiver_change
|
|
162
|
+
source[:jobs] # => [:sender_change]
|
|
163
|
+
port.close
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Farce's Marshal hooks rebuild containers from logical contents and settings. The receiver gets fresh backend storage and synchronization state. Repeated references and supported cycles within the serialized graph are preserved. A reconstructed Unshared container remains unshareable.
|
|
167
|
+
|
|
168
|
+
Contents must support Marshal. Procs, anonymous classes, and live coordination objects such as queues and leases cannot be serialized. A serialization failure raises `TypeError` before the value is sent or stored. Unlike native Ractor copying, Marshal may also reconstruct nested shareable objects. Top-level shareable values still pass through unchanged.
|
|
169
|
+
|
|
116
170
|
### `:move`: hand off a finished batch
|
|
117
171
|
|
|
118
172
|
Moving is useful when a producer has finished building a message and will never use it again. It avoids copying the payload. Do not retain a plan to reuse the original or its moved nested objects after sending.
|
|
@@ -262,7 +316,7 @@ proxy.length # => 1
|
|
|
262
316
|
port.close
|
|
263
317
|
```
|
|
264
318
|
|
|
265
|
-
Each delegated call involves a request and response.
|
|
319
|
+
Each delegated call involves a request and response. Arguments and return values use `:smart` rules by default. Unsupported values raise unless a wrapper selects another mode. Methods that return the original object return its proxy. Keep the owning Ractor alive while callers use the proxy. Calls raise `Farce::Ractor::RemoteError` after the owner exits. Access through other references to the original object still needs its own coordination.
|
|
266
320
|
|
|
267
321
|
On JRuby and TruffleRuby, ordinary values are already shareable, so this mode passes them through without creating a proxy.
|
|
268
322
|
|
|
@@ -326,7 +380,7 @@ Automatic local transfer takes precedence even over an explicit `mode:` or `move
|
|
|
326
380
|
|
|
327
381
|
## Other classes that support modes
|
|
328
382
|
|
|
329
|
-
The same choices appear in several Farce APIs. Containers normally default to `:
|
|
383
|
+
The same choices appear in several Farce APIs. Containers normally default to `:smart` and unwrap their managed values when you read them. Setting a container's mode does not make the values it returns shareable.
|
|
330
384
|
|
|
331
385
|
| Class | Where to select a mode | What it controls |
|
|
332
386
|
| --- | --- | --- |
|
|
@@ -394,7 +448,7 @@ retries.push({ attempt: 2 }, at: Farce::Clock.now, mode: :make_shareable)
|
|
|
394
448
|
Farce::Ractor.shareable?(retries.pop) # => true
|
|
395
449
|
```
|
|
396
450
|
|
|
397
|
-
Wrapping happens before waiting for queue space or an exchange partner. A failed `try_push` or a timeout can therefore leave a value moved or frozen. Use copying when you need to retain the original for a retry. Also, reading a moved payload can claim it: `TimerQueue#peek` opens the payload even though it leaves the item queued.
|
|
451
|
+
Wrapping happens before waiting for queue space or an exchange partner. A failed `try_push` or a timeout can therefore leave a value moved or frozen. Use copying when you need to retain the original for a retry. Also, reading a moved payload can claim it: `TimerQueue#peek` opens the payload even though it leaves the item queued. Cancelling a timer through the handle returned by `push` or `try_push` does not open or claim the payload.
|
|
398
452
|
|
|
399
453
|
### Exchange mutable messages with a partner
|
|
400
454
|
|
|
@@ -521,20 +575,23 @@ envelope.value[:ids] # => [1, 2]
|
|
|
521
575
|
envelope.value.equal?(envelope.value) # => true
|
|
522
576
|
```
|
|
523
577
|
|
|
524
|
-
`Envelope.new` defaults to `:copy` and accepts `:copy`, `:move`, or `:local`. These are envelope types, not the full set of manager modes. A shareable payload produces an `Envelope::Share`. The other manager modes either prepare shareable data directly or raise an error.
|
|
578
|
+
`Envelope.new` defaults to `:copy` and accepts `:copy`, `:marshal`, `:move`, or `:local`. These are envelope types, not the full set of manager modes. A shareable payload produces an `Envelope::Share`. The other manager modes either prepare shareable data directly or raise an error.
|
|
525
579
|
|
|
526
580
|
### When copying and moving happen
|
|
527
581
|
|
|
528
582
|
A copy envelope copies its non-shareable payload into internal storage when created. Each Ractor that opens it gets another copy, cached for that Ractor. A move envelope moves the payload into storage immediately, then moves it out when the winning Ractor first opens it. Forwarding either envelope does not add a payload copy or move at every hop.
|
|
529
583
|
|
|
584
|
+
A Marshal envelope serializes its payload when created. Each Ractor that opens it reconstructs one value from the same frozen bytes and caches it. Forwarding the envelope does not serialize or reconstruct its payload again.
|
|
585
|
+
|
|
530
586
|
| Envelope | Who can open it? | What repeated reads return |
|
|
531
587
|
| --- | --- | --- |
|
|
532
588
|
| `Envelope::Copy` | Any Ractor. | That Ractor's cached copy. |
|
|
589
|
+
| `Envelope::Marshal` | Any Ractor. | That Ractor's cached reconstruction. |
|
|
533
590
|
| `Envelope::Move` | The first Ractor to claim it. | The owning Ractor's received object. |
|
|
534
591
|
| `Envelope::Local` | Its creating Ractor. | The original object. |
|
|
535
592
|
| `Envelope::Share` | Any Ractor. | The shared payload. |
|
|
536
593
|
|
|
537
|
-
A claim belongs to a Ractor, not a thread or fiber, and cannot be revoked. `claim` returns the envelope on success or `nil` if another Ractor owns it. `claim!` and `value` raise `Farce::Envelope::AlreadyClaimed` on failure. Use `claimed?` to ask whether it has an owner and `owned?` to ask whether the current Ractor can open it. Copy and share envelopes report both as true because every Ractor can open them.
|
|
594
|
+
A claim belongs to a Ractor, not a thread or fiber, and cannot be revoked. `claim` returns the envelope on success or `nil` if another Ractor owns it. `claim!` and `value` raise `Farce::Envelope::AlreadyClaimed` on failure. Use `claimed?` to ask whether it has an owner and `owned?` to ask whether the current Ractor can open it. Copy, Marshal, and share envelopes report both as true because every Ractor can open them.
|
|
538
595
|
|
|
539
596
|
### Forward envelopes without opening them
|
|
540
597
|
|
|
@@ -573,7 +630,7 @@ Both queues accept the envelope in `:raise` mode because the wrapper is shareabl
|
|
|
573
630
|
|
|
574
631
|
### Mode managers prepare values and open their own envelopes
|
|
575
632
|
|
|
576
|
-
`Farce::ModeManager` provides two core operations: `wrap` prepares a value for shared storage, and `unwrap` retrieves values from envelopes that this manager created. It passes already-shareable values through unchanged. For non-shareable values, `:copy`, `:move`, and `:local` create managed envelopes. The remaining modes prepare shareable data directly, create a `Farce::Mutable` or `Farce::Proxy`, or raise. Mutable and proxy wrappers remain wrapped when read.
|
|
633
|
+
`Farce::ModeManager` provides two core operations: `wrap` prepares a value for shared storage, and `unwrap` retrieves values from envelopes that this manager created. It passes already-shareable values through unchanged. For non-shareable values, `:copy`, `:marshal`, `:move`, and `:local` create managed envelopes. Smart modes choose their representation through rules, dedicated proxies, and their fallback behavior. The remaining modes prepare shareable data directly, create a `Farce::Mutable` or `Farce::Proxy`, or raise. Mutable and proxy wrappers remain wrapped when read.
|
|
577
634
|
|
|
578
635
|
```ruby
|
|
579
636
|
manager = Farce::ModeManager.new(mode: :copy)
|
|
@@ -602,7 +659,7 @@ Keep one manager per instance when building an abstraction around shareable stor
|
|
|
602
659
|
class WorkInbox
|
|
603
660
|
include Farce::Shareable
|
|
604
661
|
|
|
605
|
-
def initialize(mode: :
|
|
662
|
+
def initialize(mode: :smart)
|
|
606
663
|
@manager = Farce::ModeManager.new(mode: mode)
|
|
607
664
|
@storage = Farce::Queue.new(mode: :raise)
|
|
608
665
|
super()
|