pipemaster 0.4.2 → 0.5.0
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 +20 -0
- data/Gemfile +1 -2
- data/lib/pipemaster/configurator.rb +24 -4
- data/lib/pipemaster/server.rb +52 -10
- data/pipemaster.gemspec +1 -1
- data/test/test_helper.rb +1 -1
- data/test/unit/test_configurator.rb +17 -2
- data/test/unit/test_server.rb +47 -7
- metadata +13 -6
data/CHANGELOG
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
0.5.0 (2010-03-01)
|
2
|
+
This release adds background processes. The Pipefile can specify multiple
|
3
|
+
background activities. Pipemaster starts each activity by forking a new child
|
4
|
+
process (with the same environment as all other workers).
|
5
|
+
|
6
|
+
When Pipemaster terminates, it terminates all child processes (QUIT or TERM,
|
7
|
+
depending on signal). In addition, when reloading the configuration (HUP),
|
8
|
+
Pipemaster asks all background processes to terminate gracefully and restarts
|
9
|
+
new background processes based on the new configuration.
|
10
|
+
|
11
|
+
To define a background process:
|
12
|
+
|
13
|
+
background :resque do
|
14
|
+
resque = Resque::Worker.new(:tasks)
|
15
|
+
resque.work(5) # interval, will block
|
16
|
+
end
|
17
|
+
|
18
|
+
* Added: Background processes.
|
19
|
+
* Changed: Configuration method app becomes setup. Same semantics, just better name.
|
20
|
+
|
1
21
|
0.4.2 (2010-02-19)
|
2
22
|
* Fix: Pipe command exits with retcode on successful completion.
|
3
23
|
|
data/Gemfile
CHANGED
@@ -21,6 +21,7 @@ module Pipemaster
|
|
21
21
|
server.logger.info("forked child re-executing...")
|
22
22
|
},
|
23
23
|
:pid => nil,
|
24
|
+
:background => {},
|
24
25
|
:commands => {}
|
25
26
|
}
|
26
27
|
|
@@ -116,10 +117,10 @@ module Pipemaster
|
|
116
117
|
Server::START_CTX[:cwd] = ENV["PWD"] = path
|
117
118
|
end
|
118
119
|
|
119
|
-
#
|
120
|
-
#
|
121
|
-
def
|
122
|
-
set_hook(:
|
120
|
+
# Setup block runs on startup. Put all the heavy stuff here (e.g. loading
|
121
|
+
# libraries, initializing state from database).
|
122
|
+
def setup(*args, &block)
|
123
|
+
set_hook(:setup, block_given? ? block : args[0], 0)
|
123
124
|
end
|
124
125
|
|
125
126
|
# Sets after_fork hook to a given block. This block will be called by
|
@@ -144,6 +145,25 @@ module Pipemaster
|
|
144
145
|
set_hook(:before_exec, block_given? ? block : args[0], 1)
|
145
146
|
end
|
146
147
|
|
148
|
+
# Background process: the block is executed in a child process. The master
|
149
|
+
# will tell the child process to stop/terminate using appropriate signals,
|
150
|
+
# restart the process after a successful upgrade. Block accepts two
|
151
|
+
# arguments: server and worker.
|
152
|
+
def background(name_or_hash, a_proc = nil, &block)
|
153
|
+
set[:background] = {} if set[:background] == :unset
|
154
|
+
if Hash === name_or_hash
|
155
|
+
name_or_hash.each_pair do |name, a_proc|
|
156
|
+
background name, a_proc
|
157
|
+
end
|
158
|
+
else
|
159
|
+
name = name_or_hash.to_sym
|
160
|
+
a_proc ||= block
|
161
|
+
arity = a_proc.arity
|
162
|
+
(arity == 0 || arity < 0) or raise ArgumentError, "background #{name}#{a_proc.inspect} has invalid arity: #{arity} (need 0)"
|
163
|
+
set[:background][name] = a_proc
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
147
167
|
# Sets listeners to the given +addresses+, replacing or augmenting the
|
148
168
|
# current set. This is for internal API use only, do not use it in your
|
149
169
|
# Pipemaster config file. Use listen instead.
|
data/lib/pipemaster/server.rb
CHANGED
@@ -7,9 +7,11 @@ require "pipemaster/socket_helper"
|
|
7
7
|
module Pipemaster
|
8
8
|
|
9
9
|
class Server < Struct.new(:listener_opts, :timeout, :logger,
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
10
|
+
:setup, :before_fork, :after_fork, :before_exec,
|
11
|
+
:background, :commands,
|
12
|
+
:pid, :reexec_pid, :master_pid,
|
13
|
+
:config, :ready_pipe, :init_listeners)
|
14
|
+
|
13
15
|
|
14
16
|
include SocketHelper
|
15
17
|
|
@@ -22,6 +24,9 @@ module Pipemaster
|
|
22
24
|
# This hash maps PIDs to Workers
|
23
25
|
WORKERS = {}
|
24
26
|
|
27
|
+
# Background workers.
|
28
|
+
BACKGROUND = []
|
29
|
+
|
25
30
|
# We populate this at startup so we can figure out how to reexecute
|
26
31
|
# and upgrade the currently running instance of Pipemaster
|
27
32
|
# This Hash is considered a stable interface and changing its contents
|
@@ -75,7 +80,6 @@ module Pipemaster
|
|
75
80
|
config.commit!(self, :skip => [:listeners, :pid])
|
76
81
|
end
|
77
82
|
|
78
|
-
attr_accessor :commands
|
79
83
|
|
80
84
|
def start
|
81
85
|
self.master_pid = $$
|
@@ -88,12 +92,12 @@ module Pipemaster
|
|
88
92
|
Pipemaster::Util.reopen_logs
|
89
93
|
logger.info "master done reopening logs"
|
90
94
|
end
|
91
|
-
trap(:HUP) { reloaded = true ; load_config! }
|
95
|
+
trap(:HUP) { reloaded = true ; load_config! ; restart_background }
|
92
96
|
trap(:USR2) { reexec }
|
93
97
|
|
94
98
|
proc_name "pipemaster"
|
95
|
-
logger.info "
|
96
|
-
|
99
|
+
logger.info "running setup"
|
100
|
+
setup.call if setup
|
97
101
|
|
98
102
|
logger.info "master process ready" # test_exec.rb relies on this message
|
99
103
|
if ready_pipe
|
@@ -110,9 +114,9 @@ module Pipemaster
|
|
110
114
|
end
|
111
115
|
config_listeners.each { |addr| listen(addr) }
|
112
116
|
|
113
|
-
reloaded = nil
|
114
117
|
begin
|
115
118
|
reloaded = false
|
119
|
+
restart_background
|
116
120
|
while selected = Kernel.select(LISTENERS)
|
117
121
|
selected.first.each do |socket|
|
118
122
|
client = socket.accept_nonblock
|
@@ -295,6 +299,7 @@ module Pipemaster
|
|
295
299
|
Process.kill(signal, wpid)
|
296
300
|
rescue Errno::ESRCH
|
297
301
|
worker = WORKERS.delete(wpid)
|
302
|
+
BACKGROUND.delete(wpid)
|
298
303
|
end
|
299
304
|
end
|
300
305
|
|
@@ -309,7 +314,8 @@ module Pipemaster
|
|
309
314
|
self.pid = pid.chomp('.oldbin') if pid
|
310
315
|
proc_name 'master'
|
311
316
|
else
|
312
|
-
|
317
|
+
WORKERS.delete(wpid) rescue nil
|
318
|
+
BACKGROUND.delete(wpid)
|
313
319
|
logger.info "reaped #{status.inspect} "
|
314
320
|
end
|
315
321
|
end
|
@@ -414,10 +420,46 @@ module Pipemaster
|
|
414
420
|
ensure
|
415
421
|
socket.close_write
|
416
422
|
socket.close
|
417
|
-
exit
|
423
|
+
exit!
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def restart_background
|
428
|
+
# Gracefully shut down all backgroud processes.
|
429
|
+
BACKGROUND.delete_if { |wpid| Process.kill(:QUIT, wpid) rescue true }
|
430
|
+
# Start them again.
|
431
|
+
background.each do |name, block|
|
432
|
+
worker = Worker.new
|
433
|
+
before_fork.call self, worker
|
434
|
+
pid = fork { run_in_background name, worker, &block }
|
435
|
+
BACKGROUND << pid
|
436
|
+
WORKERS[pid] = worker
|
418
437
|
end
|
419
438
|
end
|
420
439
|
|
440
|
+
def run_in_background(name, worker, &block)
|
441
|
+
trap(:QUIT) { exit }
|
442
|
+
[:TERM, :INT].each { |sig| trap(sig) { exit! } }
|
443
|
+
[:USR1, :USR2].each { |sig| trap(sig, nil) }
|
444
|
+
trap(:CHLD, 'DEFAULT')
|
445
|
+
|
446
|
+
WORKERS.clear
|
447
|
+
LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
448
|
+
after_fork.call self, worker
|
449
|
+
proc_name "pipemaster: #{name}"
|
450
|
+
logger.info "#{Process.pid} background worker #{name}"
|
451
|
+
block.call
|
452
|
+
logger.info "#{Process.pid} finished worker #{name}"
|
453
|
+
rescue SystemExit => ex
|
454
|
+
logger.info "#{Process.pid} finished worker #{name}"
|
455
|
+
rescue =>ex
|
456
|
+
logger.info "#{Process.pid} failed: #{ex.message}"
|
457
|
+
socket.write "#{ex.class.name}: #{ex.message}\n"
|
458
|
+
socket.write 127.chr
|
459
|
+
ensure
|
460
|
+
exit!
|
461
|
+
end
|
462
|
+
|
421
463
|
end
|
422
464
|
end
|
423
465
|
|
data/pipemaster.gemspec
CHANGED
data/test/test_helper.rb
CHANGED
@@ -41,7 +41,7 @@ def redirect_test_io
|
|
41
41
|
orig_err = STDERR.dup
|
42
42
|
orig_out = STDOUT.dup
|
43
43
|
STDERR.reopen("test_stderr.#{$$}.log", "a")
|
44
|
-
STDOUT.reopen("test_stdout.#{$$}.log", "a")
|
44
|
+
#STDOUT.reopen("test_stdout.#{$$}.log", "a")
|
45
45
|
STDERR.sync = STDOUT.sync = true
|
46
46
|
|
47
47
|
at_exit do
|
@@ -5,7 +5,7 @@ require 'tempfile'
|
|
5
5
|
require 'pipemaster'
|
6
6
|
|
7
7
|
TestStruct = Struct.new(
|
8
|
-
*(Pipemaster::Configurator::DEFAULTS.keys + %w(listener_opts listeners)))
|
8
|
+
*(Pipemaster::Configurator::DEFAULTS.keys + %w(listener_opts listeners background commands)))
|
9
9
|
class TestConfigurator < Test::Unit::TestCase
|
10
10
|
|
11
11
|
def test_config_init
|
@@ -147,5 +147,20 @@ class TestConfigurator < Test::Unit::TestCase
|
|
147
147
|
end
|
148
148
|
end
|
149
149
|
|
150
|
-
|
150
|
+
def test_background_proc
|
151
|
+
test_struct = TestStruct.new
|
152
|
+
[ proc { }, Proc.new { |*a| }, lambda { |*a| } ].each do |my_proc|
|
153
|
+
Pipemaster::Configurator.new(:background => { :foo => my_proc }).commit!(test_struct)
|
154
|
+
assert_equal my_proc, test_struct.background[:foo]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_background_wrong_arity
|
159
|
+
[ proc { |a| }, Proc.new { |a,b| }, lambda { |a,b,c| } ].each do |my_proc|
|
160
|
+
assert_raises(ArgumentError) do
|
161
|
+
Pipemaster::Configurator.new(:background => { :foo => my_proc })
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
151
165
|
|
166
|
+
end
|
data/test/unit/test_server.rb
CHANGED
@@ -13,7 +13,7 @@ class ServerTest < Test::Unit::TestCase
|
|
13
13
|
redirect_test_io do
|
14
14
|
wait_master_ready("test_stderr.#$$.log")
|
15
15
|
File.truncate("test_stderr.#$$.log", 0)
|
16
|
-
Process.kill
|
16
|
+
Process.kill :QUIT, @pid
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -22,7 +22,7 @@ class ServerTest < Test::Unit::TestCase
|
|
22
22
|
redirect_test_io do
|
23
23
|
@server = Pipemaster::Server.new({:listeners => [ "127.0.0.1:#{@port}" ]}.merge(options || {}))
|
24
24
|
@pid = fork { @server.start }
|
25
|
-
at_exit { Process.kill @pid
|
25
|
+
at_exit { Process.kill :QUIT, @pid }
|
26
26
|
wait_master_ready("test_stderr.#$$.log")
|
27
27
|
end
|
28
28
|
end
|
@@ -95,17 +95,57 @@ class ServerTest < Test::Unit::TestCase
|
|
95
95
|
tmp.close!
|
96
96
|
end
|
97
97
|
|
98
|
-
def
|
98
|
+
def test_running_setup
|
99
99
|
iam = "sad"
|
100
|
-
start :
|
100
|
+
start :setup => lambda { iam.replace "happy" }, :commands => { :iam => lambda { $stdout << iam } }
|
101
101
|
assert_equal "happy", hit("127.0.0.1:#@port", :iam).last
|
102
102
|
end
|
103
103
|
|
104
|
-
def
|
104
|
+
def test_running_setup_again
|
105
105
|
text = "foo"
|
106
|
-
start :
|
106
|
+
start :setup => lambda { text << "bar" }, :commands => { :text => lambda { $stdout << text } }
|
107
107
|
assert_equal "foobar", hit("127.0.0.1:#@port", :text).last
|
108
|
-
Process.kill
|
108
|
+
Process.kill :HUP, @pid
|
109
109
|
assert_equal "foobar", hit("127.0.0.1:#@port", :text).last
|
110
110
|
end
|
111
|
+
|
112
|
+
def test_running_background
|
113
|
+
sync = Tempfile.new("sync")
|
114
|
+
start :background => { :chmod => lambda { |*_| sync.chmod 0 while sleep 0.01 } }
|
115
|
+
assert_equal 0, sync.stat.mode & 1
|
116
|
+
sync.chmod 1 ; sleep 0.1
|
117
|
+
assert_equal 0, sync.stat.mode & 1
|
118
|
+
ensure
|
119
|
+
sync.close!
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_stopping_background
|
123
|
+
sync = Tempfile.new("sync")
|
124
|
+
start :background => { :chmod => lambda { |*_| sync.chmod 0 while sleep 0.01 } }
|
125
|
+
sync.chmod 1 ; sleep 0.1
|
126
|
+
assert_equal 0, sync.stat.mode & 1
|
127
|
+
Process.kill :QUIT, @pid
|
128
|
+
sleep 0.1
|
129
|
+
sync.chmod 1 ; sleep 0.1
|
130
|
+
assert_equal 1, sync.stat.mode & 1
|
131
|
+
ensure
|
132
|
+
sync.close!
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_restarting_background
|
136
|
+
config = Tempfile.new("config")
|
137
|
+
config.write "$flag = 0" ; config.flush
|
138
|
+
sync = Tempfile.new("sync")
|
139
|
+
start :config_file => config.path, :background => { :chmod => lambda { |*_| sync.chmod $flag while sleep 0.01 } }
|
140
|
+
sync.chmod 1 ; sleep 0.1
|
141
|
+
assert_equal 0, sync.stat.mode & 1
|
142
|
+
config.rewind ; config.write "$flag = 1" ; config.flush
|
143
|
+
Process.kill :HUP, @pid
|
144
|
+
sleep 0.1
|
145
|
+
sync.chmod 0 ; sleep 0.2
|
146
|
+
assert_equal 1, sync.stat.mode & 1
|
147
|
+
ensure
|
148
|
+
sync.close!
|
149
|
+
end
|
150
|
+
|
111
151
|
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pipemaster
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 5
|
8
|
+
- 0
|
9
|
+
version: 0.5.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Assaf Arkin
|
@@ -9,7 +14,7 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-03-01 00:00:00 -08:00
|
13
18
|
default_executable:
|
14
19
|
dependencies: []
|
15
20
|
|
@@ -51,7 +56,7 @@ licenses: []
|
|
51
56
|
post_install_message: To get started run pipemaster --help
|
52
57
|
rdoc_options:
|
53
58
|
- --title
|
54
|
-
- Pipemaster 0.
|
59
|
+
- Pipemaster 0.5.0
|
55
60
|
- --main
|
56
61
|
- README.rdoc
|
57
62
|
- --webcvs
|
@@ -62,18 +67,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
62
67
|
requirements:
|
63
68
|
- - ">="
|
64
69
|
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
65
72
|
version: "0"
|
66
|
-
version:
|
67
73
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
74
|
requirements:
|
69
75
|
- - ">="
|
70
76
|
- !ruby/object:Gem::Version
|
77
|
+
segments:
|
78
|
+
- 0
|
71
79
|
version: "0"
|
72
|
-
version:
|
73
80
|
requirements: []
|
74
81
|
|
75
82
|
rubyforge_project:
|
76
|
-
rubygems_version: 1.3.
|
83
|
+
rubygems_version: 1.3.6
|
77
84
|
signing_key:
|
78
85
|
specification_version: 3
|
79
86
|
summary: Use the fork
|