farce 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +127 -1
- data/README.md +111 -17
- data/docs/gems/dry-types.md +8 -8
- data/docs/modes.md +75 -15
- data/docs/scopes.md +3 -1
- data/docs/variants.md +18 -0
- 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/executor.rb +130 -0
- data/lib/farce/abstract/flag.rb +73 -0
- data/lib/farce/abstract/pool.rb +334 -0
- data/lib/farce/abstract/queue.rb +4 -3
- data/lib/farce/abstract/scheduler.rb +539 -93
- 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/jruby/signal.rb +7 -4
- data/lib/farce/engine/jvm/flag.rb +15 -3
- data/lib/farce/engine/ruby/shared/require_hook.rb +18 -0
- data/lib/farce/engine/ruby.rb +7 -0
- data/lib/farce/engine/shared/main_scheduler.rb +2 -2
- data/lib/farce/engine/shared/parallel_scheduler.rb +1 -1
- data/lib/farce/engine/shared/proxy_owner.rb +1 -1
- data/lib/farce/engine/shared/unshared_atom.rb +9 -8
- 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/shared.rb +9 -0
- 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/lease_initialization.rb +1 -1
- data/lib/farce/internal/local_executor_methods.rb +33 -0
- data/lib/farce/internal/local_scheduled_task.rb +27 -0
- data/lib/farce/internal/map_value_modes.rb +1 -1
- data/lib/farce/internal/scheduled_task.rb +1 -1
- data/lib/farce/internal/scheduler_lifecycle.rb +1 -1
- data/lib/farce/internal/thread_pool_supervisor.rb +45 -0
- data/lib/farce/internal/unshared_pool_worker.rb +14 -0
- data/lib/farce/internal.rb +14 -0
- 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/pool.rb +111 -0
- data/lib/farce/local/scheduler.rb +219 -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 +2 -290
- 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 +9 -4
- data/lib/farce/queue.rb +1 -1
- data/lib/farce/scheduler.rb +1 -502
- data/lib/farce/set.rb +1 -1
- data/lib/farce/sorted_set.rb +1 -1
- data/lib/farce/task.rb +285 -0
- data/lib/farce/{thread_scheduler.rb → thread_executor.rb} +7 -7
- 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/pool.rb +44 -0
- data/lib/farce/unshared/scheduler.rb +89 -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 +20 -25
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 606351822ed0519ce92d1027233eec3295633e5107f7f94ac8649222fbb25fc1
|
|
4
|
+
data.tar.gz: 4f896509b22bd8c18c2864dceda3c46bf8a49f412f88c31ee96eeb00956b9ddb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7a0834837c23ed28d44af2a060b0f553f5d41cfdc46b976796cefc1fff5406b976e8358626f51ceabee55755b6589536e39db311c5aeb775077dfe1d99cb97d6
|
|
7
|
+
data.tar.gz: d74c7e6c959fc09b97c8b5bff1d844b3623c9f698995a05d2f72be175a54f41b769b052287f0dea4e13b7ee97dca0dfcac5925260ee8a6b15443c95c67c20735
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,132 @@ Farce follows [Semantic Versioning 2.0](http://semver.org/). Any public method o
|
|
|
4
4
|
|
|
5
5
|
## Development Releases
|
|
6
6
|
|
|
7
|
+
### Farce 0.3.0 (2026-10-13)
|
|
8
|
+
|
|
9
|
+
#### New Features
|
|
10
|
+
|
|
11
|
+
##### Breaking Changes
|
|
12
|
+
|
|
13
|
+
* `Abstract::Scheduler` is no longer a superclass of `Pool`. Use `Abstract::Executor` instead.
|
|
14
|
+
|
|
15
|
+
##### Executor Improvements
|
|
16
|
+
|
|
17
|
+
`Scheduler` and `Pool` have the following improvements:
|
|
18
|
+
|
|
19
|
+
* They now support `try_schedule`, which discards tasks and returns `nil` when their internal queues are at capacity.
|
|
20
|
+
* They now have `Unshared` and `Local` variants, running in a Ractor-local context (Pool will manage a Thread pool instead of a Ractor pool).
|
|
21
|
+
|
|
22
|
+
Creates a thread pool with a maximum size of 2 and a queue capacity of 10:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# A thread pool local to the current Ractor
|
|
26
|
+
thread_pool = Farce::Unshared::Pool.new(max_size: 2, capacity: 10)
|
|
27
|
+
|
|
28
|
+
# this should only run approximately 10 tasks on CRuby
|
|
29
|
+
500.times { thread_pool.try_schedule { puts "Task executed" } }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
##### Tasks
|
|
33
|
+
|
|
34
|
+
A new class, `Task`, has been introduced to represent asynchronous tasks that can be scheduled and executed within Farce's executor framework,
|
|
35
|
+
executed manually, or handed off to any other executor supporting procs or callable objects.
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
task = Farce::Task.schedule do
|
|
39
|
+
puts "Task started"
|
|
40
|
+
sleep 1
|
|
41
|
+
:success
|
|
42
|
+
ensure
|
|
43
|
+
puts "Task finished"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
task.alive? # => true
|
|
47
|
+
task.value # => :success (blocks until the task completes)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
#### Bug Fixes
|
|
51
|
+
|
|
52
|
+
* Calling `wait_*` methods on atoms on JRuby no longer blocks a fiber scheduler.
|
|
53
|
+
|
|
54
|
+
### Farce 0.2.0 (2026-10-11)
|
|
55
|
+
|
|
56
|
+
#### New Features
|
|
57
|
+
|
|
58
|
+
##### Waiting on Flags
|
|
59
|
+
|
|
60
|
+
You can now wait for a `Farce::Flag` or `Farce::Local::Flag` to change its value:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
flag = Farce::Flag.new
|
|
64
|
+
|
|
65
|
+
Thread.new do
|
|
66
|
+
flag.wait_until_set
|
|
67
|
+
puts "Flag has been activated!"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
flag.set
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
##### Timer Queue Entries
|
|
74
|
+
|
|
75
|
+
`TimerQueue#push` and `TimerQueue#try_push` now return a `TimerQueue::Entry` object on a successful push.
|
|
76
|
+
This allows you to cancel a scheduled value using the returned entry:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
queue = Farce::TimerQueue.new
|
|
80
|
+
entry = queue.push(:work, delay: 60)
|
|
81
|
+
entry.delete # => true
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
##### Expanded Vector Functionality
|
|
85
|
+
|
|
86
|
+
Improved Array compatibility for `Vector`, `Strict::Vector`, `Unshared::Vector`, and `Local::Vector`:
|
|
87
|
+
|
|
88
|
+
* Added `to_ary`, `delete`, and `delete_if`.
|
|
89
|
+
* Added range and start/length indexing, slice assignment, multiple-value `push` and `append`, and `pop(count)`.
|
|
90
|
+
* Added `shift`, `unshift`, `prepend`, `insert`, `delete_at`, `slice!`, `replace`, and `fill`.
|
|
91
|
+
* Added `map!`, `collect!`, `keep_if`, `select!`, `filter!`, `reject!`, `compact!`, and `uniq!`.
|
|
92
|
+
* Added `reverse!`, `rotate!`, `shuffle!`, `sort!`, `sort_by!`, and `flatten!`.
|
|
93
|
+
* Added `flatten`, `transpose`, `product`, `combination`, `repeated_combination`, `permutation`, and `repeated_permutation`.
|
|
94
|
+
* Added lexicographic `<=>`. Equality and hashing continue to use object identity.
|
|
95
|
+
|
|
96
|
+
##### Miscellaneous
|
|
97
|
+
|
|
98
|
+
* Unshareable objects may now be frozen.
|
|
99
|
+
* Trying to make an unshareable object shareable raises a better error message now (`Ractor::Error` with a descriptive message, instead of a `NoMethodError`).
|
|
100
|
+
* 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.
|
|
101
|
+
|
|
102
|
+
#### Bug Fixes
|
|
103
|
+
|
|
104
|
+
* 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`.
|
|
105
|
+
|
|
106
|
+
#### Performance Improvements
|
|
107
|
+
|
|
108
|
+
##### Rework CRuby native extension
|
|
109
|
+
|
|
110
|
+
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).
|
|
111
|
+
|
|
112
|
+
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.
|
|
113
|
+
|
|
114
|
+
| Operation | Before | After | Change |
|
|
115
|
+
| --------------------------------------------------- | ------- | ------- | ----------- |
|
|
116
|
+
| `Map#each` over 1024 entries | 34.1 μs | 35.2 μs | 3% slower |
|
|
117
|
+
| Transaction with a map with 16 entries | 25.2 μs | 25.3 μs | 0.4% slower |
|
|
118
|
+
| Transaction with a map with 1024 entries | 26.7 μs | 29.2 μs | 9% slower |
|
|
119
|
+
| Transaction with a vector with 10,000 entries | 26.1 μs | 29.4 μs | 13% slower |
|
|
120
|
+
| GC while keeping a vector with 10,000 entries alive | 67.6 μs | 28.6 μs | 58% faster |
|
|
121
|
+
|
|
122
|
+
Beyond these specific cases:
|
|
123
|
+
* Other iteration methods will have a similar performance impact to `Map#each` (for instance, `Map#transform_values`).
|
|
124
|
+
* Methods not involving iterations, like reading or writing a specific map entry, do not show any measurable performance impact.
|
|
125
|
+
|
|
126
|
+
Measurements were taken on CRuby 4.0.7 (arm64-darwin23) with YJIT enabled. Measurements taken against Ruby's main branch show similar results.
|
|
127
|
+
|
|
128
|
+
#### Other changes
|
|
129
|
+
|
|
130
|
+
* Avoid circular require warnings when automatically loading third-party integrations.
|
|
131
|
+
* Do not install a require hook if automatic integration loading is disabled.
|
|
132
|
+
|
|
7
133
|
### Farce 0.1.0 (2026-10-09)
|
|
8
134
|
|
|
9
|
-
|
|
135
|
+
Initial release.
|
data/README.md
CHANGED
|
@@ -117,6 +117,7 @@ puts output
|
|
|
117
117
|
- [`Farce.schedule`](#farceschedule)
|
|
118
118
|
- [Schedulers](#schedulers)
|
|
119
119
|
- [Ractor Pools](#ractor-pools)
|
|
120
|
+
- [Tasks](#tasks)
|
|
120
121
|
- [Third-Party Fiber Schedulers](#third-party-fiber-schedulers)
|
|
121
122
|
- [Integrations](#integrations)
|
|
122
123
|
- [Active Support](#active-support)
|
|
@@ -133,6 +134,7 @@ puts output
|
|
|
133
134
|
- [Constants](#constants)
|
|
134
135
|
- [Compatibility and Dependencies](#compatibility-and-dependencies)
|
|
135
136
|
- [Ruby](#ruby)
|
|
137
|
+
- [Optional Dependencies](#optional-dependencies)
|
|
136
138
|
- [Similar Projects](#similar-projects)
|
|
137
139
|
- [Installation](#installation)
|
|
138
140
|
- [Globally](#globally)
|
|
@@ -415,6 +417,23 @@ list.to_a # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
|
415
417
|
list.sum # => 45
|
|
416
418
|
```
|
|
417
419
|
|
|
420
|
+
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.
|
|
421
|
+
|
|
422
|
+
```ruby
|
|
423
|
+
list = Farce::Vector[1, 2, 3, 4]
|
|
424
|
+
|
|
425
|
+
begin
|
|
426
|
+
list.map! do |value|
|
|
427
|
+
raise "error" if value == 2
|
|
428
|
+
value * 2
|
|
429
|
+
end
|
|
430
|
+
rescue => e
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# This would return 2 if list was an array
|
|
434
|
+
list.first # => 1
|
|
435
|
+
```
|
|
436
|
+
|
|
418
437
|
#### Sets
|
|
419
438
|
|
|
420
439
|
`Farce::Set` is to `Set` what `Farce::Map` is to `Hash`. It implements the same interface, with additional atomic operations.
|
|
@@ -536,7 +555,7 @@ end
|
|
|
536
555
|
|
|
537
556
|
#### Queues
|
|
538
557
|
|
|
539
|
-
`Farce::Queue` is
|
|
558
|
+
`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
559
|
|
|
541
560
|
```ruby
|
|
542
561
|
queue = Farce::Queue.new
|
|
@@ -569,7 +588,7 @@ queue.pop # => :b
|
|
|
569
588
|
|
|
570
589
|
##### Timer Queues
|
|
571
590
|
|
|
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 `
|
|
591
|
+
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
592
|
|
|
574
593
|
```ruby
|
|
575
594
|
queue = Farce::TimerQueue.new
|
|
@@ -579,7 +598,16 @@ queue.push(:ok, in: 1.5)
|
|
|
579
598
|
queue.pop # => :ok
|
|
580
599
|
```
|
|
581
600
|
|
|
582
|
-
Timer queues are useful for implementing schedulers (which is what Farce uses them for under the hood)
|
|
601
|
+
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.
|
|
602
|
+
|
|
603
|
+
```ruby
|
|
604
|
+
queue = Farce::TimerQueue.new
|
|
605
|
+
entry = queue.push(:work, in: 60)
|
|
606
|
+
|
|
607
|
+
entry.delete # => true
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
You can also delete a value if you know its timestamp:
|
|
583
611
|
|
|
584
612
|
```ruby
|
|
585
613
|
queue = Farce::TimerQueue.new
|
|
@@ -589,6 +617,9 @@ queue.push(:first, at: timestamp - 0.1)
|
|
|
589
617
|
queue.push(:second, at: timestamp)
|
|
590
618
|
queue.push(:third, at: timestamp + 0.1)
|
|
591
619
|
|
|
620
|
+
fourth = queue.push(:fourth, at: timestamp + 0.2)
|
|
621
|
+
queue.delete(fourth)
|
|
622
|
+
|
|
592
623
|
queue.delete(:second, at: timestamp)
|
|
593
624
|
|
|
594
625
|
result = []
|
|
@@ -601,7 +632,7 @@ result # => [:first, :third]
|
|
|
601
632
|
Queues support setting a maximum capacity. If the capacity has been reached it will block any pushes to create back pressure.
|
|
602
633
|
|
|
603
634
|
```ruby
|
|
604
|
-
queue = Farce::Queue.new(2)
|
|
635
|
+
queue = Farce::Queue.new(capacity: 2)
|
|
605
636
|
|
|
606
637
|
queue.try_push(:a) # => true
|
|
607
638
|
queue.try_push(:b) # => true
|
|
@@ -683,17 +714,22 @@ The [default variants](#variants-and-scopes) of most [data structures](#data-str
|
|
|
683
714
|
|
|
684
715
|
These modes are:
|
|
685
716
|
|
|
686
|
-
| Mode
|
|
687
|
-
|
|
|
688
|
-
| `:
|
|
689
|
-
| `:
|
|
690
|
-
| `:
|
|
691
|
-
| `:
|
|
692
|
-
| `:
|
|
693
|
-
| `:
|
|
694
|
-
| `:
|
|
695
|
-
| `:
|
|
696
|
-
| `:
|
|
717
|
+
| Mode | What happens to non-shareable data | A typical use |
|
|
718
|
+
| --- | --- | --- |
|
|
719
|
+
| `:smart` (default) | Applies smart rules or a dedicated proxy, raising for unsupported values. | Share supported values with less copying. |
|
|
720
|
+
| `:smart_raise` | Applies smart rules or a dedicated proxy, with a raising fallback. | Reject unsupported transfers. |
|
|
721
|
+
| `:smart_copy` | Applies smart rules or a dedicated proxy, then tries copying and Marshal. Default for `Farce::Port`. | Send values with a copying fallback. |
|
|
722
|
+
| `:smart_move` | Applies smart rules or a dedicated proxy, then tries moving. | Hand off unsupported values. |
|
|
723
|
+
| `:copy` | Transfers a copy and leaves the original usable. | Send a snapshot of a request. |
|
|
724
|
+
| `:marshal` | Serializes a snapshot and reconstructs it once per receiving Ractor. | Transfer an Unshared container.|
|
|
725
|
+
| `:move` | Transfers ownership and makes the original inaccessible. | Hand a completed batch to a consumer. |
|
|
726
|
+
| `:local` | Keeps the same object in its originating Ractor. | Pass work between local threads or fibers. |
|
|
727
|
+
| `:make_shareable` | Calls `Ractor.make_shareable` on the original. | Publish finished configuration. |
|
|
728
|
+
| `:mutable` | Copies non-shareable objects into a [`Farce::Mutable`](#mutables). | Synchronize mutations across Ractors. |
|
|
729
|
+
| `:shareable_copy` | Makes a shareable copy and leaves the original alone. | Publish a snapshot of an editable document. |
|
|
730
|
+
| `:dedup` | Deduplicates the value, then makes it shareable. May update and freeze the original. | Reuse repeated message contents. |
|
|
731
|
+
| `:proxy` | Creates a [`Farce::Proxy`](#proxies) that executes calls in the original Ractor. | Share access to a mutable object. |
|
|
732
|
+
| `:raise` | Raises `Ractor::IsolationError`. | Enforce a shareable-data boundary. |
|
|
697
733
|
|
|
698
734
|
Here's an example:
|
|
699
735
|
|
|
@@ -974,7 +1010,7 @@ list = Farce::Vector.new
|
|
|
974
1010
|
list[0] = 42
|
|
975
1011
|
|
|
976
1012
|
# Atomically replace list[0] with 256 if the value is still 42
|
|
977
|
-
list.
|
|
1013
|
+
list.compare_and_set(0, 42, 256)
|
|
978
1014
|
```
|
|
979
1015
|
|
|
980
1016
|
#### Observability and Signaling
|
|
@@ -1187,6 +1223,43 @@ pool = Farce::Pool.new(min_size: 4, max_size: 10, grow_after: 0.1)
|
|
|
1187
1223
|
pool.schedule { puts "Hello from the pool!" }
|
|
1188
1224
|
```
|
|
1189
1225
|
|
|
1226
|
+
#### Tasks
|
|
1227
|
+
|
|
1228
|
+
If you want to retrieve the result of an asynchronous operation, or monitor if it is still running, you can use `Farce::Task`:
|
|
1229
|
+
|
|
1230
|
+
```ruby
|
|
1231
|
+
task = Farce::Task.schedule(1, 2) do |a, b|
|
|
1232
|
+
sleep 1 # simulate intense computation
|
|
1233
|
+
a + b
|
|
1234
|
+
end
|
|
1235
|
+
|
|
1236
|
+
task.alive? # => true
|
|
1237
|
+
task.value # => 3 (after approximately 1 second)
|
|
1238
|
+
task.join # would block, but doesn't, as the task has already completed
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
Tasks can also be created without immediately scheduling them, allowing you to control when and how they are executed:
|
|
1242
|
+
|
|
1243
|
+
```ruby
|
|
1244
|
+
task = Farce::Task.new { :success }
|
|
1245
|
+
task.alive? # => true
|
|
1246
|
+
task.value(0.1) # => nil (after 100 milliseconds)
|
|
1247
|
+
|
|
1248
|
+
Farce.schedule(&task)
|
|
1249
|
+
task.value # => :success
|
|
1250
|
+
```
|
|
1251
|
+
|
|
1252
|
+
This allows you to use third-party executors:
|
|
1253
|
+
|
|
1254
|
+
```ruby
|
|
1255
|
+
require "concurrent"
|
|
1256
|
+
executor = Concurrent.global_fast_executor
|
|
1257
|
+
task = Farce::Task.new { :success }
|
|
1258
|
+
executor.post(&task)
|
|
1259
|
+
|
|
1260
|
+
task.value # => :success
|
|
1261
|
+
```
|
|
1262
|
+
|
|
1190
1263
|
#### Third-Party Fiber Schedulers
|
|
1191
1264
|
|
|
1192
1265
|
Both `Farce::Scheduler` and `Farce::Pool` run a Fiber scheduler under the hood to execute tasks. This is a Farce-internal scheduler by default, but you can replace it with your own if you want:
|
|
@@ -1385,7 +1458,7 @@ Other classes Farce provides include:
|
|
|
1385
1458
|
* `Exchanger`: A synchronization point for two-way data swapping between Threads, Ractors, and/or Fibers. Drop-in replacement for concurrent-ruby's exchanger.
|
|
1386
1459
|
* `Lazy`: Lazily initialized value.
|
|
1387
1460
|
* `LazyRef`: A [reference](#references) for a lazily initialized value.
|
|
1388
|
-
* `
|
|
1461
|
+
* `ThreadExecutor`: A [task executor](#scheduling-code) creating a new thread for each unit of work. Used on TruffleRuby.
|
|
1389
1462
|
* `Walker`: A tool for walking a Ruby object tree.
|
|
1390
1463
|
* `WeakValue`: A value object version of [`WeakRef`](#weakref). Allows handling references more explicitly, without automatic method delegation.
|
|
1391
1464
|
|
|
@@ -1433,6 +1506,27 @@ Moreover:
|
|
|
1433
1506
|
* Dropping support for a CRuby version is only done in major releases.
|
|
1434
1507
|
* 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
1508
|
|
|
1509
|
+
### Optional Dependencies
|
|
1510
|
+
|
|
1511
|
+
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.
|
|
1512
|
+
|
|
1513
|
+
Farce should be compatible with these versions:
|
|
1514
|
+
|
|
1515
|
+
| Gem | Minimum Version | Release Date |
|
|
1516
|
+
|-------------------|-----------------|--------------|
|
|
1517
|
+
| `ractor-tmvar` | 0.3.0 | 2026-10-06 |
|
|
1518
|
+
| `msgpack` | 1.7.0 | 2023-03-29 |
|
|
1519
|
+
| `concurrent-ruby` | 1.1.8 | 2021-01-20 |
|
|
1520
|
+
| `dry-types` | 1.0.0 | 2019-04-23 |
|
|
1521
|
+
| `activesupport` | 5.1.0 | 2017-04-27 |
|
|
1522
|
+
| `oj` | 2.8.1 | 2014-04-21 |
|
|
1523
|
+
| `bson` | 2.0.0 | 2013-12-02 |
|
|
1524
|
+
| `psych` | 1.1.0 | 2011-03-30 |
|
|
1525
|
+
|
|
1526
|
+
Any version of `cbor`, `json`, `ractor-sharing`, `sorted_set`, and `weakref` should be supported
|
|
1527
|
+
|
|
1528
|
+
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).
|
|
1529
|
+
|
|
1436
1530
|
### Similar Projects
|
|
1437
1531
|
|
|
1438
1532
|
* [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
|
| --- | --- | --- |
|
|
@@ -337,6 +391,7 @@ The same choices appear in several Farce APIs. Containers normally default to `:
|
|
|
337
391
|
| `Farce::WeakAtom` | `new`, `store`, `update`, and other replacement operations | Weakly held values. Supports only `:raise`, `:make_shareable`, and `:dedup`. Defaults to `:raise`. |
|
|
338
392
|
| `Farce::Atom` | `new`, `store`, `swap`, `update`, and other replacement operations | The stored value. |
|
|
339
393
|
| `Farce::Lazy`, `Farce::LazyRef` | `new` | The result computed once by the factory. |
|
|
394
|
+
| `Farce::Task` | `new`, `schedule`, `try_schedule`, `in_parallel`, `on_main`, `add_argument` | Arguments and the successful result. `add_argument` overrides the mode for one argument. Scheduling can select local mode automatically. |
|
|
340
395
|
| `Farce::Molecule` | `define`, `new` | Newly created field atoms. |
|
|
341
396
|
| `Farce::Vector` | `new`, `push`, `store`, `update`, and other replacement operations | Element values. |
|
|
342
397
|
| `Farce::WeakSet` | `new`, `add`, `add?` | Weakly held elements. Supports only `:raise`, `:make_shareable`, and `:dedup`. Defaults to `:raise`. |
|
|
@@ -346,9 +401,11 @@ The same choices appear in several Farce APIs. Containers normally default to `:
|
|
|
346
401
|
| `Farce::TreeMap` | `new` | Values only. Keys follow the tree map's own rules. |
|
|
347
402
|
| `Farce::LRUMap` | `new` | Values only. Individual value hits and writes update eviction order. |
|
|
348
403
|
| `Farce::LFUMap` | `new` | Values only. Individual value hits and writes update eviction frequency. |
|
|
349
|
-
| `Farce::Scheduler` | `schedule` | Task arguments, with automatic local transfer enabled by default. |
|
|
350
|
-
| `Farce::
|
|
351
|
-
| `Farce::
|
|
404
|
+
| `Farce::Scheduler` | `schedule`, `try_schedule` | Task arguments, with automatic local transfer enabled by default. |
|
|
405
|
+
| `Farce::Unshared::Scheduler`, `Farce::Local::Scheduler` | `schedule`, `try_schedule`, `execute` | Always local. With `auto_local: false`, only `:local` and smart modes are accepted. |
|
|
406
|
+
| `Farce::ThreadExecutor` | `schedule`, `try_schedule`, `execute` | Accepts scheduler options but always keeps arguments local. |
|
|
407
|
+
| `Farce::Pool` | `schedule`, `try_schedule` | Task arguments. `:local` is rejected. |
|
|
408
|
+
| `Farce::Unshared::Pool`, `Farce::Local::Pool` | `schedule`, `try_schedule`, `execute` | Always local. With `auto_local: false`, only `:local` and smart modes are accepted. |
|
|
352
409
|
|
|
353
410
|
`Farce::Lazy` runs its shareable factory once and applies the mode to the result. With
|
|
354
411
|
`:copy`, each Ractor receives its own cached copy. Use `Farce::Strict::Lazy` when the
|
|
@@ -394,7 +451,7 @@ retries.push({ attempt: 2 }, at: Farce::Clock.now, mode: :make_shareable)
|
|
|
394
451
|
Farce::Ractor.shareable?(retries.pop) # => true
|
|
395
452
|
```
|
|
396
453
|
|
|
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.
|
|
454
|
+
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
455
|
|
|
399
456
|
### Exchange mutable messages with a partner
|
|
400
457
|
|
|
@@ -487,7 +544,7 @@ For containers that retain values, `:copy` gives each Ractor its own copy of a s
|
|
|
487
544
|
|
|
488
545
|
### Pass task data as arguments
|
|
489
546
|
|
|
490
|
-
|
|
547
|
+
Schedulers and pools accept `mode:` for task arguments in `schedule` and `try_schedule`. Pass mutable data as arguments instead of capturing it from the surrounding scope. A non-local task's block must be convertible to a shareable proc.
|
|
491
548
|
|
|
492
549
|
```ruby
|
|
493
550
|
pool = Farce::Pool.new(max_size: 2)
|
|
@@ -503,7 +560,7 @@ pool.close
|
|
|
503
560
|
results.close
|
|
504
561
|
```
|
|
505
562
|
|
|
506
|
-
A scheduler defaults to `auto_local: true`, preserving the block and its arguments when scheduling from its owning Ractor. Set `auto_local: false` to enforce the requested mode there.
|
|
563
|
+
A scheduler defaults to `auto_local: true`, preserving the block and its arguments when scheduling from its owning Ractor. Set `auto_local: false` to enforce the requested mode there. `Farce::Pool` may choose another Ractor for any task, so it rejects `:local` and does not apply automatic local transfer. If you want a Pool with `local` support, use `Farce::Unshared::Pool` or `Farce::Local::Pool` instead.
|
|
507
564
|
|
|
508
565
|
## Under the hood
|
|
509
566
|
|
|
@@ -521,20 +578,23 @@ envelope.value[:ids] # => [1, 2]
|
|
|
521
578
|
envelope.value.equal?(envelope.value) # => true
|
|
522
579
|
```
|
|
523
580
|
|
|
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.
|
|
581
|
+
`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
582
|
|
|
526
583
|
### When copying and moving happen
|
|
527
584
|
|
|
528
585
|
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
586
|
|
|
587
|
+
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.
|
|
588
|
+
|
|
530
589
|
| Envelope | Who can open it? | What repeated reads return |
|
|
531
590
|
| --- | --- | --- |
|
|
532
591
|
| `Envelope::Copy` | Any Ractor. | That Ractor's cached copy. |
|
|
592
|
+
| `Envelope::Marshal` | Any Ractor. | That Ractor's cached reconstruction. |
|
|
533
593
|
| `Envelope::Move` | The first Ractor to claim it. | The owning Ractor's received object. |
|
|
534
594
|
| `Envelope::Local` | Its creating Ractor. | The original object. |
|
|
535
595
|
| `Envelope::Share` | Any Ractor. | The shared payload. |
|
|
536
596
|
|
|
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.
|
|
597
|
+
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
598
|
|
|
539
599
|
### Forward envelopes without opening them
|
|
540
600
|
|
|
@@ -573,7 +633,7 @@ Both queues accept the envelope in `:raise` mode because the wrapper is shareabl
|
|
|
573
633
|
|
|
574
634
|
### Mode managers prepare values and open their own envelopes
|
|
575
635
|
|
|
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.
|
|
636
|
+
`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
637
|
|
|
578
638
|
```ruby
|
|
579
639
|
manager = Farce::ModeManager.new(mode: :copy)
|
|
@@ -602,7 +662,7 @@ Keep one manager per instance when building an abstraction around shareable stor
|
|
|
602
662
|
class WorkInbox
|
|
603
663
|
include Farce::Shareable
|
|
604
664
|
|
|
605
|
-
def initialize(mode: :
|
|
665
|
+
def initialize(mode: :smart)
|
|
606
666
|
@manager = Farce::ModeManager.new(mode: mode)
|
|
607
667
|
@storage = Farce::Queue.new(mode: :raise)
|
|
608
668
|
super()
|
data/docs/scopes.md
CHANGED
|
@@ -238,7 +238,7 @@ Pass the needed data as task arguments when crossing an execution boundary. Use
|
|
|
238
238
|
|
|
239
239
|
## Classes that support scopes
|
|
240
240
|
|
|
241
|
-
|
|
241
|
+
The classes in this table accept the same five scopes. The scope determines which backing container or resource an operation uses. Each class retains its own behavior, such as queue ordering or weak references.
|
|
242
242
|
|
|
243
243
|
| Class | What each scope gets |
|
|
244
244
|
| --- | --- |
|
|
@@ -266,6 +266,8 @@ These classes accept the same five scopes. The scope determines which backing co
|
|
|
266
266
|
| `Farce::Local::Lease` | An independently initialized resource to borrow. |
|
|
267
267
|
| `Farce::Local::LeaseMap` | An independently initialized set of named resources. |
|
|
268
268
|
| `Farce::Local::LeasePool` | A pool with its own resources and capacity. |
|
|
269
|
+
| `Farce::Local::Scheduler` | A task scheduler with its own worker and lifecycle. |
|
|
270
|
+
| `Farce::Local::Pool` | An elastic pool of thread workers with independent scaling and lifecycle. |
|
|
269
271
|
|
|
270
272
|
### Count within each scope
|
|
271
273
|
|