aw
Runs a Ruby block in a forked process.
What the block returns comes back to the parent, and what it raises is raised again there. What it does to the process stays in the child: mutations, globals, constants, the current directory, ENV, a crash. What it does outside, to files or to other processes, does not.
- Version
- 1.0.0
- Ruby
- 3.3 or later, with
fork(2): CRuby on Linux, macOS and the BSDs - Dependencies
- None
- License
- MIT
- Used by
- Fix 1.0
Install
Add gem "aw" to your Gemfile, or run gem install aw. Without fork(2), on Windows or JRuby, Aw.supported? answers false, and fork! and fork? raise NotImplementedError.
Usage
fork! returns what the block returned, and fork? tells whether the block returned rather than raised. The list changes in the child only:
require "aw"
list = ["foo"]
Aw.fork! { list << "bar" } # => ["foo", "bar"]
list # => ["foo"]
Aw.fork? { list.fetch(0) } # => true
Aw.fork? { list.fetch(1) } # => false
Errors
fork! raises again in the parent what the block raised, with its message, its backtrace and its cause. That includes the SystemExit of an exit or an abort, and the SignalException of a signal that Ruby turns into an exception, such as TERM: left uncaught, they end the parent as they would have ended the child.
fork! and fork? raise Aw::ChildError when the child delivers no result: a signal such as KILL ended it, the child called exit! or exec, or the block left with break, return or throw. The error’s status is the child’s Process::Status:
require "aw"
raised = begin
Aw.fork! { Integer("forty-two") }
rescue ArgumentError => e
e
end
raised.message # => "invalid value for Integer(): \"forty-two\""
failed = begin
Aw.fork! { Process.kill(:KILL, Process.pid) }
rescue Aw::ChildError => e
e
end
failed.status.signaled? # => true
The value or the exception travels through a pipe with Marshal, and comes back as a copy. When Marshal cannot dump it (a Proc, an IO, an object with singleton methods, an instance of an anonymous class, or anything that holds one) or the parent cannot load it (an instance of a class the parent does not know), fork! raises Aw::ChildError too. fork? transfers neither, so it answers even when they could not come back.
Limits
- Aw is not a sandbox. It keeps in the child the changes that code you trust makes to the process; a hostile block runs as your user, and can make
fork!return or raise anything, makefork?answertrue, run the parent’smarshal_loadmethods and autoloads, make the parent hold as much memory as it writes, signal it, and leave processes behind. Only thestatusof anAw::ChildErrorcomes from the system. - Aw has no timeout:
fork!andfork?wait until the child exits, and, when it delivers no result, until any process the block forked withoutexecexits too. Wrap the call in aTimeoutif the block might never end: an interrupted parent kills and reaps the child before going on. - What the block does outside the process stays done. Files and connections the parent opened are shared with the child, offsets included: let the block open its own connections.
- Only the thread that calls
fork!orfork?exists in the child. - A call costs a few milliseconds, more for a large process: about 2 ms at 50 MB, and 20 ms at 700 MB.
The README details the guarantees, and what else to know about fork: buffered output, fiber schedulers, macOS, SIGCHLD and SimpleCov.
Documentation
- README: what comes back and what cannot, the guarantees, things to know about fork, and the changes in 1.0.
- API reference
- RubyGems
- Source code