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
@@ -10,11 +10,19 @@ module Farce
10
10
  include MarshalSupport::Reject
11
11
  include Freeze::Unfreezable
12
12
 
13
- def initialize = @queue = Thread::Queue.new
14
- def closed? = @queue.closed?
13
+ def initialize
14
+ @queue = Thread::Queue.new
15
+ @select_mutex = Mutex.new
16
+ @select_waiters = []
17
+ end
18
+
19
+ def closed? = @queue.closed?
15
20
 
16
21
  def close
17
- @queue.close
22
+ @select_mutex.synchronize do
23
+ @queue.close
24
+ @select_waiters.each(&:broadcast)
25
+ end
18
26
  self
19
27
  end
20
28
 
@@ -29,14 +37,44 @@ module Farce
29
37
  raise Ractor::Error, "can not move #{value.class} object" unless value.is_a?(Unshareable::Movable)
30
38
  end
31
39
  @queue.push(value)
40
+ notify_selectors
32
41
  self
33
42
  rescue ::ClosedQueueError
34
43
  raise Ractor::ClosedError, "The port was already closed"
35
44
  end
36
45
 
46
+ # @api private
47
+ def register_selector(waiter)
48
+ @select_mutex.synchronize { @select_waiters << waiter }
49
+ end
50
+
51
+ # @api private
52
+ def unregister_selector(waiter)
53
+ @select_mutex.synchronize { @select_waiters.delete(waiter) }
54
+ end
55
+
56
+ # @api private
57
+ def select_receive
58
+ @select_mutex.synchronize do
59
+ raise Ractor::ClosedError, "The port was already closed" if closed?
60
+ begin
61
+ value = @queue.pop(true)
62
+ rescue ThreadError
63
+ return
64
+ end
65
+ [self, value]
66
+ end
67
+ end
68
+
37
69
  alias << send
38
70
 
39
71
  def inspect = "#<#{self.class.name} to:#1 id:#{object_id}>"
72
+
73
+ private
74
+
75
+ def notify_selectors
76
+ @select_mutex.synchronize { @select_waiters.each(&:broadcast) }
77
+ end
40
78
  end
41
79
 
42
80
  BasePort = Port
@@ -12,10 +12,10 @@ module Farce
12
12
  attr_reader :capacity
13
13
 
14
14
  def initialize(capacity: 1024, track_age: false)
15
- @queue = capacity ? Thread::SizedQueue.new(capacity) : Thread::Queue.new
16
- @capacity = capacity
17
- @signal = Signal.new
18
- @tracking = track_age ? { lock: Mutex.new, timestamps: [], generation: 0 } : nil
15
+ @queue = capacity ? Thread::SizedQueue.new(capacity) : Thread::Queue.new
16
+ @capacity = capacity
17
+ @signal = Signal.new
18
+ @tracking = track_age ? { lock: Mutex.new, timestamps: [], generation: 0 } : nil
19
19
  @lifecycle = { lock: Mutex.new, hard_closed: false }
20
20
  Freeze.publish(self)
21
21
  end
@@ -63,8 +63,8 @@ module Farce
63
63
  with_storage(name) { _1[_2] = value }
64
64
  end
65
65
 
66
- def select(...)
67
- raise "TODO: not implemented"
66
+ def select(*sources, timeout: nil)
67
+ RactorSelector.new.select(*sources, timeout:)
68
68
  end
69
69
 
70
70
  alias recv receive
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # @!visibility private
7
+ module Internal # :nodoc: all
8
+ # Wait for shim port readiness without consuming messages from losing ports.
9
+ class RactorSelector
10
+ def select(*sources, timeout: nil)
11
+ raise ArgumentError, "specify at least one Ractor::Port or Ractor" if sources.empty?
12
+ sources.each do |source|
13
+ next if source.is_a?(Ractor) || source.is_a?(Port)
14
+ raise ArgumentError, "should be Ractor::Port or Ractor"
15
+ end
16
+
17
+ timeout = duration(timeout)
18
+ deadline = Clock.now + timeout if timeout
19
+ monitors = {}.compare_by_identity
20
+ ports = sources.uniq.map do |source|
21
+ next source unless source.is_a?(Ractor)
22
+
23
+ port = BasePort.new
24
+ monitors[port] = source
25
+ source.monitor(port)
26
+ port
27
+ end
28
+
29
+ result = wait(ports, deadline)
30
+ return unless result
31
+
32
+ port, value = result
33
+ ractor = monitors[port]
34
+ ractor ? [ractor, ractor.value] : [port, value]
35
+ ensure
36
+ monitors&.each do |port, ractor|
37
+ ractor.unmonitor(port)
38
+ port.close
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def duration(value)
45
+ return if value.nil?
46
+ raise TypeError, "cannot convert String into time interval" if value.is_a?(String)
47
+ value = Float(value)
48
+ raise RangeError, "timeout out of range" unless value.finite?
49
+ raise ArgumentError, "time interval must not be negative" if value.negative?
50
+ value
51
+ end
52
+
53
+ def wait(ports, deadline)
54
+ wakeup = Signal.new
55
+ ports.each { it.register_selector(wakeup) }
56
+ while true
57
+ # Observe before checking readiness so a send during that check
58
+ # changes the generation and prevents the following wait from parking.
59
+ generation = wakeup.generation
60
+ ports.each do |port|
61
+ result = port.select_receive
62
+ return result if result
63
+ end
64
+ remaining = deadline - Clock.now if deadline
65
+ return if remaining && !remaining.positive?
66
+ return unless ReservationWaiting.wait(wakeup, generation, deadline)
67
+ end
68
+ ensure
69
+ ports.each { it.unregister_selector(wakeup) }
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # Defines a reusable extension for adding methods to target classes.
7
+ # These methods can either be activated as refinements, or installed globally to extend the target classes.
8
+ #
9
+ # ```ruby
10
+ # DoubleSize = Farce::Extension.define(String) do
11
+ # def double_size = size * 2
12
+ # end
13
+ #
14
+ # module Namespace
15
+ # using DoubleSize
16
+ # "hello".double_size # => 10
17
+ # end
18
+ #
19
+ # "hello".respond_to?(:double_size) # => false
20
+ #
21
+ # DoubleSize.install
22
+ # "hello".respond_to?(:double_size) # => true
23
+ # ```
24
+ #
25
+ # ## Extending multiple classes
26
+ #
27
+ # An extension can also modify multiple target classes simultaneously:
28
+ #
29
+ # ```ruby
30
+ # DoubleSize = Farce::Extension.new do
31
+ # define(String) { def double_size = size * 2 }
32
+ # define(Array) { def double_size = sum(&:double_size) }
33
+ # end
34
+ #
35
+ # using DoubleSize
36
+ # ["hello", "foo"].double_size # => 16
37
+ # ```
38
+ class Extension < Module
39
+ # @api private
40
+ class Mixin < Module # :nodoc:
41
+ attr_reader :extension, :target
42
+
43
+ def initialize(extension, target, &)
44
+ @extension = extension
45
+ @target = target
46
+ super(&)
47
+ end
48
+
49
+ def name = -"#{@extension.name}[#{@target.inspect}]"
50
+
51
+ def install
52
+ return if @target.ancestors.include?(self)
53
+ @target.prepend(self)
54
+ end
55
+ end
56
+ private_constant :Mixin
57
+
58
+ # @overload define(*targets)
59
+ # Defines an extension for the given target classes.
60
+ # @param targets [Array<Class>] the target classes to define methods for
61
+ # @yield the block containing method definitions
62
+ # @yieldreceiver [Module, Refinement] the module/refinement used for extending the target class.
63
+ # @return [Extension] the newly created extension instance
64
+ # @see Extension#define
65
+ def self.define(...) = new { define(...) }
66
+
67
+ # Creates a new extension.
68
+ # @yield Optional block for `define` calls or adding methods to the extension itself.
69
+ # @yieldreceiver [Extension] the extension instance itself.
70
+ def initialize
71
+ check_state!
72
+ @definitions = Internal::StrictMap.new
73
+ @lock = Lock.new
74
+ @installed = Flag.new
75
+ super
76
+ end
77
+
78
+ # @api private
79
+ def [](target) = @definitions[target]
80
+
81
+ # Defines additional methods for a given target class.
82
+ #
83
+ # The given block will be invoked at least twice. Once in the context of a refinement for the target class,
84
+ # and once in the context of the mixin module that will be prepended to the target class.
85
+ #
86
+ # @example
87
+ # extension = Farce::Extension.new
88
+ # extension.define(String) { def double_size = size * 2 }
89
+ #
90
+ # module Namespace
91
+ # using extension
92
+ # String.method_defined?(:double_size) # => true
93
+ # end
94
+ #
95
+ # String.method_defined?(:double_size) # => false
96
+ # extension.install
97
+ # String.method_defined?(:double_size) # => true
98
+ #
99
+ # @param targets [Array<Class>] the target classes to define methods for
100
+ # @yield the block containing method definitions
101
+ # @yieldreceiver [Module, Refinement] the module/refinement used for extending the target class.
102
+ # @return [self] the extension instance
103
+ def define(*targets, &)
104
+ @lock.synchronize do
105
+ check_state!
106
+ targets.each do |target|
107
+ refine(target, &)
108
+ mixin = @definitions.store_if_absent(target) { Mixin.new(self, target) }
109
+ mixin.module_eval(&)
110
+ mixin.install if installed?
111
+ end
112
+ end
113
+ self
114
+ end
115
+
116
+ # Installs the extension globally.
117
+ # @return [void]
118
+ def install
119
+ @lock.synchronize do
120
+ return false if installed?
121
+ check_state!(allow_frozen: true)
122
+ @definitions.each_value(&:install)
123
+ yield if block_given?
124
+ @installed.set
125
+ end
126
+ end
127
+
128
+ # @return [Boolean] whether the extension has been installed
129
+ def installed? = @installed.set?
130
+
131
+ # Freezes the extension.
132
+ #
133
+ # No {#define definitions} can be added after freezing the extension.
134
+ # @return [self] the frozen extension
135
+ def freeze
136
+ return self if frozen?
137
+ @lock.synchronize do
138
+ @definitions.freeze
139
+ super
140
+ end
141
+ end
142
+
143
+ private
144
+
145
+ def check_state!(allow_frozen: false)
146
+ raise Ractor::IsolationError, "must be called from main Ractor" if Internal.native_ractors? && !Ractor.main?
147
+ Internal::Freeze.check(self) unless allow_frozen
148
+ end
149
+ end
150
+ end
@@ -13,7 +13,10 @@ module Farce
13
13
  # Take, put, and swap operate on the value staged in this attempt.
14
14
  # An empty read, take, or swap, or a put into a full TMVar fails the
15
15
  # attempt. Farce.transaction retries these conditions within its retry limit.
16
- # The try_* methods return nil or false without failing the attempt.
16
+ # The try_* methods return nil or false when unavailable without failing
17
+ # the attempt. Blocks passed to try_read and try_take run only when full,
18
+ # including for stored nil, and return the block's result.
19
+ # Create an empty TMVar with Ractor::TMVar.new and a full nil with .new(nil).
17
20
  # Assigned values are made shareable, matching Ractor::TVar's setter.
18
21
  #
19
22
  # Run outside Ractor.atomically, as required by {RactorTVar}.
@@ -21,6 +21,7 @@ module Farce
21
21
  INSTANCE = Freeze.publish(new)
22
22
  DEFAULT_PORT = BasePort.new
23
23
 
24
+ def alive? = true
24
25
  def default_port = DEFAULT_PORT
25
26
  def inspect = "#<Farce::Ractor#1 running>"
26
27
  def join = sleep
@@ -78,7 +79,7 @@ module Farce
78
79
  ensure
79
80
  state.mutex.synchronize do
80
81
  state.monitors.each do |monitor|
81
- monitor << state.status
82
+ FakeRactor.send_status(monitor, self, state.status)
82
83
  end
83
84
  state.monitors = nil
84
85
  end
@@ -97,6 +98,8 @@ module Farce
97
98
  Farce::Ractor[key] = value
98
99
  end
99
100
 
101
+ def alive? = STATES[self].status == :running
102
+
100
103
  def monitor(port) # rubocop:disable Naming/PredicateMethod
101
104
  state = STATES[self]
102
105
 
@@ -108,7 +111,7 @@ module Farce
108
111
  end
109
112
  end
110
113
 
111
- port << state.status
114
+ FakeRactor.send_status(port, self, state.status)
112
115
  false
113
116
  end
114
117
 
@@ -148,6 +151,11 @@ module Farce
148
151
  undef clone
149
152
  end
150
153
 
154
+ def self.send_status(port, ractor, status)
155
+ payload = System.ruby_version >= "4.1" ? [ractor, status].freeze : status
156
+ port << payload
157
+ end
158
+
151
159
  def self.current
152
160
  # JRuby fibers expose a backing thread with a different thread group.
153
161
  thread = Internal.storage_thread(Thread.current)
@@ -82,15 +82,13 @@ module Farce
82
82
 
83
83
  def unblock(_blocker, fiber) # rubocop:disable Naming/PredicateMethod
84
84
  return false if @closed || !(token = current_wait(fiber))
85
- @mailbox << Completion.new(token, true, nil).freeze
86
- wakeup
85
+ complete_wait(token, true)
87
86
  true
88
87
  end
89
88
 
90
89
  def fiber_interrupt(fiber, exception) # rubocop:disable Naming/PredicateMethod
91
90
  return false if @closed || !(token = current_wait(fiber))
92
- @mailbox << Completion.new(token, nil, exception).freeze
93
- wakeup
91
+ complete_wait(token, nil, exception)
94
92
  true
95
93
  end
96
94
 
@@ -202,6 +200,11 @@ module Farce
202
200
 
203
201
  private
204
202
 
203
+ def complete_wait(token, value, error = nil)
204
+ @mailbox << Completion.new(token, value, error).freeze
205
+ wakeup
206
+ end
207
+
205
208
  def finish_close
206
209
  return if @closed
207
210
  shutdown
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # Special, write-only port that discards all messages.
7
+ # If the Ruby environment supports ports, this will be a real Port object.
8
+ # It can therefore be passed to any API expecting a `Ractor::Port`, including C code.
9
+ # Cannot be read from, but does not accumulate messages in memory.
10
+ # @return [Ractor::Port] Write-only port
11
+ NULL_PORT = Internal::NullPort.new
12
+ end
data/lib/farce/system.rb CHANGED
@@ -7,8 +7,16 @@ require "etc"
7
7
  module Farce
8
8
  # Information about the system running Farce.
9
9
  module System
10
- WINDOWS = Gem.win_platform?
11
- private_constant :WINDOWS
10
+ ruby_version = Gem.ruby_version.release
11
+ ruby_version = ::Ractor.make_shareable(ruby_version, copy: true) if defined?(::Ractor)
12
+ RUBY_VERSION = ruby_version
13
+ WINDOWS = Gem.win_platform?
14
+ private_constant :RUBY_VERSION, :WINDOWS
15
+
16
+ # A Version object representing the current Ruby release, without prerelease suffixes.
17
+ # Safe to call from any Ractor.
18
+ # @return [#<=>, #to_s]
19
+ def self.ruby_version = RUBY_VERSION
12
20
 
13
21
  # Whether the current operating system is Windows.
14
22
  # Safe to call from any Ractor.
data/lib/farce/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
 
5
5
  module Farce
6
6
  # @return [String] the current version of Farce
7
- VERSION = "0.3.0"
7
+ VERSION = "0.4.0"
8
8
  end
data/lib/farce.rb CHANGED
@@ -46,7 +46,7 @@ require "farce/system"
46
46
  # defined?(VERSION) # => false
47
47
  # ```
48
48
  module Farce
49
- include Internal::Autoloads
49
+ include Internal::Autoloads[null_port: :NULL_PORT]
50
50
 
51
51
  # The valid storage scopes for local values and collections.
52
52
  # @return [::Set<Symbol>]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: farce
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: arm-linux-gnu
6
6
  authors:
7
7
  - Konstantin Haase
@@ -22,11 +22,22 @@ files:
22
22
  - README.md
23
23
  - SECURITY.md
24
24
  - docs/benchmarks.md
25
+ - docs/compatibility.md
26
+ - docs/concurrency.md
27
+ - docs/data-structures.md
25
28
  - docs/gems/dry-types.md
26
29
  - docs/gems/msgpack.md
27
30
  - docs/gems/ractor-shim.md
31
+ - docs/installation.md
32
+ - docs/integrations.md
33
+ - docs/introduction.md
28
34
  - docs/modes.md
35
+ - docs/scheduling.md
29
36
  - docs/scopes.md
37
+ - docs/sharing.md
38
+ - docs/shims.md
39
+ - docs/utilities.md
40
+ - docs/variants-and-scopes.md
30
41
  - docs/variants.md
31
42
  - ext/ext_helper.rb
32
43
  - ext/farce/README.md
@@ -110,6 +121,7 @@ files:
110
121
  - lib/farce/abstract/weak_set.rb
111
122
  - lib/farce/abstract/weak_value_map.rb
112
123
  - lib/farce/atom.rb
124
+ - lib/farce/backports.rb
113
125
  - lib/farce/class_mirror.rb
114
126
  - lib/farce/clock.rb
115
127
  - lib/farce/config.rb
@@ -122,6 +134,7 @@ files:
122
134
  - lib/farce/engine/jruby/lease_waiting.rb
123
135
  - lib/farce/engine/jruby/map.rb
124
136
  - lib/farce/engine/jruby/mutable_numeric_copy.rb
137
+ - lib/farce/engine/jruby/scheduler_synchronization.rb
125
138
  - lib/farce/engine/jruby/signal.rb
126
139
  - lib/farce/engine/jvm.rb
127
140
  - lib/farce/engine/jvm/concurrent_weak_registry.rb
@@ -135,17 +148,21 @@ files:
135
148
  - lib/farce/engine/ruby.rb
136
149
  - lib/farce/engine/ruby/3.4/farce.so
137
150
  - lib/farce/engine/ruby/3.4/fiber_scheduler.rb
151
+ - lib/farce/engine/ruby/3.4/null_port.rb
152
+ - lib/farce/engine/ruby/3.4/patch.rb
138
153
  - lib/farce/engine/ruby/3.4/port.rb
139
154
  - lib/farce/engine/ruby/3.4/ractor_methods.rb
140
155
  - lib/farce/engine/ruby/3.4/ractor_selector.rb
141
156
  - lib/farce/engine/ruby/3.4/rebind.so
142
157
  - lib/farce/engine/ruby/3.4/vault.rb
143
158
  - lib/farce/engine/ruby/4.0/farce.so
159
+ - lib/farce/engine/ruby/4.0/null_port.rb
144
160
  - lib/farce/engine/ruby/4.0/port.rb
145
161
  - lib/farce/engine/ruby/4.0/ractor_methods.rb
146
162
  - lib/farce/engine/ruby/4.0/ractor_selector.rb
147
163
  - lib/farce/engine/ruby/4.0/rebind.so
148
164
  - lib/farce/engine/ruby/4.0/vault.rb
165
+ - lib/farce/engine/ruby/4.1/null_port.rb
149
166
  - lib/farce/engine/ruby/4.1/port.rb
150
167
  - lib/farce/engine/ruby/4.1/ractor_methods.rb
151
168
  - lib/farce/engine/ruby/4.1/ractor_selector.rb
@@ -174,12 +191,14 @@ files:
174
191
  - lib/farce/engine/shared/lease_pool.rb
175
192
  - lib/farce/engine/shared/main_scheduler.rb
176
193
  - lib/farce/engine/shared/map_key_coordination.rb
194
+ - lib/farce/engine/shared/null_port.rb
177
195
  - lib/farce/engine/shared/parallel_scheduler.rb
178
196
  - lib/farce/engine/shared/port.rb
179
197
  - lib/farce/engine/shared/portable_bounded_map.rb
180
198
  - lib/farce/engine/shared/proxy_owner.rb
181
199
  - lib/farce/engine/shared/queue.rb
182
200
  - lib/farce/engine/shared/ractor_methods.rb
201
+ - lib/farce/engine/shared/ractor_selector.rb
183
202
  - lib/farce/engine/shared/rebindable.rb
184
203
  - lib/farce/engine/shared/strict_atom.rb
185
204
  - lib/farce/engine/shared/strict_map.rb
@@ -218,6 +237,7 @@ files:
218
237
  - lib/farce/envelope.rb
219
238
  - lib/farce/error.rb
220
239
  - lib/farce/exchanger.rb
240
+ - lib/farce/extension.rb
221
241
  - lib/farce/flag.rb
222
242
  - lib/farce/integrations.rb
223
243
  - lib/farce/integrations/active_support.rb
@@ -346,6 +366,7 @@ files:
346
366
  - lib/farce/mode_manager/smart_rule.rb
347
367
  - lib/farce/molecule.rb
348
368
  - lib/farce/mutable.rb
369
+ - lib/farce/null_port.rb
349
370
  - lib/farce/pool.rb
350
371
  - lib/farce/port.rb
351
372
  - lib/farce/priority_queue.rb