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.
- data/lib/rexec/daemon/controller.rb +189 -172
- data/lib/rexec/task.rb +320 -306
- data/lib/rexec/version.rb +2 -2
- data/test/task_test.rb +1 -1
- metadata +4 -4
@@ -17,176 +17,193 @@ require 'rexec/daemon/pidfile'
|
|
17
17
|
require 'rexec/task'
|
18
18
|
|
19
19
|
module RExec
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
data/lib/rexec/task.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
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
|
data/lib/rexec/version.rb
CHANGED
data/test/task_test.rb
CHANGED
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
|
-
|
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:
|
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:
|
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
|