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 +4 -0
- data/etc/pipemaster +2 -3
- data/lib/pipemaster/server.rb +61 -36
- data/lib/pipemaster/worker.rb +26 -0
- data/pipemaster.gemspec +1 -1
- data/test/unit/test_server.rb +3 -3
- metadata +4 -4
data/CHANGELOG
CHANGED
data/etc/pipemaster
CHANGED
@@ -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
|
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
|
;;
|
data/lib/pipemaster/server.rb
CHANGED
@@ -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
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 "#{
|
447
|
+
logger.info "exit command #{name}"
|
422
448
|
socket.write 0.chr
|
423
449
|
rescue SystemExit => ex
|
424
|
-
logger.info "#{
|
450
|
+
logger.info "exit command #{name} with #{ex.status}"
|
425
451
|
socket.write ex.status.chr
|
426
452
|
rescue Exception => ex
|
427
|
-
logger.info "#{
|
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
|
-
|
460
|
-
logger.info "
|
485
|
+
$0 = "pipemaster/#{name}"
|
486
|
+
logger.info "background worker #{name}"
|
461
487
|
block.call
|
462
|
-
logger.info "
|
488
|
+
logger.info "finished worker #{name}"
|
463
489
|
rescue SystemExit => ex
|
464
|
-
logger.info "
|
490
|
+
logger.info "finished worker #{name} with #{ex.status}"
|
465
491
|
rescue =>ex
|
466
|
-
logger.info "#{
|
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
|
-
|
data/lib/pipemaster/worker.rb
CHANGED
@@ -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
|
data/pipemaster.gemspec
CHANGED
data/test/unit/test_server.rb
CHANGED
@@ -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
|
-
-
|
9
|
-
version: 0.5.
|
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-
|
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.
|
59
|
+
- Pipemaster 0.5.2
|
60
60
|
- --main
|
61
61
|
- README.rdoc
|
62
62
|
- --webcvs
|