procodile 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/procodile +1 -1
- data/lib/procodile/cli.rb +139 -88
- data/lib/procodile/config.rb +17 -5
- data/lib/procodile/control_server.rb +7 -0
- data/lib/procodile/control_session.rb +3 -4
- data/lib/procodile/instance.rb +102 -61
- data/lib/procodile/process.rb +61 -3
- data/lib/procodile/status_cli_output.rb +6 -7
- data/lib/procodile/supervisor.rb +102 -73
- data/lib/procodile/tcp_proxy.rb +112 -0
- data/lib/procodile/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6afcf104b9bc4268bc21c634075b7cd4c8ef8d33
|
4
|
+
data.tar.gz: a46fd5857741a36d7d2584a4d4cea4ed07c5dbfa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85ea38210c9cf42dde381b7ed2a8966578047356cfdd6deed42942ea49f6880a8fb32317b00d98586054925ab7d8acbcc716577db27b1a8796aaa506dd195a7a
|
7
|
+
data.tar.gz: d43970fcd524920fae6d7980546de10c9f0980354d8e80cf33d2569d8d90253ce1cafd7b85d98db865aac982aaf9ca61123492d0f774f798d7ad0d16024cc3fb
|
data/bin/procodile
CHANGED
data/lib/procodile/cli.rb
CHANGED
@@ -45,6 +45,12 @@ module Procodile
|
|
45
45
|
# Help
|
46
46
|
#
|
47
47
|
|
48
|
+
command def proxy
|
49
|
+
require 'procodile/tcp_proxy'
|
50
|
+
p = Procodile::TCPProxy.new(Supervisor.new(@config, {}))
|
51
|
+
p.run
|
52
|
+
end
|
53
|
+
|
48
54
|
desc "Shows this help output"
|
49
55
|
command def help
|
50
56
|
puts "\e[45;37mWelcome to Procodile v#{Procodile::VERSION}\e[0m"
|
@@ -71,6 +77,18 @@ module Procodile
|
|
71
77
|
cli.options[:processes] = processes
|
72
78
|
end
|
73
79
|
|
80
|
+
opts.on("-t", "--tag TAGNAME", "Tag all started processes with the given tag") do |tag|
|
81
|
+
cli.options[:tag] = tag
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on("--no-supervisor", "Do not start a supervisor if its not running") do
|
85
|
+
cli.options[:start_supervisor] = false
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on("--no-processes", "Do not start any processes (only applicable when supervisor is stopped)") do
|
89
|
+
cli.options[:start_processes] = false
|
90
|
+
end
|
91
|
+
|
74
92
|
opts.on("-f", "--foreground", "Run the supervisor in the foreground") do
|
75
93
|
cli.options[:foreground] = true
|
76
94
|
end
|
@@ -79,61 +97,65 @@ module Procodile
|
|
79
97
|
cli.options[:clean] = true
|
80
98
|
end
|
81
99
|
|
82
|
-
opts.on("-
|
83
|
-
cli.options[:
|
100
|
+
opts.on("--no-respawn", "Disable respawning for all processes") do
|
101
|
+
cli.options[:respawn] = false
|
84
102
|
end
|
85
103
|
|
86
104
|
opts.on("--stop-when-none", "Stop the supervisor when all processes are stopped") do
|
87
105
|
cli.options[:stop_when_none] = true
|
88
106
|
end
|
89
107
|
|
108
|
+
opts.on("-x", "--proxy", "Enables the Procodile proxy service") do
|
109
|
+
cli.options[:proxy] = true
|
110
|
+
end
|
111
|
+
|
90
112
|
opts.on("-d", "--dev", "Run in development mode") do
|
91
113
|
cli.options[:development] = true
|
92
|
-
cli.options[:
|
114
|
+
cli.options[:respawn] = false
|
93
115
|
cli.options[:foreground] = true
|
94
116
|
cli.options[:stop_when_none] = true
|
117
|
+
cli.options[:proxy] = true
|
95
118
|
end
|
96
119
|
end
|
97
120
|
command def start
|
98
|
-
if
|
99
|
-
|
100
|
-
|
101
|
-
raise Error, "No processes were started. The type you entered might already be running or isn't defined."
|
102
|
-
else
|
103
|
-
instances.each do |instance|
|
104
|
-
puts "Started #{instance['description']} (PID: #{instance['pid']})"
|
105
|
-
end
|
106
|
-
return
|
121
|
+
if supervisor_running?
|
122
|
+
if @options[:foreground]
|
123
|
+
raise Error, "Cannot be started in the foreground because supervisor already running"
|
107
124
|
end
|
108
|
-
end
|
109
125
|
|
110
|
-
|
111
|
-
|
112
|
-
|
126
|
+
if @options.has_key?(:respawn)
|
127
|
+
raise Error, "Cannot disable respawning because supervisor is already running"
|
128
|
+
end
|
113
129
|
|
114
|
-
|
130
|
+
if @options[:stop_when_none]
|
131
|
+
raise Error, "Cannot stop supervisor when none running because supervisor is already running"
|
132
|
+
end
|
115
133
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
puts "Removed all old pid & sock files"
|
120
|
-
end
|
134
|
+
if @options[:proxy]
|
135
|
+
raise Error, "Cannot enable the proxy when the supervisor is running"
|
136
|
+
end
|
121
137
|
|
122
|
-
|
123
|
-
|
124
|
-
|
138
|
+
instances = ControlClient.run(@config.sock_path, 'start_processes', :processes => process_names_from_cli_option, :tag => @options[:tag])
|
139
|
+
if instances.empty?
|
140
|
+
puts "No processes to start."
|
141
|
+
else
|
142
|
+
instances.each do |instance|
|
143
|
+
puts "Started".color(32) + " #{instance['description']} (PID: #{instance['pid']})"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
return
|
125
147
|
else
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
148
|
+
# The supervisor isn't actually running. We need to start it before processes can be
|
149
|
+
# begin being processed
|
150
|
+
if @options[:start_supervisor] == false
|
151
|
+
raise Error, "Supervisor is not running and cannot be started because --no-supervisor is set"
|
152
|
+
else
|
153
|
+
start_supervisor do |supervisor|
|
154
|
+
unless @options[:start_processes] == false
|
155
|
+
supervisor.start_processes(process_names_from_cli_option, :tag => @options[:tag])
|
156
|
+
end
|
157
|
+
end
|
133
158
|
end
|
134
|
-
::Process.detach(pid)
|
135
|
-
File.open(pid_path, 'w') { |f| f.write(pid) }
|
136
|
-
puts "Started #{@config.app_name} supervisor with PID #{pid}"
|
137
159
|
end
|
138
160
|
end
|
139
161
|
|
@@ -147,23 +169,27 @@ module Procodile
|
|
147
169
|
cli.options[:processes] = processes
|
148
170
|
end
|
149
171
|
|
150
|
-
opts.on("-s", "--stop-supervisor", "Stop the ") do
|
172
|
+
opts.on("-s", "--stop-supervisor", "Stop the supervisor process when all processes are stopped") do
|
151
173
|
cli.options[:stop_supervisor] = true
|
152
174
|
end
|
153
175
|
end
|
154
176
|
command def stop
|
155
|
-
if
|
177
|
+
if supervisor_running?
|
156
178
|
options = {}
|
157
179
|
instances = ControlClient.run(@config.sock_path, 'stop', :processes => process_names_from_cli_option, :stop_supervisor => @options[:stop_supervisor])
|
158
180
|
if instances.empty?
|
159
|
-
puts "
|
181
|
+
puts "No processes were stopped."
|
160
182
|
else
|
161
183
|
instances.each do |instance|
|
162
|
-
puts "
|
184
|
+
puts "Stopped".color(31) + " #{instance['description']} (PID: #{instance['pid']})"
|
163
185
|
end
|
164
186
|
end
|
187
|
+
|
188
|
+
if @options[:stop_supervisor]
|
189
|
+
puts "Supervisor will be stopped when processes are stopped."
|
190
|
+
end
|
165
191
|
else
|
166
|
-
raise Error, "
|
192
|
+
raise Error, "Procodile supervisor isn't running"
|
167
193
|
end
|
168
194
|
end
|
169
195
|
|
@@ -176,34 +202,34 @@ module Procodile
|
|
176
202
|
opts.on("-p", "--processes a,b,c", "Only restart the listed processes or process types") do |processes|
|
177
203
|
cli.options[:processes] = processes
|
178
204
|
end
|
205
|
+
|
206
|
+
opts.on("-t", "--tag TAGNAME", "Tag all started processes with the given tag") do |tag|
|
207
|
+
cli.options[:tag] = tag
|
208
|
+
end
|
179
209
|
end
|
180
210
|
command def restart
|
181
|
-
if
|
182
|
-
|
183
|
-
instances = ControlClient.run(@config.sock_path, 'restart', :processes => process_names_from_cli_option)
|
211
|
+
if supervisor_running?
|
212
|
+
instances = ControlClient.run(@config.sock_path, 'restart', :processes => process_names_from_cli_option, :tag => @options[:tag])
|
184
213
|
if instances.empty?
|
185
214
|
puts "There are no processes to restart."
|
186
215
|
else
|
187
|
-
instances.each do |
|
188
|
-
|
216
|
+
instances.each do |old_instance, new_instance|
|
217
|
+
if old_instance && new_instance
|
218
|
+
if old_instance['description'] == new_instance['description']
|
219
|
+
puts "Restarted".color(35) + " #{old_instance['description']}"
|
220
|
+
else
|
221
|
+
puts "Restarted".color(35) + " #{old_instance['description']} -> #{new_instance['description']}"
|
222
|
+
end
|
223
|
+
elsif old_instance
|
224
|
+
puts "Stopped".color(31) + " #{old_instance['description']}"
|
225
|
+
elsif new_instance
|
226
|
+
puts "Started".color(32) + " #{new_instance['description']}"
|
227
|
+
end
|
228
|
+
$stdout.flush
|
189
229
|
end
|
190
230
|
end
|
191
231
|
else
|
192
|
-
raise Error, "
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
#
|
197
|
-
# Stop Supervisor
|
198
|
-
#
|
199
|
-
|
200
|
-
desc "Stop the supervisor without stopping processes"
|
201
|
-
command def stop_supervisor
|
202
|
-
if running?
|
203
|
-
::Process.kill('TERM', current_pid)
|
204
|
-
puts "Supervisor will be stopped in a moment."
|
205
|
-
else
|
206
|
-
raise Error, "#{@config.app_name} supervisor isn't running"
|
232
|
+
raise Error, "Procodile supervisor isn't running"
|
207
233
|
end
|
208
234
|
end
|
209
235
|
|
@@ -213,11 +239,11 @@ module Procodile
|
|
213
239
|
|
214
240
|
desc "Reload Procodile configuration"
|
215
241
|
command def reload
|
216
|
-
if
|
242
|
+
if supervisor_running?
|
217
243
|
ControlClient.run(@config.sock_path, 'reload_config')
|
218
|
-
puts "Reloaded
|
244
|
+
puts "Reloaded Procodile config"
|
219
245
|
else
|
220
|
-
raise Error, "
|
246
|
+
raise Error, "Procodile supervisor isn't running"
|
221
247
|
end
|
222
248
|
end
|
223
249
|
|
@@ -232,21 +258,21 @@ module Procodile
|
|
232
258
|
end
|
233
259
|
end
|
234
260
|
command def check_concurrency
|
235
|
-
if
|
261
|
+
if supervisor_running?
|
236
262
|
reply = ControlClient.run(@config.sock_path, 'check_concurrency', :reload => @options[:reload])
|
237
263
|
if reply['started'].empty? && reply['stopped'].empty?
|
238
|
-
puts "
|
264
|
+
puts "Processes are running as configured"
|
239
265
|
else
|
240
266
|
reply['started'].each do |instance|
|
241
|
-
puts "Started #{instance['description']}
|
267
|
+
puts "Started".color(32) + " #{instance['description']} (PID: #{instance['pid']})"
|
242
268
|
end
|
243
269
|
|
244
270
|
reply['stopped'].each do |instance|
|
245
|
-
puts "Stopped #{instance['description']}
|
271
|
+
puts "Stopped".color(31) + " #{instance['description']} (PID: #{instance['pid']})"
|
246
272
|
end
|
247
273
|
end
|
248
274
|
else
|
249
|
-
raise Error, "
|
275
|
+
raise Error, "Procodile supervisor isn't running"
|
250
276
|
end
|
251
277
|
end
|
252
278
|
|
@@ -261,7 +287,7 @@ module Procodile
|
|
261
287
|
end
|
262
288
|
end
|
263
289
|
command def status
|
264
|
-
if
|
290
|
+
if supervisor_running?
|
265
291
|
status = ControlClient.run(@config.sock_path, 'status')
|
266
292
|
if @options[:json]
|
267
293
|
puts status.to_json
|
@@ -270,7 +296,7 @@ module Procodile
|
|
270
296
|
StatusCLIOutput.new(status).print_all
|
271
297
|
end
|
272
298
|
else
|
273
|
-
|
299
|
+
raise Error, "Procodile supervisor isn't running"
|
274
300
|
end
|
275
301
|
end
|
276
302
|
|
@@ -294,18 +320,7 @@ module Procodile
|
|
294
320
|
|
295
321
|
private
|
296
322
|
|
297
|
-
def
|
298
|
-
|
299
|
-
socket = UNIXSocket.new(@config.sock_path)
|
300
|
-
# Get the connection confirmation
|
301
|
-
connection = socket.gets
|
302
|
-
return false unless connection == 'READY'
|
303
|
-
# Send a command.
|
304
|
-
ensure
|
305
|
-
socket.close rescue nil
|
306
|
-
end
|
307
|
-
|
308
|
-
def running?
|
323
|
+
def supervisor_running?
|
309
324
|
if pid = current_pid
|
310
325
|
::Process.getpgid(pid) ? true : false
|
311
326
|
else
|
@@ -334,17 +349,53 @@ module Procodile
|
|
334
349
|
if processes.empty?
|
335
350
|
raise Error, "No process names provided"
|
336
351
|
end
|
337
|
-
processes.each do |process|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
end
|
352
|
+
#processes.each do |process|
|
353
|
+
# process_name, _ = process.split('.', 2)
|
354
|
+
# unless @config.process_list.keys.include?(process_name.to_s)
|
355
|
+
# raise Error, "Process '#{process_name}' is not configured. You may need to reload your config."
|
356
|
+
# end
|
357
|
+
#end
|
343
358
|
processes
|
344
359
|
else
|
345
360
|
nil
|
346
361
|
end
|
347
362
|
end
|
348
363
|
|
364
|
+
def start_supervisor(&after_start)
|
365
|
+
run_options = {}
|
366
|
+
run_options[:respawn] = @options[:respawn]
|
367
|
+
run_options[:stop_when_none] = @options[:stop_when_none]
|
368
|
+
run_options[:proxy] = @options[:proxy]
|
369
|
+
|
370
|
+
processes = process_names_from_cli_option
|
371
|
+
|
372
|
+
if @options[:clean]
|
373
|
+
FileUtils.rm_rf(Dir[File.join(@config.pid_root, '*')])
|
374
|
+
puts "Emptied PID directory"
|
375
|
+
end
|
376
|
+
|
377
|
+
if !Dir[File.join(@config.pid_root, "*")].empty?
|
378
|
+
raise Error, "The PID directory (#{@config.pid_root}) is not empty. Cannot start unless things are clean."
|
379
|
+
end
|
380
|
+
|
381
|
+
$0="[procodile] #{@config.app_name} (#{@config.root})"
|
382
|
+
if @options[:foreground]
|
383
|
+
File.open(pid_path, 'w') { |f| f.write(::Process.pid) }
|
384
|
+
Supervisor.new(@config, run_options).start(&after_start)
|
385
|
+
else
|
386
|
+
FileUtils.rm_f(File.join(@config.pid_root, "*.pid"))
|
387
|
+
pid = fork do
|
388
|
+
STDOUT.reopen(@config.log_path, 'a')
|
389
|
+
STDOUT.sync = true
|
390
|
+
STDERR.reopen(@config.log_path, 'a')
|
391
|
+
STDERR.sync = true
|
392
|
+
Supervisor.new(@config, run_options).start(&after_start)
|
393
|
+
end
|
394
|
+
::Process.detach(pid)
|
395
|
+
File.open(pid_path, 'w') { |f| f.write(pid) }
|
396
|
+
puts "Started Procodile supervisor with PID #{pid}"
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
349
400
|
end
|
350
401
|
end
|
data/lib/procodile/config.rb
CHANGED
@@ -15,6 +15,10 @@ module Procodile
|
|
15
15
|
raise Error, "Procfile not found at #{procfile_path}"
|
16
16
|
end
|
17
17
|
FileUtils.mkdir_p(pid_root)
|
18
|
+
|
19
|
+
@processes = process_list.each_with_index.each_with_object({}) do |((name, command), index), hash|
|
20
|
+
hash[name] = create_process(name, command, COLORS[index.divmod(COLORS.size)[1]])
|
21
|
+
end
|
18
22
|
end
|
19
23
|
|
20
24
|
def reload
|
@@ -27,6 +31,7 @@ module Procodile
|
|
27
31
|
if @processes
|
28
32
|
process_list.each do |name, command|
|
29
33
|
if process = @processes[name]
|
34
|
+
process.removed = false
|
30
35
|
# This command is already in our list. Add it.
|
31
36
|
if process.command != command
|
32
37
|
process.command = command
|
@@ -38,6 +43,15 @@ module Procodile
|
|
38
43
|
@processes[name] = create_process(name, command, COLORS[@processes.size.divmod(COLORS.size)[1]])
|
39
44
|
end
|
40
45
|
end
|
46
|
+
|
47
|
+
removed_processes = @processes.keys - process_list.keys
|
48
|
+
removed_processes.each do |process_name|
|
49
|
+
if p = @processes[process_name]
|
50
|
+
p.removed = true
|
51
|
+
@processes.delete(process_name)
|
52
|
+
Procodile.log nil, 'system', "#{process_name} has been removed to the Procfile. It will be removed when it is stopped."
|
53
|
+
end
|
54
|
+
end
|
41
55
|
end
|
42
56
|
end
|
43
57
|
|
@@ -46,13 +60,11 @@ module Procodile
|
|
46
60
|
end
|
47
61
|
|
48
62
|
def processes
|
49
|
-
@processes ||=
|
50
|
-
hash[name] = create_process(name, command, COLORS[index.divmod(COLORS.size)[1]])
|
51
|
-
end
|
63
|
+
@processes ||= {}
|
52
64
|
end
|
53
65
|
|
54
66
|
def process_list
|
55
|
-
@process_list ||= YAML.load_file(procfile_path)
|
67
|
+
@process_list ||= YAML.load_file(procfile_path) || {}
|
56
68
|
end
|
57
69
|
|
58
70
|
def options
|
@@ -92,7 +104,7 @@ module Procodile
|
|
92
104
|
end
|
93
105
|
|
94
106
|
def sock_path
|
95
|
-
File.join(pid_root, '
|
107
|
+
File.join(pid_root, 'procodile.sock')
|
96
108
|
end
|
97
109
|
|
98
110
|
private
|
@@ -4,6 +4,13 @@ require 'procodile/control_session'
|
|
4
4
|
module Procodile
|
5
5
|
class ControlServer
|
6
6
|
|
7
|
+
def self.start(supervisor)
|
8
|
+
Thread.new do
|
9
|
+
socket = ControlServer.new(supervisor)
|
10
|
+
socket.listen
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
7
14
|
def initialize(supervisor)
|
8
15
|
@supervisor = supervisor
|
9
16
|
end
|
@@ -14,7 +14,6 @@ module Procodile
|
|
14
14
|
options = JSON.parse(options)
|
15
15
|
if self.class.instance_methods(false).include?(command.to_sym) && command != 'receive_data'
|
16
16
|
begin
|
17
|
-
Procodile.log nil, 'control', "Received #{command} command"
|
18
17
|
public_send(command, options)
|
19
18
|
rescue Procodile::Error => e
|
20
19
|
Procodile.log nil, 'control', "Error: #{e.message}".color(31)
|
@@ -26,7 +25,7 @@ module Procodile
|
|
26
25
|
end
|
27
26
|
|
28
27
|
def start_processes(options)
|
29
|
-
instances = @supervisor.start_processes(options['processes'])
|
28
|
+
instances = @supervisor.start_processes(options['processes'], :tag => options['tag'])
|
30
29
|
"200 " + instances.map(&:to_hash).to_json
|
31
30
|
end
|
32
31
|
|
@@ -36,8 +35,8 @@ module Procodile
|
|
36
35
|
end
|
37
36
|
|
38
37
|
def restart(options)
|
39
|
-
instances = @supervisor.restart(:processes => options['processes'])
|
40
|
-
"200 " + instances.map
|
38
|
+
instances = @supervisor.restart(:processes => options['processes'], :tag => options['tag'])
|
39
|
+
"200 " + instances.map { |a| a.map { |i| i ? i.to_hash : nil } }.to_json
|
41
40
|
end
|
42
41
|
|
43
42
|
def reload_config(options)
|
data/lib/procodile/instance.rb
CHANGED
@@ -6,13 +6,14 @@ module Procodile
|
|
6
6
|
attr_accessor :pid
|
7
7
|
attr_reader :id
|
8
8
|
attr_accessor :process
|
9
|
-
|
9
|
+
attr_reader :tag
|
10
|
+
attr_reader :port
|
10
11
|
|
11
|
-
def initialize(process, id)
|
12
|
+
def initialize(supervisor, process, id)
|
13
|
+
@supervisor = supervisor
|
12
14
|
@process = process
|
13
15
|
@id = id
|
14
16
|
@respawns = 0
|
15
|
-
@respawnable = true
|
16
17
|
@started_at = nil
|
17
18
|
end
|
18
19
|
|
@@ -23,21 +24,33 @@ module Procodile
|
|
23
24
|
"#{@process.name}.#{@id}"
|
24
25
|
end
|
25
26
|
|
27
|
+
#
|
28
|
+
# Return the status of this instance
|
29
|
+
#
|
30
|
+
def status
|
31
|
+
if stopped?
|
32
|
+
'Stopped'
|
33
|
+
elsif stopping?
|
34
|
+
'Stopping'
|
35
|
+
elsif running?
|
36
|
+
'Running'
|
37
|
+
elsif failed?
|
38
|
+
'Failed'
|
39
|
+
else
|
40
|
+
'Unknown'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
26
44
|
#
|
27
45
|
# Return an array of environment variables that should be set
|
28
46
|
#
|
29
47
|
def environment_variables
|
30
|
-
@process.
|
48
|
+
vars = @process.environment_variables.merge({
|
31
49
|
'PID_FILE' => self.pid_file_path,
|
32
50
|
'APP_ROOT' => @process.config.root
|
33
51
|
})
|
34
|
-
|
35
|
-
|
36
|
-
#
|
37
|
-
# Should this instance still be monitored by the supervisor?
|
38
|
-
#
|
39
|
-
def unmonitored?
|
40
|
-
@monitored == false
|
52
|
+
vars['PORT'] = @port.to_s if @port
|
53
|
+
vars
|
41
54
|
end
|
42
55
|
|
43
56
|
#
|
@@ -62,9 +75,9 @@ module Procodile
|
|
62
75
|
#
|
63
76
|
# Is this process running? Pass an option to check the given PID instead of the instance
|
64
77
|
#
|
65
|
-
def running?
|
66
|
-
if
|
67
|
-
::Process.getpgid(
|
78
|
+
def running?
|
79
|
+
if @pid
|
80
|
+
::Process.getpgid(@pid) ? true : false
|
68
81
|
else
|
69
82
|
false
|
70
83
|
end
|
@@ -75,38 +88,39 @@ module Procodile
|
|
75
88
|
#
|
76
89
|
# Start a new instance of this process
|
77
90
|
#
|
78
|
-
def start
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
91
|
+
def start
|
92
|
+
if stopping?
|
93
|
+
Procodile.log(@process.log_color, description, "Process is stopped/stopping therefore cannot be started again.")
|
94
|
+
return false
|
95
|
+
end
|
96
|
+
|
97
|
+
update_pid
|
98
|
+
if running?
|
85
99
|
Procodile.log(@process.log_color, description, "Already running with PID #{@pid}")
|
86
|
-
@started_at = File.mtime(self.pid_file_path)
|
87
100
|
nil
|
88
101
|
else
|
102
|
+
|
103
|
+
if @process.proxy? && @supervisor.tcp_proxy
|
104
|
+
allocate_port
|
105
|
+
end
|
106
|
+
|
89
107
|
if self.process.log_path
|
90
108
|
log_destination = File.open(self.process.log_path, 'a')
|
91
|
-
|
109
|
+
io = nil
|
92
110
|
else
|
93
111
|
reader, writer = IO.pipe
|
94
112
|
log_destination = writer
|
95
|
-
|
113
|
+
io = reader
|
96
114
|
end
|
97
|
-
|
115
|
+
@tag = @supervisor.tag.dup if @supervisor.tag
|
98
116
|
Dir.chdir(@process.config.root)
|
99
117
|
@pid = ::Process.spawn(environment_variables, @process.command, :out => log_destination, :err => log_destination, :pgroup => true)
|
100
|
-
|
118
|
+
log_destination.close
|
101
119
|
File.open(pid_file_path, 'w') { |f| f.write(@pid.to_s + "\n") }
|
120
|
+
@supervisor.add_instance(self, io)
|
102
121
|
::Process.detach(@pid)
|
122
|
+
Procodile.log(@process.log_color, description, "Started with PID #{@pid}" + (@tag ? " (tagged with #{@tag})" : ''))
|
103
123
|
@started_at = Time.now
|
104
|
-
|
105
|
-
if block_given?
|
106
|
-
block.call(self, return_value)
|
107
|
-
end
|
108
|
-
|
109
|
-
return_value
|
110
124
|
end
|
111
125
|
end
|
112
126
|
|
@@ -114,7 +128,21 @@ module Procodile
|
|
114
128
|
# Is this instance supposed to be stopping/be stopped?
|
115
129
|
#
|
116
130
|
def stopping?
|
117
|
-
@stopping
|
131
|
+
@stopping ? true : false
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Is this stopped?
|
136
|
+
#
|
137
|
+
def stopped?
|
138
|
+
@stopped || false
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Has this failed?
|
143
|
+
#
|
144
|
+
def failed?
|
145
|
+
@failed ? true : false
|
118
146
|
end
|
119
147
|
|
120
148
|
#
|
@@ -122,7 +150,7 @@ module Procodile
|
|
122
150
|
# tells us that we want it to be stopped.
|
123
151
|
#
|
124
152
|
def stop
|
125
|
-
@stopping =
|
153
|
+
@stopping = Time.now
|
126
154
|
update_pid
|
127
155
|
if self.running?
|
128
156
|
Procodile.log(@process.log_color, description, "Sending #{@process.term_signal} to #{@pid}")
|
@@ -138,8 +166,8 @@ module Procodile
|
|
138
166
|
#
|
139
167
|
def on_stop
|
140
168
|
@started_at = nil
|
169
|
+
@stopped = true
|
141
170
|
tidy
|
142
|
-
unmonitor
|
143
171
|
end
|
144
172
|
|
145
173
|
#
|
@@ -153,9 +181,8 @@ module Procodile
|
|
153
181
|
#
|
154
182
|
# Retarts the process using the appropriate method from the process configuraiton
|
155
183
|
#
|
156
|
-
def restart
|
184
|
+
def restart
|
157
185
|
Procodile.log(@process.log_color, description, "Restarting using #{@process.restart_mode} mode")
|
158
|
-
@restarting = true
|
159
186
|
update_pid
|
160
187
|
case @process.restart_mode
|
161
188
|
when 'usr1', 'usr2'
|
@@ -164,23 +191,24 @@ module Procodile
|
|
164
191
|
Procodile.log(@process.log_color, description, "Sent #{@process.restart_mode.upcase} signal to process #{@pid}")
|
165
192
|
else
|
166
193
|
Procodile.log(@process.log_color, description, "Process not running already. Starting it.")
|
167
|
-
|
194
|
+
on_stop
|
195
|
+
@process.create_instance(@supervisor).start
|
168
196
|
end
|
197
|
+
self
|
169
198
|
when 'start-term'
|
170
|
-
|
171
|
-
start
|
172
|
-
|
173
|
-
|
199
|
+
new_instance = @process.create_instance(@supervisor)
|
200
|
+
new_instance.start
|
201
|
+
stop
|
202
|
+
new_instance
|
174
203
|
when 'term-start'
|
175
204
|
stop
|
205
|
+
new_instance = @process.create_instance(@supervisor)
|
176
206
|
Thread.new do
|
177
|
-
# Wait for this process to stop and when it has, run it.
|
178
207
|
sleep 0.5 while running?
|
179
|
-
start
|
208
|
+
new_instance.start
|
180
209
|
end
|
210
|
+
new_instance
|
181
211
|
end
|
182
|
-
ensure
|
183
|
-
@restarting = false
|
184
212
|
end
|
185
213
|
|
186
214
|
#
|
@@ -190,6 +218,7 @@ module Procodile
|
|
190
218
|
pid_from_file = self.pid_from_file
|
191
219
|
if pid_from_file && pid_from_file != @pid
|
192
220
|
@pid = pid_from_file
|
221
|
+
@started_at = File.mtime(self.pid_file_path)
|
193
222
|
Procodile.log(@process.log_color, description, "PID file changed. Updated pid to #{@pid}")
|
194
223
|
true
|
195
224
|
else
|
@@ -200,10 +229,8 @@ module Procodile
|
|
200
229
|
#
|
201
230
|
# Check the status of this process and handle as appropriate.
|
202
231
|
#
|
203
|
-
def check
|
204
|
-
|
205
|
-
return if @restarting
|
206
|
-
return if unmonitored?
|
232
|
+
def check(options = {})
|
233
|
+
return if failed?
|
207
234
|
|
208
235
|
if self.running?
|
209
236
|
# Everything is OK. The process is running.
|
@@ -213,7 +240,7 @@ module Procodile
|
|
213
240
|
# the file in case the process has changed itself.
|
214
241
|
return check if update_pid
|
215
242
|
|
216
|
-
if @
|
243
|
+
if @supervisor.allow_respawning?
|
217
244
|
if can_respawn?
|
218
245
|
Procodile.log(@process.log_color, description, "Process has stopped. Respawning...")
|
219
246
|
start
|
@@ -221,24 +248,17 @@ module Procodile
|
|
221
248
|
elsif respawns >= @process.max_respawns
|
222
249
|
Procodile.log(@process.log_color, description, "\e[41;37mWarning:\e[0m\e[31m this process has been respawned #{respawns} times and keeps dying.\e[0m")
|
223
250
|
Procodile.log(@process.log_color, description, "It will not be respawned automatically any longer and will no longer be managed.".color(31))
|
251
|
+
@failed = Time.now
|
224
252
|
tidy
|
225
|
-
unmonitor
|
226
253
|
end
|
227
254
|
else
|
228
255
|
Procodile.log(@process.log_color, description, "Process has stopped. Respawning not available.")
|
256
|
+
@failed = Time.now
|
229
257
|
tidy
|
230
|
-
unmonitor
|
231
258
|
end
|
232
259
|
end
|
233
260
|
end
|
234
261
|
|
235
|
-
#
|
236
|
-
# Mark this process as dead and tidy up after it
|
237
|
-
#
|
238
|
-
def unmonitor
|
239
|
-
@monitored = false
|
240
|
-
end
|
241
|
-
|
242
262
|
#
|
243
263
|
# Can this process be respawned if needed?
|
244
264
|
#
|
@@ -277,10 +297,31 @@ module Procodile
|
|
277
297
|
:description => self.description,
|
278
298
|
:pid => self.pid,
|
279
299
|
:respawns => self.respawns,
|
300
|
+
:status => self.status,
|
280
301
|
:running => self.running?,
|
281
|
-
:started_at => @started_at ? @started_at.to_i : nil
|
302
|
+
:started_at => @started_at ? @started_at.to_i : nil,
|
303
|
+
:tag => self.tag,
|
304
|
+
:port => @port
|
282
305
|
}
|
283
306
|
end
|
284
307
|
|
308
|
+
|
309
|
+
#
|
310
|
+
# Find a port number for this instance to listen on. We just check that nothing is already listening on it.
|
311
|
+
# The process is expected to take it straight away if it wants it.
|
312
|
+
#
|
313
|
+
def allocate_port
|
314
|
+
until @port
|
315
|
+
possible_port = rand(10000) + 20000
|
316
|
+
begin
|
317
|
+
server = TCPServer.new('127.0.0.1', possible_port)
|
318
|
+
server.close
|
319
|
+
return @port = possible_port
|
320
|
+
rescue
|
321
|
+
# Nah.
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
285
326
|
end
|
286
327
|
end
|
data/lib/procodile/process.rb
CHANGED
@@ -3,11 +3,14 @@ require 'procodile/instance'
|
|
3
3
|
module Procodile
|
4
4
|
class Process
|
5
5
|
|
6
|
+
MUTEX = Mutex.new
|
7
|
+
|
6
8
|
attr_reader :config
|
7
9
|
attr_reader :name
|
8
10
|
attr_accessor :command
|
9
11
|
attr_accessor :options
|
10
12
|
attr_accessor :log_color
|
13
|
+
attr_accessor :removed
|
11
14
|
|
12
15
|
def initialize(config, name, command, options = {})
|
13
16
|
@config = config
|
@@ -15,6 +18,29 @@ module Procodile
|
|
15
18
|
@command = command
|
16
19
|
@options = options
|
17
20
|
@log_color = 0
|
21
|
+
@instance_index = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Increase the instance index and return
|
26
|
+
#
|
27
|
+
def get_instance_id
|
28
|
+
MUTEX.synchronize do
|
29
|
+
@instance_index = 0 if @instance_index == 10000
|
30
|
+
@instance_index += 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Return all environment variables for this process
|
36
|
+
#
|
37
|
+
def environment_variables
|
38
|
+
global_variables = @config.environment_variables
|
39
|
+
process_vars = @config.process_options[@name] ? @config.process_options[@name]['env'] || {} : {}
|
40
|
+
process_local_vars = @config.local_process_options[@name] ? @config.local_process_options[@name]['env'] || {} : {}
|
41
|
+
global_variables.merge(process_vars.merge(process_local_vars)).each_with_object({}) do |(key, value), hash|
|
42
|
+
hash[key.to_s] = value.to_s
|
43
|
+
end
|
18
44
|
end
|
19
45
|
|
20
46
|
#
|
@@ -65,11 +91,40 @@ module Procodile
|
|
65
91
|
@options['restart_mode'] || 'term-start'
|
66
92
|
end
|
67
93
|
|
94
|
+
#
|
95
|
+
# Is this process enabled for proxying?
|
96
|
+
#
|
97
|
+
def proxy?
|
98
|
+
@options.has_key?('proxy_port')
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Return the port for the proxy to listen on for this process type
|
103
|
+
#
|
104
|
+
def proxy_port
|
105
|
+
proxy? ? @options['proxy_port'].to_i : nil
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# Return the port for the proxy to listen on for this process type
|
110
|
+
#
|
111
|
+
def proxy_address
|
112
|
+
proxy? ? @options['proxy_address'] || '127.0.0.1' : nil
|
113
|
+
end
|
114
|
+
|
115
|
+
|
68
116
|
#
|
69
117
|
# Generate an array of new instances for this process (based on its quantity)
|
70
118
|
#
|
71
|
-
def generate_instances(quantity = self.quantity
|
72
|
-
quantity.times.map { |i|
|
119
|
+
def generate_instances(supervisor, quantity = self.quantity)
|
120
|
+
quantity.times.map { |i| create_instance(supervisor) }
|
121
|
+
end
|
122
|
+
|
123
|
+
#
|
124
|
+
# Create a new instance
|
125
|
+
#
|
126
|
+
def create_instance(supervisor)
|
127
|
+
Instance.new(supervisor, self, get_instance_id)
|
73
128
|
end
|
74
129
|
|
75
130
|
#
|
@@ -84,7 +139,10 @@ module Procodile
|
|
84
139
|
:respawn_window => self.respawn_window,
|
85
140
|
:command => self.command,
|
86
141
|
:restart_mode => self.restart_mode,
|
87
|
-
:log_path => self.log_path
|
142
|
+
:log_path => self.log_path,
|
143
|
+
:removed => self.removed ? true : false,
|
144
|
+
:proxy_port => proxy_port,
|
145
|
+
:proxy_address => proxy_address
|
88
146
|
}
|
89
147
|
end
|
90
148
|
|
@@ -41,20 +41,19 @@ module Procodile
|
|
41
41
|
puts "||".color(process['log_color']) + " Respawning " + "#{process['max_respawns']} every #{process['respawn_window']} seconds"
|
42
42
|
puts "||".color(process['log_color']) + " Restart mode " + process['restart_mode']
|
43
43
|
puts "||".color(process['log_color']) + " Log path " + (process['log_path'] || "none specified")
|
44
|
+
puts "||".color(process['log_color']) + " Address/Port " + (process['proxy_port'] ? "#{process['proxy_address']}:#{process['proxy_port']}" : "none")
|
44
45
|
instances = @status['instances'][process['name']]
|
45
46
|
if instances.empty?
|
46
47
|
puts "||".color(process['log_color']) + " No processes running."
|
47
48
|
else
|
48
49
|
instances.each do |instance|
|
49
50
|
print "|| => ".color(process['log_color']) + instance['description'].to_s.ljust(17, ' ').color(process['log_color'])
|
50
|
-
|
51
|
-
print 'Running'.color("32")
|
52
|
-
else
|
53
|
-
print 'Stopped'.color("31")
|
54
|
-
end
|
51
|
+
print instance['status'].ljust(10, ' ')
|
55
52
|
print " " + formatted_timestamp(instance['started_at']).ljust(10, ' ')
|
56
|
-
print "
|
57
|
-
print "
|
53
|
+
print " " + instance['pid'].to_s.ljust(6, ' ')
|
54
|
+
print " " + instance['respawns'].to_s.ljust(4, ' ')
|
55
|
+
print " " + (instance['port'] || "-").to_s.ljust(6, ' ')
|
56
|
+
print " " + (instance['tag'] || "-").to_s
|
58
57
|
puts
|
59
58
|
end
|
60
59
|
end
|
data/lib/procodile/supervisor.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'procodile/control_server'
|
2
|
+
require 'procodile/tcp_proxy'
|
2
3
|
|
3
4
|
module Procodile
|
4
5
|
class Supervisor
|
@@ -6,6 +7,8 @@ module Procodile
|
|
6
7
|
attr_reader :config
|
7
8
|
attr_reader :processes
|
8
9
|
attr_reader :started_at
|
10
|
+
attr_reader :tag
|
11
|
+
attr_reader :tcp_proxy
|
9
12
|
|
10
13
|
def initialize(config, run_options = {})
|
11
14
|
@config = config
|
@@ -20,28 +23,34 @@ module Procodile
|
|
20
23
|
@signal_handler.register('HUP') { reload_config }
|
21
24
|
end
|
22
25
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
def allow_respawning?
|
27
|
+
@run_options[:respawn] != false
|
28
|
+
end
|
29
|
+
|
30
|
+
def start(&after_start)
|
31
|
+
Procodile.log nil, "system", "Procodile supervisor started with PID #{::Process.pid}"
|
32
|
+
if @run_options[:respawn] == false
|
33
|
+
Procodile.log nil, "system", "Automatic respawning is disabled"
|
27
34
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
35
|
+
ControlServer.start(self)
|
36
|
+
if @run_options[:proxy]
|
37
|
+
Procodile.log nil, "system", "Proxy is enabled"
|
38
|
+
@tcp_proxy = TCPProxy.start(self)
|
31
39
|
end
|
32
|
-
start_processes(options[:processes])
|
33
40
|
watch_for_output
|
34
41
|
@started_at = Time.now
|
42
|
+
after_start.call(self) if block_given?
|
35
43
|
loop { supervise; sleep 3 }
|
36
44
|
end
|
37
45
|
|
38
|
-
def start_processes(types =
|
46
|
+
def start_processes(types = nil, options = {})
|
47
|
+
@tag = options[:tag]
|
39
48
|
reload_config
|
40
49
|
Array.new.tap do |instances_started|
|
41
50
|
@config.processes.each do |name, process|
|
42
|
-
next if types && !types.include?(name.to_s)
|
43
|
-
next if @processes[process] && !@processes[process].empty?
|
44
|
-
instances =
|
51
|
+
next if types && !types.include?(name.to_s) # Not a process we want
|
52
|
+
next if @processes[process] && !@processes[process].empty? # Process type already running
|
53
|
+
instances = process.generate_instances(self).each(&:start)
|
45
54
|
instances_started.push(*instances)
|
46
55
|
end
|
47
56
|
end
|
@@ -51,7 +60,7 @@ module Procodile
|
|
51
60
|
if options[:stop_supervisor]
|
52
61
|
@run_options[:stop_when_none] = true
|
53
62
|
end
|
54
|
-
|
63
|
+
reload_config
|
55
64
|
Array.new.tap do |instances_stopped|
|
56
65
|
if options[:processes].nil?
|
57
66
|
Procodile.log nil, "system", "Stopping all #{@config.app_name} processes"
|
@@ -73,53 +82,48 @@ module Procodile
|
|
73
82
|
end
|
74
83
|
|
75
84
|
def restart(options = {})
|
85
|
+
@tag = options[:tag]
|
76
86
|
reload_config
|
77
87
|
Array.new.tap do |instances_restarted|
|
78
88
|
if options[:processes].nil?
|
79
89
|
Procodile.log nil, "system", "Restarting all #{@config.app_name} processes"
|
80
|
-
@processes.
|
81
|
-
instances.each do |instance|
|
82
|
-
instance.restart { |_, io| add_reader(instance, io) }
|
83
|
-
instances_restarted << instance
|
84
|
-
end
|
85
|
-
end
|
86
|
-
instances_restarted.push(*check_instance_quantities[:started])
|
90
|
+
instances = @processes.values.flatten
|
87
91
|
else
|
88
92
|
instances = process_names_to_instances(options[:processes])
|
89
93
|
Procodile.log nil, "system", "Restarting #{instances.size} process(es)"
|
90
|
-
instances.each do |instance|
|
91
|
-
instance.restart { |_, io| add_reader(instance, io) }
|
92
|
-
instances_restarted << instance
|
93
|
-
end
|
94
|
-
instances_restarted.push(*check_instance_quantities(options[:processes])[:started])
|
95
94
|
end
|
95
|
+
|
96
|
+
# Stop any processes that are no longer wanted at this point
|
97
|
+
instances_restarted.push(*check_instance_quantities(:stopped, options[:processes])[:stopped].map { |i| [i, nil]})
|
98
|
+
|
99
|
+
instances.each do |instance|
|
100
|
+
next if instance.stopping?
|
101
|
+
new_instance = instance.restart
|
102
|
+
instances_restarted << [instance, new_instance]
|
103
|
+
end
|
104
|
+
|
105
|
+
# Start any processes that are needed at this point
|
106
|
+
instances_restarted.push(*check_instance_quantities(:started, options[:processes])[:started].map { |i| [nil, i]})
|
96
107
|
end
|
97
108
|
end
|
98
109
|
|
99
110
|
def stop_supervisor
|
100
|
-
Procodile.log nil, 'system', "Stopping
|
101
|
-
FileUtils.rm_f(File.join(@config.pid_root, '
|
111
|
+
Procodile.log nil, 'system', "Stopping Procodile supervisor"
|
112
|
+
FileUtils.rm_f(File.join(@config.pid_root, 'procodile.pid'))
|
102
113
|
::Process.exit 0
|
103
114
|
end
|
104
115
|
|
105
116
|
def supervise
|
106
|
-
#
|
107
|
-
# be removed from the list.
|
108
|
-
remove_unmonitored_instances
|
109
|
-
|
110
|
-
# Remove processes that have been stopped
|
117
|
+
# Tell instances that have been stopped that they have been stopped
|
111
118
|
remove_stopped_instances
|
112
119
|
|
120
|
+
# Remove removed processes
|
121
|
+
remove_removed_processes
|
122
|
+
|
113
123
|
# Check all instances that we manage and let them do their things.
|
114
124
|
@processes.each do |_, instances|
|
115
125
|
instances.each do |instance|
|
116
126
|
instance.check
|
117
|
-
if instance.unmonitored?
|
118
|
-
if @run_options[:brittle]
|
119
|
-
Procodile.log nil, "system", "Stopping everything because a process has died in brittle mode."
|
120
|
-
return stop
|
121
|
-
end
|
122
|
-
end
|
123
127
|
end
|
124
128
|
end
|
125
129
|
|
@@ -162,16 +166,31 @@ module Procodile
|
|
162
166
|
}
|
163
167
|
end
|
164
168
|
|
165
|
-
private
|
166
|
-
|
167
169
|
def add_reader(instance, io)
|
168
|
-
return unless io
|
169
170
|
@readers[io] = instance
|
170
171
|
@signal_handler.notice
|
171
172
|
end
|
172
173
|
|
174
|
+
def add_instance(instance, io = nil)
|
175
|
+
add_reader(instance, io) if io
|
176
|
+
@processes[instance.process] ||= []
|
177
|
+
unless @processes[instance.process].include?(instance)
|
178
|
+
@processes[instance.process] << instance
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def remove_instance(instance)
|
183
|
+
if @processes[instance.process]
|
184
|
+
@processes[instance.process].delete(instance)
|
185
|
+
@readers.delete(instance)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
173
191
|
def watch_for_output
|
174
192
|
Thread.new do
|
193
|
+
buffer = {}
|
175
194
|
loop do
|
176
195
|
io = IO.select([@signal_handler.pipe[:reader]] + @readers.keys, nil, nil, 30)
|
177
196
|
@signal_handler.handle
|
@@ -184,13 +203,19 @@ module Procodile
|
|
184
203
|
end
|
185
204
|
|
186
205
|
if reader.eof?
|
206
|
+
reader.close
|
207
|
+
buffer.delete(reader)
|
187
208
|
@readers.delete(reader)
|
188
209
|
else
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
210
|
+
buffer[reader] ||= ""
|
211
|
+
buffer[reader] << reader.read_nonblock(4096)
|
212
|
+
while buffer[reader].index("\n")
|
213
|
+
line, buffer[reader] = buffer[reader].split("\n", 2)
|
214
|
+
if instance = @readers[reader]
|
215
|
+
Procodile.log instance.process.log_color, instance.description, "=> ".color(instance.process.log_color) + line
|
216
|
+
else
|
217
|
+
Procodile.log nil, 'unknown', data
|
218
|
+
end
|
194
219
|
end
|
195
220
|
end
|
196
221
|
end
|
@@ -199,45 +224,36 @@ module Procodile
|
|
199
224
|
end
|
200
225
|
end
|
201
226
|
|
202
|
-
def check_instance_quantities(processes = nil)
|
227
|
+
def check_instance_quantities(type = :both, processes = nil)
|
203
228
|
{:started => [], :stopped => []}.tap do |status|
|
204
229
|
@processes.each do |process, instances|
|
205
230
|
next if processes && !processes.include?(process.name)
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
status[:started] = start_instances(process.generate_instances(quantity_needed, start_id))
|
231
|
+
active_instances = instances.select(&:running?)
|
232
|
+
|
233
|
+
if type == :both || type == :stopped
|
234
|
+
if active_instances.size > process.quantity
|
235
|
+
quantity_to_stop = active_instances.size - process.quantity
|
236
|
+
Procodile.log nil, "system", "Stopping #{quantity_to_stop} #{process.name} process(es)"
|
237
|
+
status[:stopped] = active_instances.last(quantity_to_stop).each(&:stop)
|
238
|
+
end
|
215
239
|
end
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
240
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
@processes[instance.process] << instance
|
228
|
-
end
|
229
|
-
end
|
241
|
+
if type == :both || type == :started
|
242
|
+
if active_instances.size < process.quantity
|
243
|
+
quantity_needed = process.quantity - active_instances.size
|
244
|
+
Procodile.log nil, "system", "Starting #{quantity_needed} more #{process.name} process(es)"
|
245
|
+
status[:started] = process.generate_instances(self, quantity_needed).each(&:start)
|
246
|
+
end
|
247
|
+
end
|
230
248
|
|
231
|
-
|
232
|
-
@processes.each do |_, instances|
|
233
|
-
instances.reject!(&:unmonitored?)
|
249
|
+
end
|
234
250
|
end
|
235
251
|
end
|
236
252
|
|
237
253
|
def remove_stopped_instances
|
238
254
|
@processes.each do |_, instances|
|
239
255
|
instances.reject! do |instance|
|
240
|
-
if
|
256
|
+
if instance.stopping? && !instance.running?
|
241
257
|
instance.on_stop
|
242
258
|
true
|
243
259
|
else
|
@@ -247,6 +263,19 @@ module Procodile
|
|
247
263
|
end
|
248
264
|
end
|
249
265
|
|
266
|
+
def remove_removed_processes
|
267
|
+
@processes.reject! do |process, instances|
|
268
|
+
if process.removed && instances.empty?
|
269
|
+
if @tcp_proxy
|
270
|
+
@tcp_proxy.remove_process(process)
|
271
|
+
end
|
272
|
+
true
|
273
|
+
else
|
274
|
+
false
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
250
279
|
def process_names_to_instances(names)
|
251
280
|
names.each_with_object([]) do |name, array|
|
252
281
|
if name =~ /\A(.*)\.(\d+)\z/
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Procodile
|
2
|
+
class TCPProxy
|
3
|
+
|
4
|
+
def self.start(supervisor)
|
5
|
+
proxy = new(supervisor)
|
6
|
+
proxy.start
|
7
|
+
proxy
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(supervisor)
|
11
|
+
@supervisor = supervisor
|
12
|
+
@thread = nil
|
13
|
+
@listeners = {}
|
14
|
+
@stopped_processes = []
|
15
|
+
@sp_reader, @sp_writer = IO.pipe
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
@supervisor.config.processes.each { |_, p| add_process(p) }
|
20
|
+
Thread.new do
|
21
|
+
listen
|
22
|
+
Procodile.log nil, 'proxy', "Stopped listening on all ports"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_process(process)
|
27
|
+
if process.proxy?
|
28
|
+
@listeners[TCPServer.new(process.proxy_address, process.proxy_port)] = process
|
29
|
+
Procodile.log nil, 'proxy', "Proxying traffic on #{process.proxy_address}:#{process.proxy_port} to #{process.name}".color(32)
|
30
|
+
@sp_writer.write_nonblock('.')
|
31
|
+
end
|
32
|
+
rescue => e
|
33
|
+
Procodile.log nil, 'proxy', "Exception: #{e.class}: #{e.message}"
|
34
|
+
Procodile.log nil, 'proxy', e.backtrace[0,5].join("\n")
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove_process(process)
|
38
|
+
@stopped_processes << process
|
39
|
+
@sp_writer.write_nonblock('.')
|
40
|
+
end
|
41
|
+
|
42
|
+
def listen
|
43
|
+
loop do
|
44
|
+
io = IO.select([@sp_reader] + @listeners.keys, nil, nil, 30)
|
45
|
+
if io && io.first
|
46
|
+
io.first.each do |io|
|
47
|
+
if io == @sp_reader
|
48
|
+
io.read_nonblock(999)
|
49
|
+
next
|
50
|
+
end
|
51
|
+
|
52
|
+
Thread.new(io.accept, io) do |client, server|
|
53
|
+
handle_client(client, server)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
@stopped_processes.reject do |process|
|
59
|
+
if io = @listeners.key(process)
|
60
|
+
Procodile.log nil, 'proxy', "Stopped proxy listener for #{process.name}"
|
61
|
+
io.close
|
62
|
+
@listeners.delete(io)
|
63
|
+
end
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
rescue => e
|
68
|
+
Procodile.log nil, 'proxy', "Exception: #{e.class}: #{e.message}"
|
69
|
+
Procodile.log nil, 'proxy', e.backtrace[0,5].join("\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
def handle_client(client, server)
|
73
|
+
process = @listeners[server]
|
74
|
+
instances = @supervisor.processes[process] || []
|
75
|
+
if instances.empty?
|
76
|
+
Procodile.log nil, 'proxy', "There are no processes running for #{process.name}"
|
77
|
+
else
|
78
|
+
instance = instances[rand(instances.size)]
|
79
|
+
backend_socket = TCPSocket.new('127.0.0.1', instance.port) rescue nil
|
80
|
+
if backend_socket.nil?
|
81
|
+
Procodile.log nil, 'proxy', "Could not connect to #{instance.description}:#{instance.port}"
|
82
|
+
return
|
83
|
+
end
|
84
|
+
readers = {:backend => backend_socket, :client => client}
|
85
|
+
loop do
|
86
|
+
io = IO.select(readers.values, nil, nil, 0.5)
|
87
|
+
if io && io.first
|
88
|
+
io.first.each do |io|
|
89
|
+
readers.keys.each do |key|
|
90
|
+
next unless readers[key] == io
|
91
|
+
opposite_side = key == :client ? :backend : :client
|
92
|
+
if io.eof?
|
93
|
+
readers[opposite_side].shutdown(Socket::SHUT_WR) rescue nil
|
94
|
+
readers.delete(opposite_side)
|
95
|
+
else
|
96
|
+
readers[opposite_side].write(io.readpartial(1024)) rescue nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
rescue => e
|
104
|
+
Procodile.log nil, 'proxy', "Exception: #{e.class}: #{e.message}"
|
105
|
+
Procodile.log nil, 'proxy', e.backtrace[0,5].join("\n")
|
106
|
+
ensure
|
107
|
+
backend_socket.close rescue nil
|
108
|
+
client.close rescue nil
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
data/lib/procodile/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: procodile
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Cooke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -46,6 +46,7 @@ files:
|
|
46
46
|
- lib/procodile/signal_handler.rb
|
47
47
|
- lib/procodile/status_cli_output.rb
|
48
48
|
- lib/procodile/supervisor.rb
|
49
|
+
- lib/procodile/tcp_proxy.rb
|
49
50
|
- lib/procodile/version.rb
|
50
51
|
homepage: https://github.com/adamcooke/procodile
|
51
52
|
licenses:
|