rails_daemons 1.0.0
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/.gitignore +17 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Capfile +25 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +218 -0
- data/Rakefile +1 -0
- data/Thorfile +14 -0
- data/bin/daemon +4 -0
- data/lib/rails_daemons.rb +6 -0
- data/lib/rails_daemons/capistrano.rb +7 -0
- data/lib/rails_daemons/railtie.rb +4 -0
- data/lib/rails_daemons/tasks/rails_daemons.cap +40 -0
- data/lib/rails_daemons/tasks/rails_daemons.thor +33 -0
- data/lib/rails_daemons/thor.rb +2 -0
- data/lib/rails_daemons/utils.rb +25 -0
- data/lib/rails_daemons/version.rb +3 -0
- data/lib/rails_daemons/worker.rb +214 -0
- data/rails_daemons.gemspec +25 -0
- metadata +122 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 341d44fec654ca5a7bd975916ab575d877103b1c
|
|
4
|
+
data.tar.gz: 171b1b87e26e5468321c4683e82e93bf598fb643
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1623e1db2fea6fc3f69f4b2ca4680453c4ab59a8eda59dfd117394b62ba5aeac725f2dcef972122bebe397622a829790f01a42007318343f601076c404261a90
|
|
7
|
+
data.tar.gz: 3cdabbaeb5782b96c560296afb3b2e488fda08d62fb2d6cf4f090bf0d6b3509328b289ad3970a29de81928fb378f2e755604775d22e4f841e54eb142f9fb9432
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rails_daemons
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.1.5
|
data/Capfile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Load DSL and Setup Up Stages
|
|
2
|
+
require 'capistrano/setup'
|
|
3
|
+
|
|
4
|
+
# Includes default deployment tasks
|
|
5
|
+
require 'capistrano/deploy'
|
|
6
|
+
|
|
7
|
+
# Includes tasks from other gems included in your Gemfile
|
|
8
|
+
#
|
|
9
|
+
# For documentation on these, see for example:
|
|
10
|
+
#
|
|
11
|
+
# https://github.com/capistrano/rvm
|
|
12
|
+
# https://github.com/capistrano/rbenv
|
|
13
|
+
# https://github.com/capistrano/chruby
|
|
14
|
+
# https://github.com/capistrano/bundler
|
|
15
|
+
# https://github.com/capistrano/rails/tree/master/assets
|
|
16
|
+
# https://github.com/capistrano/rails/tree/master/migrations
|
|
17
|
+
#
|
|
18
|
+
# require 'capistrano/rvm'
|
|
19
|
+
# require 'capistrano/rbenv'
|
|
20
|
+
# require 'capistrano/chruby'
|
|
21
|
+
# require 'capistrano/bundler'
|
|
22
|
+
# require 'capistrano/rails/assets'
|
|
23
|
+
# require 'capistrano/rails/migrations'
|
|
24
|
+
|
|
25
|
+
require 'rails_daemons/capistrano'
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2014 Sergey Malykh
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# RailsDaemons
|
|
2
|
+
|
|
3
|
+
Background workers for Rails. The gem is designed to safely handling exceptions occured in workers. Daemons restarts gracefully to achieve zerro downtime. RailsDaemons includes [Capistrano](https://github.com/capistrano/capistrano) and [Monit](https://mmonit.com/monit/) support. The gem also supports logrotate by safely handling signal USR1.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'rails_daemons'
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install rails_daemons
|
|
18
|
+
|
|
19
|
+
## Capistrano integration
|
|
20
|
+
|
|
21
|
+
Add ```require 'rails_daemons/capistrano'``` to Capfile
|
|
22
|
+
|
|
23
|
+
This will get available commands:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
cap production daemon:start[<worker_name>]
|
|
27
|
+
cap production daemon:restart[<worker_name>]
|
|
28
|
+
cap production daemon:stop[<worker_name>]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Background worker controls
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
bundle exec daemon start <worker_name>
|
|
35
|
+
bundle exec daemon restart <worker_name>
|
|
36
|
+
bundle exec daemon stop <worker_name>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Worker construction
|
|
40
|
+
|
|
41
|
+
Create background worker.
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
class ParserWorker
|
|
45
|
+
include RailsDaemons::Worker
|
|
46
|
+
|
|
47
|
+
def work
|
|
48
|
+
# your work here
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Also you can specify:
|
|
54
|
+
|
|
55
|
+
* ```tick``` - delay between daemon cycles (in seconds, default 1.0)
|
|
56
|
+
* ```start``` - work that should be done once on the daemon`s start (and restart)
|
|
57
|
+
* ```shutdown``` - work that should be done before the daemon`s stop (either on exception or regular stop)
|
|
58
|
+
|
|
59
|
+
Example usage (with Mongoid):
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
# app/models/parsing.rb
|
|
63
|
+
class Parsing
|
|
64
|
+
include Mongoid::Document
|
|
65
|
+
|
|
66
|
+
field :url
|
|
67
|
+
field :state, default: 'pending'
|
|
68
|
+
|
|
69
|
+
scope :pending, ->() { where( state: 'pending' ) }
|
|
70
|
+
scope :running, ->() { where( state: 'running' ) }
|
|
71
|
+
|
|
72
|
+
class << self
|
|
73
|
+
def start
|
|
74
|
+
[ 'http://www.example.com' ].each do |url|
|
|
75
|
+
create!( url: url )
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def process( logger )
|
|
81
|
+
set( state: 'running' )
|
|
82
|
+
|
|
83
|
+
# do the job
|
|
84
|
+
|
|
85
|
+
rescue => e
|
|
86
|
+
logger.info e.inspect
|
|
87
|
+
|
|
88
|
+
set( state: 'halted' )
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# app/daemons/parser_worker.rb
|
|
93
|
+
class ParserWorker
|
|
94
|
+
include RailsDaemons::Worker
|
|
95
|
+
|
|
96
|
+
def tick
|
|
97
|
+
3 # in seconds
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# daemon start
|
|
101
|
+
def start
|
|
102
|
+
require 'mechanize'
|
|
103
|
+
require 'rufus-scheduler'
|
|
104
|
+
|
|
105
|
+
scheduler = Rufus::Scheduler.new
|
|
106
|
+
|
|
107
|
+
scheduler.every '12h' do
|
|
108
|
+
Parsing.start # create parsing jobs every 12 hours
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# main work
|
|
113
|
+
def work
|
|
114
|
+
Parsing.pending.each do |parsing|
|
|
115
|
+
t = Thread.new do
|
|
116
|
+
parsing.process( $logger )
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# exceptions handled in method ```process``` (just for an example)
|
|
120
|
+
t.abort_on_exception = false
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# stop the daemon
|
|
125
|
+
def shutdown
|
|
126
|
+
Parsing.running.each do |parsing|
|
|
127
|
+
parsing.set( state: 'stopped' )
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Monit integration:
|
|
135
|
+
|
|
136
|
+
Put the following code to /etc/init.d/<worker_name>, replace <worker_name> with you name.
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
#!/bin/bash
|
|
140
|
+
### BEGIN INIT INFO
|
|
141
|
+
# Provides: <worker_name>
|
|
142
|
+
# Required-Start: $all
|
|
143
|
+
# Required-Stop: $network $local_fs $syslog
|
|
144
|
+
# Default-Start: 2 3 4 5
|
|
145
|
+
# Default-Stop: 0 1 6
|
|
146
|
+
# Short-Description: Start daemon at boot
|
|
147
|
+
# Description: Enable daemon at boot time.
|
|
148
|
+
### END INIT INFO
|
|
149
|
+
|
|
150
|
+
set -u
|
|
151
|
+
set -e
|
|
152
|
+
|
|
153
|
+
# Change these to match your app:
|
|
154
|
+
APP_NAME=app
|
|
155
|
+
ENV=production
|
|
156
|
+
USER=user
|
|
157
|
+
APP_ROOT="/home/$USER/$APP_NAME/current"
|
|
158
|
+
|
|
159
|
+
SET_PATH="cd $APP_ROOT; rvm use `cat $APP_ROOT/.ruby-version`@`cat $APP_ROOT/.ruby-gemset`"
|
|
160
|
+
OUT=">> $APP_ROOT/log/worker_name.$ENV.monit.log 2>&1"
|
|
161
|
+
|
|
162
|
+
cd $APP_ROOT || exit 1
|
|
163
|
+
|
|
164
|
+
case ${1-help} in
|
|
165
|
+
start)
|
|
166
|
+
su - $USER -c "$SET_PATH; RAILS_ENV=$ENV bundle exec daemon start <worker_name> $OUT"
|
|
167
|
+
;;
|
|
168
|
+
stop)
|
|
169
|
+
su - $USER -c "$SET_PATH; RAILS_ENV=$ENV bundle exec daemon stop <worker_name> $OUT"
|
|
170
|
+
;;
|
|
171
|
+
restart|reload)
|
|
172
|
+
su - $USER -c "$SET_PATH; RAILS_ENV=$ENV bundle exec daemon restart <worker_name> $OUT"
|
|
173
|
+
;;
|
|
174
|
+
*)
|
|
175
|
+
echo >&2 "Usage: $0 <start|stop|restart>"
|
|
176
|
+
exit 1
|
|
177
|
+
;;
|
|
178
|
+
esac
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Monit task:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
check process <worker_name> with pidfile /<path_to_project>/current/tmp/pids/<worker_name>.production.pid
|
|
185
|
+
start program = "/etc/init.d/<worker_name> start"
|
|
186
|
+
stop program = "/etc/init.d/<worker_name> stop"
|
|
187
|
+
if changed pid for 3 times within 5 cycles then restart
|
|
188
|
+
if 5 restarts within 5 cycles then timeout
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Logrotate integration
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
/<path_to_project>/shared/log/*.log
|
|
195
|
+
{
|
|
196
|
+
su <user> <group>
|
|
197
|
+
daily
|
|
198
|
+
missingok
|
|
199
|
+
rotate 360
|
|
200
|
+
compress
|
|
201
|
+
delaycompress
|
|
202
|
+
notifempty
|
|
203
|
+
dateext
|
|
204
|
+
create 0660 <user> <group>
|
|
205
|
+
postrotate
|
|
206
|
+
[ ! -f /<path_to_project>/shared/tmp/pids/unicorn.pid ] || kill -USR1 `cat /<path_to_project>/shared/tmp/pids/unicorn.pid`
|
|
207
|
+
[ ! -f /<path_to_project>/shared/tmp/pids/<worker_name>.production.pid ] || kill -USR1 `cat /<path_to_project>/shared/tmp/pids/<worker_name>.production.pid`
|
|
208
|
+
endscript
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Contributing
|
|
213
|
+
|
|
214
|
+
1. Fork it
|
|
215
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
216
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
217
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
218
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/Thorfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# https://shvets.github.io/blog/2013/12/14/using_thor_as_rake_replacement.html
|
|
2
|
+
unless defined? Thor::Runner
|
|
3
|
+
require 'bundler'
|
|
4
|
+
|
|
5
|
+
gems = Bundler::Definition.build(Bundler.default_gemfile, Bundler.default_lockfile, nil).requested_specs
|
|
6
|
+
|
|
7
|
+
gem = gems.find { |gem| gem.name == 'thor' }
|
|
8
|
+
|
|
9
|
+
load "#{ENV['GEM_HOME']}/gems/#{gem.name}-#{gem.version}/bin/thor"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
Dir.glob("lib/rails_daemons/tasks/*.thor") do |name|
|
|
13
|
+
Thor::Util.load_thorfile(name)
|
|
14
|
+
end
|
data/bin/daemon
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
namespace :daemon do
|
|
2
|
+
desc 'Start background worker'
|
|
3
|
+
task :start, :worker_name do |task, args|
|
|
4
|
+
raise 'Worked is not specified' if args[:worker_name].nil?
|
|
5
|
+
|
|
6
|
+
on roles(:app) do
|
|
7
|
+
within release_path do
|
|
8
|
+
with rails_env: fetch(:rails_env) do
|
|
9
|
+
execute :bundle, :exec, :daemon, "start #{args[:worker_name]}"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc 'Restart background worker'
|
|
16
|
+
task :restart, :worker_name do |task, args|
|
|
17
|
+
raise 'Worked is not specified' if args[:worker_name].nil?
|
|
18
|
+
|
|
19
|
+
on roles(:app) do
|
|
20
|
+
within release_path do
|
|
21
|
+
with rails_env: fetch(:rails_env) do
|
|
22
|
+
execute :bundle, :exec, :daemon, "restart #{args[:worker_name]}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
desc 'Stop background worker'
|
|
29
|
+
task :stop, :worker_name do |task, args|
|
|
30
|
+
raise 'Worked is not specified' if args[:worker_name].nil?
|
|
31
|
+
|
|
32
|
+
on roles(:app) do
|
|
33
|
+
within release_path do
|
|
34
|
+
with rails_env: fetch(:rails_env) do
|
|
35
|
+
execute :bundle, :exec, :daemon, "stop #{args[:worker_name]}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'thor/rails'
|
|
2
|
+
|
|
3
|
+
module ::RailsDaemons
|
|
4
|
+
class Daemon < Thor
|
|
5
|
+
include Thor::Rails
|
|
6
|
+
namespace :daemon
|
|
7
|
+
|
|
8
|
+
no_commands do
|
|
9
|
+
def get_daemon( name )
|
|
10
|
+
RailsDaemons.qualified_const_get( name.camelize )
|
|
11
|
+
rescue NameError => e
|
|
12
|
+
puts "Unknown daemon '#{name}'"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
desc "start <worker_name>", "Start background worker"
|
|
17
|
+
def start( name )
|
|
18
|
+
daemon = get_daemon( name )
|
|
19
|
+
return if daemon.nil?
|
|
20
|
+
daemon.new.daemonize
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
desc "restart <worker_name>", "Restart background worker (alias for start command)"
|
|
24
|
+
def restart( name )
|
|
25
|
+
invoke :start, [ name ]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
desc "stop <worker_name>", "Stop background worker"
|
|
29
|
+
def stop( name )
|
|
30
|
+
get_daemon( name ).stop
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module RailsDaemons
|
|
2
|
+
module Utils
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
def join( *paths )
|
|
6
|
+
# TODO: remove this dirty code
|
|
7
|
+
return Rails.root.join( *paths ) if Rails.root.to_s !~ /.*\/releases\/\d{14}/
|
|
8
|
+
|
|
9
|
+
paths = [ '..', '..', 'current' ] + paths
|
|
10
|
+
|
|
11
|
+
path = Rails.root.join( *paths )
|
|
12
|
+
FileUtils.mkdir_p( File.dirname( path ) )
|
|
13
|
+
path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def logger( file_name )
|
|
17
|
+
logger = Logger.new( join( 'log', file_name ) )
|
|
18
|
+
logger.level = Logger::INFO
|
|
19
|
+
logger.datetime_format = "%Y-%m-%d %H:%M:%S"
|
|
20
|
+
logger.formatter = Logger::Formatter.new
|
|
21
|
+
|
|
22
|
+
logger
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
require 'rails_daemons/utils'
|
|
2
|
+
require 'unicorn/util'
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
|
|
5
|
+
module RailsDaemons
|
|
6
|
+
module Worker
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
def daemonize
|
|
11
|
+
pid = fork do
|
|
12
|
+
$logger = Utils.logger( "#{self.class.worker_name}.#{Rails.env}.log" )
|
|
13
|
+
$logger.level = Logger::INFO
|
|
14
|
+
|
|
15
|
+
working
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Process.detach( pid )
|
|
19
|
+
|
|
20
|
+
puts "#{self.class.worker_name} (#{pid}) started."
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def working
|
|
24
|
+
$stop_working = false
|
|
25
|
+
$reopening = false
|
|
26
|
+
|
|
27
|
+
Signal.trap "INT" do
|
|
28
|
+
Thread.new do
|
|
29
|
+
shutdowning
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Signal.trap "USR1" do
|
|
34
|
+
$reopening = true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
stop_old_worker
|
|
38
|
+
|
|
39
|
+
starting
|
|
40
|
+
|
|
41
|
+
loop do
|
|
42
|
+
reopen_logs if $reopening
|
|
43
|
+
break if $stop_working
|
|
44
|
+
|
|
45
|
+
work
|
|
46
|
+
|
|
47
|
+
sleep tick
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def reopen_logs
|
|
52
|
+
pid = self.class.get_pid
|
|
53
|
+
|
|
54
|
+
logger.info "Reopen logs #{self.class.worker_name} (#{pid})"
|
|
55
|
+
|
|
56
|
+
Unicorn::Util.reopen_logs
|
|
57
|
+
|
|
58
|
+
logger.info "Logs reopened #{self.class.worker_name} (#{pid})"
|
|
59
|
+
|
|
60
|
+
$reopening = false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def logger
|
|
64
|
+
$logger
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def starting
|
|
68
|
+
$0 = "RAILS_ENV=#{Rails.env} " + Utils.join( '' ).to_s + " bundle exec thor daemon:start #{self.class}"
|
|
69
|
+
|
|
70
|
+
redirect_io
|
|
71
|
+
start
|
|
72
|
+
store_pid
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def redirect_io
|
|
76
|
+
# https://github.com/ghazel/daemons/blob/d09e132ea67001ba4d6bf6481fb53c4bd4fd9195/lib/daemons/daemonize.rb#L241
|
|
77
|
+
begin; STDIN.reopen "/dev/null"; rescue ::Exception; end
|
|
78
|
+
|
|
79
|
+
begin
|
|
80
|
+
STDOUT.reopen( Utils.join( 'log', "#{self.class.worker_name}.#{Rails.env}.out.log" ), "a" )
|
|
81
|
+
STDOUT.sync = true
|
|
82
|
+
rescue ::Exception
|
|
83
|
+
begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
begin; STDERR.reopen STDOUT; rescue ::Exception; end
|
|
87
|
+
STDERR.sync = true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def start
|
|
91
|
+
puts "Start #{self.class.name}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def work
|
|
95
|
+
raise 'Not implemented! Write your own work'
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def tick
|
|
99
|
+
1.0
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def shutdowning
|
|
103
|
+
pid = self.class.get_pid
|
|
104
|
+
|
|
105
|
+
logger.info "Graceful shutdown #{self.class.worker_name} (#{pid})"
|
|
106
|
+
$stop_working = true
|
|
107
|
+
|
|
108
|
+
shutdown
|
|
109
|
+
|
|
110
|
+
puts "#{self.class.worker_name} (#{pid}) stopped."
|
|
111
|
+
|
|
112
|
+
exit 0
|
|
113
|
+
|
|
114
|
+
rescue => e
|
|
115
|
+
logger.error "exiting #{self.class.worker_name}, unable to stop gracefully"
|
|
116
|
+
logger.error e.message
|
|
117
|
+
logger.error e.backtrace.join( "\n" )
|
|
118
|
+
|
|
119
|
+
File.remove( self.class.pid_file ) if File.exists?( self.class.pid_file )
|
|
120
|
+
|
|
121
|
+
exit 1
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def shutdown
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def stop_old_worker
|
|
128
|
+
return unless File.file?( self.class.pid_file )
|
|
129
|
+
|
|
130
|
+
pid = self.class.get_pid
|
|
131
|
+
|
|
132
|
+
unless self.class.running?( pid )
|
|
133
|
+
logger.info "Stale pid file (#{pid}), deleting"
|
|
134
|
+
File.delete( self.class.pid_file )
|
|
135
|
+
|
|
136
|
+
return
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
logger.info "Killing old worker (#{pid})"
|
|
140
|
+
Process.kill( "INT", pid )
|
|
141
|
+
|
|
142
|
+
# wait for die
|
|
143
|
+
32.times do
|
|
144
|
+
sleep 3
|
|
145
|
+
|
|
146
|
+
unless self.class.running?( pid )
|
|
147
|
+
logger.info "Old worker (#{pid}) died by himself"
|
|
148
|
+
|
|
149
|
+
return
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
return unless self.class.running?( pid )
|
|
154
|
+
|
|
155
|
+
logger.error "Old worker (#{pid}) isn't going to die, doing kill -9"
|
|
156
|
+
|
|
157
|
+
Process.kill( "KILL", pid )
|
|
158
|
+
File.delete( self.class.pid_file )
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def store_pid
|
|
162
|
+
FileUtils.mkdir_p( File.dirname( self.class.pid_file ) )
|
|
163
|
+
IO.write( self.class.pid_file, Process.pid.to_s )
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
module ClassMethods
|
|
168
|
+
def daemonize
|
|
169
|
+
self.new.daemonize
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def stop
|
|
173
|
+
pid = get_pid
|
|
174
|
+
|
|
175
|
+
if running?( pid )
|
|
176
|
+
Process.kill( 'INT', pid )
|
|
177
|
+
else
|
|
178
|
+
puts "Worker #{name} (#{pid}) not running"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def get_pid
|
|
183
|
+
return unless File.exists?( pid_file )
|
|
184
|
+
File.read( pid_file ).to_i
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def pid_file
|
|
188
|
+
Utils.join( 'tmp', 'pids', "#{worker_name}.#{Rails.env}.pid" )
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def worker_name
|
|
192
|
+
name.underscore
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def running?( pid )
|
|
196
|
+
return false if pid.blank?
|
|
197
|
+
|
|
198
|
+
# https://github.com/ghazel/daemons/blob/d09e132ea67001ba4d6bf6481fb53c4bd4fd9195/lib/daemons/pid.rb#L17
|
|
199
|
+
# Check if process is in existence
|
|
200
|
+
# The simplest way to do this is to send signal '0'
|
|
201
|
+
# (which is a single system call) that doesn't actually
|
|
202
|
+
# send a signal
|
|
203
|
+
begin
|
|
204
|
+
Process.kill(0, pid)
|
|
205
|
+
return true
|
|
206
|
+
rescue Errno::ESRCH
|
|
207
|
+
return false
|
|
208
|
+
rescue ::Exception # for example on EPERM (process exists but does not belong to us)
|
|
209
|
+
return true
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
require 'rails_daemons/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "rails_daemons"
|
|
7
|
+
spec.version = RailsDaemons::VERSION
|
|
8
|
+
spec.authors = ["Sergey Malykh"]
|
|
9
|
+
spec.email = ["xronos.i.am@gmail.com"]
|
|
10
|
+
spec.description = %q{Daemons for Rails. Can be restarted on the host by Thor or remotely by Capistrano, monitored by Monit}
|
|
11
|
+
spec.summary = %q{Daemons for Rails}
|
|
12
|
+
spec.homepage = "https://github.com/xronos-i-am/rails_daemons"
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
|
|
15
|
+
spec.files = `git ls-files`.split($/)
|
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
spec.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
spec.add_development_dependency "capistrano", '~> 3.3'
|
|
21
|
+
spec.add_development_dependency "bundler"
|
|
22
|
+
|
|
23
|
+
spec.add_dependency "unicorn"
|
|
24
|
+
spec.add_dependency "thor-rails"
|
|
25
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails_daemons
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sergey Malykh
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-01-16 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: capistrano
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.3'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.3'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: bundler
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: unicorn
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: thor-rails
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
description: Daemons for Rails. Can be restarted on the host by Thor or remotely by
|
|
70
|
+
Capistrano, monitored by Monit
|
|
71
|
+
email:
|
|
72
|
+
- xronos.i.am@gmail.com
|
|
73
|
+
executables:
|
|
74
|
+
- daemon
|
|
75
|
+
extensions: []
|
|
76
|
+
extra_rdoc_files: []
|
|
77
|
+
files:
|
|
78
|
+
- ".gitignore"
|
|
79
|
+
- ".ruby-gemset"
|
|
80
|
+
- ".ruby-version"
|
|
81
|
+
- Capfile
|
|
82
|
+
- Gemfile
|
|
83
|
+
- LICENSE.txt
|
|
84
|
+
- README.md
|
|
85
|
+
- Rakefile
|
|
86
|
+
- Thorfile
|
|
87
|
+
- bin/daemon
|
|
88
|
+
- lib/rails_daemons.rb
|
|
89
|
+
- lib/rails_daemons/capistrano.rb
|
|
90
|
+
- lib/rails_daemons/railtie.rb
|
|
91
|
+
- lib/rails_daemons/tasks/rails_daemons.cap
|
|
92
|
+
- lib/rails_daemons/tasks/rails_daemons.thor
|
|
93
|
+
- lib/rails_daemons/thor.rb
|
|
94
|
+
- lib/rails_daemons/utils.rb
|
|
95
|
+
- lib/rails_daemons/version.rb
|
|
96
|
+
- lib/rails_daemons/worker.rb
|
|
97
|
+
- rails_daemons.gemspec
|
|
98
|
+
homepage: https://github.com/xronos-i-am/rails_daemons
|
|
99
|
+
licenses:
|
|
100
|
+
- MIT
|
|
101
|
+
metadata: {}
|
|
102
|
+
post_install_message:
|
|
103
|
+
rdoc_options: []
|
|
104
|
+
require_paths:
|
|
105
|
+
- lib
|
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - ">="
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '0'
|
|
116
|
+
requirements: []
|
|
117
|
+
rubyforge_project:
|
|
118
|
+
rubygems_version: 2.4.4
|
|
119
|
+
signing_key:
|
|
120
|
+
specification_version: 4
|
|
121
|
+
summary: Daemons for Rails
|
|
122
|
+
test_files: []
|