Changelog

Farce follows Semantic Versioning 2.0. Any public method on any public class or module documented in the README or via YARD and not declared private is part of the public API.

Overview

Version Date Important Changes
0.6.0 2026-11-04 Performance improvements, better counters, and WeakCache
0.5.0 2026-10-21 Publishers, memoization, pool and queue resizing, and better concurrent-ruby compatibility
0.4.1 2026-10-18 Include improved io_uring support in prebuilt Linux binaries
0.4.0 2026-10-16 Introduce Farce::Backports
0.3.0 2026-10-13 Introduce Farce::Task and executor improvements
0.2.0 2026-10-11 Improvements for flags, vectors, and timer queues, and performance
0.1.0 2026-10-09 Initial release

Release History

Farce 0.6.0 (2026-11-04)

Breaking Changes

  • Counter and Local::Counter arithmetic methods (increment, add, decrement, subtract, and remove) now return the integer produced by the atomic update instead of the counter. Use number = counter.increment to allocate sequence numbers without a separate read. Calls can no longer be chained.

New Features

  • Farce.dedup can now deduplicate Regexp, Range, Data, and SortedSet instances.
  • Farce::Walker (and thus Farce.freeze_graph) correctly walk Struct and Range instances now.
  • Add Farce::Strict::WeakCache. It offers a subset of the API of Farce::Strict::WeakValueMap, but without its performance penalty on CRuby 3.x and 4.0 (on other Rubies, including CRuby 4.1, it's just a tiny wrapper around the well-performing WeakValueMap).

Performance improvements

  • Faster construction of Map and Vector instances.
  • Speed up atoms and memoization.

Farce 0.5.0 (2026-10-21)

New Features

Publishers

Farce::Publisher broadcasts messages to multiple subscribers. It supports Ractors, ports, queues, atoms, counters, flags, collections, signals, other publishers, and callbacks.

publisher = Farce::Publisher.new
counter   = Farce::Counter.new
queue     = Farce::Queue.new

publisher.subscribe(counter)
publisher.subscribe(queue)
publisher.publish(:hello)

counter.value # => 1
queue.pop     # => :hello
Memoization

Added Farce::Memoize for memoization with scopes, transfer modes, and weak values, including after freezing.

class MyClass
  include Farce::Memoize

  # Memoize #expensive_method, shared across Ractors
  memoize def expensive_method = expensive_computation

  # Memoize #method_with_unsharable_value for each Ractor separately
  def method_with_unsharable_value = unsharable_computation
  memoize :method_with_unsharable_value, scope: :ractor
end
Better concurrent-ruby Compatibility
  • Added Farce::Concurrent, with replacements for CountDownLatch, CyclicBarrier, Event, FiberLocalVar, ThreadLocalVar, LockLocalVar, and Tuple, plus aliases for existing Farce classes.
  • Maps now support all public API methods of Concurrent::Map.
  • Added Counter#update to atomically replace the value using a block.
  • Added true?, false?, make_true, and make_false to flags.
Queue and Pool Tuning
  • Queues, priority queues, timer queues, schedulers, and pools now support resizing.
  • Pools now support tuning after construction. Increasing the minimum starts workers immediately. Excess workers retire when idle without interrupting running tasks.
  • Added parallel_pool_* configuration options for the shared CRuby pool used by Farce.in_parallel.
Miscellaneous
  • Added timeout: to executor schedule and execute methods.
  • Added Farce::Clock.remaining and refactored code to use it.
  • Vectors now accept an integer size in their constructors, filling the initial entries with nil.

Bug Fixes

  • Prevent waits from spinning when an unchanged value has non-reflexive equality, such as Float::NAN, or overrides ==, !=, or equal?. Predicates receive unwrapped values.
  • Prevent graph traversal from retrying indefinitely when a map value cannot be matched using ordinary equality.
  • Fix vector wait timeout fallbacks on CRuby and TruffleRuby.

Other changes

  • Added pairing_heap to the priority queue benchmark and refreshed the published benchmark results. It is the fastest priority queue implementation written in pure Ruby.

Farce 0.4.1 (2026-10-18)

  • Make sure prebuilt Linux binaries include liburing support.
  • Use a Futex for efficient fiber synchronization if possible.
  • Consistent wrap_struct_name across the native extension for debugging.

Farce 0.4.0 (2026-10-16)

New Features

Ractor Backports

Farce::Backports makes newer Ractor APIs available on older Ruby versions:

  • Added Ractor#default_port, #join, #monitor, #unmonitor, and #value on Ruby versions prior to 4.0.
  • Added Ractor#alive? on Ruby versions prior to 4.1.
  • Added optional timeout: support for Ractor.select, Ractor#receive, and Ractor::Port#receive on Ruby versions prior to 4.1.

You can activate the backports for a lexical scope with using Farce::Backports, install them globally with Farce::Backports.install, or call methods on Farce::Backports directly. Global installation also defines Ractor and Ractor::Port when they are not natively available.

using Farce::Backports

ractor = Farce::Ractor.new { 42 }
ractor.join
ractor.value # => 42
Ractor Improvements
  • Farce::Ractor.select is now implemented on JRuby and TruffleRuby. It supports selecting from Ractors and ports, optional timeouts, and waiting without blocking a Fiber scheduler.
  • The Ractor polyfill now supports alive? and follows Ruby 4.1's monitor notification format when running on Ruby 4.1 or later.
Miscellaneous
  • Added set? and unset? predicates to Flag and Local::Flag.
  • Added Farce::NULL_PORT, a write-only port that discards messages and can be used to ignore Ractor exit notifications.
  • Added Farce::System.ruby_version, which returns the current Ruby release as a version object without prerelease suffixes. It can be called from any Ractor.
  • Added Farce::Extension to easily define modules that can be used both as refinements or installed globally into existing classes, used by Farce::Backports.

Bug Fixes

  • Work around a JRuby synchronization race that could lose a mutex wakeup and leave a scheduled Fiber blocked.

Other changes

  • Reorganized the README into a shorter introduction with topic-specific guides in the docs directory.
  • Updated compatibility documentation and tests for ractor-tmvar 0.4.0.

Farce 0.3.0 (2026-10-13)

New Features

Breaking Changes
  • Abstract::Scheduler is no longer a superclass of Pool. Use Abstract::Executor instead.
Executor Improvements

Scheduler and Pool have the following improvements:

  • They now support try_schedule, which discards tasks and returns nil when their internal queues are at capacity.
  • They now have Unshared and Local variants, running in a Ractor-local context (Pool will manage a Thread pool instead of a Ractor pool).

Creates a thread pool with a maximum size of 2 and a queue capacity of 10:

# A thread pool local to the current Ractor
thread_pool = Farce::Unshared::Pool.new(max_size: 2, capacity: 10)

# this should only run approximately 10 tasks on CRuby
500.times { thread_pool.try_schedule { puts "Task executed" } }
Tasks

A new class, Task, has been introduced to represent asynchronous tasks that can be scheduled and executed within Farce's executor framework, executed manually, or handed off to any other executor supporting procs or callable objects.

task = Farce::Task.schedule do
  puts "Task started"
  sleep 1
  :success
ensure
  puts "Task finished"
end

task.alive? # => true
task.value  # => :success (blocks until the task completes)

Bug Fixes

  • Calling wait_* methods on atoms on JRuby no longer blocks a fiber scheduler.

Farce 0.2.0 (2026-10-11)

New Features

Waiting on Flags

You can now wait for a Farce::Flag or Farce::Local::Flag to change its value:

flag = Farce::Flag.new

Thread.new do
  flag.wait_until_set
  puts "Flag has been activated!"
end

flag.set
Timer Queue Entries

TimerQueue#push and TimerQueue#try_push now return a TimerQueue::Entry object on a successful push. This allows you to cancel a scheduled value using the returned entry:

queue = Farce::TimerQueue.new
entry = queue.push(:work, delay: 60)
entry.delete # => true
Expanded Vector Functionality

Improved Array compatibility for Vector, Strict::Vector, Unshared::Vector, and Local::Vector:

  • Added to_ary, delete, and delete_if.
  • Added range and start/length indexing, slice assignment, multiple-value push and append, and pop(count).
  • Added shift, unshift, prepend, insert, delete_at, slice!, replace, and fill.
  • Added map!, collect!, keep_if, select!, filter!, reject!, compact!, and uniq!.
  • Added reverse!, rotate!, shuffle!, sort!, sort_by!, and flatten!.
  • Added flatten, transpose, product, combination, repeated_combination, permutation, and repeated_permutation.
  • Added lexicographic <=>. Equality and hashing continue to use object identity.
Miscellaneous
  • Unshareable objects may now be frozen.
  • Trying to make an unshareable object shareable raises a better error message now (Ractor::Error with a descriptive message, instead of a NoMethodError).
  • Added a new transfer mode: :marshal, which will copy objects between Ractors using Ruby's Marshal serialization.

Bug Fixes

  • Improve compatibility with other gems modifying Kernel#require, work around an issue with Ruby 4.0's runtime ractor require patch and alias methods chains not working nicely with Module#prepend.

Performance Improvements

Rework CRuby native extension

Incorporated the advice from "Tips for updating Ruby C extensions for efficiently using Ruby 4 VM APIs".

With this change, all long-living Farce objects can be compacted and moved to old GC generations. This was not the case previously for maps and vectors that had snapshots taken of them (either for iteration or for transactions). These ended up being permanently kept in young generations. A long-living map or vector would therefore cause frequent GC runs to be quite expensive.

Operation Before After Change
Map#each over 1024 entries 34.1 μs 35.2 μs 3% slower
Transaction with a map with 16 entries 25.2 μs 25.3 μs 0.4% slower
Transaction with a map with 1024 entries 26.7 μs 29.2 μs 9% slower
Transaction with a vector with 10,000 entries 26.1 μs 29.4 μs 13% slower
GC while keeping a vector with 10,000 entries alive 67.6 μs 28.6 μs 58% faster

Beyond these specific cases:

  • Other iteration methods will have a similar performance impact to Map#each (for instance, Map#transform_values).
  • Methods not involving iterations, like reading or writing a specific map entry, do not show any measurable performance impact.

Measurements were taken on CRuby 4.0.7 (arm64-darwin23) with YJIT enabled. Measurements taken against Ruby's main branch show similar results.

Other changes

  • Avoid circular require warnings when automatically loading third-party integrations.
  • Do not install a require hook if automatic integration loading is disabled.

Farce 0.1.0 (2026-10-09)

Initial release.