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 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
- input = $stdin if tty || !$stdin.isatty
37
- exit Pipemaster::Client.new(address).capture(input, $stdout).run(*ARGV)
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
@@ -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)
@@ -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)
@@ -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 1.chr
413
+ socket.write 127.chr
393
414
  ensure
394
415
  socket.close_write
395
416
  socket.close
@@ -1,13 +1,13 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "pipemaster"
3
- spec.version = "0.4.0"
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
@@ -48,16 +48,38 @@ class ServerTest < Test::Unit::TestCase
48
48
 
49
49
  def test_no_command
50
50
  start
51
- results = hit("127.0.0.1:#@port", "test")
52
- assert_equal 1, results.first
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
- results = hit("127.0.0.1:#@port", "args", "foo", "bar")
58
- assert_equal "foo - bar", results.last
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.0
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.0
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.1
76
+ rubygems_version: 1.3.5
76
77
  signing_key:
77
- specification_version: 2
78
+ specification_version: 3
78
79
  summary: Use the fork
79
80
  test_files: []
80
81