pipemaster 0.4.0 → 0.4.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/CHANGELOG +9 -0
- data/bin/pipe +15 -2
- data/bin/pipemaster +1 -1
- data/etc/pipemaster +50 -0
- data/lib/pipemaster/configurator.rb +6 -0
- data/lib/pipemaster/server.rb +33 -12
- data/pipemaster.gemspec +2 -2
- data/test/unit/test_server.rb +39 -4
- metadata +7 -6
data/CHANGELOG
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
0.4.1
|
2
|
+
* Added: Service control file in etc/pipemaster (puts this in your /etc/init.d directory)
|
3
|
+
* Added: pipe command gets --retcode/-c option to return exit code in case of failure.
|
4
|
+
* Added: Pipefile uses app { } for application loading block.
|
5
|
+
* Changed: Return status for errors is 127.
|
6
|
+
* Fixed: pipemaster command always defaulting to run as daemon.
|
7
|
+
* Fixed: Added time delay when retrying after master loop exception (typically, failure to bind on port).
|
8
|
+
* Fixed: Can't stop server.
|
9
|
+
|
1
10
|
0.4.0 (2010-02-19)
|
2
11
|
* Added: pipe command (client).
|
3
12
|
* Added: Pipemaster::Client gets pipe and capture methods, global address setting.
|
data/bin/pipe
CHANGED
@@ -5,6 +5,7 @@ require "optparse"
|
|
5
5
|
|
6
6
|
ENV["PIPE_ENV"] ||= "development"
|
7
7
|
address = nil
|
8
|
+
ret_code = nil
|
8
9
|
tty = false
|
9
10
|
|
10
11
|
opts = OptionParser.new("", 24, ' ') do |opts|
|
@@ -20,6 +21,10 @@ opts = OptionParser.new("", 24, ' ') do |opts|
|
|
20
21
|
address = a
|
21
22
|
end
|
22
23
|
|
24
|
+
opts.on("-c", "--retcode CODE", "return code in case of failure (default: raise exception)") do |r|
|
25
|
+
ret_code = r
|
26
|
+
end
|
27
|
+
|
23
28
|
opts.on("-h", "--help", "Show this message") do
|
24
29
|
puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
|
25
30
|
exit
|
@@ -33,5 +38,13 @@ opts = OptionParser.new("", 24, ' ') do |opts|
|
|
33
38
|
opts.parse! ARGV
|
34
39
|
end
|
35
40
|
|
36
|
-
|
37
|
-
|
41
|
+
begin
|
42
|
+
input = $stdin if tty || !$stdin.isatty
|
43
|
+
exit Pipemaster::Client.new(address).capture(input, $stdout).run(*ARGV)
|
44
|
+
rescue Exception=>ex
|
45
|
+
if ret_code
|
46
|
+
exit ret_code.to_i
|
47
|
+
else
|
48
|
+
raise
|
49
|
+
end
|
50
|
+
end
|
data/bin/pipemaster
CHANGED
@@ -76,5 +76,5 @@ config = ARGV[0] || "Pipefile"
|
|
76
76
|
abort "configuration file #{config} not found" unless File.exist?(config)
|
77
77
|
options[:config_file] = config
|
78
78
|
options[:working_directory] = File.dirname(config)
|
79
|
-
Pipemaster::Launcher.daemonize!(options)
|
79
|
+
Pipemaster::Launcher.daemonize!(options) if daemonize
|
80
80
|
Pipemaster::Server.run(options)
|
data/etc/pipemaster
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
APP_ROOT=/var/myapp
|
3
|
+
ENV=production
|
4
|
+
# Remember to use same PID path in your Pipefile.
|
5
|
+
PID=/var/run/pipemaster.pid
|
6
|
+
CMD="pipemaster -D -E $ENV"
|
7
|
+
|
8
|
+
old_pid="$PID.oldbin"
|
9
|
+
|
10
|
+
cd $APP_ROOT || exit 1
|
11
|
+
|
12
|
+
sig () {
|
13
|
+
test -s "$PID" && kill -$1 `cat $PID`
|
14
|
+
}
|
15
|
+
oldsig () {
|
16
|
+
test -s $old_pid && kill -$1 `cat $old_pid`
|
17
|
+
}
|
18
|
+
|
19
|
+
case $1 in
|
20
|
+
start)
|
21
|
+
sig 0 && echo >&2 "Already running" && exit 0
|
22
|
+
$CMD
|
23
|
+
;;
|
24
|
+
stop)
|
25
|
+
sig QUIT && exit 0
|
26
|
+
echo >&2 "Not running"
|
27
|
+
;;
|
28
|
+
force-stop)
|
29
|
+
sig TERM && exit 0
|
30
|
+
echo >&2 "Not running"
|
31
|
+
;;
|
32
|
+
restart|reload)
|
33
|
+
sig HUP && echo reloaded OK && exit 0
|
34
|
+
echo >&2 "Couldn't reload, starting '$CMD' instead"
|
35
|
+
$CMD
|
36
|
+
;;
|
37
|
+
upgrade)
|
38
|
+
sig USR2 && sleep 3 && sig 0 && oldsig QUIT && exit 0
|
39
|
+
echo >&2 "Couldn't upgrade, starting '$CMD' instead"
|
40
|
+
$CMD
|
41
|
+
;;
|
42
|
+
status)
|
43
|
+
sig 0 && echo >&2 "Running" && exit 0
|
44
|
+
echo >&2 "Not running"
|
45
|
+
;;
|
46
|
+
*)
|
47
|
+
echo >&2 "Usage: $0 <start|stop|status|restart|upgrade|force-stop>"
|
48
|
+
exit 1
|
49
|
+
;;
|
50
|
+
esac
|
@@ -116,6 +116,12 @@ module Pipemaster
|
|
116
116
|
Server::START_CTX[:cwd] = ENV["PWD"] = path
|
117
117
|
end
|
118
118
|
|
119
|
+
# Application is a block we run at startup. This is the heavy stuff (e.g.
|
120
|
+
# starting up ActiveRecord) we want to run once and fork from.
|
121
|
+
def app(*args, &block)
|
122
|
+
set_hook(:app, block_given? ? block : args[0], 0)
|
123
|
+
end
|
124
|
+
|
119
125
|
# Sets after_fork hook to a given block. This block will be called by
|
120
126
|
# the worker after forking.
|
121
127
|
def after_fork(*args, &block)
|
data/lib/pipemaster/server.rb
CHANGED
@@ -7,7 +7,7 @@ require "pipemaster/socket_helper"
|
|
7
7
|
module Pipemaster
|
8
8
|
|
9
9
|
class Server < Struct.new(:listener_opts, :timeout, :logger,
|
10
|
-
:before_fork, :after_fork, :before_exec,
|
10
|
+
:app, :before_fork, :after_fork, :before_exec,
|
11
11
|
:pid, :reexec_pid, :init_listeners,
|
12
12
|
:master_pid, :config, :ready_pipe)
|
13
13
|
|
@@ -78,28 +78,30 @@ module Pipemaster
|
|
78
78
|
attr_accessor :commands
|
79
79
|
|
80
80
|
def start
|
81
|
+
self.master_pid = $$
|
81
82
|
trap(:QUIT) { stop }
|
82
83
|
[:TERM, :INT].each { |sig| trap(sig) { stop false } }
|
83
84
|
self.pid = config[:pid]
|
84
|
-
|
85
|
-
proc_name "pipemaster"
|
86
|
-
logger.info "master process ready" # test_exec.rb relies on this message
|
87
|
-
if ready_pipe
|
88
|
-
ready_pipe.syswrite($$.to_s)
|
89
|
-
ready_pipe.close rescue nil
|
90
|
-
self.ready_pipe = nil
|
91
|
-
end
|
92
|
-
|
93
85
|
trap(:CHLD) { reap_all_workers }
|
94
86
|
trap :USR1 do
|
95
87
|
logger.info "master reopening logs..."
|
96
88
|
Pipemaster::Util.reopen_logs
|
97
89
|
logger.info "master done reopening logs"
|
98
90
|
end
|
99
|
-
reloaded = nil
|
100
91
|
trap(:HUP) { reloaded = true ; load_config! }
|
101
92
|
trap(:USR2) { reexec }
|
102
93
|
|
94
|
+
proc_name "pipemaster"
|
95
|
+
logger.info "loading application"
|
96
|
+
app.call if app
|
97
|
+
|
98
|
+
logger.info "master process ready" # test_exec.rb relies on this message
|
99
|
+
if ready_pipe
|
100
|
+
ready_pipe.syswrite($$.to_s)
|
101
|
+
ready_pipe.close rescue nil
|
102
|
+
self.ready_pipe = nil
|
103
|
+
end
|
104
|
+
|
103
105
|
config_listeners = config[:listeners].dup
|
104
106
|
if config_listeners.empty? && LISTENERS.empty?
|
105
107
|
config_listeners << DEFAULT_LISTEN
|
@@ -108,6 +110,7 @@ module Pipemaster
|
|
108
110
|
end
|
109
111
|
config_listeners.each { |addr| listen(addr) }
|
110
112
|
|
113
|
+
reloaded = nil
|
111
114
|
begin
|
112
115
|
reloaded = false
|
113
116
|
while selected = Kernel.select(LISTENERS)
|
@@ -125,6 +128,7 @@ module Pipemaster
|
|
125
128
|
rescue => ex
|
126
129
|
logger.error "Unhandled master loop exception #{ex.inspect}."
|
127
130
|
logger.error ex.backtrace.join("\n")
|
131
|
+
sleep 1 # This is often failure to bind, so wait a bit
|
128
132
|
retry
|
129
133
|
end
|
130
134
|
self
|
@@ -135,6 +139,23 @@ module Pipemaster
|
|
135
139
|
logger.info "master complete"
|
136
140
|
unlink_pid_safe(pid) if pid
|
137
141
|
end
|
142
|
+
|
143
|
+
# Terminates all workers, but does not exit master process
|
144
|
+
def stop(graceful = true)
|
145
|
+
self.listeners = []
|
146
|
+
limit = Time.now + timeout
|
147
|
+
until WORKERS.empty? || Time.now > limit
|
148
|
+
kill_each_worker(graceful ? :QUIT : :TERM)
|
149
|
+
sleep(0.1)
|
150
|
+
reap_all_workers
|
151
|
+
end
|
152
|
+
kill_each_worker(:KILL)
|
153
|
+
end
|
154
|
+
|
155
|
+
# delivers a signal to each worker
|
156
|
+
def kill_each_worker(signal)
|
157
|
+
WORKERS.keys.each { |wpid| kill_worker(signal, wpid) }
|
158
|
+
end
|
138
159
|
|
139
160
|
# replaces current listener set with +listeners+. This will
|
140
161
|
# close the socket if it will not exist in the new listener set
|
@@ -389,7 +410,7 @@ module Pipemaster
|
|
389
410
|
rescue Exception => ex
|
390
411
|
logger.info "#{Process.pid} failed: #{ex.message}"
|
391
412
|
socket.write "#{ex.class.name}: #{ex.message}\n"
|
392
|
-
socket.write
|
413
|
+
socket.write 127.chr
|
393
414
|
ensure
|
394
415
|
socket.close_write
|
395
416
|
socket.close
|
data/pipemaster.gemspec
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
spec.name = "pipemaster"
|
3
|
-
spec.version = "0.4.
|
3
|
+
spec.version = "0.4.1"
|
4
4
|
spec.author = "Assaf Arkin"
|
5
5
|
spec.email = "assaf@labnotes.org"
|
6
6
|
spec.homepage = "http://github.com/assaf/pipemaster"
|
7
7
|
spec.summary = "Use the fork"
|
8
8
|
spec.post_install_message = "To get started run pipemaster --help"
|
9
9
|
|
10
|
-
spec.files = Dir["{bin,lib,test}/**/*", "CHANGELOG", "LICENSE", "README.rdoc", "Rakefile", "Gemfile", "pipemaster.gemspec"]
|
10
|
+
spec.files = Dir["{bin,lib,test,etc}/**/*", "CHANGELOG", "LICENSE", "README.rdoc", "Rakefile", "Gemfile", "pipemaster.gemspec"]
|
11
11
|
spec.executables = %w{pipe pipemaster}
|
12
12
|
|
13
13
|
spec.has_rdoc = true
|
data/test/unit/test_server.rb
CHANGED
@@ -48,16 +48,38 @@ class ServerTest < Test::Unit::TestCase
|
|
48
48
|
|
49
49
|
def test_no_command
|
50
50
|
start
|
51
|
-
|
52
|
-
assert_equal
|
51
|
+
result = hit("127.0.0.1:#@port", :nosuch)
|
52
|
+
assert_equal 127, result.first
|
53
|
+
assert_equal "ArgumentError: No command nosuch\n", result.last
|
53
54
|
end
|
54
55
|
|
55
56
|
def test_arguments
|
56
57
|
start :commands => { :args => lambda { |x,y| $stdout << "#{x} - #{y}" } }
|
57
|
-
|
58
|
-
assert_equal "foo - bar",
|
58
|
+
result = hit("127.0.0.1:#@port", :args, "foo", "bar")
|
59
|
+
assert_equal "foo - bar", result.last
|
59
60
|
end
|
60
61
|
|
62
|
+
def test_return_code
|
63
|
+
start :commands => { :success => lambda { "hi" }, :fail => lambda { fail }, :exit => lambda { exit 5 } }
|
64
|
+
assert_equal 0, hit("127.0.0.1:#@port", :success).first
|
65
|
+
assert_equal 127, hit("127.0.0.1:#@port", :fail).first
|
66
|
+
assert_equal 5, hit("127.0.0.1:#@port", :exit).first
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_exit
|
70
|
+
start :commands => { :exit => lambda { exit 6 } }
|
71
|
+
result = hit("127.0.0.1:#@port", :exit)
|
72
|
+
assert_equal 6, result.first
|
73
|
+
assert_equal "", result.last
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_error
|
77
|
+
start :commands => { :fail => lambda { fail "oops" } }
|
78
|
+
result = hit("127.0.0.1:#@port", :fail)
|
79
|
+
assert_equal 127, result.first
|
80
|
+
assert_equal "RuntimeError: oops\n", result.last
|
81
|
+
end
|
82
|
+
|
61
83
|
def test_streams
|
62
84
|
start :commands => { :reverse => lambda { $stdout << $stdin.read.reverse } }
|
63
85
|
client = Pipemaster::Client.new("127.0.0.1:#@port")
|
@@ -73,4 +95,17 @@ class ServerTest < Test::Unit::TestCase
|
|
73
95
|
tmp.close!
|
74
96
|
end
|
75
97
|
|
98
|
+
def test_loading_app
|
99
|
+
iam = "sad"
|
100
|
+
start :app => lambda { iam.replace "happy" }, :commands => { :iam => lambda { $stdout << iam } }
|
101
|
+
assert_equal "happy", hit("127.0.0.1:#@port", :iam).last
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_reloading_app
|
105
|
+
text = "foo"
|
106
|
+
start :app => lambda { text << "bar" }, :commands => { :text => lambda { $stdout << text } }
|
107
|
+
assert_equal "foobar", hit("127.0.0.1:#@port", :text).last
|
108
|
+
Process.kill "HUP", @pid
|
109
|
+
assert_equal "foobar", hit("127.0.0.1:#@port", :text).last
|
110
|
+
end
|
76
111
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pipemaster
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Assaf Arkin
|
@@ -26,7 +26,6 @@ extra_rdoc_files:
|
|
26
26
|
files:
|
27
27
|
- bin/pipe
|
28
28
|
- bin/pipemaster
|
29
|
-
- lib/pipemaster
|
30
29
|
- lib/pipemaster/client.rb
|
31
30
|
- lib/pipemaster/configurator.rb
|
32
31
|
- lib/pipemaster/launcher.rb
|
@@ -36,9 +35,9 @@ files:
|
|
36
35
|
- lib/pipemaster/worker.rb
|
37
36
|
- lib/pipemaster.rb
|
38
37
|
- test/test_helper.rb
|
39
|
-
- test/unit
|
40
38
|
- test/unit/test_configurator.rb
|
41
39
|
- test/unit/test_server.rb
|
40
|
+
- etc/pipemaster
|
42
41
|
- CHANGELOG
|
43
42
|
- LICENSE
|
44
43
|
- README.rdoc
|
@@ -47,10 +46,12 @@ files:
|
|
47
46
|
- pipemaster.gemspec
|
48
47
|
has_rdoc: true
|
49
48
|
homepage: http://github.com/assaf/pipemaster
|
49
|
+
licenses: []
|
50
|
+
|
50
51
|
post_install_message: To get started run pipemaster --help
|
51
52
|
rdoc_options:
|
52
53
|
- --title
|
53
|
-
- Pipemaster 0.4.
|
54
|
+
- Pipemaster 0.4.1
|
54
55
|
- --main
|
55
56
|
- README.rdoc
|
56
57
|
- --webcvs
|
@@ -72,9 +73,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
73
|
requirements: []
|
73
74
|
|
74
75
|
rubyforge_project:
|
75
|
-
rubygems_version: 1.3.
|
76
|
+
rubygems_version: 1.3.5
|
76
77
|
signing_key:
|
77
|
-
specification_version:
|
78
|
+
specification_version: 3
|
78
79
|
summary: Use the fork
|
79
80
|
test_files: []
|
80
81
|
|