farce 0.3.0-arm-linux-gnu → 0.4.0-arm-linux-gnu
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/README.md +117 -1489
- data/docs/compatibility.md +74 -0
- data/docs/concurrency.md +151 -0
- data/docs/data-structures.md +313 -0
- data/docs/installation.md +63 -0
- data/docs/integrations.md +124 -0
- data/docs/introduction.md +266 -0
- data/docs/scheduling.md +176 -0
- data/docs/sharing.md +211 -0
- data/docs/shims.md +144 -0
- data/docs/utilities.md +93 -0
- data/docs/variants-and-scopes.md +92 -0
- data/lib/farce/abstract/flag.rb +6 -0
- data/lib/farce/backports.rb +274 -0
- data/lib/farce/engine/jruby/fiber_scheduler.rb +3 -0
- data/lib/farce/engine/jruby/scheduler_synchronization.rb +87 -0
- data/lib/farce/engine/ruby/3.4/null_port.rb +21 -0
- data/lib/farce/engine/ruby/3.4/patch.rb +67 -0
- data/lib/farce/engine/ruby/3.4/port.rb +13 -12
- data/lib/farce/engine/ruby/3.4/ractor_methods.rb +0 -1
- data/lib/farce/engine/ruby/4.0/null_port.rb +36 -0
- data/lib/farce/engine/ruby/4.0/port.rb +1 -2
- data/lib/farce/engine/ruby/4.1/null_port.rb +5 -0
- data/lib/farce/engine/ruby.rb +7 -0
- data/lib/farce/engine/shared/null_port.rb +18 -0
- data/lib/farce/engine/shared/port.rb +41 -3
- data/lib/farce/engine/shared/queue.rb +4 -4
- data/lib/farce/engine/shared/ractor_methods.rb +2 -2
- data/lib/farce/engine/shared/ractor_selector.rb +73 -0
- data/lib/farce/extension.rb +150 -0
- data/lib/farce/integrations/ractor_tmvar.rb +4 -1
- data/lib/farce/internal/fake_ractor.rb +10 -2
- data/lib/farce/internal/scheduler_lifecycle.rb +7 -4
- data/lib/farce/null_port.rb +12 -0
- data/lib/farce/system.rb +10 -2
- data/lib/farce/version.rb +1 -1
- data/lib/farce.rb +1 -1
- metadata +22 -1
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
# Backports for Ractor and related functionality to older Ruby versions.
|
|
7
|
+
#
|
|
8
|
+
# ## Activating Backports
|
|
9
|
+
#
|
|
10
|
+
# There are three ways of using these backports:
|
|
11
|
+
# * Activating them globally via: {.install}
|
|
12
|
+
# * Using them within a specific scope via refinements: `using Farce::Backports`
|
|
13
|
+
# * Calling methods on `Farce::Backports` directly
|
|
14
|
+
#
|
|
15
|
+
# Only the first method will also define the global constants `Ractor` and `Ractor::Port`
|
|
16
|
+
# if they are not natively available.
|
|
17
|
+
#
|
|
18
|
+
# @example Activating the backports globally
|
|
19
|
+
# Farce::Backports.install
|
|
20
|
+
# port = Ractor.current.default_port
|
|
21
|
+
#
|
|
22
|
+
# @example Using the backports within a specific namespace via refinements
|
|
23
|
+
# # Within a namespace
|
|
24
|
+
# module MyNamespace
|
|
25
|
+
# using Farce::Backports
|
|
26
|
+
#
|
|
27
|
+
# def self.send_to(ractor_or_port, message)
|
|
28
|
+
# port = ractor_or_port.default_port if ractor_or_port.is_a?(Ractor)
|
|
29
|
+
# port.send(message)
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# @example Activating the backports for a specific file
|
|
34
|
+
# # top level of the file
|
|
35
|
+
# using Farce::Backports
|
|
36
|
+
#
|
|
37
|
+
# ractor = Ractor.new { sleep 10 }
|
|
38
|
+
# monitor = Ractor.new { puts "exit status: #{receive}" }
|
|
39
|
+
#
|
|
40
|
+
# monitor.monitor(ractor.default_port)
|
|
41
|
+
# monitor.join
|
|
42
|
+
#
|
|
43
|
+
# @example Activating the backports for a given proc (Ruby 4.1+ only)
|
|
44
|
+
# callback = ->(ractor) { ractor.default_port }
|
|
45
|
+
# callback.refined(Farce::Backports).call(Ractor.current)
|
|
46
|
+
#
|
|
47
|
+
# @example Calling methods on `Farce::Backports` directly
|
|
48
|
+
# port = Farce::Backports.default_port(Ractor.current)
|
|
49
|
+
#
|
|
50
|
+
# @!method alive?(ractor)
|
|
51
|
+
# @scope class
|
|
52
|
+
# Backport for {https://docs.ruby-lang.org/en/master/Ractor.html#method-i-alive-3F Ractor#alive?}
|
|
53
|
+
# @example
|
|
54
|
+
# using Farce::Backports
|
|
55
|
+
# ractor = Ractor.new { sleep 10 }
|
|
56
|
+
# ractor.alive? # => true
|
|
57
|
+
# @param ractor [Ractor] the ractor to check
|
|
58
|
+
# @return [Boolean] whether the ractor is alive
|
|
59
|
+
#
|
|
60
|
+
# @!method default_port(ractor)
|
|
61
|
+
# @scope class
|
|
62
|
+
# Backport for {https://docs.ruby-lang.org/en/4.0/Ractor.html#method-i-default_port Ractor.default_port}
|
|
63
|
+
# @example
|
|
64
|
+
# using Farce::Backports
|
|
65
|
+
# ractor = Ractor.new { puts receive }
|
|
66
|
+
# port = ractor.default_port
|
|
67
|
+
# port.send("message")
|
|
68
|
+
# @param ractor [Ractor] the ractor for which to obtain the default port
|
|
69
|
+
# @return [Ractor::Port] the default port of the given ractor
|
|
70
|
+
#
|
|
71
|
+
# @!method join(ractor)
|
|
72
|
+
# @scope class
|
|
73
|
+
# Backport for {https://docs.ruby-lang.org/en/4.0/Ractor.html#method-i-join Ractor#join}
|
|
74
|
+
# @example
|
|
75
|
+
# using Farce::Backports
|
|
76
|
+
# ractor = Ractor.new { sleep 10 }
|
|
77
|
+
# ractor.join # blocks until the ractor exits
|
|
78
|
+
# @param ractor [Ractor] the ractor to join
|
|
79
|
+
# @return [Ractor] the joined ractor
|
|
80
|
+
#
|
|
81
|
+
# @!method monitor(ractor, port)
|
|
82
|
+
# @scope class
|
|
83
|
+
# Backport for {https://docs.ruby-lang.org/en/4.0/Ractor.html#method-i-monitor Ractor#monitor}
|
|
84
|
+
# @example
|
|
85
|
+
# using Farce::Backports
|
|
86
|
+
# ractor = Ractor.new { raise "I'm not happy!" }
|
|
87
|
+
# port = Farce::Ractor::Port.new
|
|
88
|
+
# ractor.monitor(port)
|
|
89
|
+
# port.receive # => :aborted
|
|
90
|
+
# @param ractor [Ractor] the ractor to monitor
|
|
91
|
+
# @param port [Ractor::Port] the port to monitor the ractor with
|
|
92
|
+
# @return [Boolean] whether the Ractor was still running when the monitor was set up
|
|
93
|
+
#
|
|
94
|
+
# @!method unmonitor(ractor, port)
|
|
95
|
+
# @scope class
|
|
96
|
+
# Backport for {https://docs.ruby-lang.org/en/4.0/Ractor.html#method-i-unmonitor Ractor#unmonitor}
|
|
97
|
+
# @example
|
|
98
|
+
# using Farce::Backports
|
|
99
|
+
# ractor = Ractor.new { sleep 10; raise "I'm not happy!" }
|
|
100
|
+
# port = Farce::Ractor::Port.new
|
|
101
|
+
# ractor.monitor(port)
|
|
102
|
+
# ractor.unmonitor(port)
|
|
103
|
+
# port.receive # blocks forever
|
|
104
|
+
# @param ractor [Ractor] the ractor to unmonitor
|
|
105
|
+
# @param port [Ractor::Port] the port to unmonitor the ractor with
|
|
106
|
+
# @return [Ractor] the unmonitored ractor
|
|
107
|
+
#
|
|
108
|
+
# @!method receive(ractor_or_port, timeout: nil)
|
|
109
|
+
# @scope class
|
|
110
|
+
# Backport for the optional timeout parameter of
|
|
111
|
+
# {https://docs.ruby-lang.org/en/master/Ractor.html#method-i-receive Ractor#receive} and
|
|
112
|
+
# {https://docs.ruby-lang.org/en/master/Ractor/Port.html#method-i-receive Ractor::Port#receive}
|
|
113
|
+
# @example
|
|
114
|
+
# using Farce::Backports
|
|
115
|
+
# port = Farce::Ractor::Port.new
|
|
116
|
+
# port.send("message")
|
|
117
|
+
# Farce::Backports.receive(port, timeout: 0.1) # => "message"
|
|
118
|
+
# @param ractor_or_port [Ractor, Ractor::Port] the ractor or port to receive from
|
|
119
|
+
# @param timeout [Numeric, nil] the timeout for receiving
|
|
120
|
+
# @return [Object] the received value
|
|
121
|
+
#
|
|
122
|
+
# @!method select(*ractors_and_ports, timeout: nil)
|
|
123
|
+
# @scope class
|
|
124
|
+
# Backport for the optional timeout parameter of
|
|
125
|
+
# {https://docs.ruby-lang.org/en/master/Ractor.html#method-c-select Ractor.select}
|
|
126
|
+
#
|
|
127
|
+
# @!method value(ractor)
|
|
128
|
+
# @scope class
|
|
129
|
+
# Backport for {https://docs.ruby-lang.org/en/4.0/Ractor.html#method-i-value Ractor#value}
|
|
130
|
+
# @example
|
|
131
|
+
# using Farce::Backports
|
|
132
|
+
# ractor = Ractor.new { 42 }
|
|
133
|
+
# ractor.value # => 42
|
|
134
|
+
# @param ractor [Ractor] the ractor to obtain the value from
|
|
135
|
+
# @return [Object] the value of the given ractor
|
|
136
|
+
module Backports
|
|
137
|
+
# @!visibility private
|
|
138
|
+
Extension = Farce::Extension.new
|
|
139
|
+
private_constant :Extension
|
|
140
|
+
include Extension
|
|
141
|
+
|
|
142
|
+
if Internal.native_ractors?
|
|
143
|
+
if needs_timeouts = ::Ractor.instance_method(:receive).arity.zero?
|
|
144
|
+
Extension.define(::Ractor) do
|
|
145
|
+
def receive(timeout: nil)
|
|
146
|
+
return super() if timeout.nil?
|
|
147
|
+
Internal::RactorSelector.current.ractor_receive(self, timeout:)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
Extension.define(::Ractor.singleton_class) do
|
|
152
|
+
def select(*sources, timeout: nil, **)
|
|
153
|
+
# The selector helper must reach Ruby's native wait without reentering itself.
|
|
154
|
+
return super(*sources, **) if timeout.nil? && (Internal.native_ports? || sources.all?(::Ractor))
|
|
155
|
+
Farce::Ractor.select(*sources, timeout:, **)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def self.receive(ractor_or_port, timeout: nil)
|
|
160
|
+
ractor_or_port = default_port(ractor_or_port) if ractor_or_port.is_a?(Ractor)
|
|
161
|
+
return ractor_or_port.receive if timeout.nil?
|
|
162
|
+
return ractor_or_port.receive(timeout:) unless Internal.native_ports?
|
|
163
|
+
Internal::RactorSelector.current.ractor_receive(ractor_or_port, timeout:)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
if Internal.native_ports?
|
|
168
|
+
unless ::Ractor.method_defined?(:alive?)
|
|
169
|
+
Extension.define(::Ractor) { def alive? = Backports.alive?(self) }
|
|
170
|
+
|
|
171
|
+
def self.alive?(ractor)
|
|
172
|
+
ractor.monitor(NULL_PORT)
|
|
173
|
+
ensure
|
|
174
|
+
ractor.unmonitor(NULL_PORT)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
if needs_timeouts
|
|
179
|
+
Extension.define(::Ractor::Port) do
|
|
180
|
+
def receive(timeout: nil)
|
|
181
|
+
return super() if timeout.nil?
|
|
182
|
+
Internal::RactorSelector.current.ractor_receive(self, timeout:)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
else
|
|
187
|
+
Extension.define(::Ractor) do
|
|
188
|
+
def alive? = Backports.alive?(self)
|
|
189
|
+
def default_port = Backports.default_port(self)
|
|
190
|
+
def join = Backports.join(self)
|
|
191
|
+
def monitor(port) = Backports.monitor(self, port)
|
|
192
|
+
def unmonitor(port) = Backports.unmonitor(self, port)
|
|
193
|
+
def value = Backports.value(self)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def self.alive?(ractor)
|
|
197
|
+
return true if Ractor.main == ractor
|
|
198
|
+
state = Internal::RactorState.for(ractor)
|
|
199
|
+
return state.alive? if state
|
|
200
|
+
!ractor.inspect.end_with?("terminated>")
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def self.default_port(ractor) = Farce::Ractor::Port.new(ractor)
|
|
204
|
+
|
|
205
|
+
def self.join(ractor)
|
|
206
|
+
Internal::RactorState.for(ractor)&.join
|
|
207
|
+
ractor
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def self.monitor(ractor, port)
|
|
211
|
+
return true if Ractor.main == ractor
|
|
212
|
+
|
|
213
|
+
state = Internal::RactorState.for(ractor)
|
|
214
|
+
return state.monitor(port) if state
|
|
215
|
+
|
|
216
|
+
raise Ractor::Error, "#{ractor.inspect} cannot be monitored" unless ractor.inspect.end_with?("terminated>")
|
|
217
|
+
|
|
218
|
+
success = true
|
|
219
|
+
|
|
220
|
+
begin
|
|
221
|
+
ractor.take
|
|
222
|
+
rescue Ractor::ClosedError
|
|
223
|
+
# no-op
|
|
224
|
+
rescue Ractor::RemoteError
|
|
225
|
+
success = false
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
port.send(success ? :exited : :aborted)
|
|
229
|
+
false
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def self.unmonitor(ractor, port)
|
|
233
|
+
Internal::RactorState.for(ractor)&.unmonitor(port)
|
|
234
|
+
ractor
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def self.value(ractor)
|
|
238
|
+
Internal::RactorState.for(ractor)&.join
|
|
239
|
+
values = ::Ractor.store_if_absent("Ractor#value") { ObjectSpace::WeakKeyMap.new }
|
|
240
|
+
entry = values[ractor] ||= [Mutex.new]
|
|
241
|
+
entry.first.synchronize { entry << ractor.take if entry.size == 1 }
|
|
242
|
+
entry[1]
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
%i[alive? default_port join monitor unmonitor value].each do |method|
|
|
248
|
+
next if respond_to?(method)
|
|
249
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
250
|
+
def self.#{method}(ractor, ...) = ractor.#{method}(...)
|
|
251
|
+
RUBY
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
unless respond_to?(:receive)
|
|
255
|
+
def self.receive(ractor_or_port, timeout: nil)
|
|
256
|
+
ractor_or_port = default_port(ractor_or_port) if ractor_or_port.is_a?(Ractor)
|
|
257
|
+
timeout.nil? ? ractor_or_port.receive : ractor_or_port.receive(timeout:)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def self.select(...) = Farce::Ractor.select(...)
|
|
262
|
+
|
|
263
|
+
# Installs the Farce backports for Ractor and Ractor::Port globally.
|
|
264
|
+
def self.install
|
|
265
|
+
Extension.install do
|
|
266
|
+
if Internal.native_ractors?
|
|
267
|
+
::Ractor.const_set(:Port, Farce::Ractor::Port) unless Internal.native_ports?
|
|
268
|
+
else
|
|
269
|
+
Object.const_set(:Ractor, Farce::Ractor)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
2
4
|
|
|
3
5
|
module Farce
|
|
4
6
|
module Internal
|
|
@@ -17,6 +19,7 @@ module Farce
|
|
|
17
19
|
class FiberScheduler
|
|
18
20
|
include SelectScheduler
|
|
19
21
|
prepend SchedulerLifecycle
|
|
22
|
+
prepend SchedulerSynchronization
|
|
20
23
|
|
|
21
24
|
def initialize(backend: :auto)
|
|
22
25
|
raise ArgumentError, "unavailable JVM driver: #{backend}" unless %i[auto nio].include?(backend)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
module Internal # :nodoc: all
|
|
7
|
+
# JRuby can release a mutex and notify its waiter before block is entered.
|
|
8
|
+
module SchedulerSynchronization
|
|
9
|
+
class WaitMutex < ::Mutex
|
|
10
|
+
def synchronize
|
|
11
|
+
# Registering a wait must never recursively invoke this scheduler.
|
|
12
|
+
Fiber.blocking { super }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
MutexWait = Struct.new(:blocker, :notification)
|
|
16
|
+
private_constant :WaitMutex, :MutexWait
|
|
17
|
+
|
|
18
|
+
def initialize(...)
|
|
19
|
+
super
|
|
20
|
+
@wait_mutex = WaitMutex.new
|
|
21
|
+
@mutex_wakeups = {}.compare_by_identity
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def block(blocker, timeout = nil)
|
|
25
|
+
return super unless blocker.is_a?(::Mutex)
|
|
26
|
+
timeout = duration_value(timeout)
|
|
27
|
+
check_fiber
|
|
28
|
+
waiting = @wait_mutex.synchronize do
|
|
29
|
+
@mutex_wakeups.fetch(Fiber.current).tap { it.blocker = blocker }
|
|
30
|
+
end
|
|
31
|
+
token = arm_wait
|
|
32
|
+
early = @wait_mutex.synchronize do
|
|
33
|
+
waiting.notification.tap { waiting.notification = nil }
|
|
34
|
+
end
|
|
35
|
+
resume_wait(token, true) if early.equal?(blocker)
|
|
36
|
+
wait_with_timeout(token, timeout, false)
|
|
37
|
+
ensure
|
|
38
|
+
retire_wait(token) if token
|
|
39
|
+
@wait_mutex.synchronize { waiting.blocker = waiting.notification = nil } if waiting
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def unblock(blocker, fiber)
|
|
43
|
+
return super unless blocker.is_a?(::Mutex)
|
|
44
|
+
@wait_mutex.synchronize do
|
|
45
|
+
return false if @closed || @stopping || !(waiting = @mutex_wakeups[fiber])
|
|
46
|
+
token = @waiting[fiber]
|
|
47
|
+
if token
|
|
48
|
+
return false unless waiting.blocker.equal?(blocker)
|
|
49
|
+
complete_wait(token, true)
|
|
50
|
+
return true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Keep one early notification per admitted fiber. Matching the blocker
|
|
54
|
+
# prevents it from waking a different wait, and retirement consumes it.
|
|
55
|
+
waiting.notification = blocker
|
|
56
|
+
true
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def start_fiber(fiber)
|
|
63
|
+
@wait_mutex.synchronize { @mutex_wakeups[fiber] = MutexWait.new }
|
|
64
|
+
super
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def finish_fiber
|
|
68
|
+
@wait_mutex.synchronize { @mutex_wakeups.delete(Fiber.current) }
|
|
69
|
+
super
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def retire_wait(token)
|
|
73
|
+
@wait_mutex.synchronize do
|
|
74
|
+
wait = @waits[token]
|
|
75
|
+
waiting = @mutex_wakeups[wait.fiber] if wait
|
|
76
|
+
waiting.blocker = waiting.notification = nil if waiting
|
|
77
|
+
end
|
|
78
|
+
super
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def destroy
|
|
82
|
+
@wait_mutex&.synchronize { @mutex_wakeups&.clear }
|
|
83
|
+
super
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
module Internal
|
|
7
|
+
class NullPort < Internal::Port
|
|
8
|
+
def self.new = super(nil)
|
|
9
|
+
def closed? = false
|
|
10
|
+
def inspect = "Farce::NULL_PORT"
|
|
11
|
+
def pretty_print(pp) = pp.text(inspect)
|
|
12
|
+
|
|
13
|
+
def send(message, move: false)
|
|
14
|
+
Ractor.new { receive }.send(message, move:) if move && !::Ractor.shareable?(message)
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
alias << send
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
require "objspace"
|
|
6
|
+
|
|
7
|
+
module Farce
|
|
8
|
+
module Internal
|
|
9
|
+
class RactorState
|
|
10
|
+
def self.for(ractor) = RACTOR_STATES[ractor.object_id] # rubocop:disable Lint/HashCompareByIdentity
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@state = Internal::Atom.new
|
|
14
|
+
@lock = Internal::Lock.new
|
|
15
|
+
@monitors = Internal::Map.new(compare_by_identity: true)
|
|
16
|
+
freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def monitor(port)
|
|
20
|
+
@lock.synchronize do
|
|
21
|
+
if state = @state.value
|
|
22
|
+
port.send(state)
|
|
23
|
+
false
|
|
24
|
+
else
|
|
25
|
+
@monitors[port] = true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def alive? = @state.value.nil?
|
|
31
|
+
def join = @state.wait_until_non_nil
|
|
32
|
+
def unmonitor(port) = @lock.synchronize { @monitors.delete(port) }
|
|
33
|
+
|
|
34
|
+
def set(value)
|
|
35
|
+
@lock.synchronize do
|
|
36
|
+
@state.value = value
|
|
37
|
+
@monitors.each_key { it.send(value) }
|
|
38
|
+
@monitors.clear
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
RACTOR_STATES = Internal::Map.new(compare_by_identity: true)
|
|
44
|
+
|
|
45
|
+
module RactorPatch
|
|
46
|
+
def new(*, **, &callback)
|
|
47
|
+
raise ArgumentError, "must be called with a block" unless callback
|
|
48
|
+
callback = Farce::Ractor.shareable_proc(&callback) unless Ractor.shareable?(callback)
|
|
49
|
+
ractor = super(callback, *, **) do |callback, *args|
|
|
50
|
+
ractor_state = RACTOR_STATES[Ractor.current.object_id] = RactorState.new # rubocop:disable Lint/HashCompareByIdentity
|
|
51
|
+
result = instance_exec(*args, &callback)
|
|
52
|
+
ractor_state.set(:exited)
|
|
53
|
+
result
|
|
54
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
55
|
+
ractor_state.set(:aborted)
|
|
56
|
+
raise e
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
RACTOR_STATES.wait_until_non_nil(ractor.object_id)
|
|
60
|
+
ObjectSpace.define_finalizer(ractor, proc { RACTOR_STATES.delete(it) })
|
|
61
|
+
ractor
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
::Ractor.singleton_class.prepend(RactorPatch)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -107,32 +107,33 @@ module Farce
|
|
|
107
107
|
|
|
108
108
|
def self.new(source = UNDEFINED)
|
|
109
109
|
case source
|
|
110
|
-
when UNDEFINED then super(QueueReader.new)
|
|
111
|
-
when ::Ractor then super(RactorReader.new(source))
|
|
110
|
+
when UNDEFINED then super(QueueReader.new, ::Ractor.current)
|
|
111
|
+
when ::Ractor then super(RactorReader.new(source), source)
|
|
112
|
+
when nil then super(nil, false)
|
|
112
113
|
when Port then source
|
|
113
114
|
else raise ArgumentError, "wrong number of arguments (given 1, expected 0)"
|
|
114
115
|
end
|
|
115
116
|
end
|
|
116
117
|
|
|
117
|
-
def initialize(reader)
|
|
118
|
-
@owner =
|
|
118
|
+
def initialize(reader, owner)
|
|
119
|
+
@owner = owner
|
|
119
120
|
@reader = reader
|
|
120
121
|
Freeze.publish(self)
|
|
121
122
|
end
|
|
122
123
|
|
|
123
124
|
def ==(other) = other.is_a?(Port) && @reader == other.reader
|
|
124
125
|
|
|
125
|
-
def closed? = @reader
|
|
126
|
+
def closed? = @reader&.closed? || RactorReader.status(@owner) == "terminated"
|
|
126
127
|
|
|
127
128
|
def close
|
|
128
129
|
check_owner!
|
|
129
|
-
@reader
|
|
130
|
+
@reader&.close
|
|
130
131
|
self
|
|
131
132
|
end
|
|
132
133
|
|
|
133
134
|
def send(message, move: false)
|
|
134
135
|
raise ::Ractor::ClosedError, "The port was already closed" if closed?
|
|
135
|
-
@reader
|
|
136
|
+
@reader&.send(message, move:)
|
|
136
137
|
self
|
|
137
138
|
end
|
|
138
139
|
|
|
@@ -140,11 +141,11 @@ module Farce
|
|
|
140
141
|
|
|
141
142
|
def receive(timeout: nil)
|
|
142
143
|
check_owner!
|
|
143
|
-
raise ::Ractor::ClosedError, "The port was already closed" if @reader
|
|
144
|
+
raise ::Ractor::ClosedError, "The port was already closed" if @reader&.closed?
|
|
144
145
|
if selector = RactorSelector.for_call(self)
|
|
145
146
|
selector.ractor_receive(self, timeout:)
|
|
146
147
|
else
|
|
147
|
-
@reader
|
|
148
|
+
@reader&.receive(timeout:)
|
|
148
149
|
end
|
|
149
150
|
end
|
|
150
151
|
|
|
@@ -153,8 +154,8 @@ module Farce
|
|
|
153
154
|
@reader.selector_source if @reader.is_a?(RactorReader)
|
|
154
155
|
end
|
|
155
156
|
|
|
156
|
-
def selector_watch(...) = @reader
|
|
157
|
-
def selector_poll(&) = @reader
|
|
157
|
+
def selector_watch(...) = @reader&.selector_watch(...)
|
|
158
|
+
def selector_poll(&) = @reader&.selector_poll(&)
|
|
158
159
|
|
|
159
160
|
def inspect
|
|
160
161
|
class_name = instance_of?(Port) ? "Farce::Ractor::Port" : self.class.name
|
|
@@ -177,7 +178,7 @@ module Farce
|
|
|
177
178
|
|
|
178
179
|
def check_owner!
|
|
179
180
|
return true if @owner.nil? || ::Ractor.current == @owner
|
|
180
|
-
raise ::Ractor::Error, "only allowed from the creator
|
|
181
|
+
raise ::Ractor::Error, "only allowed from the creator Ractor of this port"
|
|
181
182
|
end
|
|
182
183
|
end
|
|
183
184
|
|
|
@@ -17,7 +17,6 @@ module Farce
|
|
|
17
17
|
selector ? selector.ractor_select(*sources, timeout:, **) : ::Ractor.select(*sources, **)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# TODO: Wrapper/Extension to add #default_port, etc.
|
|
21
20
|
def new(...) = ::Ractor.new(...)
|
|
22
21
|
def current = ::Ractor.current
|
|
23
22
|
def main = ::Ractor.main
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
module Internal
|
|
7
|
+
class NullPort < ::Ractor::Port
|
|
8
|
+
class << self
|
|
9
|
+
alias new! new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.new
|
|
13
|
+
retriever = ::Ractor::Port.new
|
|
14
|
+
|
|
15
|
+
::Ractor.new(retriever) do |retriever|
|
|
16
|
+
null_port = NullPort.new!
|
|
17
|
+
retriever.send(null_port)
|
|
18
|
+
|
|
19
|
+
Thread.current.priority = -15
|
|
20
|
+
Darwin.set_qos_class(Darwin::BACKGROUND, Darwin::MIN_RELATIVE_PRIORITY) if defined? Darwin.set_qos_class
|
|
21
|
+
null_port.receive while true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
retriever.receive
|
|
25
|
+
ensure
|
|
26
|
+
retriever&.close
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def send(_, move: false) = move ? super : self
|
|
30
|
+
def inspect = "Farce::NULL_PORT"
|
|
31
|
+
def pretty_print(pp) = pp.text(inspect)
|
|
32
|
+
|
|
33
|
+
alias << send
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -10,8 +10,7 @@ module Farce
|
|
|
10
10
|
include Freeze::Unfreezable
|
|
11
11
|
|
|
12
12
|
def receive(timeout: nil)
|
|
13
|
-
selector = RactorSelector.for_call(self)
|
|
14
|
-
selector ||= RactorSelector.current unless timeout.nil?
|
|
13
|
+
selector = timeout.nil? ? RactorSelector.for_call(self) : RactorSelector.current
|
|
15
14
|
selector ? selector.ractor_receive(self, timeout:) : super()
|
|
16
15
|
end
|
|
17
16
|
end
|
data/lib/farce/engine/ruby.rb
CHANGED
|
@@ -34,6 +34,12 @@ module Farce
|
|
|
34
34
|
# simplecov:enable
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
begin
|
|
38
|
+
require "#{ENGINE_PATH}/patch"
|
|
39
|
+
rescue LoadError => e
|
|
40
|
+
raise e unless e.path == "#{ENGINE_PATH}/patch"
|
|
41
|
+
end
|
|
42
|
+
|
|
37
43
|
unless const_defined?(:NATIVE_WEAK_MAPS, false) && NATIVE_WEAK_MAPS
|
|
38
44
|
autoload :WeakMap, "farce/engine/ruby/shared/weak_map"
|
|
39
45
|
autoload :WeakKeyMap, "farce/engine/ruby/shared/weak_map"
|
|
@@ -66,6 +72,7 @@ module Farce
|
|
|
66
72
|
|
|
67
73
|
autoload :BasePort, "#{ENGINE_PATH}/port"
|
|
68
74
|
autoload :Port, "#{ENGINE_PATH}/port"
|
|
75
|
+
autoload :NullPort, "#{ENGINE_PATH}/null_port"
|
|
69
76
|
autoload :RactorMethods, "#{ENGINE_PATH}/ractor_methods"
|
|
70
77
|
autoload :Vault, "#{ENGINE_PATH}/vault"
|
|
71
78
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
module Internal
|
|
7
|
+
class NullPort < Internal::Port
|
|
8
|
+
def initialize = nil # rubocop:disable Lint/MissingSuper
|
|
9
|
+
def close = raise Ractor::Error, "only allowed from the creator Ractor of this port"
|
|
10
|
+
def closed? = false
|
|
11
|
+
def receive(...) = raise Ractor::Error, "only allowed from the creator Ractor of this port"
|
|
12
|
+
def send(...) = self
|
|
13
|
+
def inspect = "Farce::NULL_PORT"
|
|
14
|
+
def pretty_print(pp) = pp.text(inspect)
|
|
15
|
+
alias << send
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|