farce 0.3.0-arm-linux-gnu → 0.4.0-arm-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/README.md +117 -1489
  4. data/docs/compatibility.md +74 -0
  5. data/docs/concurrency.md +151 -0
  6. data/docs/data-structures.md +313 -0
  7. data/docs/installation.md +63 -0
  8. data/docs/integrations.md +124 -0
  9. data/docs/introduction.md +266 -0
  10. data/docs/scheduling.md +176 -0
  11. data/docs/sharing.md +211 -0
  12. data/docs/shims.md +144 -0
  13. data/docs/utilities.md +93 -0
  14. data/docs/variants-and-scopes.md +92 -0
  15. data/lib/farce/abstract/flag.rb +6 -0
  16. data/lib/farce/backports.rb +274 -0
  17. data/lib/farce/engine/jruby/fiber_scheduler.rb +3 -0
  18. data/lib/farce/engine/jruby/scheduler_synchronization.rb +87 -0
  19. data/lib/farce/engine/ruby/3.4/null_port.rb +21 -0
  20. data/lib/farce/engine/ruby/3.4/patch.rb +67 -0
  21. data/lib/farce/engine/ruby/3.4/port.rb +13 -12
  22. data/lib/farce/engine/ruby/3.4/ractor_methods.rb +0 -1
  23. data/lib/farce/engine/ruby/4.0/null_port.rb +36 -0
  24. data/lib/farce/engine/ruby/4.0/port.rb +1 -2
  25. data/lib/farce/engine/ruby/4.1/null_port.rb +5 -0
  26. data/lib/farce/engine/ruby.rb +7 -0
  27. data/lib/farce/engine/shared/null_port.rb +18 -0
  28. data/lib/farce/engine/shared/port.rb +41 -3
  29. data/lib/farce/engine/shared/queue.rb +4 -4
  30. data/lib/farce/engine/shared/ractor_methods.rb +2 -2
  31. data/lib/farce/engine/shared/ractor_selector.rb +73 -0
  32. data/lib/farce/extension.rb +150 -0
  33. data/lib/farce/integrations/ractor_tmvar.rb +4 -1
  34. data/lib/farce/internal/fake_ractor.rb +10 -2
  35. data/lib/farce/internal/scheduler_lifecycle.rb +7 -4
  36. data/lib/farce/null_port.rb +12 -0
  37. data/lib/farce/system.rb +10 -2
  38. data/lib/farce/version.rb +1 -1
  39. data/lib/farce.rb +1 -1
  40. metadata +22 -1
@@ -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 = ::Ractor.current
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.closed? || RactorReader.status(@owner) == "terminated"
126
+ def closed? = @reader&.closed? || RactorReader.status(@owner) == "terminated"
126
127
 
127
128
  def close
128
129
  check_owner!
129
- @reader.close
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.send(message, move:)
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.closed?
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.receive(timeout:)
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.selector_watch(...)
157
- def selector_poll(&) = @reader.selector_poll(&)
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 ::Ractor of this port"
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ require "farce/engine/ruby/4.0/null_port"
@@ -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