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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 812b0f566074632d4c15caddfd02e6b1ddf8ee88
4
- data.tar.gz: 2acf4f380545089801e036b22aebbd1937d5be6b
3
+ metadata.gz: 6afcf104b9bc4268bc21c634075b7cd4c8ef8d33
4
+ data.tar.gz: a46fd5857741a36d7d2584a4d4cea4ed07c5dbfa
5
5
  SHA512:
6
- metadata.gz: 38ee04794ce4b93d537f20704cae020dd3f03e5af610f181a3a2c11a1657e5fc48dc486ad759d74cd60187e8859869176dd8d19df1f3ef6bcd7d01ca2e8bd11e
7
- data.tar.gz: 57ff3aef3f9e8dcbbe3c0871f68e15e82b1fa18c10e035d891042774340388bf2de595bc1de95e6343e3b5f743e9ebd13a126c9db105efa925ddaaa8c034e1ba
6
+ metadata.gz: 85ea38210c9cf42dde381b7ed2a8966578047356cfdd6deed42942ea49f6880a8fb32317b00d98586054925ab7d8acbcc716577db27b1a8796aaa506dd195a7a
7
+ data.tar.gz: d43970fcd524920fae6d7980546de10c9f0980354d8e80cf33d2569d8d90253ce1cafd7b85d98db865aac982aaf9ca61123492d0f774f798d7ad0d16024cc3fb
data/bin/procodile CHANGED
@@ -26,7 +26,7 @@ begin
26
26
  option_block.call(opts, cli)
27
27
  end
28
28
  end.parse!
29
- rescue OptionParser::InvalidOption => e
29
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
30
30
  $stderr.puts "Error: #{e.message}".color(31)
31
31
  exit 1
32
32
  end
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("-b", "--brittle", "Kill everything when one process exits") do
83
- cli.options[:brittle] = true
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[:brittle] = true
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 running?
99
- instances = ControlClient.run(@config.sock_path, 'start_processes', :processes => process_names_from_cli_option)
100
- if instances.empty?
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
- run_options = {}
111
- run_options[:brittle] = @options[:brittle]
112
- run_options[:stop_when_none] = @options[:stop_when_none]
126
+ if @options.has_key?(:respawn)
127
+ raise Error, "Cannot disable respawning because supervisor is already running"
128
+ end
113
129
 
114
- processes = process_names_from_cli_option
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
- if @options[:clean]
117
- FileUtils.rm_f(File.join(@config.pid_root, '*.pid'))
118
- FileUtils.rm_f(File.join(@config.pid_root, '*.sock'))
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
- if @options[:foreground]
123
- File.open(pid_path, 'w') { |f| f.write(::Process.pid) }
124
- Supervisor.new(@config, run_options).start(:processes => processes)
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
- FileUtils.rm_f(File.join(@config.pid_root, "*.pid"))
127
- pid = fork do
128
- STDOUT.reopen(@config.log_path, 'a')
129
- STDOUT.sync = true
130
- STDERR.reopen(@config.log_path, 'a')
131
- STDERR.sync = true
132
- Supervisor.new(@config, run_options).start(:processes => processes)
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 running?
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 "There are no processes to stop."
181
+ puts "No processes were stopped."
160
182
  else
161
183
  instances.each do |instance|
162
- puts "Stopping #{instance['description']} (PID: #{instance['pid']})"
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, "#{@config.app_name} supervisor isn't running"
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 running?
182
- options = {}
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 |instance|
188
- puts "Restarting #{instance['description']} (PID: #{instance['pid']})"
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, "#{@config.app_name} supervisor isn't running"
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 running?
242
+ if supervisor_running?
217
243
  ControlClient.run(@config.sock_path, 'reload_config')
218
- puts "Reloaded config for #{@config.app_name}"
244
+ puts "Reloaded Procodile config"
219
245
  else
220
- raise Error, "#{@config.app_name} supervisor isn't running"
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 running?
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 "Everything looks good!"
264
+ puts "Processes are running as configured"
239
265
  else
240
266
  reply['started'].each do |instance|
241
- puts "Started #{instance['description']}".color(32)
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']}".color(31)
271
+ puts "Stopped".color(31) + " #{instance['description']} (PID: #{instance['pid']})"
246
272
  end
247
273
  end
248
274
  else
249
- raise Error, "#{@config.app_name} supervisor isn't running"
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 running?
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
- puts "#{@config.app_name} supervisor not running"
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 send_to_socket(command, options = {})
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
- process_name, _ = process.split('.', 2)
339
- unless @config.process_list.keys.include?(process_name.to_s)
340
- raise Error, "Process '#{process_name}' is not configured. You may need to reload your config."
341
- end
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
@@ -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 ||= process_list.each_with_index.each_with_object({}) do |((name, command), index), hash|
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, 'supervisor.sock')
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(&:to_hash).to_json
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)
@@ -6,13 +6,14 @@ module Procodile
6
6
  attr_accessor :pid
7
7
  attr_reader :id
8
8
  attr_accessor :process
9
- attr_accessor :respawnable
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.config.environment_variables.merge({
48
+ vars = @process.environment_variables.merge({
31
49
  'PID_FILE' => self.pid_file_path,
32
50
  'APP_ROOT' => @process.config.root
33
51
  })
34
- end
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?(force_pid = nil)
66
- if force_pid || @pid
67
- ::Process.getpgid(force_pid || @pid) ? true : false
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(&block)
79
- @stopping = false
80
- existing_pid = self.pid_from_file
81
- if running?(existing_pid)
82
- # If the PID in the file is already running, we should just just continue
83
- # to monitor this process rather than spawning a new one.
84
- @pid = existing_pid
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
- return_value = nil
109
+ io = nil
92
110
  else
93
111
  reader, writer = IO.pipe
94
112
  log_destination = writer
95
- return_value = reader
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
- Procodile.log(@process.log_color, description, "Started with PID #{@pid}")
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 || false
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 = true
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(&block)
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
- start(&block)
194
+ on_stop
195
+ @process.create_instance(@supervisor).start
168
196
  end
197
+ self
169
198
  when 'start-term'
170
- old_process_pid = @pid
171
- start(&block)
172
- Procodile.log(@process.log_color, description, "Sent #{@process.term_signal} signal to old PID #{old_process_pid} (forgetting now)")
173
- ::Process.kill(@process.term_signal, old_process_pid)
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(&block)
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
- # Don't do any checking if we're in the midst of a restart
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 @respawnable
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
@@ -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, start_number = 1)
72
- quantity.times.map { |i| Instance.new(self, i + start_number) }
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
- if instance['running']
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 " pid: " + instance['pid'].to_s.ljust(7, ' ')
57
- print " respawns: " + instance['respawns'].to_s.ljust(7, ' ')
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
@@ -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 start(options = {})
24
- Procodile.log nil, "system", "#{@config.app_name} supervisor started with PID #{::Process.pid}"
25
- if @run_options[:brittle]
26
- Procodile.log nil, "system", "Running in brittle mode"
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
- Thread.new do
29
- socket = ControlServer.new(self)
30
- socket.listen
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) # Not a process we want
43
- next if @processes[process] && !@processes[process].empty? # Process type already running
44
- instances = start_instances(process.generate_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.each do |_, instances|
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 #{@config.app_name} supervisor"
101
- FileUtils.rm_f(File.join(@config.pid_root, 'supervisor.pid'))
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
- # Tidy up any instances that we no longer wish to be managed. They will
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
- data = reader.gets
190
- if instance = @readers[reader]
191
- Procodile.log instance.process.log_color, instance.description, "=> ".color(instance.process.log_color) + data
192
- else
193
- Procodile.log nil, 'unknown', data
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
- if instances.size > process.quantity
207
- quantity_to_stop = instances.size - process.quantity
208
- Procodile.log nil, "system", "Stopping #{quantity_to_stop} #{process.name} process(es)"
209
- status[:stopped] = instances.last(quantity_to_stop).each(&:stop)
210
- elsif instances.size < process.quantity
211
- quantity_needed = process.quantity - instances.size
212
- start_id = instances.last ? instances.last.id + 1 : 1
213
- Procodile.log nil, "system", "Starting #{quantity_needed} more #{process.name} process(es) (start with #{start_id})"
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
- def start_instances(instances)
221
- instances.each do |instance|
222
- if @run_options[:brittle]
223
- instance.respawnable = false
224
- end
225
- instance.start { |_, io| add_reader(instance, io) }
226
- @processes[instance.process] ||= []
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
- def remove_unmonitored_instances
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 !instance.running? && instance.stopping?
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
@@ -1,3 +1,3 @@
1
1
  module Procodile
2
- VERSION = '1.0.3'
2
+ VERSION = '1.0.4'
3
3
  end
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.3
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-18 00:00:00.000000000 Z
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: