rexec 1.1.12 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -17,176 +17,193 @@ require 'rexec/daemon/pidfile'
17
17
  require 'rexec/task'
18
18
 
19
19
  module RExec
20
- module Daemon
21
- # Daemon startup timeout
22
- TIMEOUT = 5
23
-
24
- # This module contains functionality related to starting and stopping the daemon, and code for processing command line input.
25
- module Controller
26
- # This function is called from the daemon executable. It processes ARGV and checks whether the user is asking for
27
- # <tt>start</tt>, <tt>stop</tt>, <tt>restart</tt> or <tt>status</tt>.
28
- def self.daemonize(daemon)
29
- #puts "Running in #{WorkingDirectory}, logs in #{LogDirectory}"
30
- case !ARGV.empty? && ARGV[0]
31
- when 'start'
32
- start(daemon)
33
- status(daemon)
34
- when 'stop'
35
- stop(daemon)
36
- status(daemon)
37
- PidFile.cleanup(daemon)
38
- when 'restart'
39
- stop(daemon)
40
- PidFile.cleanup(daemon)
41
- start(daemon)
42
- status(daemon)
43
- when 'status'
44
- status(daemon)
45
- else
46
- puts "Invalid command. Please specify start, restart, stop or status."
47
- exit
48
- end
49
- end
50
-
51
- # This function starts the supplied daemon
52
- def self.start(daemon)
53
- puts "Starting daemon..."
54
-
55
- case PidFile.status(daemon)
56
- when :running
57
- $stderr.puts "Daemon already running!"
58
- return
59
- when :stopped
60
- # We are good to go...
61
- else
62
- $stderr.puts "Daemon in unknown state! Will clear previous state and continue."
63
- status(daemon)
64
- PidFile.clear(daemon)
65
- end
66
-
67
- daemon.prefork
68
- daemon.mark_err_log
69
-
70
- fork do
71
- Process.setsid
72
- exit if fork
73
-
74
- PidFile.store(daemon, Process.pid)
75
-
76
- File.umask 0000
77
- Dir.chdir daemon.working_directory
78
-
79
- $stdin.reopen "/dev/null"
80
- $stdout.reopen daemon.log_fn, "a"
81
- $stderr.reopen daemon.err_fn, "a"
82
-
83
- main = Thread.new do
84
- begin
85
- daemon.run
86
- rescue
87
- $stderr.puts "=== Daemon Exception Backtrace @ #{Time.now.to_s} ==="
88
- $stderr.puts "#{$!.class}: #{$!.message}"
89
- $!.backtrace.each { |at| $stderr.puts at }
90
- $stderr.puts "=== Daemon Crashed ==="
91
-
92
- $stderr.flush
93
- end
94
- end
95
-
96
- trap("INT") do
97
- daemon.shutdown
98
- main.exit
20
+ module Daemon
21
+ # Daemon startup timeout
22
+ TIMEOUT = 5
23
+
24
+ # This module contains functionality related to starting and stopping the daemon, and code for processing command line input.
25
+ module Controller
26
+ # This function is called from the daemon executable. It processes ARGV and checks whether the user is asking for
27
+ # <tt>start</tt>, <tt>stop</tt>, <tt>restart</tt> or <tt>status</tt>.
28
+ def self.daemonize(daemon)
29
+ #puts "Running in #{WorkingDirectory}, logs in #{LogDirectory}"
30
+ case !ARGV.empty? && ARGV[0]
31
+ when 'start'
32
+ start(daemon)
33
+ status(daemon)
34
+ when 'stop'
35
+ stop(daemon)
36
+ status(daemon)
37
+ PidFile.cleanup(daemon)
38
+ when 'restart'
39
+ stop(daemon)
40
+ PidFile.cleanup(daemon)
41
+ start(daemon)
42
+ status(daemon)
43
+ when 'status'
44
+ status(daemon)
45
+ else
46
+ puts "Invalid command. Please specify start, restart, stop or status."
47
+ exit
48
+ end
49
+ end
50
+
51
+ # This function starts the supplied daemon
52
+ def self.start(daemon)
53
+ puts "Starting daemon..."
54
+
55
+ case PidFile.status(daemon)
56
+ when :running
57
+ $stderr.puts "Daemon already running!"
58
+ return
59
+ when :stopped
60
+ # We are good to go...
61
+ else
62
+ $stderr.puts "Daemon in unknown state! Will clear previous state and continue."
63
+ status(daemon)
64
+ PidFile.clear(daemon)
65
+ end
66
+
67
+ daemon.prefork
68
+ daemon.mark_err_log
69
+
70
+ fork do
71
+ Process.setsid
72
+ exit if fork
73
+
74
+ PidFile.store(daemon, Process.pid)
75
+
76
+ File.umask 0000
77
+ Dir.chdir daemon.working_directory
78
+
79
+ $stdin.reopen "/dev/null"
80
+ $stdout.reopen daemon.log_fn, "a"
81
+ $stdout.sync = true
82
+
83
+ $stderr.reopen daemon.err_fn, "a"
84
+ $stderr.sync = true
85
+
86
+ begin
87
+ error = nil
88
+
89
+ main = Thread.new do
90
+ begin
91
+ daemon.run
92
+ rescue
93
+ error = $!
94
+ end
95
+ end
96
+
97
+ trap("INT") do
98
+ begin
99
+ daemon.shutdown
100
+ main.exit
101
+ rescue
102
+ error = $!
103
+ end
104
+ end
105
+
106
+ trap("TERM") do
107
+ exit!
108
+ end
109
+
110
+ main.join
111
+
112
+ raise error if error
113
+ rescue
114
+ $stderr.puts "=== Daemon Exception Backtrace @ #{Time.now.to_s} ==="
115
+ $stderr.puts "#{$!.class}: #{$!.message}"
116
+ $!.backtrace.each { |at| $stderr.puts at }
117
+ $stderr.puts "=== Daemon Crashed ==="
118
+ $stderr.flush
119
+ ensure
120
+ $stderr.puts "=== Daemon Stopping @ #{Time.now.to_s} ==="
121
+ $stderr.flush
99
122
  end
100
-
101
- trap("TERM") do
102
- exit!
103
- end
104
-
105
- main.join
106
- end
107
-
108
- puts "Waiting for daemon to start..."
109
- sleep 0.1
110
- timer = TIMEOUT
111
- pid = PidFile.recall(daemon)
112
-
113
- while pid == nil and timer > 0
114
- # Wait a moment for the forking to finish...
115
- puts "Waiting for daemon to start (#{timer}/#{TIMEOUT})"
116
- sleep 1
117
-
118
- # If the daemon has crashed, it is never going to start...
119
- break if daemon.crashed?
120
-
121
- pid = PidFile.recall(daemon)
122
-
123
- timer -= 1
124
- end
125
- end
126
-
127
- # Prints out the status of the daemon
128
- def self.status(daemon)
129
- case PidFile.status(daemon)
130
- when :running
131
- puts "Daemon status: running pid=#{PidFile.recall(daemon)}"
132
- when :unknown
133
- if daemon.crashed?
134
- puts "Daemon status: crashed"
135
-
136
- $stdout.flush
137
- daemon.tail_err_log($stderr)
138
- else
139
- puts "Daemon status: unknown"
140
- end
141
- when :stopped
142
- puts "Daemon status: stopped"
143
- end
144
- end
145
-
146
- # Stops the daemon process.
147
- def self.stop(daemon)
148
- puts "Stopping daemon..."
149
-
150
- # Check if the pid file exists...
151
- if !File.file?(daemon.pid_fn)
152
- puts "Pid file not found. Is the daemon running?"
153
- return
154
- end
155
-
156
- pid = PidFile.recall(daemon)
157
-
158
- # Check if the daemon is already stopped...
159
- unless PidFile.running(daemon)
160
- puts "Pid #{pid} is not running. Has daemon crashed?"
161
- return
162
- end
163
-
164
- pid = PidFile.recall(daemon)
165
- Process.kill("INT", pid)
166
- sleep 0.1
167
-
168
- # Kill/Term loop - if the daemon didn't die easily, shoot
169
- # it a few more times.
170
- attempts = 5
171
- while PidFile.running(daemon) and attempts > 0
172
- sig = (attempts < 2) ? "KILL" : "TERM"
173
-
174
- puts "Sending #{sig} to pid #{pid}..."
175
- Process.kill(sig, pid)
176
-
177
- sleep 1 unless first
178
- attempts -= 1
179
- end
180
-
181
- # If after doing our best the daemon is still running (pretty odd)...
182
- if PidFile.running(daemon)
183
- puts "Daemon appears to be still running!"
184
- return
185
- end
186
-
187
- # Otherwise the daemon has been stopped.
188
- PidFile.clear(daemon)
189
- end
190
- end
191
- end
192
- end
123
+ end
124
+
125
+ puts "Waiting for daemon to start..."
126
+ sleep 0.1
127
+ timer = TIMEOUT
128
+ pid = PidFile.recall(daemon)
129
+
130
+ while pid == nil and timer > 0
131
+ # Wait a moment for the forking to finish...
132
+ puts "Waiting for daemon to start (#{timer}/#{TIMEOUT})"
133
+ sleep 1
134
+
135
+ # If the daemon has crashed, it is never going to start...
136
+ break if daemon.crashed?
137
+
138
+ pid = PidFile.recall(daemon)
139
+
140
+ timer -= 1
141
+ end
142
+ end
143
+
144
+ # Prints out the status of the daemon
145
+ def self.status(daemon)
146
+ case PidFile.status(daemon)
147
+ when :running
148
+ puts "Daemon status: running pid=#{PidFile.recall(daemon)}"
149
+ when :unknown
150
+ if daemon.crashed?
151
+ puts "Daemon status: crashed"
152
+
153
+ $stdout.flush
154
+ daemon.tail_err_log($stderr)
155
+ else
156
+ puts "Daemon status: unknown"
157
+ end
158
+ when :stopped
159
+ puts "Daemon status: stopped"
160
+ end
161
+ end
162
+
163
+ # Stops the daemon process.
164
+ def self.stop(daemon)
165
+ puts "Stopping daemon..."
166
+
167
+ # Check if the pid file exists...
168
+ if !File.file?(daemon.pid_fn)
169
+ puts "Pid file not found. Is the daemon running?"
170
+ return
171
+ end
172
+
173
+ pid = PidFile.recall(daemon)
174
+
175
+ # Check if the daemon is already stopped...
176
+ unless PidFile.running(daemon)
177
+ puts "Pid #{pid} is not running. Has daemon crashed?"
178
+ return
179
+ end
180
+
181
+ pid = PidFile.recall(daemon)
182
+ Process.kill("INT", pid)
183
+ sleep 0.1
184
+
185
+ # Kill/Term loop - if the daemon didn't die easily, shoot
186
+ # it a few more times.
187
+ attempts = 5
188
+ while PidFile.running(daemon) and attempts > 0
189
+ sig = (attempts < 2) ? "KILL" : "TERM"
190
+
191
+ puts "Sending #{sig} to pid #{pid}..."
192
+ Process.kill(sig, pid)
193
+
194
+ sleep 1
195
+ attempts -= 1
196
+ end
197
+
198
+ # If after doing our best the daemon is still running (pretty odd)...
199
+ if PidFile.running(daemon)
200
+ puts "Daemon appears to be still running!"
201
+ return
202
+ end
203
+
204
+ # Otherwise the daemon has been stopped.
205
+ PidFile.clear(daemon)
206
+ end
207
+ end
208
+ end
209
+ end
@@ -13,313 +13,327 @@
13
13
  # You should have received a copy of the GNU General Public License
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
- class String
17
- # Helper for turning a string into a shell argument
18
- def to_arg
19
- match(/\s/) ? dump : self
20
- end
21
-
22
- def to_cmd
23
- return self
24
- end
25
- end
16
+ require 'thread'
26
17
 
27
- class Array
28
- # Helper for turning an array of items into a command line string
29
- # <tt>["ls", "-la", "/My Path"].to_cmd => "ls -la \"/My Path\""</tt>
30
- def to_cmd
31
- collect{ |a| a.to_arg }.join(" ")
32
- end
33
- end
18
+ module RExec
19
+ RD = 0
20
+ WR = 1
34
21
 
35
- class Pathname
36
- # Helper for turning a pathname into a command line string
37
- def to_cmd
38
- to_s
39
- end
40
- end
22
+ # This function closes all IO other than $stdin, $stdout, $stderr
23
+ def self.close_io(except = [$stdin, $stdout, $stderr])
24
+ # Make sure all file descriptors are closed
25
+ ObjectSpace.each_object(IO) do |io|
26
+ unless except.include?(io)
27
+ io.close rescue nil
28
+ end
29
+ end
30
+ end
41
31
 
42
- module RExec
43
- RD = 0
44
- WR = 1
45
-
46
- # This function closes all IO other than $stdin, $stdout, $stderr
47
- def self.close_io(except = [$stdin, $stdout, $stderr])
48
- # Make sure all file descriptors are closed
49
- ObjectSpace.each_object(IO) do |io|
50
- unless except.include?(io)
51
- io.close rescue nil
52
- end
53
- end
54
- end
55
-
56
- class Task
57
- private
58
- def self.pipes_for_options(options)
59
- pipes = [[nil, nil], [nil, nil], [nil, nil]]
60
-
61
- if options[:passthrough]
62
- passthrough = options[:passthrough]
63
-
64
- if passthrough == :all
65
- passthrough = [:in, :out, :err]
66
- elsif passthrough.kind_of?(Symbol)
67
- passthrough = [passthrough]
68
- end
69
-
70
- passthrough.each do |name|
71
- case(name)
72
- when :in
73
- options[:in] = $stdin
74
- when :out
75
- options[:out] = $stdout
76
- when :err
77
- options[:err] = $stderr
78
- end
79
- end
80
- end
81
-
82
- modes = [RD, WR, WR]
83
- {:in => 0, :out => 1, :err => 2}.each do |name, idx|
84
- m = modes[idx]
85
- p = options[name]
86
-
87
- if p.kind_of?(IO)
88
- pipes[idx][m] = p
89
- elsif p.kind_of?(Array) and p.size == 2
90
- pipes[idx] = p
91
- else
92
- pipes[idx] = IO.pipe
93
- end
94
- end
95
-
96
- return pipes
97
- end
98
-
99
- # Close all the supplied pipes
100
- def close_pipes(*pipes)
101
- pipes.compact!
102
-
103
- pipes.each do |pipe|
104
- pipe.close unless pipe.closed?
105
- end
106
- end
107
-
108
- # Dump any remaining data from the pipes, until they are closed.
109
- def dump_pipes(*pipes)
110
- pipes.compact!
111
-
112
- pipes.delete_if { |pipe| pipe.closed? }
113
- # Dump any output that was not consumed (errors, etc)
114
- while pipes.size > 0
115
- result = IO.select(pipes)
116
-
117
- result[0].each do |pipe|
118
- if pipe.closed? || pipe.eof?
119
- pipes.delete(pipe)
120
- next
121
- end
122
-
123
- $stderr.puts pipe.readline.chomp
124
- end
125
- end
126
- end
127
-
128
- public
129
- # Returns true if the given pid is a current process
130
- def self.running?(pid)
131
- gpid = Process.getpgid(pid) rescue nil
132
-
133
- return gpid != nil ? true : false
134
- end
135
-
136
- # Very simple method to spawn a child daemon. A daemon is detatched from the controlling tty, and thus is
137
- # not killed when the parent process finishes.
138
- # <tt>
139
- # spawn_daemon do
140
- # Dir.chdir("/")
141
- # File.umask 0000
142
- # puts "Hello from daemon!"
143
- # sleep(600)
144
- # puts "This code will not quit when parent process finishes..."
145
- # puts "...but $stdout might be closed unless you set it to a file."
146
- # end
147
- # </tt>
148
- def self.spawn_daemon(&block)
149
- pid_pipe = IO.pipe
150
-
151
- fork do
152
- Process.setsid
153
- exit if fork
154
-
155
- # Send the pid back to the parent
156
- pid_pipe[RD].close
157
- pid_pipe[WR].write(Process.pid.to_s)
158
- pid_pipe[WR].close
159
-
160
- yield
161
-
162
- exit(0)
163
- end
164
-
165
- pid_pipe[WR].close
166
- pid = pid_pipe[RD].read
167
- pid_pipe[RD].close
168
-
169
- return pid.to_i
170
- end
171
-
172
- # Very simple method to spawn a child process
173
- # <tt>
174
- # spawn_child do
175
- # puts "Hello from child!"
176
- # end
177
- # </tt>
178
- def self.spawn_child(&block)
179
- pid = fork do
180
- yield
181
-
182
- exit!(0)
183
- end
184
-
185
- return pid
186
- end
187
-
188
- # Open a process. Similar to IO.popen, but provides a much more generic interface to stdin, stdout,
189
- # stderr and the pid. We also attempt to tidy up as much as possible given some kind of error or
190
- # exception. You are expected to write to output, and read from input and error.
191
- #
192
- # = Options =
193
- #
194
- # We can specify a pipe that will be redirected to the current processes pipe. A typical one is
195
- # :err, so that errors in the child process are printed directly to $stderr of the parent process.
196
- # <tt>:passthrough => :err</tt>
197
- # <tt>:passthrough => [:in, :out, :err]</tt> or <tt>:passthrough => :all</tt>
198
- #
199
- # We can specify a set of pipes other than the standard ones for redirecting to other things, eg
200
- # <tt>:out => File.open("output.log", "a")</tt>
201
- #
202
- # If you need to supply a pipe manually, you can do that too:
203
- # <tt>:in => IO.pipe</tt>
204
- #
205
- # You can specify <tt>:daemon => true</tt> to cause the child process to detatch. In this
206
- # case you will generally want to specify files for <tt>:in, :out, :err</tt> e.g.
207
- # <tt>
208
- # :in => File.open("/dev/null"),
209
- # :out => File.open("/var/log/my.log", "a"),
210
- # :err => File.open("/var/log/my.err", "a")
211
- # </tt>
212
- def self.open(command, options = {}, &block)
213
- cin, cout, cerr = pipes_for_options(options)
214
- stdpipes = [STDIN, STDOUT, STDERR]
215
-
216
- spawn = options[:daemonize] ? :spawn_daemon : :spawn_child
217
-
218
- cid = self.send(spawn) do
219
- [cin[WR], cout[RD], cerr[RD]].compact.each { |pipe| pipe.close }
220
-
221
- STDIN.reopen(cin[RD]) if cin[RD] and !stdpipes.include?(cin[RD])
222
- STDOUT.reopen(cout[WR]) if cout[WR] and !stdpipes.include?(cout[WR])
223
- STDERR.reopen(cerr[WR]) if cerr[WR] and !stdpipes.include?(cerr[WR])
224
-
225
- if command.respond_to? :call
226
- command.call
227
- else
228
- # If command is a Pathname, we need to convert it to an absolute path if possible,
229
- # otherwise if it is relative it might cause problems.
230
- if command.respond_to? :realpath
231
- command = command.realpath
232
- end
233
-
234
- if command.respond_to? :to_cmd
235
- exec(command.to_cmd)
236
- else
237
- exec(command.to_s)
238
- end
239
- end
240
- end
241
-
242
- # Don't close stdin, stdout, stderr.
243
- [cin[RD], cout[WR], cerr[WR]].compact.each { |pipe| pipe.close unless stdpipes.include?(pipe) }
244
-
245
- task = Task.new(cin[WR], cout[RD], cerr[RD], cid)
246
-
247
- if block_given?
248
- begin
249
- yield task
250
- task.close_input
251
- return task.wait
252
- ensure
253
- task.stop
254
- end
255
- else
256
- return task
257
- end
258
- end
259
-
260
- def initialize(input, output, error, pid)
261
- @input = input
262
- @output = output
263
- @error = error
264
-
265
- @pid = pid
266
- @result = nil
267
- end
268
-
269
- attr :input
270
- attr :output
271
- attr :error
272
- attr :pid
273
- attr :result
274
-
275
- def running?
276
- return self.class.running?(@pid)
277
- end
278
-
279
- # Close all connections to the child process
280
- def close
281
- close_pipes(@input, @output, @error)
282
- end
283
-
284
- # Close input pipe to child process (if applicable)
285
- def close_input
286
- @input.close if @input and !@input.closed?
287
- end
288
-
289
- # Send a signal to the child process
290
- def kill(signal = "INT")
291
- Process.kill("INT", @pid)
292
- end
293
-
294
- # Wait for the child process to finish, return the exit status.
295
- def wait
296
- begin
297
- close_input
298
-
299
- _pid, @result = Process.wait2(@pid)
300
-
301
- dump_pipes(@output, @error)
302
- ensure
303
- close_pipes(@input, @output, @error)
304
- end
305
-
306
- return @result
307
- end
308
-
309
- # Forcefully stop the child process.
310
- def stop
311
- # The process has already been stoped/waited upon
312
- return if @result
313
-
314
- begin
315
- close_input
316
- kill
317
- wait
318
-
319
- dump_pipes(@output, @error)
320
- ensure
321
- close_pipes(@output, @error)
322
- end
323
- end
324
- end
32
+ class Task
33
+ private
34
+ def self.pipes_for_options(options)
35
+ pipes = [[nil, nil], [nil, nil], [nil, nil]]
36
+
37
+ if options[:passthrough]
38
+ passthrough = options[:passthrough]
39
+
40
+ if passthrough == :all
41
+ passthrough = [:in, :out, :err]
42
+ elsif passthrough.kind_of?(Symbol)
43
+ passthrough = [passthrough]
44
+ end
45
+
46
+ passthrough.each do |name|
47
+ case(name)
48
+ when :in
49
+ options[:in] = $stdin
50
+ when :out
51
+ options[:out] = $stdout
52
+ when :err
53
+ options[:err] = $stderr
54
+ end
55
+ end
56
+ end
57
+
58
+ modes = [RD, WR, WR]
59
+ {:in => 0, :out => 1, :err => 2}.each do |name, idx|
60
+ m = modes[idx]
61
+ p = options[name]
62
+
63
+ if p.kind_of?(IO)
64
+ pipes[idx][m] = p
65
+ elsif p.kind_of?(Array) and p.size == 2
66
+ pipes[idx] = p
67
+ else
68
+ pipes[idx] = IO.pipe
69
+ end
70
+ end
71
+
72
+ return pipes
73
+ end
74
+
75
+ # Close all the supplied pipes
76
+ STDPIPES = [STDIN, STDOUT, STDERR]
77
+ def self.close_pipes(*pipes)
78
+ pipes = pipes.compact.reject{|pipe| STDPIPES.include?(pipe)}
79
+
80
+ pipes.each do |pipe|
81
+ pipe.close unless pipe.closed?
82
+ end
83
+ end
84
+
85
+ # Dump any remaining data from the pipes, until they are closed.
86
+ def self.dump_pipes(*pipes)
87
+ pipes = pipes.compact.reject{|pipe| STDPIPES.include?(pipe)}
88
+
89
+ pipes.delete_if { |pipe| pipe.closed? }
90
+ # Dump any output that was not consumed (errors, etc)
91
+ while pipes.size > 0
92
+ result = IO.select(pipes)
93
+
94
+ result[0].each do |pipe|
95
+ if pipe.closed? || pipe.eof?
96
+ pipes.delete(pipe)
97
+ next
98
+ end
99
+
100
+ $stderr.puts pipe.readline.chomp
101
+ end
102
+ end
103
+ end
104
+
105
+ public
106
+ # Returns true if the given pid is a current process
107
+ def self.running?(pid)
108
+ gpid = Process.getpgid(pid) rescue nil
109
+
110
+ return gpid != nil ? true : false
111
+ end
112
+
113
+ # Very simple method to spawn a child daemon. A daemon is detatched from the controlling tty, and thus is
114
+ # not killed when the parent process finishes.
115
+ # <tt>
116
+ # spawn_daemon do
117
+ # Dir.chdir("/")
118
+ # File.umask 0000
119
+ # puts "Hello from daemon!"
120
+ # sleep(600)
121
+ # puts "This code will not quit when parent process finishes..."
122
+ # puts "...but $stdout might be closed unless you set it to a file."
123
+ # end
124
+ # </tt>
125
+ def self.spawn_daemon(&block)
126
+ pid_pipe = IO.pipe
127
+
128
+ fork do
129
+ Process.setsid
130
+ exit if fork
131
+
132
+ # Send the pid back to the parent
133
+ pid_pipe[RD].close
134
+ pid_pipe[WR].write(Process.pid.to_s)
135
+ pid_pipe[WR].close
136
+
137
+ yield
138
+
139
+ exit(0)
140
+ end
141
+
142
+ pid_pipe[WR].close
143
+ pid = pid_pipe[RD].read
144
+ pid_pipe[RD].close
145
+
146
+ return pid.to_i
147
+ end
148
+
149
+ # Very simple method to spawn a child process
150
+ # <tt>
151
+ # spawn_child do
152
+ # puts "Hello from child!"
153
+ # end
154
+ # </tt>
155
+ def self.spawn_child(&block)
156
+ pid = fork do
157
+ yield
158
+
159
+ exit!(0)
160
+ end
161
+
162
+ return pid
163
+ end
164
+
165
+ # Open a process. Similar to IO.popen, but provides a much more generic interface to stdin, stdout,
166
+ # stderr and the pid. We also attempt to tidy up as much as possible given some kind of error or
167
+ # exception. You are expected to write to output, and read from input and error.
168
+ #
169
+ # = Options =
170
+ #
171
+ # We can specify a pipe that will be redirected to the current processes pipe. A typical one is
172
+ # :err, so that errors in the child process are printed directly to $stderr of the parent process.
173
+ # <tt>:passthrough => :err</tt>
174
+ # <tt>:passthrough => [:in, :out, :err]</tt> or <tt>:passthrough => :all</tt>
175
+ #
176
+ # We can specify a set of pipes other than the standard ones for redirecting to other things, eg
177
+ # <tt>:out => File.open("output.log", "a")</tt>
178
+ #
179
+ # If you need to supply a pipe manually, you can do that too:
180
+ # <tt>:in => IO.pipe</tt>
181
+ #
182
+ # You can specify <tt>:daemonize => true</tt> to cause the child process to detatch. In this
183
+ # case you will generally want to specify files for <tt>:in, :out, :err</tt> e.g.
184
+ # <tt>
185
+ # :in => File.open("/dev/null"),
186
+ # :out => File.open("/var/log/my.log", "a"),
187
+ # :err => File.open("/var/log/my.err", "a")
188
+ # </tt>
189
+ def self.open(command, options = {}, &block)
190
+ cin, cout, cerr = pipes_for_options(options)
191
+ spawn = options[:daemonize] ? :spawn_daemon : :spawn_child
192
+
193
+ cid = self.send(spawn) do
194
+ close_pipes(cin[WR], cout[RD], cerr[RD])
195
+
196
+ STDIN.reopen(cin[RD]) if cin[RD]
197
+ STDOUT.reopen(cout[WR]) if cout[WR]
198
+ STDERR.reopen(cerr[WR]) if cerr[WR]
199
+
200
+ if command.respond_to? :call
201
+ command.call
202
+ elsif Array === command
203
+ # If command is a Pathname, we need to convert it to an absolute path if possible,
204
+ # otherwise if it is relative it might cause problems.
205
+ if command[0].respond_to? :realpath
206
+ command[0] = command[0].realpath
207
+ end
208
+
209
+ exec *command
210
+ else
211
+ if command.respond_to? :realpath
212
+ command = command.realpath
213
+ end
214
+
215
+ exec command.to_s
216
+ end
217
+ end
218
+
219
+ close_pipes(cin[RD], cout[WR], cerr[WR])
220
+
221
+ task = Task.new(cin[WR], cout[RD], cerr[RD], cid)
222
+
223
+ if block_given?
224
+ begin
225
+ yield task
226
+ task.close_input
227
+ return task.wait
228
+ ensure
229
+ task.close
230
+ end
231
+ else
232
+ return task
233
+ end
234
+ end
235
+
236
+ def initialize(input, output, error, pid)
237
+ @input = input
238
+ @output = output
239
+ @error = error
240
+
241
+ @pid = pid
242
+
243
+ @result = nil
244
+ @status = :running
245
+ @result_lock = Mutex.new
246
+ @result_available = ConditionVariable.new
247
+ end
248
+
249
+ attr :input
250
+ attr :output
251
+ attr :error
252
+ attr :pid
253
+ attr :result
254
+
255
+ # Returns true if the current task is still running
256
+ def running?
257
+ if self.class.running?(@pid)
258
+ # The pid still seems alive, check that it isn't some other process using the same pid...
259
+ @result_lock.synchronize do
260
+ # If we haven't waited for it yet, it must be either a running process or a zombie...
261
+ return @status != :stopped
262
+ end
263
+ end
264
+
265
+ return false
266
+ end
267
+
268
+ # Close all connections to the child process
269
+ def close
270
+ begin
271
+ self.class.dump_pipes(@output, @error)
272
+ ensure
273
+ self.class.close_pipes(@input, @output, @error)
274
+ end
275
+ end
276
+
277
+ # Close input pipe to child process (if applicable)
278
+ def close_input
279
+ @input.close if @input and !@input.closed?
280
+ end
281
+
282
+ # Send a signal to the child process
283
+ def kill(signal = "INT")
284
+ if running?
285
+ Process.kill(signal, @pid)
286
+ else
287
+ raise Errno::ECHILD
288
+ end
289
+ end
290
+
291
+ # Wait for the child process to finish, return the exit status.
292
+ # This function can be called from multiple threads.
293
+ def wait
294
+ begin_wait = false
295
+
296
+ # Check to see if some other caller is already waiting on the result...
297
+ @result_lock.synchronize do
298
+ case @status
299
+ when :waiting
300
+ # If so, wait for the wait to finish...
301
+ @result_available.wait(@result_lock)
302
+ when :running
303
+ # Else, mark that we should begin waiting...
304
+ begin_wait = true
305
+ @status = :waiting
306
+ when :stopped
307
+ return @result
308
+ end
309
+ end
310
+
311
+ # If we should begin waiting (the first thread to wait)...
312
+ if begin_wait
313
+ begin
314
+ # Wait for the result...
315
+ _pid, @result = Process.wait2(@pid)
316
+ end
317
+
318
+ # The result is now available...
319
+ @result_lock.synchronize do
320
+ @status = :stopped
321
+ end
322
+
323
+ # Notify other threads...
324
+ @result_available.broadcast()
325
+ end
326
+
327
+ # Return the result
328
+ return @result
329
+ end
330
+
331
+ # Forcefully stop the child process.
332
+ def stop
333
+ if running?
334
+ close_input
335
+ kill
336
+ end
337
+ end
338
+ end
325
339
  end
@@ -16,8 +16,8 @@
16
16
  module RExec
17
17
  module VERSION #:nodoc:
18
18
  MAJOR = 1
19
- MINOR = 1
20
- TINY = 12
19
+ MINOR = 2
20
+ TINY = 1
21
21
 
22
22
  STRING = [MAJOR, MINOR, TINY].join('.')
23
23
  end
@@ -128,7 +128,7 @@ class TaskTest < Test::Unit::TestCase
128
128
 
129
129
  assert rd.closed?
130
130
 
131
- assert_raises(Errno::EPIPE) do
131
+ assert_raises(Errno::EINVAL) do
132
132
  wr.puts "The pipe is closed on the other side.."
133
133
  end
134
134
 
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 1
7
+ - 2
7
8
  - 1
8
- - 12
9
- version: 1.1.12
9
+ version: 1.2.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Samuel Williams
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-08 00:00:00 +12:00
17
+ date: 2011-07-10 00:00:00 +12:00
18
18
  default_executable: daemon-exec
19
19
  dependencies: []
20
20
 
@@ -79,7 +79,7 @@ requirements: []
79
79
  rubyforge_project:
80
80
  rubygems_version: 1.3.6
81
81
  signing_key:
82
- specification_version: 2
82
+ specification_version: 3
83
83
  summary: RExec (Remote Execution) is a tool to facilitate communicating to another ruby process and executing code.
84
84
  test_files:
85
85
  - test/daemon_test.rb