sskirby-hydra 0.16.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +55 -0
- data/TODO +18 -0
- data/VERSION +1 -0
- data/caliper.yml +6 -0
- data/hydra-icon-64x64.png +0 -0
- data/hydra.gemspec +122 -0
- data/hydra_gray.png +0 -0
- data/lib/hydra/cucumber/formatter.rb +30 -0
- data/lib/hydra/hash.rb +16 -0
- data/lib/hydra/listener/abstract.rb +30 -0
- data/lib/hydra/listener/minimal_output.rb +24 -0
- data/lib/hydra/listener/notifier.rb +17 -0
- data/lib/hydra/listener/progress_bar.rb +48 -0
- data/lib/hydra/listener/report_generator.rb +30 -0
- data/lib/hydra/master.rb +224 -0
- data/lib/hydra/message/master_messages.rb +19 -0
- data/lib/hydra/message/runner_messages.rb +46 -0
- data/lib/hydra/message/worker_messages.rb +46 -0
- data/lib/hydra/message.rb +47 -0
- data/lib/hydra/messaging_io.rb +48 -0
- data/lib/hydra/pipe.rb +61 -0
- data/lib/hydra/runner.rb +214 -0
- data/lib/hydra/safe_fork.rb +31 -0
- data/lib/hydra/spec/autorun_override.rb +12 -0
- data/lib/hydra/spec/hydra_formatter.rb +17 -0
- data/lib/hydra/ssh.rb +40 -0
- data/lib/hydra/stdio.rb +16 -0
- data/lib/hydra/sync.rb +99 -0
- data/lib/hydra/tasks.rb +256 -0
- data/lib/hydra/trace.rb +24 -0
- data/lib/hydra/worker.rb +146 -0
- data/lib/hydra.rb +16 -0
- data/test/fixtures/assert_true.rb +7 -0
- data/test/fixtures/config.yml +4 -0
- data/test/fixtures/features/step_definitions.rb +21 -0
- data/test/fixtures/features/write_alternate_file.feature +7 -0
- data/test/fixtures/features/write_file.feature +7 -0
- data/test/fixtures/hello_world.rb +3 -0
- data/test/fixtures/slow.rb +9 -0
- data/test/fixtures/sync_test.rb +8 -0
- data/test/fixtures/write_file.rb +10 -0
- data/test/fixtures/write_file_alternate_spec.rb +10 -0
- data/test/fixtures/write_file_spec.rb +9 -0
- data/test/fixtures/write_file_with_pending_spec.rb +11 -0
- data/test/master_test.rb +152 -0
- data/test/message_test.rb +31 -0
- data/test/pipe_test.rb +38 -0
- data/test/runner_test.rb +144 -0
- data/test/ssh_test.rb +14 -0
- data/test/sync_test.rb +113 -0
- data/test/test_helper.rb +60 -0
- data/test/worker_test.rb +58 -0
- metadata +179 -0
data/lib/hydra/master.rb
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'hydra/hash'
|
2
|
+
require 'open3'
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'yaml'
|
5
|
+
module Hydra #:nodoc:
|
6
|
+
# Hydra class responsible for delegate work down to workers.
|
7
|
+
#
|
8
|
+
# The Master is run once for any given testing session.
|
9
|
+
class Master
|
10
|
+
include Hydra::Messages::Master
|
11
|
+
include Open3
|
12
|
+
traceable('MASTER')
|
13
|
+
|
14
|
+
# Create a new Master
|
15
|
+
#
|
16
|
+
# Options:
|
17
|
+
# * :files
|
18
|
+
# * An array of test files to be run. These should be relative paths from
|
19
|
+
# the root of the project, since they may be run on different machines
|
20
|
+
# which may have different paths.
|
21
|
+
# * :workers
|
22
|
+
# * An array of hashes. Each hash should be the configuration options
|
23
|
+
# for a worker.
|
24
|
+
# * :listeners
|
25
|
+
# * An array of Hydra::Listener objects. See Hydra::Listener::MinimalOutput for an
|
26
|
+
# example listener
|
27
|
+
# * :verbose
|
28
|
+
# * Set to true to see lots of Hydra output (for debugging)
|
29
|
+
# * :autosort
|
30
|
+
# * Set to false to disable automatic sorting by historical run-time per file
|
31
|
+
def initialize(opts = { })
|
32
|
+
#at_exit do
|
33
|
+
trap("SIGINT") do
|
34
|
+
begin
|
35
|
+
puts "Testing halted by user. Untested files:"
|
36
|
+
puts @incomplete_files.join("\n")
|
37
|
+
rescue
|
38
|
+
trace $!.inspect
|
39
|
+
trace $!.backtrace.join("\n")
|
40
|
+
end
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
opts.stringify_keys!
|
44
|
+
config_file = opts.delete('config') { nil }
|
45
|
+
if config_file
|
46
|
+
opts.merge!(YAML.load_file(config_file).stringify_keys!)
|
47
|
+
end
|
48
|
+
@files = Array(opts.fetch('files') { nil })
|
49
|
+
raise "No files, nothing to do" if @files.empty?
|
50
|
+
@incomplete_files = @files.dup
|
51
|
+
@workers = []
|
52
|
+
@listeners = []
|
53
|
+
@event_listeners = Array(opts.fetch('listeners') { nil } )
|
54
|
+
@event_listeners.select{|l| l.is_a? String}.each do |l|
|
55
|
+
@event_listeners.delete_at(@event_listeners.index(l))
|
56
|
+
listener = eval(l)
|
57
|
+
@event_listeners << listener if listener.is_a?(Hydra::Listener::Abstract)
|
58
|
+
end
|
59
|
+
@verbose = opts.fetch('verbose') { false }
|
60
|
+
@autosort = opts.fetch('autosort') { true }
|
61
|
+
@sync = opts.fetch('sync') { nil }
|
62
|
+
@environment = opts.fetch('environment') { 'test' }
|
63
|
+
|
64
|
+
if @autosort
|
65
|
+
sort_files_from_report
|
66
|
+
@event_listeners << Hydra::Listener::ReportGenerator.new(File.new(heuristic_file, 'w'))
|
67
|
+
end
|
68
|
+
|
69
|
+
# default is one worker that is configured to use a pipe with one runner
|
70
|
+
worker_cfg = opts.fetch('workers') { [ { 'type' => 'local', 'runners' => 1} ] }
|
71
|
+
|
72
|
+
trace "Initialized"
|
73
|
+
trace " Files: (#{@files.inspect})"
|
74
|
+
trace " Workers: (#{worker_cfg.inspect})"
|
75
|
+
trace " Verbose: (#{@verbose.inspect})"
|
76
|
+
|
77
|
+
@event_listeners.each{|l| l.testing_begin(@files) }
|
78
|
+
|
79
|
+
boot_workers worker_cfg
|
80
|
+
process_messages
|
81
|
+
end
|
82
|
+
|
83
|
+
# Message handling
|
84
|
+
|
85
|
+
# Send a file down to a worker.
|
86
|
+
def send_file(worker)
|
87
|
+
f = @files.shift
|
88
|
+
if f
|
89
|
+
trace "Sending #{f.inspect}"
|
90
|
+
@event_listeners.each{|l| l.file_begin(f) }
|
91
|
+
worker[:io].write(RunFile.new(:file => f))
|
92
|
+
else
|
93
|
+
trace "No more files to send"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Process the results coming back from the worker.
|
98
|
+
def process_results(worker, message)
|
99
|
+
if message.output =~ /ActiveRecord::StatementInvalid(.*)[Dd]eadlock/ or
|
100
|
+
message.output =~ /PGError: ERROR(.*)[Dd]eadlock/ or
|
101
|
+
message.output =~ /Mysql::Error: SAVEPOINT(.*)does not exist: ROLLBACK/
|
102
|
+
trace "Deadlock detected running [#{message.file}]. Will retry at the end"
|
103
|
+
@files.push(message.file)
|
104
|
+
else
|
105
|
+
@incomplete_files.delete_at(@incomplete_files.index(message.file))
|
106
|
+
trace "#{@incomplete_files.size} Files Remaining"
|
107
|
+
@event_listeners.each{|l| l.file_end(message.file, message.output) }
|
108
|
+
if @incomplete_files.empty?
|
109
|
+
shutdown_all_workers
|
110
|
+
else
|
111
|
+
send_file(worker)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# A text report of the time it took to run each file
|
117
|
+
attr_reader :report_text
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def boot_workers(workers)
|
122
|
+
trace "Booting #{workers.size} workers"
|
123
|
+
workers.each do |worker|
|
124
|
+
worker.stringify_keys!
|
125
|
+
trace "worker opts #{worker.inspect}"
|
126
|
+
type = worker.fetch('type') { 'local' }
|
127
|
+
if type.to_s == 'local'
|
128
|
+
boot_local_worker(worker)
|
129
|
+
elsif type.to_s == 'ssh'
|
130
|
+
@workers << worker # will boot later, during the listening phase
|
131
|
+
else
|
132
|
+
raise "Worker type not recognized: (#{type.to_s})"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def boot_local_worker(worker)
|
138
|
+
runners = worker.fetch('runners') { raise "You must specify the number of runners" }
|
139
|
+
trace "Booting local worker"
|
140
|
+
pipe = Hydra::Pipe.new
|
141
|
+
child = SafeFork.fork do
|
142
|
+
pipe.identify_as_child
|
143
|
+
Hydra::Worker.new(:io => pipe, :runners => runners, :verbose => @verbose)
|
144
|
+
end
|
145
|
+
pipe.identify_as_parent
|
146
|
+
@workers << { :pid => child, :io => pipe, :idle => false, :type => :local }
|
147
|
+
end
|
148
|
+
|
149
|
+
def boot_ssh_worker(worker)
|
150
|
+
sync = Sync.new(worker, @sync, @verbose)
|
151
|
+
|
152
|
+
runners = worker.fetch('runners') { raise "You must specify the number of runners" }
|
153
|
+
command = worker.fetch('command') {
|
154
|
+
"RAILS_ENV=#{@environment} ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Worker.new(:io => Hydra::Stdio.new, :runners => #{runners}, :verbose => #{@verbose});\""
|
155
|
+
}
|
156
|
+
|
157
|
+
trace "Booting SSH worker"
|
158
|
+
ssh = Hydra::SSH.new("#{sync.ssh_opts} #{sync.connect}", sync.remote_dir, command)
|
159
|
+
return { :io => ssh, :idle => false, :type => :ssh, :connect => sync.connect }
|
160
|
+
end
|
161
|
+
|
162
|
+
def shutdown_all_workers
|
163
|
+
trace "Shutting down all workers"
|
164
|
+
@workers.each do |worker|
|
165
|
+
worker[:io].write(Shutdown.new) if worker[:io]
|
166
|
+
worker[:io].close if worker[:io]
|
167
|
+
end
|
168
|
+
@listeners.each{|t| t.exit}
|
169
|
+
end
|
170
|
+
|
171
|
+
def process_messages
|
172
|
+
Thread.abort_on_exception = true
|
173
|
+
|
174
|
+
trace "Processing Messages"
|
175
|
+
trace "Workers: #{@workers.inspect}"
|
176
|
+
@workers.each do |worker|
|
177
|
+
@listeners << Thread.new do
|
178
|
+
trace "Listening to #{worker.inspect}"
|
179
|
+
if worker.fetch('type') { 'local' }.to_s == 'ssh'
|
180
|
+
worker = boot_ssh_worker(worker)
|
181
|
+
@workers << worker
|
182
|
+
end
|
183
|
+
while true
|
184
|
+
begin
|
185
|
+
message = worker[:io].gets
|
186
|
+
trace "got message: '#{message}'"
|
187
|
+
# if it exists and its for me.
|
188
|
+
# SSH gives us back echoes, so we need to ignore our own messages
|
189
|
+
if message and !message.class.to_s.index("Worker").nil?
|
190
|
+
message.handle(self, worker)
|
191
|
+
end
|
192
|
+
rescue IOError
|
193
|
+
trace "lost Worker [#{worker.inspect}]"
|
194
|
+
trace $!.inspect
|
195
|
+
trace $!.backtrace.join("\n")
|
196
|
+
Thread.exit
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
@listeners.each{|l| l.join}
|
203
|
+
@event_listeners.each{|l| l.testing_end}
|
204
|
+
end
|
205
|
+
|
206
|
+
def sort_files_from_report
|
207
|
+
if File.exists? heuristic_file
|
208
|
+
report = YAML.load_file(heuristic_file)
|
209
|
+
return unless report
|
210
|
+
sorted_files = report.sort{ |a,b|
|
211
|
+
b[1]['duration'] <=> a[1]['duration']
|
212
|
+
}.collect{|tuple| tuple[0]}
|
213
|
+
|
214
|
+
sorted_files.each do |f|
|
215
|
+
@files.push(@files.delete_at(@files.index(f))) if @files.index(f)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def heuristic_file
|
221
|
+
@heuristic_file ||= File.join(Dir.tmpdir, 'hydra_heuristics.yml')
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
module Messages #:nodoc:
|
3
|
+
module Master #:nodoc:
|
4
|
+
# Message telling a worker to delegate a file to a runner
|
5
|
+
class RunFile < Hydra::Messages::Worker::RunFile
|
6
|
+
def handle(worker) #:nodoc:
|
7
|
+
worker.delegate_file(self)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Message telling the worker to shut down.
|
12
|
+
class Shutdown < Hydra::Messages::Worker::Shutdown
|
13
|
+
def handle(worker) #:nodoc:
|
14
|
+
worker.shutdown
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
module Messages #:nodoc:
|
3
|
+
module Runner #:nodoc:
|
4
|
+
# Message indicating that a Runner needs a file to run
|
5
|
+
class RequestFile < Hydra::Message
|
6
|
+
def handle(worker, runner) #:nodoc:
|
7
|
+
worker.request_file(self, runner)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Message for the Runner to respond with its results
|
12
|
+
class Results < Hydra::Message
|
13
|
+
# The output from running the test
|
14
|
+
attr_accessor :output
|
15
|
+
# The file that was run
|
16
|
+
attr_accessor :file
|
17
|
+
def serialize #:nodoc:
|
18
|
+
super(:output => @output, :file => @file)
|
19
|
+
end
|
20
|
+
def handle(worker, runner) #:nodoc:
|
21
|
+
worker.relay_results(self, runner)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Message a runner sends to a worker to verify the connection
|
26
|
+
class Ping < Hydra::Message
|
27
|
+
def handle(worker, runner) #:nodoc:
|
28
|
+
# We don't do anything to handle a ping. It's just to test
|
29
|
+
# the connectivity of the IO
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# The runner forks to run rspec messages
|
34
|
+
# so that specs don't get rerun. It uses
|
35
|
+
# this message to report the results. See
|
36
|
+
# Runner::run_rspec_file.
|
37
|
+
class RSpecResult < Hydra::Message
|
38
|
+
# the output of the spec
|
39
|
+
attr_accessor :output
|
40
|
+
def serialize #:nodoc:
|
41
|
+
super(:output => @output)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
module Messages #:nodoc:
|
3
|
+
module Worker #:nodoc:
|
4
|
+
# Message indicating that a worker needs a file to delegate to a runner
|
5
|
+
class RequestFile < Hydra::Message
|
6
|
+
def handle(master, worker) #:nodoc:
|
7
|
+
master.send_file(worker)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Message telling the Runner to run a file
|
12
|
+
class RunFile < Hydra::Message
|
13
|
+
# The file that should be run
|
14
|
+
attr_accessor :file
|
15
|
+
def serialize #:nodoc:
|
16
|
+
super(:file => @file)
|
17
|
+
end
|
18
|
+
def handle(runner) #:nodoc:
|
19
|
+
runner.run_file(@file)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Message to tell the Runner to shut down
|
24
|
+
class Shutdown < Hydra::Message
|
25
|
+
def handle(runner) #:nodoc:
|
26
|
+
runner.stop
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Message relaying the results of a worker up to the master
|
31
|
+
class Results < Hydra::Messages::Runner::Results
|
32
|
+
def handle(master, worker) #:nodoc:
|
33
|
+
master.process_results(worker, self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Message a worker sends to a master to verify the connection
|
38
|
+
class Ping < Hydra::Message
|
39
|
+
def handle(master, worker) #:nodoc:
|
40
|
+
# We don't do anything to handle a ping. It's just to test
|
41
|
+
# the connectivity of the IO
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
# Base message object. Used to pass messages with parameters around
|
3
|
+
# via IO objects.
|
4
|
+
# class MyMessage < Hydra::Message
|
5
|
+
# attr_accessor :my_var
|
6
|
+
# def serialize
|
7
|
+
# super(:my_var => @my_var)
|
8
|
+
# end
|
9
|
+
# end
|
10
|
+
# m = MyMessage.new(:my_var => 'my value')
|
11
|
+
# m.my_var
|
12
|
+
# => "my value"
|
13
|
+
# m.serialize
|
14
|
+
# => "{:class=>TestMessage::MyMessage, :my_var=>\"my value\"}"
|
15
|
+
# Hydra::Message.build(eval(@m.serialize)).my_var
|
16
|
+
# => "my value"
|
17
|
+
class Message
|
18
|
+
# Create a new message. Opts is a hash where the keys
|
19
|
+
# are attributes of the message and the values are
|
20
|
+
# set to the attribute.
|
21
|
+
def initialize(opts = {})
|
22
|
+
opts.delete :class
|
23
|
+
opts.each do |variable,value|
|
24
|
+
self.send("#{variable}=",value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Build a message from a hash. The hash must contain
|
29
|
+
# the :class symbol, which is the class of the message
|
30
|
+
# that it will build to.
|
31
|
+
def self.build(hash)
|
32
|
+
hash.delete(:class).new(hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Serialize the message for output on an IO channel.
|
36
|
+
# This is really just a string representation of a hash
|
37
|
+
# with no newlines. It adds in the class automatically
|
38
|
+
def serialize(opts = {})
|
39
|
+
opts.merge({:class => self.class}).inspect
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
require 'hydra/message/runner_messages'
|
45
|
+
require 'hydra/message/worker_messages'
|
46
|
+
require 'hydra/message/master_messages'
|
47
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
# Module that implemets methods that auto-serialize and deserialize messaging
|
3
|
+
# objects.
|
4
|
+
module MessagingIO
|
5
|
+
# Read a Message from the input IO object. Automatically build
|
6
|
+
# a message from the response and return it.
|
7
|
+
#
|
8
|
+
# IO.gets
|
9
|
+
# => Hydra::Message # or subclass
|
10
|
+
def gets
|
11
|
+
raise IOError unless @reader
|
12
|
+
message = @reader.gets
|
13
|
+
return nil unless message
|
14
|
+
return Message.build(eval(message.chomp))
|
15
|
+
rescue SyntaxError, NameError
|
16
|
+
# uncomment to help catch remote errors by seeing all traffic
|
17
|
+
$stderr.write "Not a message: [#{message.inspect}]\n"
|
18
|
+
return gets
|
19
|
+
end
|
20
|
+
|
21
|
+
# Write a Message to the output IO object. It will automatically
|
22
|
+
# serialize a Message object.
|
23
|
+
# IO.write Hydra::Message.new
|
24
|
+
def write(message)
|
25
|
+
raise IOError unless @writer
|
26
|
+
raise UnprocessableMessage unless message.is_a?(Hydra::Message)
|
27
|
+
@writer.write(message.serialize+"\n")
|
28
|
+
rescue Errno::EPIPE
|
29
|
+
$stderr.write $!.inspect
|
30
|
+
$stderr.write $!.backtrace.join("\n")
|
31
|
+
raise IOError
|
32
|
+
end
|
33
|
+
|
34
|
+
# Closes the IO object.
|
35
|
+
def close
|
36
|
+
@reader.close if @reader
|
37
|
+
@writer.close if @writer
|
38
|
+
end
|
39
|
+
|
40
|
+
# IO will return this error if it cannot process a message.
|
41
|
+
# For example, if you tried to write a string, it would fail,
|
42
|
+
# because the string is not a message.
|
43
|
+
class UnprocessableMessage < RuntimeError
|
44
|
+
# Custom error message
|
45
|
+
attr_accessor :message
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/hydra/pipe.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'hydra/messaging_io'
|
2
|
+
module Hydra #:nodoc:
|
3
|
+
# Read and write between two processes via pipes. For example:
|
4
|
+
# @pipe = Hydra::Pipe.new
|
5
|
+
# @child = Process.fork do
|
6
|
+
# @pipe.identify_as_child
|
7
|
+
# puts "A message from my parent:\n#{@pipe.gets.text}"
|
8
|
+
# @pipe.close
|
9
|
+
# end
|
10
|
+
# @pipe.identify_as_parent
|
11
|
+
# @pipe.write Hydra::Messages::TestMessage.new(:text => "Hello!")
|
12
|
+
# @pipe.close
|
13
|
+
#
|
14
|
+
# Note that the TestMessage class is only available in tests, and
|
15
|
+
# not in Hydra by default.
|
16
|
+
#
|
17
|
+
#
|
18
|
+
# When the process forks, the pipe is copied. When a pipe is
|
19
|
+
# identified as a parent or child, it is choosing which ends
|
20
|
+
# of the pipe to use.
|
21
|
+
#
|
22
|
+
# A pipe is actually two pipes:
|
23
|
+
#
|
24
|
+
# Parent == Pipe 1 ==> Child
|
25
|
+
# Parent <== Pipe 2 == Child
|
26
|
+
#
|
27
|
+
# It's like if you had two cardboard tubes and you were using
|
28
|
+
# them to drop balls with messages in them between processes.
|
29
|
+
# One tube is for sending from parent to child, and the other
|
30
|
+
# tube is for sending from child to parent.
|
31
|
+
class Pipe
|
32
|
+
include Hydra::MessagingIO
|
33
|
+
# Creates a new uninitialized pipe pair.
|
34
|
+
def initialize
|
35
|
+
@child_read, @parent_write = IO.pipe
|
36
|
+
@parent_read, @child_write = IO.pipe
|
37
|
+
end
|
38
|
+
|
39
|
+
# Identify this side of the pipe as the child.
|
40
|
+
def identify_as_child
|
41
|
+
@parent_write.close
|
42
|
+
@parent_read.close
|
43
|
+
@reader = @child_read
|
44
|
+
@writer = @child_write
|
45
|
+
end
|
46
|
+
|
47
|
+
# Identify this side of the pipe as the parent
|
48
|
+
def identify_as_parent
|
49
|
+
@child_write.close
|
50
|
+
@child_read.close
|
51
|
+
@reader = @parent_read
|
52
|
+
@writer = @parent_write
|
53
|
+
end
|
54
|
+
|
55
|
+
# Output pipe nicely
|
56
|
+
def inspect
|
57
|
+
"#<#{self.class} @reader=#{@reader.to_s}, @writer=#{@writer.to_s}>"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|