ringleader 1.1.2 → 1.1.3
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/Gemfile +1 -1
- data/Rakefile +14 -2
- data/dev_scripts/Procfile +2 -1
- data/dev_scripts/bad_parent.rb +15 -0
- data/dev_scripts/test.yml +3 -2
- data/lib/ringleader.rb +1 -0
- data/lib/ringleader/app.rb +4 -4
- data/lib/ringleader/cli.rb +40 -18
- data/lib/ringleader/config.rb +3 -3
- data/lib/ringleader/process.rb +50 -14
- data/lib/ringleader/socket_proxy.rb +2 -2
- data/lib/ringleader/version.rb +1 -1
- data/lib/ringleader/wait_for_exit.rb +2 -2
- data/lib/ringleader/wait_for_port.rb +3 -3
- data/ringleader.gemspec +3 -2
- metadata +25 -8
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,2 +1,14 @@
|
|
1
|
-
|
2
|
-
require
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'bundler'
|
3
|
+
Bundler::GemHelper.install_tasks
|
4
|
+
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
desc 'Default: run unit tests.'
|
8
|
+
task :default => :spec
|
9
|
+
|
10
|
+
desc "Run all specs"
|
11
|
+
RSpec::Core::RakeTask.new do |t|
|
12
|
+
t.pattern = 'spec/**/*_spec.rb'
|
13
|
+
t.rspec_opts = ["-c", "-f progress"]
|
14
|
+
end
|
data/dev_scripts/Procfile
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
trap("INT") { STDERR.puts "#{$$} ignoring INT" }
|
2
|
+
trap("HUP") { STDERR.puts "#{$$} ignoring HUP" }
|
3
|
+
trap("TERM") { STDERR.puts "#{$$} ignoring TERM" }
|
4
|
+
|
5
|
+
@extra = 0
|
6
|
+
3.times do |n|
|
7
|
+
STDERR.puts "#{$$} forking (#{n})"
|
8
|
+
if pid = fork
|
9
|
+
STDERR.puts "#{$$} forked child #{pid}"
|
10
|
+
break
|
11
|
+
else
|
12
|
+
@extra += 1
|
13
|
+
end
|
14
|
+
end
|
15
|
+
sleep 10 + @extra * 2 # do clean up, eventually, with parent dying first
|
data/dev_scripts/test.yml
CHANGED
@@ -2,9 +2,10 @@
|
|
2
2
|
test:
|
3
3
|
dir: "./dev_scripts"
|
4
4
|
# command: "ncat -k -l 10001"
|
5
|
-
command: "bundle exec foreman start
|
5
|
+
command: "bundle exec foreman start"
|
6
6
|
# command: sleep 10
|
7
7
|
server_port: 10000
|
8
8
|
app_port: 10001
|
9
|
-
idle_timeout:
|
9
|
+
idle_timeout: 5
|
10
10
|
startup_timeout: 5
|
11
|
+
kill_with: "INT"
|
data/lib/ringleader.rb
CHANGED
data/lib/ringleader/app.rb
CHANGED
@@ -11,7 +11,7 @@ module Ringleader
|
|
11
11
|
def initialize(config)
|
12
12
|
@config = config
|
13
13
|
@process = Process.new(config)
|
14
|
-
enable
|
14
|
+
async.enable unless config.disabled
|
15
15
|
start if config.run_on_load
|
16
16
|
end
|
17
17
|
|
@@ -51,7 +51,7 @@ module Ringleader
|
|
51
51
|
return if @server
|
52
52
|
@server = TCPServer.new @config.host, @config.server_port
|
53
53
|
@enabled = true
|
54
|
-
run
|
54
|
+
async.run
|
55
55
|
rescue Errno::EADDRINUSE
|
56
56
|
error "could not bind to #{@config.host}:#{@config.server_port} for #{@config.name}!"
|
57
57
|
@server = nil
|
@@ -73,7 +73,7 @@ module Ringleader
|
|
73
73
|
|
74
74
|
def run
|
75
75
|
info "listening for connections for #{@config.name} on #{@config.host}:#{@config.server_port}"
|
76
|
-
loop { handle_connection
|
76
|
+
loop { async.handle_connection @server.accept }
|
77
77
|
rescue IOError
|
78
78
|
@server.close if @server
|
79
79
|
end
|
@@ -84,7 +84,7 @@ module Ringleader
|
|
84
84
|
|
85
85
|
started = @process.start
|
86
86
|
if started
|
87
|
-
proxy_to_app
|
87
|
+
async.proxy_to_app socket
|
88
88
|
reset_activity_timer
|
89
89
|
else
|
90
90
|
error "could not start app"
|
data/lib/ringleader/cli.rb
CHANGED
@@ -2,19 +2,22 @@ module Ringleader
|
|
2
2
|
class CLI
|
3
3
|
include Celluloid::Logger
|
4
4
|
|
5
|
+
RC_FILE = File.expand_path('~/.ringleaderrc')
|
6
|
+
|
5
7
|
def run(argv)
|
6
|
-
|
7
|
-
Celluloid.logger.level = ::Logger::ERROR
|
8
|
+
configure_logging
|
8
9
|
|
9
10
|
opts = nil
|
10
11
|
Trollop.with_standard_exception_handling parser do
|
11
|
-
opts = parser.parse
|
12
|
+
opts = merge_rc_opts(parser.parse(argv))
|
12
13
|
end
|
13
14
|
|
14
15
|
die "must provide a filename" if argv.empty?
|
15
16
|
die "could not find config file #{argv.first}" unless File.exist?(argv.first)
|
16
17
|
|
17
|
-
|
18
|
+
if opts.verbose?
|
19
|
+
Celluloid.logger.level = ::Logger::DEBUG
|
20
|
+
end
|
18
21
|
|
19
22
|
apps = Config.new(argv.first, opts.boring).apps
|
20
23
|
|
@@ -30,8 +33,10 @@ module Ringleader
|
|
30
33
|
sleep
|
31
34
|
end
|
32
35
|
|
33
|
-
def configure_logging
|
34
|
-
|
36
|
+
def configure_logging
|
37
|
+
# set to INFO at first to hide celluloid's shutdown message until after
|
38
|
+
# opts are validated.
|
39
|
+
Celluloid.logger.level = ::Logger::INFO
|
35
40
|
format = "%5s %s.%06d | %s\n"
|
36
41
|
date_format = "%H:%M:%S"
|
37
42
|
Celluloid.logger.formatter = lambda do |severity, time, progname, msg|
|
@@ -117,20 +122,37 @@ something like this:
|
|
117
122
|
OPTIONS
|
118
123
|
banner
|
119
124
|
|
120
|
-
opt
|
121
|
-
:
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
:
|
126
|
-
opt
|
127
|
-
:
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
125
|
+
opt :verbose, "log at debug level",
|
126
|
+
:short => "-v", :default => false
|
127
|
+
opt :host, "host for web control panel",
|
128
|
+
:short => "-H", :default => "localhost"
|
129
|
+
opt :port, "port for the web control panel",
|
130
|
+
:short => "-p", :default => 42000
|
131
|
+
opt :boring, "use boring colors instead of a fabulous rainbow",
|
132
|
+
:short => "-b", :default => false
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def merge_rc_opts(opts)
|
138
|
+
[:verbose, :host, :port, :boring].each do |option_name|
|
139
|
+
if rc_opts.has_key?(option_name) && !opts["#{option_name}_given".to_sym]
|
140
|
+
opts[option_name] = rc_opts[option_name]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
opts
|
144
|
+
end
|
132
145
|
|
146
|
+
def rc_opts
|
147
|
+
unless @rc_opts
|
148
|
+
if File.readable?(RC_FILE)
|
149
|
+
info "reading options from ~/.ringleaderrc"
|
150
|
+
@rc_opts = parser.parse File.read(RC_FILE).strip.split(/\s+/)
|
151
|
+
else
|
152
|
+
@rc_opts = {}
|
153
|
+
end
|
133
154
|
end
|
155
|
+
@rc_opts
|
134
156
|
end
|
135
157
|
|
136
158
|
end
|
data/lib/ringleader/config.rb
CHANGED
@@ -21,7 +21,7 @@ module Ringleader
|
|
21
21
|
@apps = Hash[*configs.flatten]
|
22
22
|
end
|
23
23
|
|
24
|
-
#
|
24
|
+
# Internal: convert a YML hash to an array of name/OpenStruct pairs
|
25
25
|
#
|
26
26
|
# Does validation for each app config and raises an error if anything is
|
27
27
|
# wrong. Sets default values for missing options, and assigns colors to each
|
@@ -61,7 +61,7 @@ module Ringleader
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
-
#
|
64
|
+
# Internal: validate that the options have all of the required keys
|
65
65
|
def validate(name, options)
|
66
66
|
REQUIRED_KEYS.each do |key|
|
67
67
|
unless options.has_key?(key)
|
@@ -70,7 +70,7 @@ module Ringleader
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
#
|
73
|
+
# Internal: assign a color to each application configuration.
|
74
74
|
#
|
75
75
|
# configs - the config data to modify
|
76
76
|
# boring - use boring standard terminal colors instead of a rainbow.
|
data/lib/ringleader/process.rb
CHANGED
@@ -55,21 +55,27 @@ module Ringleader
|
|
55
55
|
def stop
|
56
56
|
return unless @pid
|
57
57
|
|
58
|
+
children = child_pids @pid
|
59
|
+
|
58
60
|
info "stopping #{@pid}"
|
61
|
+
debug "child pids: #{children.inspect}"
|
62
|
+
|
59
63
|
@master.close unless @master.closed?
|
64
|
+
|
60
65
|
debug "kill -#{config.kill_with} #{@pid}"
|
61
66
|
::Process.kill config.kill_with, -@pid
|
62
67
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
68
|
+
failsafe = after 7 do
|
69
|
+
warn "process #{@pid} did not shut down cleanly, killing it"
|
70
|
+
debug "kill -KILL #{@pid}"
|
71
|
+
::Process.kill "KILL", -@pid
|
72
|
+
reap_orphans children
|
69
73
|
end
|
70
74
|
|
71
75
|
wait :running # wait for the exit callback
|
72
|
-
|
76
|
+
failsafe.cancel
|
77
|
+
sleep 2 # give the children a chance to shut down
|
78
|
+
reap_orphans children
|
73
79
|
|
74
80
|
rescue Errno::ESRCH, Errno::EPERM
|
75
81
|
exited
|
@@ -83,8 +89,7 @@ module Ringleader
|
|
83
89
|
|
84
90
|
# Internal: callback for when the process has exited.
|
85
91
|
def exited
|
86
|
-
|
87
|
-
info "exited."
|
92
|
+
info "pid #{@pid} exited"
|
88
93
|
@running = false
|
89
94
|
@pid = nil
|
90
95
|
@wait_for_port.terminate if @wait_for_port.alive?
|
@@ -92,7 +97,7 @@ module Ringleader
|
|
92
97
|
signal :running, false
|
93
98
|
end
|
94
99
|
|
95
|
-
#
|
100
|
+
# Internal: start the application process and associated infrastructure
|
96
101
|
#
|
97
102
|
# Intended to be synchronous, as it blocks until the app has started (or
|
98
103
|
# failed to start).
|
@@ -124,7 +129,7 @@ module Ringleader
|
|
124
129
|
|
125
130
|
timer = after config.startup_timeout do
|
126
131
|
warn "application startup took more than #{config.startup_timeout}"
|
127
|
-
stop
|
132
|
+
async.stop
|
128
133
|
end
|
129
134
|
|
130
135
|
@running = wait :running
|
@@ -143,7 +148,7 @@ module Ringleader
|
|
143
148
|
end
|
144
149
|
end
|
145
150
|
|
146
|
-
#
|
151
|
+
# Internal: check if the app is already running outside ringleader
|
147
152
|
def already_running?
|
148
153
|
socket = TCPSocket.new config.host, config.app_port
|
149
154
|
socket.close
|
@@ -152,7 +157,7 @@ module Ringleader
|
|
152
157
|
false
|
153
158
|
end
|
154
159
|
|
155
|
-
#
|
160
|
+
# Internal: proxy output streams to the logger.
|
156
161
|
#
|
157
162
|
# Fire and forget, runs in its own thread.
|
158
163
|
def proxy_output(input)
|
@@ -163,7 +168,7 @@ module Ringleader
|
|
163
168
|
end
|
164
169
|
end
|
165
170
|
|
166
|
-
#
|
171
|
+
# Internal: execute a command in a clean environment (bundler)
|
167
172
|
def in_clean_environment(&block)
|
168
173
|
if Object.const_defined?(:Bundler)
|
169
174
|
::Bundler.with_clean_env(&block)
|
@@ -172,6 +177,37 @@ module Ringleader
|
|
172
177
|
end
|
173
178
|
end
|
174
179
|
|
180
|
+
# Internal: kill orphaned processes
|
181
|
+
def reap_orphans(child_pids)
|
182
|
+
child_pids.each do |pid|
|
183
|
+
next unless Sys::ProcTable.ps(pid)
|
184
|
+
error "child process #{pid} was orphaned, killing it"
|
185
|
+
begin
|
186
|
+
::Process.kill "KILL", pid
|
187
|
+
rescue Errno::ESRCH, Errno::EPERM
|
188
|
+
debug "could not kill #{pid}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Internal: returns all child pids of the given parent
|
194
|
+
def child_pids(parent_pid)
|
195
|
+
proc_table = Sys::ProcTable.ps
|
196
|
+
children_of parent_pid, proc_table
|
197
|
+
end
|
198
|
+
|
199
|
+
# Internal: find child pids given a parent pid and a proc table
|
200
|
+
def children_of(parent_pid, proc_table)
|
201
|
+
[].tap do |pids|
|
202
|
+
proc_table.each do |proc_record|
|
203
|
+
if proc_record.ppid == parent_pid
|
204
|
+
pids << proc_record.pid
|
205
|
+
pids.concat children_of proc_record.pid, proc_table
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
175
211
|
end
|
176
212
|
|
177
213
|
end
|
@@ -16,8 +16,8 @@ module Ringleader
|
|
16
16
|
debug "proxying to #{host}:#{port}"
|
17
17
|
@socket = TCPSocket.new(host, port)
|
18
18
|
|
19
|
-
proxy
|
20
|
-
proxy
|
19
|
+
async.proxy @socket, @upstream
|
20
|
+
async.proxy @upstream, @socket
|
21
21
|
|
22
22
|
rescue Errno::ECONNREFUSED
|
23
23
|
error "could not proxy to #{host}:#{port}"
|
data/lib/ringleader/version.rb
CHANGED
@@ -5,19 +5,19 @@ module Ringleader
|
|
5
5
|
|
6
6
|
def initialize(host, port, app)
|
7
7
|
@host, @port, @app = host, port, app
|
8
|
-
wait
|
8
|
+
async.wait
|
9
9
|
end
|
10
10
|
|
11
11
|
def wait
|
12
12
|
begin
|
13
|
-
|
13
|
+
TCPSocket.new @host, @port
|
14
14
|
rescue Errno::ECONNREFUSED
|
15
15
|
sleep 0.5
|
16
16
|
debug "#{@host}:#{@port} not open yet"
|
17
17
|
retry
|
18
18
|
end
|
19
19
|
debug "#{@host}:#{@port} open"
|
20
|
-
@app.port_opened
|
20
|
+
@app.async.port_opened
|
21
21
|
terminate
|
22
22
|
end
|
23
23
|
end
|
data/ringleader.gemspec
CHANGED
@@ -16,12 +16,13 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.version = Ringleader::VERSION
|
17
17
|
gem.required_ruby_version = "> 1.9.3"
|
18
18
|
|
19
|
-
gem.add_dependency "celluloid", "~> 0.
|
20
|
-
gem.add_dependency "celluloid-io", "~> 0.
|
19
|
+
gem.add_dependency "celluloid", "~> 0.12.4"
|
20
|
+
gem.add_dependency "celluloid-io", "~> 0.12.0"
|
21
21
|
gem.add_dependency "reel", "~> 0.1.0"
|
22
22
|
gem.add_dependency "trollop", "~> 1.16.2"
|
23
23
|
gem.add_dependency "rainbow", "~> 1.1.4"
|
24
24
|
gem.add_dependency "color", "~> 1.4.1"
|
25
|
+
gem.add_dependency "sys-proctable", "~> 0.9.2"
|
25
26
|
|
26
27
|
gem.add_development_dependency "rake"
|
27
28
|
gem.add_development_dependency "rspec", "~> 2.11.0"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ringleader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: celluloid
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 0.
|
21
|
+
version: 0.12.4
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.
|
29
|
+
version: 0.12.4
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: celluloid-io
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
requirements:
|
35
35
|
- - ~>
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version: 0.
|
37
|
+
version: 0.12.0
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,7 +42,7 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version: 0.
|
45
|
+
version: 0.12.0
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: reel
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -107,6 +107,22 @@ dependencies:
|
|
107
107
|
- - ~>
|
108
108
|
- !ruby/object:Gem::Version
|
109
109
|
version: 1.4.1
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: sys-proctable
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.9.2
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 0.9.2
|
110
126
|
- !ruby/object:Gem::Dependency
|
111
127
|
name: rake
|
112
128
|
requirement: !ruby/object:Gem::Requirement
|
@@ -190,6 +206,7 @@ files:
|
|
190
206
|
- assets/underscore-min.js
|
191
207
|
- bin/ringleader
|
192
208
|
- dev_scripts/Procfile
|
209
|
+
- dev_scripts/bad_parent.rb
|
193
210
|
- dev_scripts/echo_server.rb
|
194
211
|
- dev_scripts/many.yml
|
195
212
|
- dev_scripts/signaling.rb
|
@@ -244,10 +261,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
244
261
|
version: '0'
|
245
262
|
segments:
|
246
263
|
- 0
|
247
|
-
hash:
|
264
|
+
hash: -3588672138086956428
|
248
265
|
requirements: []
|
249
266
|
rubyforge_project:
|
250
|
-
rubygems_version: 1.8.
|
267
|
+
rubygems_version: 1.8.23
|
251
268
|
signing_key:
|
252
269
|
specification_version: 3
|
253
270
|
summary: Proxy TCP connections to an on-demand pool of configured applications
|