added pipe class. docced and tested
This commit is contained in:
parent
7031cb8c4c
commit
63809a7258
2
Rakefile
2
Rakefile
|
@ -10,7 +10,7 @@ begin
|
|||
gem.email = "nick@smartlogicsolutions.com"
|
||||
gem.homepage = "http://github.com/ngauthier/hydra"
|
||||
gem.authors = ["Nick Gauthier"]
|
||||
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
||||
gem.add_development_dependency "shoulda", "= 2.10.3"
|
||||
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
||||
end
|
||||
Jeweler::GemcutterTasks.new
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
require 'hydra/pipe'
|
|
@ -0,0 +1,109 @@
|
|||
module Hydra #:nodoc:
|
||||
# Read and write between two processes via pipes. For example:
|
||||
# @pipe = Hydra::Pipe.new
|
||||
# Process.fork do
|
||||
# @pipe.identify_as_child
|
||||
# sleep(1)
|
||||
# puts "A message from my parent:\n#{@pipe.gets}"
|
||||
# @pipe.close
|
||||
# end
|
||||
# @pipe.identify_as_parent
|
||||
# @pipe.write "Hello, Child!"
|
||||
# @pipe.close
|
||||
# When the process forks, the pipe is copied. When a pipe is
|
||||
# identified as a parent or child, it is choosing which ends
|
||||
# of the pipe to use.
|
||||
#
|
||||
# A pipe is actually two pipes:
|
||||
#
|
||||
# Parent === Pipe 1 ==> Child
|
||||
# Parent <== Pipe 2 === Child
|
||||
class Pipe
|
||||
# Creates a new uninitialized pipe pair.
|
||||
def initialize
|
||||
@child_read, @parent_write = IO.pipe
|
||||
@parent_read, @child_write = IO.pipe
|
||||
[@parent_write, @child_write].each{|io| io.sync = true}
|
||||
end
|
||||
|
||||
# Read a line from a pipe. It will have a trailing newline.
|
||||
def gets
|
||||
force_identification
|
||||
@reader.gets
|
||||
end
|
||||
|
||||
# Write a line to a pipe. It must have a trailing newline.
|
||||
def write(str)
|
||||
force_identification
|
||||
begin
|
||||
@writer.write(str)
|
||||
return str
|
||||
rescue Errno::EPIPE
|
||||
raise Hydra::PipeError::Broken
|
||||
end
|
||||
end
|
||||
|
||||
# Returns true if there is nothing to read (right now). However it is
|
||||
# not exactly eof, if the other side writes, this will return false.
|
||||
#
|
||||
# It's a good way to tell if there is anything to process right now,
|
||||
# otherwise, you can sleep.
|
||||
def eof?
|
||||
@reader.eof?
|
||||
end
|
||||
|
||||
# Identify this side of the pipe as the child.
|
||||
def identify_as_child
|
||||
@parent_write.close
|
||||
@parent_read.close
|
||||
@reader = @child_read
|
||||
@writer = @child_write
|
||||
end
|
||||
|
||||
# Identify this side of the pipe as the parent
|
||||
def identify_as_parent
|
||||
@child_write.close
|
||||
@child_read.close
|
||||
@reader = @parent_read
|
||||
@writer = @parent_write
|
||||
end
|
||||
|
||||
# closes the pipes. Once a pipe is closed on one end, the other
|
||||
# end will get a PipeError::Broken if it tries to write.
|
||||
def close
|
||||
done_reading
|
||||
done_writing
|
||||
end
|
||||
|
||||
private
|
||||
def done_writing #:nodoc:
|
||||
@writer.close unless @writer.closed?
|
||||
end
|
||||
|
||||
def done_reading #:nodoc:
|
||||
@reader.close unless @reader.closed?
|
||||
end
|
||||
|
||||
def force_identification #:nodoc:
|
||||
raise PipeError::Unidentified if @reader.nil? or @writer.nil?
|
||||
end
|
||||
end
|
||||
|
||||
module PipeError #:nodoc:
|
||||
# Raised if you try to read or write to a pipe when it is unidentified.
|
||||
# Use identify_as_parent and identify_as_child to identify a pipe.
|
||||
class Unidentified < RuntimeError
|
||||
def message #:nodoc:
|
||||
"Must identify as child or parent"
|
||||
end
|
||||
end
|
||||
# Raised when a pipe has been broken between two processes.
|
||||
# This happens when a process exits, and is a signal that
|
||||
# there is no more data to communicate.
|
||||
class Broken < RuntimeError
|
||||
def message #:nodoc:
|
||||
"Other side closed the connection"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
require 'helper'
|
||||
|
||||
class TestHydra < Test::Unit::TestCase
|
||||
should "probably rename this file and start testing for real" do
|
||||
flunk "hey buddy, you should probably rename this file and start testing for real"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
require 'helper'
|
||||
|
||||
class TestPipeDream < Test::Unit::TestCase
|
||||
context "a pipe" do
|
||||
setup do
|
||||
@pipe = Hydra::Pipe.new
|
||||
end
|
||||
should "be able to write messages" do
|
||||
Process.fork do
|
||||
@pipe.identify_as_child
|
||||
assert_equal "Test Message\n", @pipe.gets
|
||||
@pipe.write "Message Received\n"
|
||||
@pipe.write "Second Message\n"
|
||||
@pipe.close
|
||||
end
|
||||
@pipe.identify_as_parent
|
||||
@pipe.write "Test Message\n"
|
||||
assert_equal "Message Received\n", @pipe.gets
|
||||
assert !@pipe.eof?
|
||||
assert_equal "Second Message\n", @pipe.gets
|
||||
assert @pipe.eof?
|
||||
assert_raise Hydra::PipeError::Broken do
|
||||
@pipe.write "anybody home?"
|
||||
end
|
||||
@pipe.close
|
||||
end
|
||||
should "not allow writing if unidentified" do
|
||||
assert_raise Hydra::PipeError::Unidentified do
|
||||
@pipe.write "hey\n"
|
||||
end
|
||||
end
|
||||
should "not allow reading if unidentified" do
|
||||
assert_raise Hydra::PipeError::Unidentified do
|
||||
@pipe.gets
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue