farce 0.4.1 → 0.5.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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -2
  3. data/README.md +3 -1
  4. data/docs/benchmarks.md +37 -21
  5. data/docs/compatibility.md +13 -0
  6. data/docs/data-structures.md +37 -0
  7. data/docs/integrations.md +1 -0
  8. data/docs/introduction.md +19 -0
  9. data/docs/modes.md +2 -0
  10. data/docs/scheduling.md +35 -3
  11. data/docs/scopes.md +3 -1
  12. data/docs/shims.md +53 -0
  13. data/docs/utilities.md +31 -0
  14. data/docs/variants.md +9 -0
  15. data/ext/farce/farce.c +9 -0
  16. data/ext/farce/map.c +14 -3
  17. data/ext/farce/priority_queue.c +46 -1
  18. data/ext/farce/queue.c +141 -7
  19. data/ext/farce/vector.c +3 -3
  20. data/ext/farce/weak_map.c +12 -3
  21. data/ext/java/org/farce/PriorityQueue.java +18 -2
  22. data/lib/farce/abstract/concurrent_map.rb +122 -0
  23. data/lib/farce/abstract/counter.rb +35 -1
  24. data/lib/farce/abstract/executor.rb +30 -7
  25. data/lib/farce/abstract/flag.rb +12 -0
  26. data/lib/farce/abstract/pool.rb +158 -47
  27. data/lib/farce/abstract/priority_queue.rb +1 -1
  28. data/lib/farce/abstract/publisher.rb +189 -0
  29. data/lib/farce/abstract/queue.rb +29 -3
  30. data/lib/farce/abstract/scheduler.rb +41 -14
  31. data/lib/farce/abstract/timer_queue.rb +3 -3
  32. data/lib/farce/abstract/vector.rb +3 -1
  33. data/lib/farce/atom.rb +9 -2
  34. data/lib/farce/clock.rb +12 -0
  35. data/lib/farce/concurrent/count_down_latch.rb +37 -0
  36. data/lib/farce/concurrent/cyclic_barrier.rb +109 -0
  37. data/lib/farce/concurrent/event.rb +60 -0
  38. data/lib/farce/concurrent/fiber_local_var.rb +31 -0
  39. data/lib/farce/concurrent/local_var.rb +65 -0
  40. data/lib/farce/concurrent/thread_local_var.rb +28 -0
  41. data/lib/farce/concurrent/tuple.rb +74 -0
  42. data/lib/farce/concurrent.rb +50 -0
  43. data/lib/farce/config.rb +112 -2
  44. data/lib/farce/counter.rb +0 -16
  45. data/lib/farce/engine/jruby/map.rb +8 -6
  46. data/lib/farce/engine/jruby/signal.rb +2 -2
  47. data/lib/farce/engine/jvm/farce.jar +0 -0
  48. data/lib/farce/engine/jvm/priority_queue.rb +14 -4
  49. data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -1
  50. data/lib/farce/engine/ruby/shared/ractor_selector.rb +1 -4
  51. data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
  52. data/lib/farce/engine/shared/exchanger.rb +2 -2
  53. data/lib/farce/engine/shared/queue.rb +43 -5
  54. data/lib/farce/engine/shared/ractor_selector.rb +1 -1
  55. data/lib/farce/engine/shared/strict_map.rb +5 -0
  56. data/lib/farce/engine/shared/unshared_atom.rb +1 -1
  57. data/lib/farce/engine/shared/unshared_vector.rb +8 -7
  58. data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
  59. data/lib/farce/engine/shared/weak_atom/base.rb +5 -10
  60. data/lib/farce/engine/shared/weak_map/cell.rb +1 -1
  61. data/lib/farce/engine/truffleruby/map.rb +8 -6
  62. data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
  63. data/lib/farce/engine/truffleruby/signal.rb +1 -1
  64. data/lib/farce/integrations/concurrent.rb +16 -3
  65. data/lib/farce/internal/blocking_priority_queue.rb +4 -9
  66. data/lib/farce/internal/flag_waiting.rb +1 -1
  67. data/lib/farce/internal/lease_initialization.rb +1 -1
  68. data/lib/farce/internal/lease_pool_state.rb +1 -1
  69. data/lib/farce/internal/lease_state.rb +2 -2
  70. data/lib/farce/internal/local_executor_methods.rb +3 -3
  71. data/lib/farce/internal/map_value_modes.rb +9 -2
  72. data/lib/farce/internal/pool_settings.rb +9 -0
  73. data/lib/farce/internal/pool_worker.rb +34 -21
  74. data/lib/farce/internal/reservation_waiting.rb +1 -1
  75. data/lib/farce/internal/scheduler_lifecycle.rb +2 -2
  76. data/lib/farce/internal/weak_map_value_modes.rb +5 -0
  77. data/lib/farce/internal.rb +8 -8
  78. data/lib/farce/lazy.rb +3 -0
  79. data/lib/farce/lazy_ref.rb +3 -0
  80. data/lib/farce/local/lease.rb +2 -9
  81. data/lib/farce/local/lease_map.rb +2 -9
  82. data/lib/farce/local/pool.rb +38 -5
  83. data/lib/farce/local/publisher.rb +30 -0
  84. data/lib/farce/local/scheduler.rb +18 -5
  85. data/lib/farce/local/vector.rb +3 -2
  86. data/lib/farce/memoize.rb +314 -0
  87. data/lib/farce/pool.rb +5 -0
  88. data/lib/farce/priority_queue.rb +1 -1
  89. data/lib/farce/publisher.rb +57 -0
  90. data/lib/farce/signal.rb +1 -1
  91. data/lib/farce/strict/publisher.rb +31 -0
  92. data/lib/farce/strict/vector.rb +3 -2
  93. data/lib/farce/subscription/definitions.rb +27 -0
  94. data/lib/farce/subscription.rb +121 -0
  95. data/lib/farce/transaction/map.rb +35 -1
  96. data/lib/farce/unshared/pool.rb +1 -1
  97. data/lib/farce/unshared/publisher.rb +25 -0
  98. data/lib/farce/unshared/scheduler.rb +3 -3
  99. data/lib/farce/unshared/vector.rb +3 -2
  100. data/lib/farce/vector.rb +12 -9
  101. data/lib/farce/version.rb +1 -1
  102. data/lib/farce/walker/definitions.rb +13 -2
  103. data/lib/farce/weak_atom.rb +5 -2
  104. data/lib/farce.rb +3 -1
  105. metadata +18 -1
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # A message multiplexer.
7
+ # Implements the publisher-subscriber protocol.
8
+ #
9
+ # @example
10
+ # publisher = Farce::Publisher.new
11
+ #
12
+ # # Count how many messages have been received
13
+ # counter = Farce::Counter.new
14
+ # publisher.subscribe(counter)
15
+ #
16
+ # # Trigger a callback whenever a message is published
17
+ # publisher.subscribe do |message|
18
+ # puts "Received message: #{message}"
19
+ # end
20
+ #
21
+ # # Send messages to a queue
22
+ # queue = Farce::Queue.new
23
+ # publisher.subscribe(queue)
24
+ #
25
+ # # Keep track of the last published value
26
+ # atom = Farce::Atom.new
27
+ # publisher.subscribe(atom)
28
+ #
29
+ # # Send messages to a Ractor
30
+ # ractor = Ractor.new do
31
+ # while message = Ractor.receive
32
+ # puts "Received message in Ractor: #{message}"
33
+ # end
34
+ # end
35
+ # publisher.subscribe(ractor)
36
+ #
37
+ # # Publish some messages
38
+ # publisher.publish("Hello, world!")
39
+ # publisher.publish("Another message")
40
+ #
41
+ # queue.pop # => "Hello, world!"
42
+ # atom.value # => "Another message"
43
+ # counter.value # => 2
44
+ class Publisher < Farce::Abstract::Publisher
45
+ include Shareable::Unfreezable
46
+
47
+ # @overload initialize(*subscribers)
48
+ # (see Farce::Abstract#initialize)
49
+ def initialize(*subscribers)
50
+ @map = Strict::Map.new
51
+ super()
52
+ subscribers.each { subscribe(it) }
53
+ end
54
+
55
+ private attr_reader :map
56
+ end
57
+ end
data/lib/farce/signal.rb CHANGED
@@ -80,7 +80,7 @@ module Farce
80
80
  result = yield
81
81
  return result if result
82
82
 
83
- remaining = deadline - Clock.now if deadline
83
+ remaining = Clock.remaining(deadline)
84
84
  return if remaining && !remaining.positive?
85
85
  return unless wait(observed, timeout: remaining)
86
86
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Strict
7
+ # Strict variant of {Farce::Publisher}.
8
+ class Publisher < Farce::Abstract::Publisher
9
+ include Shareable::Unfreezable
10
+
11
+ # @overload initialize(*subscribers)
12
+ # (see Farce::Abstract#initialize)
13
+ def initialize(*subscribers)
14
+ @map = Strict::Map.new
15
+ super()
16
+ subscribers.each { subscribe(it) }
17
+ end
18
+
19
+ # (see Farce::Abstract::Publisher#publish)
20
+ def publish(message = nil)
21
+ return super if Ractor.shareable?(message)
22
+ raise Ractor::IsolationError, "value must be Ractor-shareable"
23
+ end
24
+ alias send publish
25
+ alias << publish
26
+ alias broadcast publish
27
+
28
+ private attr_reader :map
29
+ end
30
+ end
31
+ end
@@ -8,10 +8,11 @@ module Farce
8
8
  class Vector < Abstract::Vector
9
9
  include Shareable::Delegated
10
10
 
11
- # @param source [Array, nil] Initial values. The source array is not retained.
11
+ # @param source [Array, Integer, nil] Initial values or a size filled with nil. The source array is not retained.
12
12
  # @param compare_by_identity [Boolean] Whether values are compared by identity.
13
+ # @raise [ArgumentError] If the size is negative.
13
14
  def initialize(source = nil, compare_by_identity: false)
14
- @vector = Internal::Vector.new(source, compare_by_identity:)
15
+ @vector = Internal::Vector.new(normalize_vector_source(source), compare_by_identity:)
15
16
  super()
16
17
  end
17
18
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ class Subscription
7
+ Farce.on_main do
8
+ ractors = Internal.native_ractors? ? [::Ractor] : [Internal::FakeRactor::Main, Internal::FakeRactor::NotMain]
9
+
10
+ define(Internal::BasePort, *ractors) { subscriber.send(it) }
11
+ define(Abstract::Publisher) { subscriber.publish(it) }
12
+ define(Abstract::Queue) { subscriber.try_push(it) }
13
+ define(Internal::Counter, Local::Counter) { |_| subscriber.increment }
14
+ define(Internal::Flag, Local::Flag) { |_| subscriber.set }
15
+ define(Abstract::Atom) { subscriber.value = it }
16
+ define(Abstract::Set, Abstract::Vector) { subscriber << it }
17
+ define(Signal) { |_| subscriber.broadcast }
18
+
19
+ define(::Thread::Queue) { subscriber.push(it) }
20
+ define(::Thread::SizedQueue) do |payload|
21
+ subscriber.push(payload, true)
22
+ rescue ThreadError
23
+ false
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # A publisher subscription that can be paused or permanently canceled.
7
+ # Disabling skips new messages. Unsubscribing drains an existing callback queue before stopping it.
8
+ class Subscription
9
+ include Internal::Freeze::Unfreezable
10
+ include Internal::Noncopyable
11
+
12
+ # Raised when an operation is attempted on an unsubscribed subscription.
13
+ UnsubscribedError = Class.new(StandardError)
14
+
15
+ REGISTER = ClassMirror.new(self)
16
+ private_constant :REGISTER
17
+
18
+ class << self
19
+ protected :new
20
+ end
21
+
22
+ # Register delivery behavior for subscriber classes from the main Ractor.
23
+ # Subclasses inherit the nearest registered delivery method.
24
+ #
25
+ # @example
26
+ # Farce::Subscription.define(MyInbox) { |payload| subscriber.push(payload) }
27
+ #
28
+ # @param classes [Array<Class>] the subscriber classes to support
29
+ # @yieldparam payload [BasicObject] the published payload
30
+ # @yieldreceiver [Subscription] the subscription whose {#subscriber} receives the payload
31
+ # @note The block must be convertible to a shareable proc.
32
+ # @return [void]
33
+ def self.define(*classes, &)
34
+ raise LocalJumpError, "no block given" unless block_given?
35
+ definition = Internal.prepare_method_definition(&)
36
+ classes.each do |klass|
37
+ REGISTER.define(klass) do
38
+ define_method(:deliver, definition)
39
+ private :deliver
40
+ end
41
+ end
42
+ end
43
+
44
+ # @api private
45
+ def self.build(publisher, subscriber, ...)
46
+ handler = REGISTER[subscriber.class]
47
+ unless handler.private_method_defined?(:deliver)
48
+ raise ArgumentError, "Unsupported subscriber type: #{subscriber.class}"
49
+ end
50
+ handler.new(publisher, subscriber, ...)
51
+ end
52
+
53
+ # @return [BasicObject] the subscriber associated with this subscription
54
+ attr_reader :subscriber
55
+
56
+ # @return [Farce::Abstract::Publisher] the publisher associated with this subscription
57
+ attr_reader :publisher
58
+
59
+ # @api private
60
+ attr_reader :key
61
+
62
+ # @!visibility private
63
+ def initialize(publisher, subscriber, map, callback: false, source: nil)
64
+ # Keep Proc identities alive without making their captured state shareable.
65
+ @source = source && Envelope.new(source, mode: :local)
66
+ @callback = callback
67
+ @enabled = Flag.new(true)
68
+ @key = (source || subscriber).__id__
69
+ @map = map
70
+ @publisher = publisher
71
+ @subscribed = Flag.new(true)
72
+ @subscriber = subscriber
73
+ super()
74
+ __freeze__
75
+ end
76
+
77
+ # @api private
78
+ def publish(payload)
79
+ return unless enabled?
80
+ deliver(payload)
81
+ true
82
+ rescue ::ClosedQueueError, Ractor::ClosedError, FrozenError
83
+ unsubscribe
84
+ false
85
+ end
86
+
87
+ # @return [Boolean] whether the subscription is currently enabled
88
+ def enabled? = @subscribed.value && @enabled.value
89
+
90
+ # @return [Boolean] whether the subscription has not been permanently canceled
91
+ def subscribed? = @subscribed.value
92
+
93
+ # Enables the subscription, allowing it to receive published payloads.
94
+ # @raise [UnsubscribedError] if the subscription has been unsubscribed
95
+ # @return [void]
96
+ def enable
97
+ raise UnsubscribedError, "Cannot enable a subscription that has been unsubscribed" unless @subscribed.value
98
+ @enabled.value = true
99
+ end
100
+
101
+ # Disables the subscription, preventing it from receiving published payloads.
102
+ # @return [void]
103
+ def disable = @enabled.value = false
104
+
105
+ # Removes the subscription permanently, allowing it and the subscriber to be garbage collected.
106
+ # @return [BasicObject, nil] the subscriber, or nil if already unsubscribed
107
+ def unsubscribe
108
+ return unless @subscribed.compare_and_set(true, false)
109
+ @map.delete_pair(key, self)
110
+ subscriber.seal if @callback
111
+ subscriber
112
+ end
113
+ alias cancel unsubscribe
114
+
115
+ private
116
+
117
+ define_method(:__freeze__, Kernel.instance_method(:freeze))
118
+
119
+ require "farce/subscription/definitions"
120
+ end
121
+ end
@@ -16,8 +16,42 @@ module Farce
16
16
  class Map < Abstract::ConcurrentMap
17
17
  include MapOperations
18
18
 
19
+ # (see Abstract::ConcurrentMap#store_if_present)
20
+ # @param mode [Symbol, nil] The result's transfer mode, or nil to use the default.
21
+ def store_if_present(key, mode: nil)
22
+ require_block!(block_given?)
23
+ write do
24
+ canonical = prepare(key)
25
+ next unless @working.key?(canonical)
26
+ replacement = yield(unwrap_stored(@working[canonical]))
27
+ unwrap_stored(store_value(canonical, wrap(replacement, mode:)))
28
+ end
29
+ end
30
+
31
+ # (see Abstract::ConcurrentMap#replace_if_exists)
32
+ # @param mode [Symbol, nil] The replacement's transfer mode, or nil to use the default.
33
+ def replace_if_exists(key, replacement, mode: nil)
34
+ write do
35
+ canonical = prepare(key)
36
+ next unless @working.key?(canonical)
37
+ previous = @working[canonical]
38
+ store_value(canonical, wrap(replacement, mode:))
39
+ unwrap_stored(previous)
40
+ end
41
+ end
42
+
43
+ # (see Abstract::ConcurrentMap#delete_pair)
44
+ # A failed comparison invalidates the transaction attempt.
45
+ def delete_pair(key, expected)
46
+ removed = modify_entry(key) do |present, value|
47
+ present && values_equal?(value, expected) ? Internal::MAP_DELETE : Internal::MAP_KEEP
48
+ end
49
+ compared(removed)
50
+ end
51
+
19
52
  Wrapper.inherit(self, *MapOperations::READ_HELPERS, :delete_if, :reject!, :compact!, :keep_if,
20
- :select!, :filter!, :transform_values!, :merge!)
53
+ :select!, :filter!, :transform_values!, :merge!, :put, :get_and_set, :compute_if_absent,
54
+ :compute_if_present, :replace_pair, :compute, :merge_pair, :fetch_or_store, :put_if_absent)
21
55
 
22
56
  private def enlist(backend) = @transaction.enlist(backend, per_key: true)
23
57
  end
@@ -36,7 +36,7 @@ module Farce
36
36
  Internal::LocalScheduledTask.new(args, block)
37
37
  end
38
38
 
39
- def schedule_scaler(started, delay = @grow_after)
39
+ def schedule_scaler(started, delay = settings.grow_after)
40
40
  Internal::ThreadPoolSupervisor.current.schedule(self, started, delay)
41
41
  end
42
42
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Unshared
7
+ # Unshared variant of {Farce::Publisher}.
8
+ class Publisher < Abstract::Publisher
9
+ include Unshareable
10
+
11
+ # @overload initialize(*subscribers)
12
+ # (see Farce::Abstract#initialize)
13
+ def initialize(...)
14
+ @map = Unshared::Map.new
15
+ super
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :map
21
+
22
+ def default_mode = :local
23
+ end
24
+ end
25
+ end
@@ -20,12 +20,12 @@ module Farce
20
20
  # results.pop # => :done
21
21
  # scheduler.close
22
22
  #
23
- # @!method schedule(*args, mode: :smart, auto_local: true)
23
+ # @!method schedule(*args, mode: :smart, auto_local: true, timeout: nil)
24
24
  # Enqueue a task while retaining its block and argument references.
25
25
  # `mode` is ignored when `auto_local` is true. Otherwise, only local and
26
26
  # smart modes are accepted.
27
27
  # @param (see Farce::Abstract::Executor#schedule)
28
- # @return [self]
28
+ # @return [self, nil] self when submitted, or nil when the scheduling timeout expires.
29
29
  class Scheduler < Abstract::Scheduler
30
30
  include Unshareable
31
31
  include Internal::LocalExecutorMethods
@@ -80,7 +80,7 @@ module Farce
80
80
  def new_queue(capacity:) = Internal::UnsharedQueue.new(capacity:)
81
81
  def launch_callback = ->(_scheduler, priority, name) { launch!(name, priority) }
82
82
 
83
- def prepare_task(args, block, mode, auto_local)
83
+ def prepare_task(args, block, mode, auto_local, **)
84
84
  validate_local_mode(mode, auto_local)
85
85
  Internal::LocalScheduledTask.new(args, block)
86
86
  end
@@ -9,10 +9,11 @@ module Farce
9
9
  include Unshareable
10
10
  include Internal::Freeze::Delegated
11
11
 
12
- # @param source [Array, nil] Initial values. Values are retained directly.
12
+ # @param source [Array, Integer, nil] Initial values or a size filled with nil. Values are retained directly.
13
13
  # @param compare_by_identity [Boolean] Whether values are compared by identity.
14
+ # @raise [ArgumentError] If the size is negative.
14
15
  def initialize(source = nil, compare_by_identity: false)
15
- @vector = Internal::UnsharedVector.new(source, compare_by_identity:)
16
+ @vector = Internal::UnsharedVector.new(normalize_vector_source(source), compare_by_identity:)
16
17
  super()
17
18
  end
18
19
 
data/lib/farce/vector.rb CHANGED
@@ -20,10 +20,12 @@ module Farce
20
20
  include Shareable::Delegated
21
21
 
22
22
  # @!macro modes
23
- # @param source [Array, nil] Initial values. The source array is not retained.
23
+ # @param source [Array, Integer, nil] Initial values or a size filled with nil. The source array is not retained.
24
24
  # @param mode [Symbol] The default value transfer mode.
25
25
  # @param compare_by_identity [Boolean] Whether values are compared by identity.
26
+ # @raise [ArgumentError] If the size is negative.
26
27
  def initialize(source = nil, mode: :smart, compare_by_identity: false)
28
+ source = normalize_vector_source(source)
27
29
  raise TypeError, "source must be an Array" unless source.nil? || source.is_a?(Array)
28
30
  unless true.equal?(compare_by_identity) || false.equal?(compare_by_identity)
29
31
  raise ArgumentError, "compare_by_identity must be true or false"
@@ -176,11 +178,18 @@ module Farce
176
178
  replacement = @manager.wrap(replacement, mode:)
177
179
  wrapped = true
178
180
  end
179
- return true if @vector.compare_and_set(index, current, replacement, timeout: remaining_timeout(deadline))
181
+ return true if @vector.compare_and_set(index, current, replacement, timeout: Clock.remaining(deadline))
180
182
  return false if nil.equal?(current) || (deadline && Clock.now >= deadline)
181
183
  end
182
184
  end
183
185
 
186
+ # (see Farce::Abstract::Vector#wait_until)
187
+ def wait_until(index, timeout: nil)
188
+ raise LocalJumpError, "no block given" unless block_given?
189
+ result = Internal.wait_until(@vector, index, timeout:) { |current| yield(@manager.unwrap(current)) }
190
+ @manager.unwrap(result)
191
+ end
192
+
184
193
  # (see Farce::Abstract::Vector#wait_until_changed)
185
194
  def wait_until_changed(index, expected, timeout: nil)
186
195
  deadline = timeout_deadline(timeout)
@@ -189,7 +198,7 @@ module Farce
189
198
  current = @vector[index]
190
199
  return @manager.unwrap(current) unless values_equal?(current, expected)
191
200
  return if deadline && Clock.now >= deadline
192
- @vector.wait_until_changed(index, current, timeout: remaining_timeout(deadline))
201
+ @vector.wait_until_changed(index, current, timeout: Clock.remaining(deadline))
193
202
  end
194
203
  end
195
204
 
@@ -243,11 +252,5 @@ module Farce
243
252
  raise ArgumentError, "timeout must be finite and non-negative" unless timeout.finite? && !timeout.negative?
244
253
  Clock.now + timeout
245
254
  end
246
-
247
- def remaining_timeout(deadline)
248
- return unless deadline
249
- remaining = deadline - Clock.now
250
- remaining.negative? ? 0 : remaining
251
- end
252
255
  end
253
256
  end
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.4.1"
7
+ VERSION = "0.5.0"
8
8
  end
@@ -118,8 +118,19 @@ module Farce
118
118
  if !key.equal?(new_key)
119
119
  entries << [key, new_key, result]
120
120
  elsif !value.equal?(result)
121
- until target.compare_and_set(key, value, result)
122
- value = target[key]
121
+ loop do
122
+ retry_update = false
123
+ send_to(target, :modify_entry, key, canonical: true) do |present, current|
124
+ next Internal::MAP_KEEP unless present
125
+ if Internal.value_changed?(current, value, true)
126
+ value = current
127
+ retry_update = true
128
+ next Internal::MAP_KEEP
129
+ end
130
+ result
131
+ end
132
+ break unless retry_update
133
+ # Traverse replacements after releasing the key's update reservation.
123
134
  result = walker.visit(value)
124
135
  end
125
136
  end
@@ -100,14 +100,17 @@ module Farce
100
100
  matched
101
101
  end
102
102
 
103
+ # (see Abstract::Atom#wait_until)
104
+ def wait_until(timeout: nil, &) = Internal.wait_until(@atom, timeout:, &)
105
+
103
106
  # (see Abstract::Atom#wait_until_changed)
104
107
  def wait_until_changed(expected, timeout: nil, &fallback)
105
108
  deadline = Internal.timeout_deadline(timeout)
106
109
  while true
107
- current = @atom.get(timeout: Internal.remaining_timeout(deadline)) { TIMED_OUT }
110
+ current = @atom.get(timeout: Clock.remaining(deadline)) { TIMED_OUT }
108
111
  return fallback&.call if TIMED_OUT.equal?(current)
109
112
  return current unless values_equal?(current, expected)
110
- result = @atom.wait_until_changed(current, timeout: Internal.remaining_timeout(deadline)) { TIMED_OUT }
113
+ result = @atom.wait_until_changed(current, timeout: Clock.remaining(deadline)) { TIMED_OUT }
111
114
  return fallback&.call if TIMED_OUT.equal?(result)
112
115
  end
113
116
  end
data/lib/farce.rb CHANGED
@@ -429,7 +429,9 @@ module Farce
429
429
  end
430
430
 
431
431
  # Schedules work without waiting for the block to finish.
432
- # On CRuby, uses a shared Ractor pool with at most {System.cpu_count} workers.
432
+ # On CRuby, uses a shared Ractor pool with at most {System.cpu_count} workers by default.
433
+ # Set {Config}'s `parallel_pool_*` options before creating a scheduler to configure
434
+ # its worker limits, growth and shrink delays, per-worker task limit, and queue capacity.
433
435
  # On JRuby and TruffleRuby, starts a new thread for each task.
434
436
  #
435
437
  # Without a block, returns the shared scheduler. The CRuby pool starts workers
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.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Haase
@@ -114,6 +114,7 @@ files:
114
114
  - lib/farce/abstract/pool.rb
115
115
  - lib/farce/abstract/port.rb
116
116
  - lib/farce/abstract/priority_queue.rb
117
+ - lib/farce/abstract/publisher.rb
117
118
  - lib/farce/abstract/queue.rb
118
119
  - lib/farce/abstract/scheduler.rb
119
120
  - lib/farce/abstract/set.rb
@@ -131,6 +132,14 @@ files:
131
132
  - lib/farce/backports.rb
132
133
  - lib/farce/class_mirror.rb
133
134
  - lib/farce/clock.rb
135
+ - lib/farce/concurrent.rb
136
+ - lib/farce/concurrent/count_down_latch.rb
137
+ - lib/farce/concurrent/cyclic_barrier.rb
138
+ - lib/farce/concurrent/event.rb
139
+ - lib/farce/concurrent/fiber_local_var.rb
140
+ - lib/farce/concurrent/local_var.rb
141
+ - lib/farce/concurrent/thread_local_var.rb
142
+ - lib/farce/concurrent/tuple.rb
134
143
  - lib/farce/config.rb
135
144
  - lib/farce/counter.rb
136
145
  - lib/farce/deduper.rb
@@ -298,6 +307,7 @@ files:
298
307
  - lib/farce/internal/mutable_ordered_key_lock_map.rb
299
308
  - lib/farce/internal/noncopyable.rb
300
309
  - lib/farce/internal/ordered_key_lock_map.rb
310
+ - lib/farce/internal/pool_settings.rb
301
311
  - lib/farce/internal/pool_supervisor.rb
302
312
  - lib/farce/internal/pool_worker.rb
303
313
  - lib/farce/internal/portable_transaction.rb
@@ -349,6 +359,7 @@ files:
349
359
  - lib/farce/local/molecule.rb
350
360
  - lib/farce/local/pool.rb
351
361
  - lib/farce/local/priority_queue.rb
362
+ - lib/farce/local/publisher.rb
352
363
  - lib/farce/local/queue.rb
353
364
  - lib/farce/local/scheduler.rb
354
365
  - lib/farce/local/scoped.rb
@@ -365,6 +376,7 @@ files:
365
376
  - lib/farce/lock.rb
366
377
  - lib/farce/lru_map.rb
367
378
  - lib/farce/map.rb
379
+ - lib/farce/memoize.rb
368
380
  - lib/farce/mode_manager.rb
369
381
  - lib/farce/mode_manager/smart_rule.rb
370
382
  - lib/farce/molecule.rb
@@ -377,6 +389,7 @@ files:
377
389
  - lib/farce/proxy/register.rb
378
390
  - lib/farce/proxy/supervisor.rb
379
391
  - lib/farce/proxy/wrapper.rb
392
+ - lib/farce/publisher.rb
380
393
  - lib/farce/queue.rb
381
394
  - lib/farce/ractor.rb
382
395
  - lib/farce/read_write_lock.rb
@@ -404,6 +417,7 @@ files:
404
417
  - lib/farce/strict/molecule.rb
405
418
  - lib/farce/strict/port.rb
406
419
  - lib/farce/strict/priority_queue.rb
420
+ - lib/farce/strict/publisher.rb
407
421
  - lib/farce/strict/queue.rb
408
422
  - lib/farce/strict/set.rb
409
423
  - lib/farce/strict/sorted_set.rb
@@ -415,6 +429,8 @@ files:
415
429
  - lib/farce/strict/weak_map.rb
416
430
  - lib/farce/strict/weak_set.rb
417
431
  - lib/farce/strict/weak_value_map.rb
432
+ - lib/farce/subscription.rb
433
+ - lib/farce/subscription/definitions.rb
418
434
  - lib/farce/system.rb
419
435
  - lib/farce/task.rb
420
436
  - lib/farce/thread_executor.rb
@@ -452,6 +468,7 @@ files:
452
468
  - lib/farce/unshared/molecule.rb
453
469
  - lib/farce/unshared/pool.rb
454
470
  - lib/farce/unshared/priority_queue.rb
471
+ - lib/farce/unshared/publisher.rb
455
472
  - lib/farce/unshared/queue.rb
456
473
  - lib/farce/unshared/scheduler.rb
457
474
  - lib/farce/unshared/set.rb