procodile 1.0.3 → 1.0.4

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.
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: