procodile 0.0.1
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 +7 -0
- data/bin/procodile +30 -0
- data/lib/procodile.rb +2 -0
- data/lib/procodile/cli.rb +131 -0
- data/lib/procodile/config.rb +64 -0
- data/lib/procodile/error.rb +4 -0
- data/lib/procodile/instance.rb +204 -0
- data/lib/procodile/logger.rb +22 -0
- data/lib/procodile/process.rb +65 -0
- data/lib/procodile/signal_handler.rb +32 -0
- data/lib/procodile/supervisor.rb +96 -0
- metadata +56 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 12da3acd72adc0bf302ddad40c02133f308cf168
|
4
|
+
data.tar.gz: e13faabcbbadce0fd6ac91885c3b5ba937f398db
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8b61d0f1fb351bf1f05eac2ea0d22accd8ff2ebbe51fd551397ac5c8ee17693ffa36f6a7f3d2e9729774cf1dabac99358930de70c45c0aa2cd95b1d9359ffb06
|
7
|
+
data.tar.gz: b2f481642d8e7015808946d07cf6b7d0c027e1d2b8956518b27a61bd670e851e690fbf98c79cd3ec02b042d6c176c275794abe6ee2139effc234fc60152b65d9
|
data/bin/procodile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
options = {}
|
4
|
+
OptionParser.new do |opts|
|
5
|
+
opts.banner = "Usage: procodile [options]"
|
6
|
+
opts.on("-r", "--root PATH", "The path to the root of your application") do |root|
|
7
|
+
options[:root] = root
|
8
|
+
end
|
9
|
+
end.parse!
|
10
|
+
|
11
|
+
$:.unshift(File.expand_path('../../lib', __FILE__))
|
12
|
+
|
13
|
+
require 'fileutils'
|
14
|
+
require 'procodile/error'
|
15
|
+
require 'procodile/config'
|
16
|
+
require 'procodile/cli'
|
17
|
+
|
18
|
+
Thread.abort_on_exception = true
|
19
|
+
begin
|
20
|
+
|
21
|
+
config = Procodile::Config.new(options[:root] ? File.expand_path(options[:root]) : FileUtils.pwd)
|
22
|
+
command = ARGV[0]
|
23
|
+
|
24
|
+
cli = Procodile::CLI.new(config)
|
25
|
+
cli.run(command)
|
26
|
+
|
27
|
+
rescue Procodile::Error => e
|
28
|
+
$stderr.puts "\e[31merror: #{e.message}\e[0m"
|
29
|
+
exit 1
|
30
|
+
end
|
data/lib/procodile.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'procodile/error'
|
3
|
+
require 'procodile/supervisor'
|
4
|
+
require 'procodile/signal_handler'
|
5
|
+
|
6
|
+
module Procodile
|
7
|
+
class CLI
|
8
|
+
|
9
|
+
def initialize(config)
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(command)
|
14
|
+
if self.class.instance_methods(false).include?(command.to_sym) && command != 'run'
|
15
|
+
public_send(command)
|
16
|
+
else
|
17
|
+
raise Error, "Invalid command '#{command}'"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def start
|
22
|
+
if running?
|
23
|
+
raise Error, "#{@config.app_name} already running (PID: #{current_pid})"
|
24
|
+
end
|
25
|
+
FileUtils.rm_f(File.join(@config.pid_root, "*.pid"))
|
26
|
+
pid = fork do
|
27
|
+
STDOUT.reopen(log_path, 'a')
|
28
|
+
STDOUT.sync = true
|
29
|
+
STDERR.reopen(log_path, 'a')
|
30
|
+
STDERR.sync = true
|
31
|
+
supervisor = Supervisor.new(@config)
|
32
|
+
signal_handler = SignalHandler.new('TERM', 'USR1', 'USR2', 'INT', 'HUP')
|
33
|
+
signal_handler.register('TERM') { supervisor.stop }
|
34
|
+
signal_handler.register('USR1') { supervisor.restart }
|
35
|
+
signal_handler.register('USR2') { supervisor.status }
|
36
|
+
signal_handler.register('INT') { supervisor.stop_supervisor }
|
37
|
+
supervisor.start
|
38
|
+
end
|
39
|
+
::Process.detach(pid)
|
40
|
+
File.open(pid_path, 'w') { |f| f.write(pid) }
|
41
|
+
puts "Started #{@config.app_name} supervisor with PID #{pid}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop
|
45
|
+
if running?
|
46
|
+
::Process.kill('TERM', current_pid)
|
47
|
+
puts "Stopping #{@config.app_name} processes & supervisor..."
|
48
|
+
else
|
49
|
+
raise Error, "#{@config.app_name} supervisor isn't running"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def stop_supervisor
|
54
|
+
if running?
|
55
|
+
puts "This will stop the supervisor only. Any processes that it started will no longer be managed."
|
56
|
+
puts "They will need to be stopped manually. \e[34mDo you wish to continue? (yes/NO)\e[0m"
|
57
|
+
if ['y', 'yes'].include?($stdin.gets.to_s.strip.downcase)
|
58
|
+
::Process.kill('INT', current_pid)
|
59
|
+
puts "We've asked it to stop. It'll probably be done in a moment."
|
60
|
+
else
|
61
|
+
puts "OK. That's fine. You can just run `stop` to stop processes too."
|
62
|
+
end
|
63
|
+
|
64
|
+
else
|
65
|
+
raise Error, "#{@config.app_name} supervisor isn't running"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def restart
|
70
|
+
if running?
|
71
|
+
::Process.kill('USR1', current_pid)
|
72
|
+
puts "Restarting #{@config.app_name}"
|
73
|
+
else
|
74
|
+
raise Error, "#{@config.app_name} supervisor isn't running"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def status
|
79
|
+
if running?
|
80
|
+
puts "#{@config.app_name} running (PID: #{current_pid})"
|
81
|
+
::Process.kill('USR2', current_pid)
|
82
|
+
puts "Instance status details added to #{log_path}"
|
83
|
+
else
|
84
|
+
puts "#{@config.app_name} supervisor not running"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def kill
|
89
|
+
Dir[File.join(@config.pid_root, '*.pid')].each do |pid_path|
|
90
|
+
name = pid_path.split('/').last.gsub(/\.pid\z/, '')
|
91
|
+
pid = File.read(pid_path).to_i
|
92
|
+
begin
|
93
|
+
::Process.kill('KILL', pid)
|
94
|
+
puts "Sent KILL to #{pid} (#{name})"
|
95
|
+
rescue Errno::ESRCH
|
96
|
+
end
|
97
|
+
FileUtils.rm(pid_path)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def running?
|
104
|
+
if pid = current_pid
|
105
|
+
::Process.getpgid(pid) ? true : false
|
106
|
+
else
|
107
|
+
false
|
108
|
+
end
|
109
|
+
rescue Errno::ESRCH
|
110
|
+
false
|
111
|
+
end
|
112
|
+
|
113
|
+
def current_pid
|
114
|
+
if File.exist?(pid_path)
|
115
|
+
pid_file = File.read(pid_path).strip
|
116
|
+
pid_file.length > 0 ? pid_file.to_i : nil
|
117
|
+
else
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def pid_path
|
123
|
+
File.join(@config.pid_root, 'supervisor.pid')
|
124
|
+
end
|
125
|
+
|
126
|
+
def log_path
|
127
|
+
File.join(@config.log_root, 'supervisor.log')
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'procodile/error'
|
3
|
+
require 'procodile/process'
|
4
|
+
|
5
|
+
module Procodile
|
6
|
+
class Config
|
7
|
+
|
8
|
+
COLORS = [35, 31, 36, 32, 33, 34]
|
9
|
+
|
10
|
+
attr_reader :root
|
11
|
+
|
12
|
+
def initialize(root)
|
13
|
+
@root = root
|
14
|
+
unless File.exist?(procfile_path)
|
15
|
+
raise Error, "Procfile not found at #{procfile_path}"
|
16
|
+
end
|
17
|
+
|
18
|
+
FileUtils.mkdir_p(pid_root)
|
19
|
+
FileUtils.mkdir_p(log_root)
|
20
|
+
end
|
21
|
+
|
22
|
+
def app_name
|
23
|
+
options['app_name'] || 'Procodile'
|
24
|
+
end
|
25
|
+
|
26
|
+
def processes
|
27
|
+
process_list.each_with_index.each_with_object({}) do |((name, command), index), hash|
|
28
|
+
options = {'log_color' => COLORS[index.divmod(COLORS.size)[1]]}.merge(process_options[name] || {})
|
29
|
+
hash[name] = Process.new(self, name, command, options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def process_list
|
34
|
+
@processes ||= YAML.load_file(procfile_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def options
|
38
|
+
@options ||= File.exist?(options_path) ? YAML.load_file(options_path) : {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def process_options
|
42
|
+
@process_options ||= options['processes'] || {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def pid_root
|
46
|
+
File.expand_path(options['pid_root'] || 'pids', @root)
|
47
|
+
end
|
48
|
+
|
49
|
+
def log_root
|
50
|
+
File.expand_path(options['log_root'] || 'log', @root)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def procfile_path
|
56
|
+
File.join(@root, 'Procfile')
|
57
|
+
end
|
58
|
+
|
59
|
+
def options_path
|
60
|
+
File.join(@root, 'Procfile.options')
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'procodile/logger'
|
2
|
+
|
3
|
+
module Procodile
|
4
|
+
class Instance
|
5
|
+
|
6
|
+
attr_accessor :pid
|
7
|
+
|
8
|
+
def initialize(process, id)
|
9
|
+
@process = process
|
10
|
+
@id = id
|
11
|
+
@respawns = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def description
|
15
|
+
"#{@process.name}.#{@id}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def dead?
|
19
|
+
@dead || false
|
20
|
+
end
|
21
|
+
|
22
|
+
def pid_file_path
|
23
|
+
File.join(@process.config.pid_root, "#{description}.pid")
|
24
|
+
end
|
25
|
+
|
26
|
+
def log_file_path
|
27
|
+
File.join(@process.config.log_root, "#{description}.log")
|
28
|
+
end
|
29
|
+
|
30
|
+
def pid_from_file
|
31
|
+
if File.exist?(pid_file_path)
|
32
|
+
pid = File.read(pid_file_path)
|
33
|
+
pid.length > 0 ? pid.strip.to_i : nil
|
34
|
+
else
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def running?(force_pid = nil)
|
40
|
+
if force_pid || @pid
|
41
|
+
::Process.getpgid(force_pid || @pid) ? true : false
|
42
|
+
else
|
43
|
+
false
|
44
|
+
end
|
45
|
+
rescue Errno::ESRCH
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
def start
|
50
|
+
@stopping = false
|
51
|
+
Dir.chdir(@process.config.root) do
|
52
|
+
log_file = File.open(self.log_file_path, 'a')
|
53
|
+
@pid = ::Process.spawn({'PID_FILE' => pid_file_path}, @process.command, :out => log_file, :err => log_file)
|
54
|
+
Procodile.log(@process.log_color, description, "Started with PID #{@pid}")
|
55
|
+
File.open(pid_file_path, 'w') { |f| f.write(@pid.to_s + "\n") }
|
56
|
+
::Process.detach(@pid)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Is this instance supposed to be stopping/be stopped?
|
62
|
+
#
|
63
|
+
def stopping?
|
64
|
+
@stopping || false
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Send this signal the signal to stop and mark the instance in a state that
|
69
|
+
# tells us that we want it to be stopped.
|
70
|
+
#
|
71
|
+
def stop
|
72
|
+
@stopping = true
|
73
|
+
update_pid
|
74
|
+
if self.running?
|
75
|
+
pid = self.pid_from_file
|
76
|
+
Procodile.log(@process.log_color, description, "Sending TERM to #{pid}")
|
77
|
+
::Process.kill('TERM', pid)
|
78
|
+
else
|
79
|
+
Procodile.log(@process.log_color, description, "Process already stopped")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# A method that will be called when this instance has been stopped and it isn't going to be
|
85
|
+
# started again
|
86
|
+
#
|
87
|
+
def on_stop
|
88
|
+
FileUtils.rm_f(self.pid_file_path)
|
89
|
+
Procodile.log(@process.log_color, description, "Removed PID file")
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# Retarts the process using the appropriate method from the process configuraiton
|
94
|
+
#
|
95
|
+
def restart
|
96
|
+
Procodile.log(@process.log_color, description, "Restarting using #{@process.restart_mode} mode")
|
97
|
+
@restarting = true
|
98
|
+
update_pid
|
99
|
+
case @process.restart_mode
|
100
|
+
when 'usr1', 'usr2'
|
101
|
+
if running?
|
102
|
+
::Process.kill(@process.restart_mode.upcase, @pid)
|
103
|
+
Procodile.log(@process.log_color, description, "Sent #{@process.restart_mode.upcase} signal to process #{@pid}")
|
104
|
+
else
|
105
|
+
Procodile.log(@process.log_color, description, "Process not running already. Starting it.")
|
106
|
+
start
|
107
|
+
end
|
108
|
+
when 'start-term'
|
109
|
+
old_process_pid = @pid
|
110
|
+
start
|
111
|
+
Procodile.log(@process.log_color, description, "Sent TERM signal to old PID #{old_process_pid} (forgetting now)")
|
112
|
+
::Process.kill('TERM', old_process_pid)
|
113
|
+
when 'term-start'
|
114
|
+
stop
|
115
|
+
Thread.new do
|
116
|
+
# Wait for this process to stop and when it has, run it.
|
117
|
+
sleep 0.5 while running?
|
118
|
+
start
|
119
|
+
end
|
120
|
+
end
|
121
|
+
ensure
|
122
|
+
@restarting = false
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# Update the locally cached PID from that stored on the file system.
|
127
|
+
#
|
128
|
+
def update_pid
|
129
|
+
pid_from_file = self.pid_from_file
|
130
|
+
if pid_from_file != @pid
|
131
|
+
@pid = pid_from_file
|
132
|
+
Procodile.log(@process.log_color, description, "PID file changed. Updated pid to #{@pid}")
|
133
|
+
true
|
134
|
+
else
|
135
|
+
false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# Check the status of this process and handle as appropriate
|
141
|
+
#
|
142
|
+
def check
|
143
|
+
# Don't do any checking if we're in the midst of a restart
|
144
|
+
return if @restarting
|
145
|
+
|
146
|
+
if self.running?
|
147
|
+
# Everything is OK. The process is running.
|
148
|
+
else
|
149
|
+
# If the process isn't running any more, update the PID in our memory from
|
150
|
+
# the file in case the process has changed itself.
|
151
|
+
return check if update_pid
|
152
|
+
|
153
|
+
if can_respawn?
|
154
|
+
Procodile.log(@process.log_color, description, "Process has stopped. Respawning...")
|
155
|
+
start
|
156
|
+
add_respawn
|
157
|
+
elsif respawns >= @process.max_respawns
|
158
|
+
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")
|
159
|
+
Procodile.log(@process.log_color, description, "\e[31mIt will not be respawned automatically any longer and will no longer be managed.\e[0m")
|
160
|
+
die
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# Mark this process as dead and tidy up after it
|
167
|
+
#
|
168
|
+
def die
|
169
|
+
on_stop
|
170
|
+
@dead = true
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# Can this process be respawned if needed?
|
175
|
+
#
|
176
|
+
def can_respawn?
|
177
|
+
!stopping? && (respawns + 1) <= @process.max_respawns
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# Return the number of times this process has been respawned in the last hour
|
182
|
+
#
|
183
|
+
def respawns
|
184
|
+
if @respawns.nil? || @last_respawn.nil? || @last_respawn < (Time.now - @process.respawn_window)
|
185
|
+
0
|
186
|
+
else
|
187
|
+
@respawns
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
#
|
192
|
+
# Increment the counter of respawns for this process
|
193
|
+
#
|
194
|
+
def add_respawn
|
195
|
+
if @last_respawn && @last_respawn < (Time.now - @process.respawn_window)
|
196
|
+
@respawns = 1
|
197
|
+
else
|
198
|
+
@last_respawn = Time.now
|
199
|
+
@respawns += 1
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'logger'
|
2
|
+
module Procodile
|
3
|
+
|
4
|
+
def self.mutex
|
5
|
+
@mutex ||= Mutex.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.log(color, name, text)
|
9
|
+
mutex.synchronize do
|
10
|
+
text.to_s.lines.map(&:chomp).each do |message|
|
11
|
+
output = ""
|
12
|
+
output += "\e[#{color}m" if color
|
13
|
+
output += "#{Time.now.strftime("%H:%M:%S")} #{name.ljust(15, ' ')} |"
|
14
|
+
output += "\e[0m "
|
15
|
+
output += message
|
16
|
+
$stdout.puts output
|
17
|
+
$stdout.flush
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'procodile/instance'
|
2
|
+
|
3
|
+
module Procodile
|
4
|
+
class Process
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :command
|
8
|
+
attr_reader :config
|
9
|
+
|
10
|
+
def initialize(config, name, command, options = {})
|
11
|
+
@config = config
|
12
|
+
@name = name
|
13
|
+
@command = command
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Return the color for this process
|
19
|
+
#
|
20
|
+
def log_color
|
21
|
+
@options['log_color'] || 0
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# How many instances of this process should be started
|
26
|
+
#
|
27
|
+
def quantity
|
28
|
+
@options['quantity'] || 1
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# The maximum number of times this process can be respawned in the given period
|
33
|
+
#
|
34
|
+
def max_respawns
|
35
|
+
@options['max_respawns'] ? @options['max_respawns'].to_i : 5
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# The respawn window. One hour by default.
|
40
|
+
#
|
41
|
+
def respawn_window
|
42
|
+
@options['respawn_window'] ? @options['respawn_window'].to_i : 3600
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Defines how this process should be restarted
|
47
|
+
#
|
48
|
+
# start-term = start new instances and send term to children
|
49
|
+
# usr1 = just send a usr1 signal to the current instance
|
50
|
+
# usr2 = just send a usr2 signal to the current instance
|
51
|
+
# term-start = stop the old instances, when no longer running, start a new one
|
52
|
+
#
|
53
|
+
def restart_mode
|
54
|
+
@options['restart_mode'] || 'term-start'
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Generate an array of new instances for this process (based on its quantity)
|
59
|
+
#
|
60
|
+
def generate_instances
|
61
|
+
quantity.times.map { |i| Instance.new(self, i + 1) }
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Procodile
|
2
|
+
class SignalHandler
|
3
|
+
|
4
|
+
def self.queue
|
5
|
+
Thread.main[:signal_queue] ||= []
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(*signals)
|
9
|
+
@handlers = {}
|
10
|
+
Thread.new do
|
11
|
+
loop do
|
12
|
+
if signal = self.class.queue.shift
|
13
|
+
if @handlers[signal]
|
14
|
+
@handlers[signal].each(&:call)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
sleep 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
signals.each do |sig|
|
22
|
+
Signal.trap(sig, proc { SignalHandler.queue << sig })
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def register(name, &block)
|
27
|
+
@handlers[name] ||= []
|
28
|
+
@handlers[name] << block
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Procodile
|
2
|
+
class Supervisor
|
3
|
+
|
4
|
+
# Create a new supervisor instance that will be monitoring the
|
5
|
+
# processes that have been provided.
|
6
|
+
def initialize(config)
|
7
|
+
@config = config
|
8
|
+
@instances = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def start
|
12
|
+
Procodile.log nil, "system", "#{@config.app_name} supervisor started with PID #{::Process.pid}"
|
13
|
+
@config.processes.each do |name, process|
|
14
|
+
process.generate_instances.each do |instance|
|
15
|
+
instance.start
|
16
|
+
@instances << instance
|
17
|
+
end
|
18
|
+
end
|
19
|
+
supervise
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop
|
23
|
+
return if @stopping
|
24
|
+
@stopping = true
|
25
|
+
Procodile.log nil, "system", "Stopping all #{@config.app_name} processes"
|
26
|
+
@instances.each(&:stop)
|
27
|
+
end
|
28
|
+
|
29
|
+
def stop_supervisor
|
30
|
+
Procodile.log nil, 'system', "Stopping #{@config.app_name} supervisor"
|
31
|
+
FileUtils.rm_f(File.join(@config.pid_root, 'supervisor.pid'))
|
32
|
+
::Process.exit 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def restart
|
36
|
+
Procodile.log nil, 'system', "Restarting all #{@config.app_name} processes"
|
37
|
+
@instances.each(&:restart)
|
38
|
+
end
|
39
|
+
|
40
|
+
def status
|
41
|
+
Procodile.log '37;44', 'status', "Status as at: #{Time.now.utc.to_s}"
|
42
|
+
@instances.each do |instance|
|
43
|
+
if instance.running?
|
44
|
+
Procodile.log '37;44', 'status', "#{instance.description} is RUNNING (pid #{instance.pid}). Respawned #{instance.respawns} time(s)"
|
45
|
+
else
|
46
|
+
Procodile.log '37;44', 'status', "#{instance.description} is STOPPED"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def supervise
|
52
|
+
loop do
|
53
|
+
# Tidy up any instances that we no longer wish to be managed. They will
|
54
|
+
# be removed from the list.
|
55
|
+
remove_dead_instances
|
56
|
+
|
57
|
+
if @stopping
|
58
|
+
# If the system is stopping, we'll remove any instances that have already
|
59
|
+
# stopped and trigger their on_stop callback.
|
60
|
+
remove_stopped_instances
|
61
|
+
|
62
|
+
# When all the instances we manage have gone away, we can stop ourself.
|
63
|
+
if @instances.size > 0
|
64
|
+
Procodile.log nil, "system", "Waiting for #{@instances.size} processes to stop"
|
65
|
+
else
|
66
|
+
Procodile.log nil, "system", "All processes have stopped"
|
67
|
+
stop_supervisor
|
68
|
+
end
|
69
|
+
else
|
70
|
+
# Check all instances that we manage and let them do their things.
|
71
|
+
@instances.each(&:check)
|
72
|
+
end
|
73
|
+
|
74
|
+
sleep 5
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def remove_dead_instances
|
81
|
+
@instances.reject!(&:dead?)
|
82
|
+
end
|
83
|
+
|
84
|
+
def remove_stopped_instances
|
85
|
+
@instances.reject! do |instance|
|
86
|
+
if instance.running?
|
87
|
+
false
|
88
|
+
else
|
89
|
+
instance.on_stop
|
90
|
+
true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: procodile
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Cooke
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-16 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Run Ruby/Rails processes in the background on Linux servers with ease.
|
14
|
+
email:
|
15
|
+
- me@adamcooke.io
|
16
|
+
executables:
|
17
|
+
- procodile
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- bin/procodile
|
22
|
+
- lib/procodile.rb
|
23
|
+
- lib/procodile/cli.rb
|
24
|
+
- lib/procodile/config.rb
|
25
|
+
- lib/procodile/error.rb
|
26
|
+
- lib/procodile/instance.rb
|
27
|
+
- lib/procodile/logger.rb
|
28
|
+
- lib/procodile/process.rb
|
29
|
+
- lib/procodile/signal_handler.rb
|
30
|
+
- lib/procodile/supervisor.rb
|
31
|
+
homepage: https://github.com/adamcooke/procodile
|
32
|
+
licenses:
|
33
|
+
- MIT
|
34
|
+
metadata: {}
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 2.5.1
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: This gem will help you run Ruby processes from a Procfile on Linux servers
|
55
|
+
in the background.
|
56
|
+
test_files: []
|