Class: Fork
- Inherits:
-
Object
- Object
- Fork
- Defined in:
- lib/fork.rb,
lib/fork/version.rb
Overview
You should only interact between parent and fork by the means provided by the Fork class.
An object representing a fork, containing data about it like pid, exit_status, exception etc.
It also provides facilities for parent and child process to communicate with each other.
Defined Under Namespace
Classes: FlagNotSpecified, NotRunning, UndumpableException
Constant Summary collapse
- IgnoreExceptions =
Exceptions that have to be ignored in the child’s handling of exceptions
[::SystemExit]
- DefaultFlags =
The default flags Fork#initialize uses
Hash.new { |_hash, key| raise ArgumentError, "Unknown flag #{key}" }.merge({ :exceptions => false, :death_notice => false, :return => false, :to_fork => false, :from_fork => false, :ctrl => false, })
- Version =
The currently required version of the Fork gem
Gem::Version.new("1.0.1")
Instance Attribute Summary collapse
-
#ctrl ⇒ Object
readonly
Control IO (reserved for exception and death-notice passing).
-
#pid ⇒ Object
readonly
The process id of the fork.
-
#readable_io ⇒ Object
readonly
Readable IO Allows the parent to read data from the fork, and the fork to read data from the parent.
-
#writable_io ⇒ Object
readonly
Writable IO Allows the parent to write data to the fork, and the fork to write data to the parent.
Class Method Summary collapse
-
.execute(*args, &block) ⇒ Object
Create a Fork instance and immediatly start executing it.
-
.future(*args) ⇒ Proc
A simple forked-future implementation.
-
.read_marshalled(io) ⇒ Object
Reads an object sent via Fork.read_marshalled from the passed io.
-
.return(*args) ⇒ Object
A simple forked-callback implementation.
-
.write_marshalled(io, obj) ⇒ Integer
Writes an object in serialized form to the passed IO.
Instance Method Summary collapse
-
#alive? ⇒ Boolean
Whether this fork is still running (= is alive) or already exited.
-
#clone ⇒ Object
Cloning a fork instance is prohibited.
-
#close ⇒ Object
Close all IOs.
-
#complete!(pid, readable_io, writable_io, ctrl_io) ⇒ Object
Sets the io to communicate with the parent/child.
-
#dead? ⇒ Boolean
Whether this fork is still running or already exited (= is dead).
-
#death_notice? ⇒ Boolean
Whether this fork sends a death notice to the parent.
-
#dup ⇒ Object
Duping a fork instance is prohibited.
-
#exception(blocking = true) ⇒ Object
The exception that terminated the fork Requires the :exceptions flag to be set when creating the fork.
-
#execute ⇒ self
Creates the fork (subprocess) and starts executing it.
-
#exit_status(blocking = true) ⇒ Object
The exit status of this fork.
-
#failure? ⇒ Boolean
Blocks until the fork has exited.
-
#fork? ⇒ Boolean
Whether the current code is executed in the fork, as opposed to the parent.
-
#gets(*args) ⇒ String?
In the parent process: read data from the fork.
-
#handle_exceptions? ⇒ Boolean
Whether this fork sends the final exception to the parent.
-
#has_ctrl? ⇒ Boolean
Whether parent and fork use a control-io.
-
#has_in? ⇒ Boolean
Whether the other process can write to this process.
-
#has_out? ⇒ Boolean
Whether this process can write to the other process.
-
#initialize(*flags, &block) ⇒ Fork
constructor
Create a new Fork instance.
-
#inspect ⇒ Object
See Object#inspect.
-
#kill ⇒ Object
Sends the (SIG)HUP signal to this fork.
-
#kill! ⇒ Object
Sends the (SIG)KILL signal to this fork.
-
#parent? ⇒ Boolean
Whether the current code is executed in the parent of the fork.
-
#process_status(blocking = true) ⇒ Object
Process::Status for dead forks, nil for live forks.
-
#puts(*args) ⇒ nil
In the parent process: Write to the fork.
-
#read(*args) ⇒ String?
In the parent process: read data from the fork.
-
#read_nonblock(*args) ⇒ String?
In the parent process: read data from the fork.
-
#read_remaining_ctrl(_wait_upon_eof = true) ⇒ self
Read a single instruction sent via @ctrl, used by :exception, :death_notice and :return_value.
-
#receive_object ⇒ Object
In the parent process: read on object sent by the fork.
-
#return_value(blocking = true) ⇒ Object
Blocks until the fork returns.
-
#returns? ⇒ Boolean
Whether this forks terminal value is returned to the parent.
-
#send_object(obj) ⇒ Object
Sends an object to the parent process.
-
#signal(sig) ⇒ Object
Sends the given signal to this fork See Fork#kill, Fork#kill!, Process.kill.
-
#success? ⇒ Boolean
Blocks until the fork has exited.
-
#wait ⇒ Object
Wait for this fork to terminate.
-
#write(*args) ⇒ Integer
In the parent process: Write to the fork.
Constructor Details
#initialize(*flags, &block) ⇒ Fork
Create a new Fork instance. The subprocess is not immediatly executed, you must invoke #execute on the Fork instance in order to get it executed. Only then #pid, #in, #out and #ctrl will be available. Also all IO related methods won’t work before that.
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/fork.rb', line 201 def initialize(*flags, &block) raise ArgumentError, "No block given" unless block if flags.last.is_a?(Hash) then @flags = DefaultFlags.merge(flags.pop) else @flags = DefaultFlags.dup end flags.each do |flag| raise ArgumentError, "Unknown flag #{flag.inspect}" unless @flags.has_key?(flag) @flags[flag] = true end @flags[:ctrl] = true if @flags.values_at(:exceptions, :death_notice, :return).any? @flags[:exceptions] = true if @flags[:return] @parent = true @alive = nil @pid = nil @process_status = nil @readable_io = nil @writable_io = nil @ctrl = nil @block = block end |
Instance Attribute Details
#ctrl ⇒ Object (readonly)
Control IO (reserved for exception and death-notice passing)
176 177 178 |
# File 'lib/fork.rb', line 176 def ctrl @ctrl end |
#pid ⇒ Object (readonly)
You *must not* directly interact with the forked process using the pid. This may lead to unexpected conflicts with Fork’s internal mechanisms.
The process id of the fork
162 163 164 |
# File 'lib/fork.rb', line 162 def pid @pid end |
#readable_io ⇒ Object (readonly)
Readable IO Allows the parent to read data from the fork, and the fork to read data from the parent. Requires the :to_fork and/or :from_fork flag to be set.
168 169 170 |
# File 'lib/fork.rb', line 168 def readable_io @readable_io end |
#writable_io ⇒ Object (readonly)
Writable IO Allows the parent to write data to the fork, and the fork to write data to the parent. Requires the :to_fork and/or :from_fork flag to be set.
173 174 175 |
# File 'lib/fork.rb', line 173 def writable_io @writable_io end |
Class Method Details
.execute(*args, &block) ⇒ Object
Create a Fork instance and immediatly start executing it. Equivalent to just call Fork.new(*args) { … }.execute
Returns the Fork instance. See Fork#initialize
153 154 155 |
# File 'lib/fork.rb', line 153 def self.execute(*args, &block) new(*args, &block).execute end |
.future(*args) ⇒ Proc
A simple forked-future implementation. Will process the block in a fork, blocks upon request of the result until the result is present. If the forked code raises an exception, invoking call on the proc will raise that exception in the parent process.
118 119 120 121 122 123 124 |
# File 'lib/fork.rb', line 118 def self.future(*args) obj = execute :return => true do |parent| yield(*args) end lambda { obj.return_value } end |
.read_marshalled(io) ⇒ Object
Reads an object sent via Fork.read_marshalled from the passed io. Raises EOFError if the io was closed on the remote end.
75 76 77 78 79 80 81 |
# File 'lib/fork.rb', line 75 def self.read_marshalled(io) size = io.read(4) raise EOFError unless size size = size.unpack("I").first marshalled = io.read(size) Marshal.load(marshalled) end |
.return(*args) ⇒ Object
A simple forked-callback implementation. Will process the block in a fork, block until it has finished processing and returns the return value of the block. This can be useful if you want to process something that will (or might) irreversibly corrupt the environment. Doing that in a subprocess will leave the parent untouched.
141 142 143 144 145 146 |
# File 'lib/fork.rb', line 141 def self.return(*args) obj = execute :return => true do |parent| yield(*args) end obj.return_value end |
.write_marshalled(io, obj) ⇒ Integer
Writes an object in serialized form to the passed IO. Important: certain objects are not marshallable, e.g. IOs, Procs and anonymous modules and classes.
90 91 92 93 94 |
# File 'lib/fork.rb', line 90 def self.write_marshalled(io, obj) marshalled = Marshal.dump(obj) io.write([marshalled.size].pack("I")) io.write(marshalled) end |
Instance Method Details
#alive? ⇒ Boolean
Whether this fork is still running (= is alive) or already exited.
362 363 364 |
# File 'lib/fork.rb', line 362 def alive? @pid && !exit_status(false) end |
#clone ⇒ Object
Cloning a fork instance is prohibited. See Object#clone.
557 558 559 |
# File 'lib/fork.rb', line 557 def clone # :nodoc: raise TypeError, "can't clone #{self.class}" end |
#close ⇒ Object
Close all IOs
542 543 544 545 546 547 |
# File 'lib/fork.rb', line 542 def close # :nodoc: raise NotRunning, "Fork is not running yet, you must invoke #execute first." unless @pid @readable_io.close if @readable_io @writable_io.close if @writable_io @ctrl.close if @ctrl end |
#complete!(pid, readable_io, writable_io, ctrl_io) ⇒ Object
Sets the io to communicate with the parent/child
298 299 300 301 302 303 304 |
# File 'lib/fork.rb', line 298 def complete!(pid, readable_io, writable_io, ctrl_io) # :nodoc: raise "Can't call complete! more than once" if @pid @pid = pid @readable_io = readable_io @writable_io = writable_io @ctrl = ctrl_io end |
#dead? ⇒ Boolean
Whether this fork is still running or already exited (= is dead).
367 368 369 |
# File 'lib/fork.rb', line 367 def dead? !alive? end |
#death_notice? ⇒ Boolean
Returns Whether this fork sends a death notice to the parent.
261 262 263 |
# File 'lib/fork.rb', line 261 def death_notice? @flags[:death_notice] end |
#dup ⇒ Object
Duping a fork instance is prohibited. See Object#dup.
551 552 553 |
# File 'lib/fork.rb', line 551 def dup # :nodoc: raise TypeError, "can't dup #{self.class}" end |
#exception(blocking = true) ⇒ Object
The exception that terminated the fork Requires the :exceptions flag to be set when creating the fork.
343 344 345 346 347 348 349 |
# File 'lib/fork.rb', line 343 def exception(blocking=true) @exception || begin raise FlagNotSpecified, "You must set the :exceptions flag when forking in order to use this" unless handle_exceptions? _wait(blocking) @exception end end |
#execute ⇒ self
Creates the fork (subprocess) and starts executing it.
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/fork.rb', line 228 def execute ctrl_read, ctrl_write, fork_read, parent_write, parent_read, fork_write = nil fork_read, parent_write = binary_pipe if @flags[:to_fork] parent_read, fork_write = binary_pipe if @flags[:from_fork] ctrl_read, ctrl_write = binary_pipe if @flags[:ctrl] @alive = true pid = Process.fork do @parent = false parent_write.close if parent_write parent_read.close if parent_read ctrl_read.close if ctrl_read complete!(Process.pid, fork_read, fork_write, ctrl_write) child_process end fork_write.close if fork_write fork_read.close if fork_read ctrl_write.close if ctrl_write complete!(pid, parent_read, parent_write, ctrl_read) self end |
#exit_status(blocking = true) ⇒ Object
The exit status of this fork. See Process::Status#exitstatus
316 317 318 319 320 321 322 323 |
# File 'lib/fork.rb', line 316 def exit_status(blocking=true) @exit_status || begin _wait(blocking) @exit_status rescue NotRunning raise if blocking # calling exit status on a not-yet started fork is an exception, nil otherwise end end |
#failure? ⇒ Boolean
Blocks until the fork has exited.
337 338 339 |
# File 'lib/fork.rb', line 337 def failure? !success? end |
#fork? ⇒ Boolean
Returns Whether the current code is executed in the fork, as opposed to the parent.
293 294 295 |
# File 'lib/fork.rb', line 293 def fork? !@parent end |
#gets(*args) ⇒ String?
In the parent process: read data from the fork. In the forked process: read data from the parent. Works just like IO#gets.
376 377 378 379 380 381 382 |
# File 'lib/fork.rb', line 376 def gets(*args) @readable_io.gets(*args) rescue NoMethodError raise NotRunning, "Fork is not running yet, you must invoke #execute first." unless @pid raise FlagNotSpecified, "You must set the :to_fork flag when forking in order to use this" unless @readable_io raise end |
#handle_exceptions? ⇒ Boolean
Returns Whether this fork sends the final exception to the parent.
256 257 258 |
# File 'lib/fork.rb', line 256 def handle_exceptions? @flags[:exceptions] end |
#has_ctrl? ⇒ Boolean
Returns Whether parent and fork use a control-io.
281 282 283 |
# File 'lib/fork.rb', line 281 def has_ctrl? @flags[:ctrl] end |
#has_in? ⇒ Boolean
Returns Whether the other process can write to this process.
271 272 273 |
# File 'lib/fork.rb', line 271 def has_in? @flags[parent? ? :from_fork : :to_fork] end |
#has_out? ⇒ Boolean
Returns Whether this process can write to the other process.
276 277 278 |
# File 'lib/fork.rb', line 276 def has_out? @flags[parent? ? :to_fork : :from_fork] end |
#inspect ⇒ Object
See Object#inspect
563 564 565 |
# File 'lib/fork.rb', line 563 def inspect # :nodoc: sprintf "#<%p pid=%p alive=%p>", self.class, @pid, @alive end |
#kill ⇒ Object
Sends the (SIG)HUP signal to this fork. This is “gently asking the process to terminate”. This gives the process a chance to perform some cleanup. See Fork#kill!, Fork#signal, Process.kill
520 521 522 523 |
# File 'lib/fork.rb', line 520 def kill raise NotRunning, "Fork is not running yet, you must invoke #execute first." unless @pid Process.kill("HUP", @pid) end |
#kill! ⇒ Object
Sends the (SIG)KILL signal to this fork. The process will be immediatly terminated and will not have a chance to do any cleanup. See Fork#kill, Fork#signal, Process.kill
529 530 531 532 |
# File 'lib/fork.rb', line 529 def kill! raise NotRunning, "Fork is not running yet, you must invoke #execute first." unless @pid Process.kill("KILL", @pid) end |
#parent? ⇒ Boolean
Returns Whether the current code is executed in the parent of the fork.
287 288 289 |
# File 'lib/fork.rb', line 287 def parent? @parent end |
#process_status(blocking = true) ⇒ Object
Process::Status for dead forks, nil for live forks
307 308 309 310 311 312 |
# File 'lib/fork.rb', line 307 def process_status(blocking=true) @process_status || begin _wait(blocking) @process_status end end |
#puts(*args) ⇒ nil
In the parent process: Write to the fork. In the forked process: Write to the parent. Works just like IO#puts
429 430 431 432 433 434 435 |
# File 'lib/fork.rb', line 429 def puts(*args) @writable_io.puts(*args) rescue NoMethodError raise NotRunning, "Fork is not running yet, you must invoke #execute first." unless @pid raise FlagNotSpecified, "You must set the :from_fork flag when forking in order to use this" unless @writable_io raise end |
#read(*args) ⇒ String?
In the parent process: read data from the fork. In the forked process: read data from the parent. Works just like IO#read.
389 390 391 392 393 394 395 |
# File 'lib/fork.rb', line 389 def read(*args) @readable_io.read(*args) rescue NoMethodError raise NotRunning, "Fork is not running yet, you must invoke #execute first." unless @pid raise FlagNotSpecified, "You must set the :to_fork flag when forking in order to use this" unless @readable_io raise end |
#read_nonblock(*args) ⇒ String?
In the parent process: read data from the fork. In the forked process: read data from the parent. Works just like IO#read_nonblock.
402 403 404 405 406 407 408 |
# File 'lib/fork.rb', line 402 def read_nonblock(*args) @readable_io.read_nonblock(*args) rescue NoMethodError raise NotRunning, "Fork is not running yet, you must invoke #execute first." unless @pid raise FlagNotSpecified, "You must set the :to_fork flag when forking in order to use this" unless @readable_io raise end |
#read_remaining_ctrl(_wait_upon_eof = true) ⇒ self
Read a single instruction sent via @ctrl, used by :exception, :death_notice and :return_value
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 |
# File 'lib/fork.rb', line 454 def read_remaining_ctrl(_wait_upon_eof=true) # :nodoc: loop do # EOFError will terminate this loop instruction, data = *Fork.read_marshalled(@ctrl) case instruction when :exception @exception = data when :death_notice _wait if _wait_upon_eof _wait_upon_eof = false when :return_value @return_value = data else raise "Unknown control instruction #{instruction} in fork #{fork}" end end self rescue EOFError # closed _wait(false) if _wait_upon_eof # update self rescue NoMethodError raise NotRunning, "Fork is not running yet, you must invoke #execute first." unless @pid raise FlagNotSpecified, "You must set the :ctrl flag when forking in order to use this" unless @ctrl raise end |
#receive_object ⇒ Object
In the parent process: read on object sent by the fork. In the forked process: read on object sent by the parent.
416 417 418 419 420 421 422 |
# File 'lib/fork.rb', line 416 def receive_object Fork.read_marshalled(@readable_io) rescue NoMethodError raise NotRunning, "Fork is not running yet, you must invoke #execute first." unless @pid raise FlagNotSpecified, "You must set the :from_fork flag when forking in order to use this" unless @readable_io raise end |
#return_value(blocking = true) ⇒ Object
Blocks until the fork returns
352 353 354 355 356 357 358 359 |
# File 'lib/fork.rb', line 352 def return_value(blocking=true) @return_value || begin raise FlagNotSpecified, "You must set the :return flag when forking in order to use this" unless returns? _wait(blocking) raise @exception if @exception @return_value end end |
#returns? ⇒ Boolean
Returns Whether this forks terminal value is returned to the parent.
266 267 268 |
# File 'lib/fork.rb', line 266 def returns? @flags[:return] end |
#send_object(obj) ⇒ Object
Sends an object to the parent process. The parent process can read it using Fork#receive_object.
494 495 496 497 498 499 500 501 |
# File 'lib/fork.rb', line 494 def send_object(obj) Fork.write_marshalled(@writable_io, obj) self rescue NoMethodError raise NotRunning, "Fork is not running yet, you must invoke #execute first." unless @pid raise FlagNotSpecified, "You must set the :from_fork flag when forking in order to use this" unless @writable_io raise end |
#signal(sig) ⇒ Object
Sends the given signal to this fork See Fork#kill, Fork#kill!, Process.kill
536 537 538 539 |
# File 'lib/fork.rb', line 536 def signal(sig) raise NotRunning, "Fork is not running yet, you must invoke #execute first." unless @pid Process.kill(sig, @pid) end |
#success? ⇒ Boolean
Blocks until the fork has exited.
329 330 331 |
# File 'lib/fork.rb', line 329 def success? exit_status.zero? end |
#wait ⇒ Object
Wait for this fork to terminate. Returns self
511 512 513 514 |
# File 'lib/fork.rb', line 511 def wait _wait unless @process_status self end |
#write(*args) ⇒ Integer
In the parent process: Write to the fork. In the forked process: Write to the parent. Works just like IO#write
442 443 444 445 446 447 448 |
# File 'lib/fork.rb', line 442 def write(*args) @writable_io.write(*args) rescue NoMethodError raise NotRunning, "Fork is not running yet, you must invoke #execute first." unless @pid raise FlagNotSpecified, "You must set the :from_fork flag when forking in order to use this" unless @writable_io raise end |