farce 0.3.0 → 0.4.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.
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
@@ -0,0 +1,74 @@
1
+ <!--
2
+ # @title Compatibility and Dependencies
3
+ -->
4
+
5
+ # Compatibility and Dependencies
6
+
7
+ Farce has no mandatory dependencies beyond Ruby itself.
8
+
9
+ ## Ruby
10
+
11
+ Each Farce release is expected to be compatible with:
12
+
13
+ * The [latest patch release](https://www.ruby-lang.org/en/downloads/releases/) for each [CRuby](https://www.ruby-lang.org/en/) version [still receiving bug fixes](https://www.ruby-lang.org/en/downloads/branches/).
14
+ * Ruby's [master branch](https://github.com/ruby/ruby/tree/master) at the time of release (i.e., the upcoming major version of CRuby).
15
+ * The latest stable release of [JRuby](https://www.jruby.org/) and [TruffleRuby](https://truffleruby.dev/) (both in native and GraalVM modes).
16
+
17
+ Moreover:
18
+
19
+ * Dropping support for a CRuby version is only done in major releases.
20
+ * 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/).
21
+
22
+ ## Optional Dependencies
23
+
24
+ Farce's optional [integrations](integrations.md#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.
25
+
26
+ Farce should be compatible with these versions:
27
+
28
+ | Gem | Minimum Version | Release Date |
29
+ |-------------------|-----------------|--------------|
30
+ | `ractor-tmvar` | 0.4.0 | 2026-10-11 |
31
+ | `msgpack` | 1.7.0 | 2023-03-29 |
32
+ | `concurrent-ruby` | 1.1.8 | 2021-01-20 |
33
+ | `dry-types` | 1.0.0 | 2019-04-23 |
34
+ | `activesupport` | 5.1.0 | 2017-04-27 |
35
+ | `oj` | 2.8.1 | 2014-04-21 |
36
+ | `bson` | 2.0.0 | 2013-12-02 |
37
+ | `psych` | 1.1.0 | 2011-03-30 |
38
+
39
+ Any version of `cbor`, `json`, `ractor-sharing`, `sorted_set`, and `weakref` should be supported.
40
+
41
+ If you want to use any of these in a version that is incompatible with Farce, you can [disable automatic integration loading](integrations.md#disable-automatic-loading).
42
+
43
+ ## Similar Projects
44
+
45
+ * [ractor-shim](https://github.com/eregon/ractor-shim/) provides similar functionality to `Farce::Ractor`. See [the comparison document](gems/ractor-shim.md) for more details.
46
+ * [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) provides a more complete set of concurrency primitives than Farce, but is not compatible with Ractors.
47
+ * [ratomic](https://mperham.github.io/ratomic/) has overlapping functionality with Farce for basic data structures like maps, counters, and queues.
48
+ * [ractor_safe](https://github.com/jhawthorn/ractor_safe/) has overlapping functionality with Farce for basic data structures like maps, counters, and queues.
49
+ * [ractor-sharing](https://github.com/ko1/ractor-sharing) has overlapping functionality with Farce for basic data structures like maps, counters, queues, as well as software transactional memory.
50
+
51
+ All of the above projects can safely be used alongside Farce in the same application.
52
+
53
+ ## Known Issues and Limitations
54
+
55
+ ### Possible discrepancy regarding frozen state in Ruby and C
56
+
57
+ > I agree that **freezing means the object's own state is immutable**, not just its instance variables, so we should not freeze [*shareable, mutable object*]. Forbidding instance variables on them is the right approach. [...] **A shareable object that is not frozen never has instance variables**. This should also hold when C extensions define such objects in the future.
58
+ > — *Yukihiro Matsumoto* (Ruby Issue [#22291](https://bugs.ruby-lang.org/issues/22291#note-4), emphasis added)
59
+
60
+ In Ruby, Farce objects reflect their frozen state accurately. If a map returns `true` for `frozen?`, you cannot add, remove, or replace its entries.
61
+
62
+ There are some technical challenges implementing this behavior: From within Ruby, you cannot mark an object as Ractor shareable without freezing it first. This is possible from a C-extension, but then instance variables can no longer be used, so state tracking needs to happen purely at the C level or outside of the Ruby object.
63
+
64
+ To work around this, Farce follows a hybrid approach, marking objects with state purely defined in a C extension as Ractor shareable without freezing them, and reimplementing freezing behavior in Ruby for objects where this isn't safely possible.
65
+
66
+ This means:
67
+
68
+ * `frozen?` and `freeze` will behave as expected for all Farce classes.
69
+ * `Kernel.instance_method(:frozen?).bind_call(object)` might report a different value from `object.frozen?`
70
+ * C-level checks for frozen state, such as `RB_OBJ_FROZEN`, might differ from what Ruby-level methods report.
71
+
72
+ ## Read Next
73
+
74
+ [README](../README.md#features) · [Installation](installation.md) · [Integrations](integrations.md)
@@ -0,0 +1,151 @@
1
+ <!--
2
+ # @title Concurrency
3
+ -->
4
+
5
+ # Concurrency
6
+
7
+ Farce is built for concurrent and parallel code execution.
8
+
9
+ ## Locks
10
+
11
+ Farce ships with two lock classes:
12
+ * `Lock` is a drop-in replacement for Ruby's `Mutex`.
13
+ * `ReadWriteLock` exposes `with_read_lock`/`with_write_lock` to allow multiple concurrent reads, but exclusive write access.
14
+
15
+ However, Farce exposes many APIs to eliminate the need for locks altogether.
16
+
17
+ ## Atomic Operations
18
+
19
+ Farce's [data structures](data-structures.md#data-structures) all expose a range of methods for atomic operations:
20
+
21
+ ```ruby
22
+ # A Hash-like object
23
+ map = Farce::Map.new
24
+
25
+ # Atomically store something for :key if it hasn't been set
26
+ map.store_if_absent(:key) { "initial value" }
27
+
28
+ # Atomically update :key
29
+ map.update(:key, &:upcase)
30
+
31
+ # An Array-like object
32
+ list = Farce::Vector.new
33
+ list[0] = 42
34
+
35
+ # Atomically replace list[0] with 256 if the value is still 42
36
+ list.compare_and_set(0, 42, 256)
37
+ ```
38
+
39
+ ## Observability and Signaling
40
+
41
+ All the [data structures](data-structures.md#data-structures) come with extra observability methods, which eliminate the need for using [condition variables](https://docs.ruby-lang.org/en/4.0/Thread/ConditionVariable.html) and [mutexes](https://docs.ruby-lang.org/en/4.0/Thread/Mutex.html).
42
+
43
+ Even setting aside that these don't work across ractors, this drastically reduces the risk of race conditions (very easy to do if you <u>don't</u> use the same mutex everywhere) or blocking code you don't need to block (very easy to do if you <u>do</u> use the same mutex everywhere).
44
+
45
+ So instead of sharing locks you can simply wait for a change to happen!
46
+
47
+ ```ruby
48
+ # block until the stored value for :key is greater than 10
49
+ map.wait_until(:key) { it > 10 }
50
+
51
+ # block until the value for an atom is no longer :initial
52
+ atom.wait_until_changed(:initial)
53
+
54
+ # block until a counter has reached at least 20
55
+ counter.wait_while_below(20)
56
+ ```
57
+
58
+ If these conditions are met right away, these never block.
59
+
60
+ But what about more complex conditions, involving multiple variables, or objects from other libraries that don't implement similar methods? No need to reach for a lock! You can use a Signal!
61
+
62
+ ```ruby
63
+ signal = Farce::Signal.new
64
+ target = 100
65
+ counter = Farce::Counter.new
66
+
67
+ # A Thread that keeps reducing the target value every 20 milliseconds
68
+ Thread.new do
69
+ while target.positive?
70
+ sleep 0.02
71
+ target -= 1
72
+ signal.broadcast # notify everyone else
73
+ end
74
+ end
75
+
76
+ # A Ractor that counts up in 10 millisecond intervals
77
+ Ractor.new(counter, signal) do |counter, signal|
78
+ while counter < 100
79
+ sleep 0.01
80
+ counter.increment
81
+ signal.broadcast # notify everyone else
82
+ end
83
+ end
84
+
85
+ # wait until the counter is at or above the target
86
+ signal.wait_until { counter >= target }
87
+ ```
88
+
89
+ ## Transactions
90
+
91
+ This all sounds great. But you still might want to reach for a lock if you want to modify more than one data structure, if their state is tightly coupled (i.e., updating one without yet updating the other would leave your code in an invalid state).
92
+
93
+ And a lock is an acceptable solution here. Again, you might want to wrap all the read access in a lock, as there will still be an invalid state. And you also want to roll back any changes already made while holding the lock, if an exception occurs. That is the standard approach in a lot of Ruby code.
94
+
95
+ Farce offers an alternative. It implements [STM-style transactions](https://en.wikipedia.org/wiki/Software_transactional_memory). You supply a block of code that makes changes to multiple data structures. These changes are only written to these data structures in a commit phase after the block finishes, and they either all succeed or all fail, and they only succeed if the values you've read from any of these data structures haven't changed.
96
+
97
+ Otherwise the block is rerun.
98
+
99
+ And this isn't limited to special `TVar` containers, like the APIs provided by concurrent-ruby or ractor-sharing. It supports Farce's main data structures, including [maps](data-structures.md#maps), [vectors](data-structures.md#vectors), [sets](data-structures.md#sets), [atoms](data-structures.md#atoms), and [molecules](data-structures.md#molecules). Moreover, it also supports [mutables](sharing.md#mutables), meaning you can turn most Ruby objects into something transaction compatible fairly easily!
100
+
101
+ In contrast to other implementations mentioned above, there is no implicit tracking. You need to explicitly add an object to a transaction. This also avoids any uncertainty around nested transactions and unexpected rollbacks.
102
+
103
+ ```ruby
104
+ accounts = Farce::Map.new({a: 100, b: 200})
105
+
106
+ # transfer 80 from :b to :a, but only if both succeed
107
+ success = Farce.transaction(accounts) do |tx, accounts|
108
+ tx.abort! if accounts[:b] < 80
109
+ accounts[:a] += 80
110
+ accounts[:b] -= 80
111
+ end
112
+
113
+ if success
114
+ puts "Transaction succeeded"
115
+ else
116
+ puts "Transaction failed"
117
+ end
118
+ ```
119
+
120
+ In the above example, `accounts` was added to the transaction right away. But you can also add new objects to the transaction programmatically by calling `tx[object]`. All reads and writes need to happen through the wrapper object returned by that call (or passed to the block).
121
+
122
+ ### Maps and Sets
123
+
124
+ Maps and sets use fine-grained transaction tracking. In the above example, if accounts had another entry, `:c`, its value changing would not impact the transaction at all.
125
+
126
+ Similarly, if your transaction uses `Transaction::Map#size` as input, only a change in size would trigger a rerun, not a change in content.
127
+
128
+ This is ideal for scenarios where you use maps as a general data store and sets to track whether an operation was performed on an object (a common pattern to prevent infinite recursion).
129
+
130
+ ### TVars
131
+
132
+ Farce doesn't come with a `TVar` class. You can just use an [atom](data-structures.md#atoms) instead.
133
+
134
+ But it does come with built-in support for [`Concurrent::TVar`](https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/TVar.html) and [`Ractor::TVar`](https://github.com/ko1/ractor-sharing/blob/main/docs/tvar.md) (assuming you also load these libraries, see [integrations](integrations.md#integrations)).
135
+
136
+ ```ruby
137
+ tvar = Concurrent::TVar.new(50)
138
+ map = Farce::Map.new({a: 100, b: 200})
139
+
140
+ Farce.transaction(map, tvar) do |_, map, tvar|
141
+ # subtract tvar's value from a and b, then set tvar to 0
142
+ # no invalid in-between state is ever visible to anything outside of this transaction
143
+ map[:a] -= tvar.value
144
+ map[:b] -= tvar.value
145
+ tvar.value = 0
146
+ end
147
+ ```
148
+
149
+ ## Read Next
150
+
151
+ [README](../README.md#features) · [Data Structures](data-structures.md) · [Scheduling Code](scheduling.md)
@@ -0,0 +1,313 @@
1
+ <!--
2
+ # @title Data Structures
3
+ -->
4
+
5
+ # Data Structures
6
+
7
+ Farce provides a range of data structures that are ractor-safe, mutable, and expose high-level concurrency APIs in addition to standard Ruby APIs for the built-in classes it offers replacements for.
8
+
9
+ ## Contents
10
+
11
+ - [Data Structures](#data-structures)
12
+ - [Contents](#contents)
13
+ - [Maps](#maps)
14
+ - [Vectors](#vectors)
15
+ - [Sets](#sets)
16
+ - [Atoms](#atoms)
17
+ - [Counters](#counters)
18
+ - [Flags](#flags)
19
+ - [References](#references)
20
+ - [Molecules](#molecules)
21
+ - [Queues](#queues)
22
+ - [Priority Queues](#priority-queues)
23
+ - [Timer Queues](#timer-queues)
24
+ - [Queue capacity](#queue-capacity)
25
+ - [Read Next](#read-next)
26
+
27
+ ## Maps
28
+
29
+ Maps are Hash-like key-value data structures. They do not preserve insertion order.
30
+
31
+ They implement almost all methods Ruby's Hash offers:
32
+
33
+ ```ruby
34
+ map = Farce::Map.new
35
+ map[:foo] = :bar
36
+
37
+ map.merge! answer: 42
38
+ map.transform_values! { -it.to_s }
39
+ map.to_h # => {answer: "42", foo: "bar"}
40
+ ```
41
+
42
+ In addition, they come with a range of [atomic operations](concurrency.md#atomic-operations), and have built-in key normalization support:
43
+
44
+ ```ruby
45
+ # normalize_keys can be a symbol, proc, hash, or another map
46
+ map = Farce::Map.new({ a: 10 }, normalize_keys: :to_s)
47
+
48
+ # Atomic upsert operation
49
+ 2.times { map.upsert(:b, 42) { it * map[:a] } }
50
+
51
+ # Keys have been converted to strings
52
+ map.keys.sort # => ["a", "b"]
53
+ ```
54
+
55
+ Besides the standard map implementation, Farce includes a range of specialized maps:
56
+
57
+ * `LRUMap` will evict the least recently used entry to not grow beyond the maximum size.
58
+ * `LFUMap` will evict the least frequently used entry to not grow beyond the maximum size.
59
+ * `LeaseMap` manages [leases](sharing.md#leases) associated with known keys.
60
+ * `TreeMap` uses a [red-black tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree) instead of a [hash table](https://en.wikipedia.org/wiki/Hash_table) to store its entries, keeping them sorted by the key's value.
61
+ * `WeakMap` only holds weak references to its keys and values, automatically removing entries when the corresponding key or value gets garbage collected.
62
+ * `WeakKeyMap` only holds weak references to its keys, automatically removing entries when the corresponding key gets garbage collected.
63
+ * `WeakValueMap` only holds weak references to its values, automatically removing entries when the corresponding value gets garbage collected.
64
+
65
+ For example, bounded maps are useful for implementing caches:
66
+
67
+ ```ruby
68
+ cache = Farce::LRUMap.new(max_size: 2)
69
+
70
+ cache[:first] = 1
71
+ cache[:second] = 2
72
+ cache[:first] # makes sure :first was accessed more recently than :second
73
+ cache[:third] = 3
74
+
75
+ cache.key?(:second) # => false
76
+ ```
77
+
78
+ ## Vectors
79
+
80
+ `Farce::Vector` is to `Array` what `Farce::Map` is to `Hash`. It implements the same interface, with additional atomic operations.
81
+
82
+ ```ruby
83
+ list = Farce::Vector.new
84
+ 10.times { list << it }
85
+ list.to_a # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
86
+ list.sum # => 45
87
+ ```
88
+
89
+ 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.
90
+
91
+ ```ruby
92
+ list = Farce::Vector[1, 2, 3, 4]
93
+
94
+ begin
95
+ list.map! do |value|
96
+ raise "error" if value == 2
97
+ value * 2
98
+ end
99
+ rescue => e
100
+ end
101
+
102
+ # This would return 2 if list was an array
103
+ list.first # => 1
104
+ ```
105
+
106
+ ## Sets
107
+
108
+ `Farce::Set` is to `Set` what `Farce::Map` is to `Hash`. It implements the same interface, with additional atomic operations.
109
+
110
+ ```ruby
111
+ jobs = Farce::Set[:compile, :test]
112
+ jobs.add? :publish # => jobs
113
+ jobs.add? :test # => nil
114
+ jobs.include? :publish # => true
115
+ ```
116
+
117
+ Besides the standard set implementation, Farce includes two specialized sets:
118
+
119
+ * `SortedSet` keeps its entries in order based on value comparison.
120
+ * `WeakSet` only holds weak references to its entries.
121
+
122
+ ## Atoms
123
+
124
+ Atoms are value containers. These store a single reference to any Ruby object, and expose atomic operations for mutating these values:
125
+
126
+ ```ruby
127
+ atom = Farce::Atom.new("initial value")
128
+ atom.value # => "initial value"
129
+
130
+ # atoms are ractor-shareable
131
+ Ractor.new(atom) do |atom|
132
+ atom.update { |current| current + " updated" } # => "initial value updated"
133
+ end
134
+
135
+ # waits for the other ractor to perform its update
136
+ atom.wait_until_changed "initial value"
137
+ ```
138
+
139
+ `WeakAtom` only keeps a weak reference for its current value, reverting to `nil` when the current value gets garbage collected.
140
+
141
+ ## Counters
142
+
143
+ Ractor-shareable, atomic counter. This is a stateful, numeric object.
144
+
145
+ It is similar to creating an [atom](#atoms) for an integer, but significantly faster, as it will use a truly lock-free implementation on most CPU/Ruby combinations.
146
+
147
+ ```ruby
148
+ counter = Farce::Counter.new
149
+ counter.value # => 0
150
+
151
+ # Increase the counter by 1
152
+ counter.increment
153
+ counter.value # => 1
154
+
155
+ # Increase the counter by 5 on another Ractor
156
+ Ractor.new(counter) { it.add(5) }.join
157
+
158
+ counter.value # => 6
159
+ ```
160
+
161
+ ## Flags
162
+
163
+ Ractor-shareable, atomic boolean.
164
+
165
+ Just like Counter for Integer, this is similar to creating an [atom](#atoms) for a boolean, but significantly faster, as it will use a truly lock-free implementation on most CPU/Ruby combinations.
166
+
167
+ ```ruby
168
+ flag = Farce::Flag.new(true)
169
+ flag.value # => true
170
+ ```
171
+
172
+ ## References
173
+
174
+ Atoms, as well as other value objects, like counters and flags, can be wrapped in a reference object, which delegates all methods to the given atom for convenience:
175
+
176
+ ```ruby
177
+ atom = Farce::Atom.new(42)
178
+ ref = Farce::Reference.new(atom)
179
+
180
+ ref.to_s # => "42"
181
+ ref > 10 # => true
182
+
183
+ atom.value = :foo
184
+ ref.to_s # => "foo"
185
+ ```
186
+
187
+ ## Molecules
188
+
189
+ Molecules are structures made up of multiple atoms. Clever naming, right?
190
+
191
+ They are similar to Ruby's built-in `Struct` and `Data` classes, but their members are stored in atoms:
192
+
193
+ ```ruby
194
+ Request = Farce::Molecule.define(:verb, :path)
195
+ request = Request.new("GET", "/index.html")
196
+ request.verb # => "GET"
197
+ request.verb = "POST"
198
+
199
+ # This doesn't succeed, as the verb isn't "HEAD"
200
+ request.verb_atom.compare_and_set("HEAD", "GET")
201
+ request.verb # => "POST"
202
+ ```
203
+
204
+ You can also pass a block to add additional methods:
205
+
206
+ ```ruby
207
+ Request = Farce::Molecule.define(:verb, :path) do
208
+ def to_s = "#{verb} #{path}"
209
+ end
210
+
211
+ Request.new(verb: "GET", path: "/").to_s # => "GET /"
212
+ ```
213
+
214
+ You can also inherit from the generated class:
215
+
216
+ ```ruby
217
+ class Request < Farce::Molecule.define(:verb, :path)
218
+ def to_s = "#{verb} #{path}"
219
+ end
220
+ ```
221
+
222
+ ## Queues
223
+
224
+ `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.md#sharing-modes).
225
+
226
+ ```ruby
227
+ queue = Farce::Queue.new
228
+ queue.push(:ok)
229
+ queue.pop # => :ok
230
+ queue.try_pop # => nil
231
+ ```
232
+
233
+ Queues provide additional features over Ruby's queue:
234
+ * Like the rest of Farce, they work with Ractors (implementing [sharing modes](sharing.md#sharing-modes) for their [default variant](variants-and-scopes.md#variants-and-scopes)).
235
+ * In addition to closing, they allow sealing, which prevents pushes but still allows pulling from the queue until it has been drained.
236
+ * They have an API to wait for them to be ready for a push or pull based on capacity without actually adding or removing values.
237
+ * They can optionally monitor how long the oldest item has been sitting in the queue (which is great for building auto-scaling on top of the queue).
238
+
239
+ The [strict variant](variants-and-scopes.md#available-variants) has comparable performance to `SizedQueue`, while the default variant pays a fixed overhead for [handling unshareable data](sharing.md#sharing-unshareable-data). Both variants outperform other third-party, Ractor-shareable queues (none of which are Fiber scheduler compatible, which Farce's queues are).
240
+
241
+ ### Priority Queues
242
+
243
+ In addition to normal queues, which are FIFO, Farce also offers a `PriorityQueue`, where values are removed from the queue in order of priority instead of insertion order:
244
+
245
+ ```ruby
246
+ # use order: :descending to reverse priorities
247
+ queue = Farce::PriorityQueue.new
248
+
249
+ queue.push :a, priority: 2
250
+ queue.push :b, priority: 1
251
+
252
+ queue.pop # => :b
253
+ ```
254
+
255
+ ### Timer Queues
256
+
257
+ Timer queues are similar to priority queues, sorted by float values, but these values are timestamps (based on [Farce::Clock](utilities.md#additional-classes), not on Ruby's Time, which isn't monotonic). Any `pop` will block until the next timestamp has been reached.
258
+
259
+ ```ruby
260
+ queue = Farce::TimerQueue.new
261
+ queue.push(:ok, in: 1.5)
262
+
263
+ # blocks for ~1.5 seconds
264
+ queue.pop # => :ok
265
+ ```
266
+
267
+ 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.
268
+
269
+ ```ruby
270
+ queue = Farce::TimerQueue.new
271
+ entry = queue.push(:work, in: 60)
272
+
273
+ entry.delete # => true
274
+ ```
275
+
276
+ You can also delete a value if you know its timestamp:
277
+
278
+ ```ruby
279
+ queue = Farce::TimerQueue.new
280
+ timestamp = Farce.clock(in: 0.2)
281
+
282
+ queue.push(:first, at: timestamp - 0.1)
283
+ queue.push(:second, at: timestamp)
284
+ queue.push(:third, at: timestamp + 0.1)
285
+
286
+ fourth = queue.push(:fourth, at: timestamp + 0.2)
287
+ queue.delete(fourth)
288
+
289
+ queue.delete(:second, at: timestamp)
290
+
291
+ result = []
292
+ result << queue.pop until queue.empty?
293
+ result # => [:first, :third]
294
+ ```
295
+
296
+ ### Queue capacity
297
+
298
+ Queues support setting a maximum capacity. If the capacity has been reached it will block any pushes to create back pressure.
299
+
300
+ ```ruby
301
+ queue = Farce::Queue.new(capacity: 2)
302
+
303
+ queue.try_push(:a) # => true
304
+ queue.try_push(:b) # => true
305
+ queue.try_push(:c) # => false
306
+ ```
307
+
308
+ Normal queues have a default capacity of 1024. You can explicitly set the capacity to `nil` to create an unbounded queue.
309
+ Priority queues and timer queues do not have a default capacity, so earlier entries are not getting blocked unexpectedly.
310
+
311
+ ## Read Next
312
+
313
+ [README](../README.md#features) · [Sharing Unshareable Data](sharing.md) · [Concurrency](concurrency.md) · [Variants and Scopes](variants-and-scopes.md)
@@ -0,0 +1,63 @@
1
+ <!--
2
+ # @title Installation
3
+ -->
4
+
5
+ # Installation
6
+
7
+ ## Globally
8
+
9
+ To install Farce globally, you can use the following command:
10
+
11
+ ```console
12
+ $ gem install farce
13
+ ```
14
+
15
+ ## As a project dependency
16
+
17
+ If you want to use Farce directly in your project, it is recommended to do so via [Bundler](https://bundler.io).
18
+ Add Farce to your `Gemfile`:
19
+
20
+ ```ruby
21
+ source "https://gem.coop" # or "https://rubygems.org"
22
+
23
+ gem "farce"
24
+ ```
25
+
26
+ Then run `bundle install` to install the dependencies.
27
+
28
+ ## As a library dependency
29
+
30
+ Farce's main purpose is to be used as a dependency for other libraries. As such, it will most commonly be added as a [runtime dependency](https://guides.rubygems.org/specification-reference/#add_dependency) to your gemspec:
31
+
32
+ ```ruby
33
+ Gem::Specification.new do |spec|
34
+ # ...
35
+ spec.add_dependency "farce"
36
+ end
37
+ ```
38
+
39
+ ## Local setup
40
+
41
+ If you want to work on Farce itself, you can clone the repository and use [mise](https://mise.jdx.dev) to set everything up:
42
+
43
+ For more details, or if you aren't using mise, check the [contribution guidelines](../CONTRIBUTING.md).
44
+
45
+ ```console
46
+ $ git clone https://github.com/rkh/farce.git # prefix with `jj` if you're using Jujutsu
47
+ $ cd farce
48
+ $ mise run
49
+ ```
50
+
51
+ ## Loading Farce
52
+ You should always require `farce`, rather than any other files in `lib`. Other files are not intended as entry points.
53
+
54
+ ```ruby
55
+ require "farce"
56
+ ```
57
+
58
+ Constants (classes, modules, etc.) under the `Farce` namespace are loaded lazily (thread- and ractor-safe), so there is no
59
+ need to specifically load any particular file.
60
+
61
+ ## Read Next
62
+
63
+ [README](../README.md#features) · [Introduction](introduction.md) · [Compatibility and Dependencies](compatibility.md)