pipemaster 0.5.1 → 0.5.2

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,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