rexec 1.1.12 → 1.2.1

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.
@@ -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