pipemaster 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ 0.5.2 (2010-03-02)
2
+ * Added: Worker gets user method. Better here than on server. Server method is deprecated and will be removed soon.
3
+ * Fixed: service pipemaster upgrade now working as expected.
4
+
1
5
  0.5.1 (2010-03-01)
2
6
  * Added: Ping command:
3
7
  $ pipe ping
@@ -1,8 +1,7 @@
1
1
  #!/bin/sh
2
2
  APP_ROOT=/var/myapp
3
+ PID=/var/run/pipemaster.pid # Remember to use same PID path in your Pipefile.
3
4
  ENV=production
4
- # Remember to use same PID path in your Pipefile.
5
- PID=/var/run/pipemaster.pid
6
5
  CMD="pipemaster -D -E $ENV"
7
6
 
8
7
  old_pid="$PID.oldbin"
@@ -35,7 +34,7 @@ restart|reload)
35
34
  $CMD
36
35
  ;;
37
36
  upgrade)
38
- sig USR2 && sleep 3 && sig 0 && oldsig QUIT && exit 0
37
+ sig USR2 && sleep 5 && sig 0 && oldsig QUIT && exit 0
39
38
  echo >&2 "Couldn't upgrade, starting '$CMD' instead"
40
39
  $CMD
41
40
  ;;
@@ -82,9 +82,50 @@ module Pipemaster
82
82
 
83
83
 
84
84
  def start
85
+ BasicSocket.do_not_reverse_lookup = true
86
+
87
+ # inherit sockets from parents, they need to be plain Socket objects
88
+ # before they become UNIXServer or TCPServer
89
+ inherited = ENV['PIPEMASTER_FD'].to_s.split(/,/).map do |fd|
90
+ io = Socket.for_fd(fd.to_i)
91
+ set_server_sockopt(io, listener_opts[sock_name(io)])
92
+ IO_PURGATORY << io
93
+ logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
94
+ server_cast(io)
95
+ end
96
+
97
+ config_listeners = config[:listeners].dup
98
+ LISTENERS.replace(inherited)
99
+ # we start out with generic Socket objects that get cast to either
100
+ # TCPServer or UNIXServer objects; but since the Socket objects
101
+ # share the same OS-level file descriptor as the higher-level *Server
102
+ # objects; we need to prevent Socket objects from being garbage-collected
103
+ config_listeners -= listener_names
104
+ if config_listeners.empty? && LISTENERS.empty?
105
+ config_listeners << Pipemaster::DEFAULT_LISTEN
106
+ init_listeners << Pipemaster::DEFAULT_LISTEN
107
+ START_CTX[:argv] << "-s#{Pipemaster::DEFAULT_LISTEN}"
108
+ end
109
+ config_listeners.each { |addr| listen(addr) }
110
+ raise ArgumentError, "no listeners" if LISTENERS.empty?
111
+
112
+ self.pid = config[:pid]
85
113
  self.master_pid = $$
114
+ if setup
115
+ if defined?(Gem) && Gem.respond_to?(:refresh)
116
+ logger.info "Refreshing Gem list"
117
+ Gem.refresh
118
+ end
119
+ setup.call
120
+ logger.info "setup completed"
121
+ end
122
+ self
123
+ end
124
+
125
+ def join
86
126
  trap(:QUIT) { stop }
87
127
  [:TERM, :INT].each { |sig| trap(sig) { stop false } }
128
+ self.master_pid = $$
88
129
  self.pid = config[:pid]
89
130
  trap(:CHLD) { reap_all_workers }
90
131
  trap :USR1 do
@@ -92,13 +133,15 @@ module Pipemaster
92
133
  Pipemaster::Util.reopen_logs
93
134
  logger.info "master done reopening logs"
94
135
  end
95
- trap(:HUP) { reloaded = true ; load_config! ; restart_background }
96
- trap(:USR2) { reexec }
97
-
98
- proc_name "pipemaster"
99
- logger.info "running setup"
100
- setup.call if setup
136
+ trap :HUP do
137
+ reloaded = true
138
+ reap_all_workers
139
+ load_config!
140
+ restart_background
141
+ end
142
+ trap(:USR2) { reap_all_workers ; reexec }
101
143
 
144
+ $0 = "pipemaster"
102
145
  logger.info "master process ready" # test_exec.rb relies on this message
103
146
  if ready_pipe
104
147
  ready_pipe.syswrite($$.to_s)
@@ -106,14 +149,6 @@ module Pipemaster
106
149
  self.ready_pipe = nil
107
150
  end
108
151
 
109
- config_listeners = config[:listeners].dup
110
- if config_listeners.empty? && LISTENERS.empty?
111
- config_listeners << DEFAULT_LISTEN
112
- init_listeners << DEFAULT_LISTEN
113
- START_CTX[:argv] << "-s#{DEFAULT_LISTEN}"
114
- end
115
- config_listeners.each { |addr| listen(addr) }
116
-
117
152
  begin
118
153
  reloaded = false
119
154
  restart_background
@@ -135,10 +170,7 @@ module Pipemaster
135
170
  sleep 1 # This is often failure to bind, so wait a bit
136
171
  retry
137
172
  end
138
- self
139
- end
140
173
 
141
- def join
142
174
  stop # gracefully shutdown all workers on our way out
143
175
  logger.info "master complete"
144
176
  unlink_pid_safe(pid) if pid
@@ -251,11 +283,6 @@ module Pipemaster
251
283
  rescue Errno::ENOENT
252
284
  end
253
285
 
254
- def proc_name(tag)
255
- $0 = ([ File.basename(START_CTX[0]), tag
256
- ]).concat(START_CTX[:argv]).join(' ')
257
- end
258
-
259
286
  # add a given address to the +listeners+ set, idempotently
260
287
  # Allows workers to add a private, per-process listener via the
261
288
  # after_fork hook. Very useful for debugging and testing.
@@ -312,7 +339,7 @@ module Pipemaster
312
339
  logger.error "reaped #{status.inspect} exec()-ed"
313
340
  self.reexec_pid = 0
314
341
  self.pid = pid.chomp('.oldbin') if pid
315
- proc_name 'master'
342
+ $0 = 'pipemaster'
316
343
  else
317
344
  WORKERS.delete(wpid) rescue nil
318
345
  BACKGROUND.delete(wpid)
@@ -363,9 +390,8 @@ module Pipemaster
363
390
  end
364
391
  end
365
392
 
366
- listener_fds = LISTENERS.map { |sock| sock.fileno }
367
- LISTENERS.delete_if { |s| s.close rescue nil ; true }
368
393
  self.reexec_pid = fork do
394
+ listener_fds = LISTENERS.map { |sock| sock.fileno }
369
395
  ENV['PIPEMASTER_FD'] = listener_fds.join(',')
370
396
  Dir.chdir(START_CTX[:cwd])
371
397
  cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
@@ -384,7 +410,7 @@ module Pipemaster
384
410
  before_exec.call(self)
385
411
  exec(*cmd)
386
412
  end
387
- proc_name 'master (old)'
413
+ $0 = 'pipemaster (old)'
388
414
  end
389
415
 
390
416
  DEFAULT_COMMANDS = {
@@ -407,7 +433,7 @@ module Pipemaster
407
433
  length = socket.readpartial(4).unpack("N")[0]
408
434
  name, *args = socket.read(length).split("\0")
409
435
 
410
- proc_name "pipemaster: #{name}"
436
+ $0 = "pipemaster $#{name}"
411
437
  logger.info "#{Process.pid} #{name} #{args.join(' ')}"
412
438
 
413
439
  ARGV.replace args
@@ -418,13 +444,13 @@ module Pipemaster
418
444
  else
419
445
  raise ArgumentError, "No command #{name}"
420
446
  end
421
- logger.info "#{Process.pid} exit"
447
+ logger.info "exit command #{name}"
422
448
  socket.write 0.chr
423
449
  rescue SystemExit => ex
424
- logger.info "#{Process.pid} exit with #{ex.status}"
450
+ logger.info "exit command #{name} with #{ex.status}"
425
451
  socket.write ex.status.chr
426
452
  rescue Exception => ex
427
- logger.info "#{Process.pid} failed: #{ex.message}"
453
+ logger.info "failed command #{name}: #{ex.message}"
428
454
  socket.write "#{ex.class.name}: #{ex.message}\n"
429
455
  socket.write 127.chr
430
456
  ensure
@@ -456,14 +482,14 @@ module Pipemaster
456
482
  WORKERS.clear
457
483
  LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
458
484
  after_fork.call self, worker
459
- proc_name "pipemaster: #{name}"
460
- logger.info "#{Process.pid} background worker #{name}"
485
+ $0 = "pipemaster/#{name}"
486
+ logger.info "background worker #{name}"
461
487
  block.call
462
- logger.info "#{Process.pid} finished worker #{name}"
488
+ logger.info "finished worker #{name}"
463
489
  rescue SystemExit => ex
464
- logger.info "#{Process.pid} finished worker #{name}"
490
+ logger.info "finished worker #{name} with #{ex.status}"
465
491
  rescue =>ex
466
- logger.info "#{Process.pid} failed: #{ex.message}"
492
+ logger.info "failed worker #{name}: #{ex.message}"
467
493
  socket.write "#{ex.class.name}: #{ex.message}\n"
468
494
  socket.write 127.chr
469
495
  ensure
@@ -472,4 +498,3 @@ module Pipemaster
472
498
 
473
499
  end
474
500
  end
475
-
@@ -1,4 +1,30 @@
1
1
  module Pipemaster
2
2
  class Worker
3
+ autoload :Etc, 'etc'
4
+
5
+ # Changes the worker process to the specified +user+ and +group+
6
+ # This is only intended to be called from within the worker
7
+ # process from the +after_fork+ hook. This should be called in
8
+ # the +after_fork+ hook after any priviledged functions need to be
9
+ # run (e.g. to set per-worker CPU affinity, niceness, etc)
10
+ #
11
+ # Any and all errors raised within this method will be propagated
12
+ # directly back to the caller (usually the +after_fork+ hook.
13
+ # These errors commonly include ArgumentError for specifying an
14
+ # invalid user/group and Errno::EPERM for insufficient priviledges
15
+ def user(user, group = nil)
16
+ # we do not protect the caller, checking Process.euid == 0 is
17
+ # insufficient because modern systems have fine-grained
18
+ # capabilities. Let the caller handle any and all errors.
19
+ uid = Etc.getpwnam(user).uid
20
+ gid = Etc.getgrnam(group).gid if group
21
+ Pipemaster::Util.chown_logs(uid, gid)
22
+ #tmp.chown(uid, gid)
23
+ if gid && Process.egid != gid
24
+ Process.initgroups(user, gid)
25
+ Process::GID.change_privilege(gid)
26
+ end
27
+ Process.euid != uid and Process::UID.change_privilege(uid)
28
+ end
3
29
  end
4
30
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "pipemaster"
3
- spec.version = "0.5.1"
3
+ spec.version = "0.5.2"
4
4
  spec.author = "Assaf Arkin"
5
5
  spec.email = "assaf@labnotes.org"
6
6
  spec.homepage = "http://github.com/assaf/pipemaster"
@@ -21,7 +21,7 @@ class ServerTest < Test::Unit::TestCase
21
21
  def start(options = nil)
22
22
  redirect_test_io do
23
23
  @server = Pipemaster::Server.new({:listeners => [ "127.0.0.1:#{@port}" ]}.merge(options || {}))
24
- @pid = fork { @server.start }
24
+ @pid = fork { @server.start.join }
25
25
  at_exit { Process.kill :QUIT, @pid }
26
26
  wait_master_ready("test_stderr.#$$.log")
27
27
  end
@@ -81,10 +81,10 @@ class ServerTest < Test::Unit::TestCase
81
81
  end
82
82
 
83
83
  def test_streams
84
- start :commands => { :reverse => lambda { $stdout << $stdin.read.reverse } }
85
- client = Pipemaster::Client.new("127.0.0.1:#@port")
86
84
  tmp = Tempfile.new('input')
87
85
  ObjectSpace.undefine_finalizer(tmp)
86
+ start :commands => { :reverse => lambda { $stdout << $stdin.read.reverse } }
87
+ client = Pipemaster::Client.new("127.0.0.1:#@port")
88
88
  tmp.write "foo bar"
89
89
  tmp.flush
90
90
  tmp.rewind
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 5
8
- - 1
9
- version: 0.5.1
8
+ - 2
9
+ version: 0.5.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Assaf Arkin
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-01 00:00:00 -08:00
17
+ date: 2010-03-02 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -56,7 +56,7 @@ licenses: []
56
56
  post_install_message: To get started run pipemaster --help
57
57
  rdoc_options:
58
58
  - --title
59
- - Pipemaster 0.5.1
59
+ - Pipemaster 0.5.2
60
60
  - --main
61
61
  - README.rdoc
62
62
  - --webcvs