farce 0.4.1-java → 0.5.0-java

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 (98) 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/lib/farce/abstract/concurrent_map.rb +122 -0
  16. data/lib/farce/abstract/counter.rb +35 -1
  17. data/lib/farce/abstract/executor.rb +30 -7
  18. data/lib/farce/abstract/flag.rb +12 -0
  19. data/lib/farce/abstract/pool.rb +158 -47
  20. data/lib/farce/abstract/priority_queue.rb +1 -1
  21. data/lib/farce/abstract/publisher.rb +189 -0
  22. data/lib/farce/abstract/queue.rb +29 -3
  23. data/lib/farce/abstract/scheduler.rb +41 -14
  24. data/lib/farce/abstract/timer_queue.rb +3 -3
  25. data/lib/farce/abstract/vector.rb +3 -1
  26. data/lib/farce/atom.rb +9 -2
  27. data/lib/farce/clock.rb +12 -0
  28. data/lib/farce/concurrent/count_down_latch.rb +37 -0
  29. data/lib/farce/concurrent/cyclic_barrier.rb +109 -0
  30. data/lib/farce/concurrent/event.rb +60 -0
  31. data/lib/farce/concurrent/fiber_local_var.rb +31 -0
  32. data/lib/farce/concurrent/local_var.rb +65 -0
  33. data/lib/farce/concurrent/thread_local_var.rb +28 -0
  34. data/lib/farce/concurrent/tuple.rb +74 -0
  35. data/lib/farce/concurrent.rb +50 -0
  36. data/lib/farce/config.rb +112 -2
  37. data/lib/farce/counter.rb +0 -16
  38. data/lib/farce/engine/jruby/map.rb +8 -6
  39. data/lib/farce/engine/jruby/signal.rb +2 -2
  40. data/lib/farce/engine/jvm/farce.jar +0 -0
  41. data/lib/farce/engine/jvm/priority_queue.rb +14 -4
  42. data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -1
  43. data/lib/farce/engine/ruby/shared/ractor_selector.rb +1 -4
  44. data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
  45. data/lib/farce/engine/shared/exchanger.rb +2 -2
  46. data/lib/farce/engine/shared/queue.rb +43 -5
  47. data/lib/farce/engine/shared/ractor_selector.rb +1 -1
  48. data/lib/farce/engine/shared/strict_map.rb +5 -0
  49. data/lib/farce/engine/shared/unshared_atom.rb +1 -1
  50. data/lib/farce/engine/shared/unshared_vector.rb +8 -7
  51. data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
  52. data/lib/farce/engine/shared/weak_atom/base.rb +5 -10
  53. data/lib/farce/engine/shared/weak_map/cell.rb +1 -1
  54. data/lib/farce/engine/truffleruby/map.rb +8 -6
  55. data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
  56. data/lib/farce/engine/truffleruby/signal.rb +1 -1
  57. data/lib/farce/integrations/concurrent.rb +16 -3
  58. data/lib/farce/internal/blocking_priority_queue.rb +4 -9
  59. data/lib/farce/internal/flag_waiting.rb +1 -1
  60. data/lib/farce/internal/lease_initialization.rb +1 -1
  61. data/lib/farce/internal/lease_pool_state.rb +1 -1
  62. data/lib/farce/internal/lease_state.rb +2 -2
  63. data/lib/farce/internal/local_executor_methods.rb +3 -3
  64. data/lib/farce/internal/map_value_modes.rb +9 -2
  65. data/lib/farce/internal/pool_settings.rb +9 -0
  66. data/lib/farce/internal/pool_worker.rb +34 -21
  67. data/lib/farce/internal/reservation_waiting.rb +1 -1
  68. data/lib/farce/internal/scheduler_lifecycle.rb +2 -2
  69. data/lib/farce/internal/weak_map_value_modes.rb +5 -0
  70. data/lib/farce/internal.rb +8 -8
  71. data/lib/farce/lazy.rb +3 -0
  72. data/lib/farce/lazy_ref.rb +3 -0
  73. data/lib/farce/local/lease.rb +2 -9
  74. data/lib/farce/local/lease_map.rb +2 -9
  75. data/lib/farce/local/pool.rb +38 -5
  76. data/lib/farce/local/publisher.rb +30 -0
  77. data/lib/farce/local/scheduler.rb +18 -5
  78. data/lib/farce/local/vector.rb +3 -2
  79. data/lib/farce/memoize.rb +314 -0
  80. data/lib/farce/pool.rb +5 -0
  81. data/lib/farce/priority_queue.rb +1 -1
  82. data/lib/farce/publisher.rb +57 -0
  83. data/lib/farce/signal.rb +1 -1
  84. data/lib/farce/strict/publisher.rb +31 -0
  85. data/lib/farce/strict/vector.rb +3 -2
  86. data/lib/farce/subscription/definitions.rb +27 -0
  87. data/lib/farce/subscription.rb +121 -0
  88. data/lib/farce/transaction/map.rb +35 -1
  89. data/lib/farce/unshared/pool.rb +1 -1
  90. data/lib/farce/unshared/publisher.rb +25 -0
  91. data/lib/farce/unshared/scheduler.rb +3 -3
  92. data/lib/farce/unshared/vector.rb +3 -2
  93. data/lib/farce/vector.rb +12 -9
  94. data/lib/farce/version.rb +1 -1
  95. data/lib/farce/walker/definitions.rb +13 -2
  96. data/lib/farce/weak_atom.rb +5 -2
  97. data/lib/farce.rb +3 -1
  98. metadata +19 -2
@@ -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,13 +1,13 @@
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: java
6
6
  authors:
7
7
  - Konstantin Haase
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-10-18 00:00:00.000000000 Z
10
+ date: 2026-10-21 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  email:
13
13
  - konstantin.mailinglists@googlemail.com
@@ -64,6 +64,7 @@ files:
64
64
  - lib/farce/abstract/pool.rb
65
65
  - lib/farce/abstract/port.rb
66
66
  - lib/farce/abstract/priority_queue.rb
67
+ - lib/farce/abstract/publisher.rb
67
68
  - lib/farce/abstract/queue.rb
68
69
  - lib/farce/abstract/scheduler.rb
69
70
  - lib/farce/abstract/set.rb
@@ -81,6 +82,14 @@ files:
81
82
  - lib/farce/backports.rb
82
83
  - lib/farce/class_mirror.rb
83
84
  - lib/farce/clock.rb
85
+ - lib/farce/concurrent.rb
86
+ - lib/farce/concurrent/count_down_latch.rb
87
+ - lib/farce/concurrent/cyclic_barrier.rb
88
+ - lib/farce/concurrent/event.rb
89
+ - lib/farce/concurrent/fiber_local_var.rb
90
+ - lib/farce/concurrent/local_var.rb
91
+ - lib/farce/concurrent/thread_local_var.rb
92
+ - lib/farce/concurrent/tuple.rb
84
93
  - lib/farce/config.rb
85
94
  - lib/farce/counter.rb
86
95
  - lib/farce/deduper.rb
@@ -248,6 +257,7 @@ files:
248
257
  - lib/farce/internal/mutable_ordered_key_lock_map.rb
249
258
  - lib/farce/internal/noncopyable.rb
250
259
  - lib/farce/internal/ordered_key_lock_map.rb
260
+ - lib/farce/internal/pool_settings.rb
251
261
  - lib/farce/internal/pool_supervisor.rb
252
262
  - lib/farce/internal/pool_worker.rb
253
263
  - lib/farce/internal/portable_transaction.rb
@@ -299,6 +309,7 @@ files:
299
309
  - lib/farce/local/molecule.rb
300
310
  - lib/farce/local/pool.rb
301
311
  - lib/farce/local/priority_queue.rb
312
+ - lib/farce/local/publisher.rb
302
313
  - lib/farce/local/queue.rb
303
314
  - lib/farce/local/scheduler.rb
304
315
  - lib/farce/local/scoped.rb
@@ -315,6 +326,7 @@ files:
315
326
  - lib/farce/lock.rb
316
327
  - lib/farce/lru_map.rb
317
328
  - lib/farce/map.rb
329
+ - lib/farce/memoize.rb
318
330
  - lib/farce/mode_manager.rb
319
331
  - lib/farce/mode_manager/smart_rule.rb
320
332
  - lib/farce/molecule.rb
@@ -327,6 +339,7 @@ files:
327
339
  - lib/farce/proxy/register.rb
328
340
  - lib/farce/proxy/supervisor.rb
329
341
  - lib/farce/proxy/wrapper.rb
342
+ - lib/farce/publisher.rb
330
343
  - lib/farce/queue.rb
331
344
  - lib/farce/ractor.rb
332
345
  - lib/farce/read_write_lock.rb
@@ -354,6 +367,7 @@ files:
354
367
  - lib/farce/strict/molecule.rb
355
368
  - lib/farce/strict/port.rb
356
369
  - lib/farce/strict/priority_queue.rb
370
+ - lib/farce/strict/publisher.rb
357
371
  - lib/farce/strict/queue.rb
358
372
  - lib/farce/strict/set.rb
359
373
  - lib/farce/strict/sorted_set.rb
@@ -365,6 +379,8 @@ files:
365
379
  - lib/farce/strict/weak_map.rb
366
380
  - lib/farce/strict/weak_set.rb
367
381
  - lib/farce/strict/weak_value_map.rb
382
+ - lib/farce/subscription.rb
383
+ - lib/farce/subscription/definitions.rb
368
384
  - lib/farce/system.rb
369
385
  - lib/farce/task.rb
370
386
  - lib/farce/thread_executor.rb
@@ -402,6 +418,7 @@ files:
402
418
  - lib/farce/unshared/molecule.rb
403
419
  - lib/farce/unshared/pool.rb
404
420
  - lib/farce/unshared/priority_queue.rb
421
+ - lib/farce/unshared/publisher.rb
405
422
  - lib/farce/unshared/queue.rb
406
423
  - lib/farce/unshared/scheduler.rb
407
424
  - lib/farce/unshared/set.rb