farce 0.4.0 → 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 (124) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -1
  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/LIBURING-LICENSE +20 -0
  16. data/ext/farce/atom.c +23 -66
  17. data/ext/farce/counter.c +1 -1
  18. data/ext/farce/depend +15 -7
  19. data/ext/farce/drivers.c +44 -2
  20. data/ext/farce/exchanger.c +1 -1
  21. data/ext/farce/extconf.rb +45 -2
  22. data/ext/farce/farce.c +17 -0
  23. data/ext/farce/flag.c +1 -1
  24. data/ext/farce/futex.c +76 -0
  25. data/ext/farce/futex.h +31 -0
  26. data/ext/farce/lock.c +16 -62
  27. data/ext/farce/map.c +24 -57
  28. data/ext/farce/native_wait.c +139 -0
  29. data/ext/farce/native_wait.h +37 -0
  30. data/ext/farce/priority_queue.c +62 -59
  31. data/ext/farce/queue.c +142 -8
  32. data/ext/farce/reactor.c +100 -11
  33. data/ext/farce/reactor.h +6 -2
  34. data/ext/farce/signal.c +17 -64
  35. data/ext/farce/tree_map.c +15 -54
  36. data/ext/farce/unshareable.c +1 -1
  37. data/ext/farce/unsupported.c +13 -1
  38. data/ext/farce/vector.c +16 -63
  39. data/ext/farce/weak_map.c +13 -4
  40. data/ext/java/org/farce/PriorityQueue.java +18 -2
  41. data/lib/farce/abstract/concurrent_map.rb +122 -0
  42. data/lib/farce/abstract/counter.rb +35 -1
  43. data/lib/farce/abstract/executor.rb +30 -7
  44. data/lib/farce/abstract/flag.rb +12 -0
  45. data/lib/farce/abstract/pool.rb +158 -47
  46. data/lib/farce/abstract/priority_queue.rb +1 -1
  47. data/lib/farce/abstract/publisher.rb +189 -0
  48. data/lib/farce/abstract/queue.rb +29 -3
  49. data/lib/farce/abstract/scheduler.rb +41 -14
  50. data/lib/farce/abstract/timer_queue.rb +3 -3
  51. data/lib/farce/abstract/vector.rb +3 -1
  52. data/lib/farce/atom.rb +9 -2
  53. data/lib/farce/clock.rb +12 -0
  54. data/lib/farce/concurrent/count_down_latch.rb +37 -0
  55. data/lib/farce/concurrent/cyclic_barrier.rb +109 -0
  56. data/lib/farce/concurrent/event.rb +60 -0
  57. data/lib/farce/concurrent/fiber_local_var.rb +31 -0
  58. data/lib/farce/concurrent/local_var.rb +65 -0
  59. data/lib/farce/concurrent/thread_local_var.rb +28 -0
  60. data/lib/farce/concurrent/tuple.rb +74 -0
  61. data/lib/farce/concurrent.rb +50 -0
  62. data/lib/farce/config.rb +112 -2
  63. data/lib/farce/counter.rb +0 -16
  64. data/lib/farce/engine/jruby/map.rb +8 -6
  65. data/lib/farce/engine/jruby/signal.rb +2 -2
  66. data/lib/farce/engine/jvm/farce.jar +0 -0
  67. data/lib/farce/engine/jvm/priority_queue.rb +14 -4
  68. data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -1
  69. data/lib/farce/engine/ruby/shared/ractor_selector.rb +1 -4
  70. data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
  71. data/lib/farce/engine/shared/exchanger.rb +2 -2
  72. data/lib/farce/engine/shared/queue.rb +43 -5
  73. data/lib/farce/engine/shared/ractor_selector.rb +1 -1
  74. data/lib/farce/engine/shared/strict_map.rb +5 -0
  75. data/lib/farce/engine/shared/unshared_atom.rb +1 -1
  76. data/lib/farce/engine/shared/unshared_vector.rb +8 -7
  77. data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
  78. data/lib/farce/engine/shared/weak_atom/base.rb +5 -10
  79. data/lib/farce/engine/shared/weak_map/cell.rb +1 -1
  80. data/lib/farce/engine/truffleruby/map.rb +8 -6
  81. data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
  82. data/lib/farce/engine/truffleruby/signal.rb +1 -1
  83. data/lib/farce/integrations/concurrent.rb +16 -3
  84. data/lib/farce/internal/blocking_priority_queue.rb +4 -9
  85. data/lib/farce/internal/flag_waiting.rb +1 -1
  86. data/lib/farce/internal/lease_initialization.rb +1 -1
  87. data/lib/farce/internal/lease_pool_state.rb +1 -1
  88. data/lib/farce/internal/lease_state.rb +2 -2
  89. data/lib/farce/internal/local_executor_methods.rb +3 -3
  90. data/lib/farce/internal/map_value_modes.rb +9 -2
  91. data/lib/farce/internal/pool_settings.rb +9 -0
  92. data/lib/farce/internal/pool_worker.rb +34 -21
  93. data/lib/farce/internal/reservation_waiting.rb +1 -1
  94. data/lib/farce/internal/scheduler_lifecycle.rb +2 -2
  95. data/lib/farce/internal/weak_map_value_modes.rb +5 -0
  96. data/lib/farce/internal.rb +8 -8
  97. data/lib/farce/lazy.rb +3 -0
  98. data/lib/farce/lazy_ref.rb +3 -0
  99. data/lib/farce/local/lease.rb +2 -9
  100. data/lib/farce/local/lease_map.rb +2 -9
  101. data/lib/farce/local/pool.rb +38 -5
  102. data/lib/farce/local/publisher.rb +30 -0
  103. data/lib/farce/local/scheduler.rb +18 -5
  104. data/lib/farce/local/vector.rb +3 -2
  105. data/lib/farce/memoize.rb +314 -0
  106. data/lib/farce/pool.rb +5 -0
  107. data/lib/farce/priority_queue.rb +1 -1
  108. data/lib/farce/publisher.rb +57 -0
  109. data/lib/farce/signal.rb +1 -1
  110. data/lib/farce/strict/publisher.rb +31 -0
  111. data/lib/farce/strict/vector.rb +3 -2
  112. data/lib/farce/subscription/definitions.rb +27 -0
  113. data/lib/farce/subscription.rb +121 -0
  114. data/lib/farce/transaction/map.rb +35 -1
  115. data/lib/farce/unshared/pool.rb +1 -1
  116. data/lib/farce/unshared/publisher.rb +25 -0
  117. data/lib/farce/unshared/scheduler.rb +3 -3
  118. data/lib/farce/unshared/vector.rb +3 -2
  119. data/lib/farce/vector.rb +12 -9
  120. data/lib/farce/version.rb +1 -1
  121. data/lib/farce/walker/definitions.rb +13 -2
  122. data/lib/farce/weak_atom.rb +5 -2
  123. data/lib/farce.rb +3 -1
  124. metadata +23 -1
@@ -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.0"
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.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Haase
@@ -42,6 +42,7 @@ files:
42
42
  - docs/variants-and-scopes.md
43
43
  - docs/variants.md
44
44
  - ext/ext_helper.rb
45
+ - ext/farce/LIBURING-LICENSE
45
46
  - ext/farce/README.md
46
47
  - ext/farce/atom.c
47
48
  - ext/farce/bounded_map.c
@@ -56,9 +57,13 @@ files:
56
57
  - ext/farce/extconf.rb
57
58
  - ext/farce/farce.c
58
59
  - ext/farce/flag.c
60
+ - ext/farce/futex.c
61
+ - ext/farce/futex.h
59
62
  - ext/farce/io.c
60
63
  - ext/farce/lock.c
61
64
  - ext/farce/map.c
65
+ - ext/farce/native_wait.c
66
+ - ext/farce/native_wait.h
62
67
  - ext/farce/priority_queue.c
63
68
  - ext/farce/queue.c
64
69
  - ext/farce/reactor.c
@@ -109,6 +114,7 @@ files:
109
114
  - lib/farce/abstract/pool.rb
110
115
  - lib/farce/abstract/port.rb
111
116
  - lib/farce/abstract/priority_queue.rb
117
+ - lib/farce/abstract/publisher.rb
112
118
  - lib/farce/abstract/queue.rb
113
119
  - lib/farce/abstract/scheduler.rb
114
120
  - lib/farce/abstract/set.rb
@@ -126,6 +132,14 @@ files:
126
132
  - lib/farce/backports.rb
127
133
  - lib/farce/class_mirror.rb
128
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
129
143
  - lib/farce/config.rb
130
144
  - lib/farce/counter.rb
131
145
  - lib/farce/deduper.rb
@@ -293,6 +307,7 @@ files:
293
307
  - lib/farce/internal/mutable_ordered_key_lock_map.rb
294
308
  - lib/farce/internal/noncopyable.rb
295
309
  - lib/farce/internal/ordered_key_lock_map.rb
310
+ - lib/farce/internal/pool_settings.rb
296
311
  - lib/farce/internal/pool_supervisor.rb
297
312
  - lib/farce/internal/pool_worker.rb
298
313
  - lib/farce/internal/portable_transaction.rb
@@ -344,6 +359,7 @@ files:
344
359
  - lib/farce/local/molecule.rb
345
360
  - lib/farce/local/pool.rb
346
361
  - lib/farce/local/priority_queue.rb
362
+ - lib/farce/local/publisher.rb
347
363
  - lib/farce/local/queue.rb
348
364
  - lib/farce/local/scheduler.rb
349
365
  - lib/farce/local/scoped.rb
@@ -360,6 +376,7 @@ files:
360
376
  - lib/farce/lock.rb
361
377
  - lib/farce/lru_map.rb
362
378
  - lib/farce/map.rb
379
+ - lib/farce/memoize.rb
363
380
  - lib/farce/mode_manager.rb
364
381
  - lib/farce/mode_manager/smart_rule.rb
365
382
  - lib/farce/molecule.rb
@@ -372,6 +389,7 @@ files:
372
389
  - lib/farce/proxy/register.rb
373
390
  - lib/farce/proxy/supervisor.rb
374
391
  - lib/farce/proxy/wrapper.rb
392
+ - lib/farce/publisher.rb
375
393
  - lib/farce/queue.rb
376
394
  - lib/farce/ractor.rb
377
395
  - lib/farce/read_write_lock.rb
@@ -399,6 +417,7 @@ files:
399
417
  - lib/farce/strict/molecule.rb
400
418
  - lib/farce/strict/port.rb
401
419
  - lib/farce/strict/priority_queue.rb
420
+ - lib/farce/strict/publisher.rb
402
421
  - lib/farce/strict/queue.rb
403
422
  - lib/farce/strict/set.rb
404
423
  - lib/farce/strict/sorted_set.rb
@@ -410,6 +429,8 @@ files:
410
429
  - lib/farce/strict/weak_map.rb
411
430
  - lib/farce/strict/weak_set.rb
412
431
  - lib/farce/strict/weak_value_map.rb
432
+ - lib/farce/subscription.rb
433
+ - lib/farce/subscription/definitions.rb
413
434
  - lib/farce/system.rb
414
435
  - lib/farce/task.rb
415
436
  - lib/farce/thread_executor.rb
@@ -447,6 +468,7 @@ files:
447
468
  - lib/farce/unshared/molecule.rb
448
469
  - lib/farce/unshared/pool.rb
449
470
  - lib/farce/unshared/priority_queue.rb
471
+ - lib/farce/unshared/publisher.rb
450
472
  - lib/farce/unshared/queue.rb
451
473
  - lib/farce/unshared/scheduler.rb
452
474
  - lib/farce/unshared/set.rb