Ractor::Dispatch
Dispatch work to a specific Ractor and get results back. The primary use case is an "escape hatch" for code running in non-main Ractors that needs to execute something only the main Ractor can do (access globals, unshareable constants, ENV, etc).
Usage
require "ractor/dispatch"
Ractor.new do
# Synchronous — blocks until the main Ractor returns the result
home = Ractor::Dispatch.main.run { ENV["HOME"] }
puts home
# Async — returns a Future immediately
future = Ractor::Dispatch.main.submit { ENV["PATH"] }
# ... do other work ...
path = future.value
end.join
The passed block is automatically made into a shareable proc. Because shareable procs can close over frozen/shareable values, this works naturally with frozen string literals and other shareable objects.
Error handling
Exceptions raised by the block are propagated back to the caller:
Ractor.new do
Ractor::Dispatch.main.run { raise "oops" } # => raises RuntimeError "oops"
end.join
Shutdown
Ractor::Dispatch.main.shutdown # closes the work port, background thread exits
Custom executors
Ractor::Dispatch.main is a convenience for the common case. You can also
create your own executor on any Ractor:
executor = Ractor::Dispatch::Executor.new
Ractor.new(executor) do |ex|
ex.run { ENV["HOME"] }
end.join
How it works
Ractor::Dispatch.main lazily creates an Executor on the main Ractor.
Executor.new creates a Ractor::Port and spawns a background Thread on
the current Ractor that loops receiving work from the port. The Executor is
then made shareable so it can be passed to other Ractors.
When submit is called, the block is made into a shareable proc, the caller
creates a reply Ractor::Port, sends [callable, args, reply_port] to the
executor's work port, and returns a Future wrapping the reply port. The
background thread receives the job, executes it, and sends the result back on
the reply port.
The key Ractor::Port property that makes this work: any Ractor can send
to a port, but only the creating Ractor can receive from it. This means
reply ports naturally route results back to the correct caller.
Requirements
Requires Ruby with Ractor::Port support (Ruby 4.0+).
It installs and can be required on Ruby 3.3+, but attempting to use the library will raise.
Development
After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/jhawthorn/ractor-dispatch. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Ractor::Dispatch project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.