sa-i_can_daemonize 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +58 -0
- data/README.txt +99 -0
- data/Rakefile +37 -0
- data/VERSION.yml +4 -0
- data/examples/feature_demo.rb +45 -0
- data/examples/rails_daemon.rb +20 -0
- data/examples/simple_daemon.rb +11 -0
- data/examples/starling_queue_daemon.rb +41 -0
- data/lib/i_can_daemonize.rb +515 -0
- data/test/simple_daemon.rb +29 -0
- data/test/test_helper.rb +14 -0
- data/test/test_i_can_daemonize.rb +47 -0
- metadata +70 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
.git/COMMIT_EDITMSG
|
2
|
+
.git/FETCH_HEAD
|
3
|
+
.git/HEAD
|
4
|
+
.git/ORIG_HEAD
|
5
|
+
.git/config
|
6
|
+
.git/description
|
7
|
+
.git/hooks/applypatch-msg
|
8
|
+
.git/hooks/commit-msg
|
9
|
+
.git/hooks/post-commit
|
10
|
+
.git/hooks/post-receive
|
11
|
+
.git/hooks/post-update
|
12
|
+
.git/hooks/pre-applypatch
|
13
|
+
.git/hooks/pre-commit
|
14
|
+
.git/hooks/pre-rebase
|
15
|
+
.git/hooks/prepare-commit-msg
|
16
|
+
.git/hooks/update
|
17
|
+
.git/index
|
18
|
+
.git/info/exclude
|
19
|
+
.git/logs/HEAD
|
20
|
+
.git/logs/refs/heads/master
|
21
|
+
.git/logs/refs/remotes/origin/HEAD
|
22
|
+
.git/logs/refs/remotes/origin/master
|
23
|
+
.git/objects/12/d7cbe8190afdd97a7a0859d9ce822ebc9846cd
|
24
|
+
.git/objects/46/c5d39136616bca92118f3dbf16e474a9df7379
|
25
|
+
.git/objects/4b/0480e9beae3ad3b1b17effeeaa1201560d7d02
|
26
|
+
.git/objects/61/22343c51b970ba436bc47e3cf97f6221492291
|
27
|
+
.git/objects/67/672462a81c734937a6b579751405f168e6fcb0
|
28
|
+
.git/objects/69/69a6a4e8a6d185528b33de649ea3feb1516fb7
|
29
|
+
.git/objects/6a/ddd18336cbaebdff0f727a06305c9232dbb058
|
30
|
+
.git/objects/6c/eeb3df1cb65a06e0b434b7a78551d1d7c1c242
|
31
|
+
.git/objects/73/ae5eefb32e4f335716aa281e9f78c3a61398b1
|
32
|
+
.git/objects/8f/9c4a5a3c9a41935160b96fe005a54b3d3f18b2
|
33
|
+
.git/objects/9c/878acd4b53fb8abaed6ba81ef78e2c3268c9f5
|
34
|
+
.git/objects/a3/5a57b3e8003dcdd039a48441554799b417779d
|
35
|
+
.git/objects/c3/d75d5327524a548ed2cfb997342b2107b83403
|
36
|
+
.git/objects/cb/34e6ff08e94036d69d296284a62d723a2d8a90
|
37
|
+
.git/objects/d4/b772650c4ad1378392aa5f2bb3c773a3818b73
|
38
|
+
.git/objects/f5/9a3860619e3b9d956caa110ff00d0449977fea
|
39
|
+
.git/objects/pack/pack-4efe288a1b0fd1df2942754b498c4480f00a7d52.idx
|
40
|
+
.git/objects/pack/pack-4efe288a1b0fd1df2942754b498c4480f00a7d52.keep
|
41
|
+
.git/objects/pack/pack-4efe288a1b0fd1df2942754b498c4480f00a7d52.pack
|
42
|
+
.git/refs/heads/master
|
43
|
+
.git/refs/remotes/origin/HEAD
|
44
|
+
.git/refs/remotes/origin/master
|
45
|
+
.gitignore
|
46
|
+
History.txt
|
47
|
+
Manifest.txt
|
48
|
+
README.txt
|
49
|
+
Rakefile
|
50
|
+
examples/feature_demo.rb
|
51
|
+
examples/rails_daemon.rb
|
52
|
+
examples/simple_daemon.rb
|
53
|
+
examples/starling_queue_daemon.rb
|
54
|
+
lib/i_can_daemonize.rb
|
55
|
+
lib/i_can_daemonize/version.rb
|
56
|
+
test/simple_daemon.rb
|
57
|
+
test/test_helper.rb
|
58
|
+
test/test_i_can_daemonize.rb
|
data/README.txt
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
= i_can_daemonize
|
2
|
+
|
3
|
+
== DESCRIPTION:
|
4
|
+
|
5
|
+
ICanDaemonize makes it dead simple to create daemons of your own.
|
6
|
+
|
7
|
+
== REQUIREMENTS:
|
8
|
+
|
9
|
+
* A Computer
|
10
|
+
* Ruby
|
11
|
+
|
12
|
+
== INSTALL:
|
13
|
+
|
14
|
+
* Get ICanDaemonize off github
|
15
|
+
|
16
|
+
== THE BASICS:
|
17
|
+
|
18
|
+
require 'rubygems'
|
19
|
+
require 'i_can_daemonize'
|
20
|
+
class MyDaemonClass
|
21
|
+
include ICanDaemonize
|
22
|
+
|
23
|
+
daemonize do
|
24
|
+
# your code here
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
Run your daemon
|
30
|
+
|
31
|
+
ruby your_daemon_script start
|
32
|
+
|
33
|
+
ICD will create a log file in log/ called your_daemon_script.log as well as a pid file in log called your_daemon_script.pid
|
34
|
+
|
35
|
+
It will essentially run the block given to daemonize within a loop.
|
36
|
+
|
37
|
+
== USAGE:
|
38
|
+
|
39
|
+
The daemonize method accepts a number of options see ICanDaemonize::ClassMethods daemonize() for options
|
40
|
+
|
41
|
+
There are a number of other blocks you can define in your class as well, including:
|
42
|
+
|
43
|
+
before do/end
|
44
|
+
|
45
|
+
after do/end
|
46
|
+
|
47
|
+
die_if do/end
|
48
|
+
|
49
|
+
exit_if do/end
|
50
|
+
|
51
|
+
See ICanDaemonize docs for more info on these options.
|
52
|
+
|
53
|
+
Your daemon can be called with a number of options
|
54
|
+
|
55
|
+
--loop-every SECONDS How long to sleep between each loop
|
56
|
+
-t, --ontop Stay on top (does not daemonize)
|
57
|
+
--instances=NUM Allow multiple instances to run simultaneously? 0 for infinite. default: 1
|
58
|
+
--log-file=LOGFILE Logfile to log to
|
59
|
+
--pid-file=PIDFILE Directory to put pidfile
|
60
|
+
-h, --help Show this message
|
61
|
+
|
62
|
+
You can add your own command line options by using the 'arg' class macro.
|
63
|
+
See http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html for more info on OptionParser.
|
64
|
+
|
65
|
+
arg('--scott-rocks', 'Does scott rock?') do |value|
|
66
|
+
@scott_rocks = value
|
67
|
+
end
|
68
|
+
|
69
|
+
== BUGS:
|
70
|
+
|
71
|
+
My method names may be too common to be included class methods
|
72
|
+
|
73
|
+
ICanDaemonize attempts to capture all STDOUT or STDERR and prepend that output with a timestamp and PID.
|
74
|
+
I don't like how this was implemented anyway. Feels dirty.
|
75
|
+
|
76
|
+
== LICENSE:
|
77
|
+
|
78
|
+
(The MIT License)
|
79
|
+
|
80
|
+
Copyright (c) 2009 Adam Pisoni, Amos Elliston
|
81
|
+
|
82
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
83
|
+
a copy of this software and associated documentation files (the
|
84
|
+
'Software'), to deal in the Software without restriction, including
|
85
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
86
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
87
|
+
permit persons to whom the Software is furnished to do so, subject to
|
88
|
+
the following conditions:
|
89
|
+
|
90
|
+
The above copyright notice and this permission notice shall be
|
91
|
+
included in all copies or substantial portions of the Software.
|
92
|
+
|
93
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
94
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
95
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
96
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
97
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
98
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
99
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rcov/rcovtask'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |s|
|
9
|
+
s.name = "sa-i_can_daemonize"
|
10
|
+
s.summary = "ICanDaemonize makes it dead simple to create daemons of your own"
|
11
|
+
s.email = "wonko9@gmail.com"
|
12
|
+
s.homepage = "http://github.com/sonian/i_can_daemonize"
|
13
|
+
s.description = "ICanDaemonize makes it dead simple to create daemons of your own"
|
14
|
+
s.authors = ["Adam Pisoni", "Amos Elliston"]
|
15
|
+
s.files = FileList["[A-Z]*", "{lib,test}/**/*"]
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
19
|
+
end
|
20
|
+
|
21
|
+
Rake::TestTask.new
|
22
|
+
|
23
|
+
Rake::RDocTask.new do |rdoc|
|
24
|
+
rdoc.rdoc_dir = 'rdoc'
|
25
|
+
rdoc.title = 'test'
|
26
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
27
|
+
rdoc.rdoc_files.include('README*')
|
28
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
29
|
+
end
|
30
|
+
|
31
|
+
Rcov::RcovTask.new do |t|
|
32
|
+
t.libs << 'test'
|
33
|
+
t.test_files = FileList['test/**/*_test.rb']
|
34
|
+
t.verbose = true
|
35
|
+
end
|
36
|
+
|
37
|
+
task :default => :test
|
data/VERSION.yml
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'i_can_daemonize'
|
3
|
+
|
4
|
+
class ICanDaemonize::FeatureDemo
|
5
|
+
include ICanDaemonize
|
6
|
+
|
7
|
+
def self.define_args(args)
|
8
|
+
# "See the OptionParser docs for more info on how to define your own args.\n http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html\n"
|
9
|
+
@options[:nobugs] = true
|
10
|
+
args.on("--scott-rocks=TRUE", "Thanks scott") do |t|
|
11
|
+
@options[:scott_rocks] = t
|
12
|
+
end
|
13
|
+
args.on("--nobugs=TRUE", "No bugs flag") do |t|
|
14
|
+
@options[:nobugs] = false if t == "1" or t.downcase == "false"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
before do
|
19
|
+
puts "The before block is executed after daemonizing, but before looping over the daemonize block"
|
20
|
+
if @options[:nobugs]
|
21
|
+
puts "Running with no bugs. Pass nobugs=false to run with bugs."
|
22
|
+
else
|
23
|
+
puts "There mite be busg"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
after do
|
28
|
+
puts "The after block is executed before the program exits gracefully, but is not run if the program dies."
|
29
|
+
end
|
30
|
+
|
31
|
+
die_if do
|
32
|
+
puts "The die_if block is executed after every loop and dies if true is returned."
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
exit_if do
|
37
|
+
puts "The exit_if block is executed after every loop and exits gracefully if true is returned."
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
daemonize(:loop_every => 3, :timeout=>2, :die_on_timeout => false) do
|
42
|
+
puts "The daemonize block is called in a loop."
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'i_can_daemonize'
|
3
|
+
begin
|
4
|
+
require File.dirname(__FILE__) + "/../config/environment"
|
5
|
+
rescue LoadError
|
6
|
+
puts "\n****** ERROR LOADING RAILS ******\n\trails_daemon.rb should be put in your RAILS_ROOT/script directory so it can find your environment.rb\n\tOr you can change the environment require on line 4.\n*********************************\n\n"
|
7
|
+
end
|
8
|
+
|
9
|
+
class ICanDaemonize::RailsDaemon
|
10
|
+
include ICanDaemonize
|
11
|
+
|
12
|
+
before do
|
13
|
+
puts "This daemon has access to your entire rails stack and will log to RAILS_ROOT/log"
|
14
|
+
end
|
15
|
+
|
16
|
+
daemonize(:loop_every => 3, :timeout=>2, :die_on_timeout => false) do
|
17
|
+
puts "The daemonize block is called in a loop."
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'pp'
|
3
|
+
require 'i_can_daemonize'
|
4
|
+
begin
|
5
|
+
require 'starling'
|
6
|
+
rescue LoadError
|
7
|
+
puts "\n****** ERROR LOADING STARLING ******\n\tStarling is not installed. Please run 'sudo gem install starling' before running this script.\n*********************************\n\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
class ICanDaemonize::StarlingDaemon
|
11
|
+
include ICanDaemonize
|
12
|
+
|
13
|
+
if ARGV.include?('start')
|
14
|
+
puts <<-DOC
|
15
|
+
|
16
|
+
This daemon will listen to a starling queue called '#{@queue_name}' and print out whatever is added
|
17
|
+
First tail this daemon's log in another window.
|
18
|
+
The log is @ #{log_file}
|
19
|
+
Run irb at the console and type
|
20
|
+
> require 'rubygems'
|
21
|
+
> require 'starling'
|
22
|
+
> starling = Starling.new('127.0.0.1:22122')
|
23
|
+
> starling.set('#{@queue_name}','Hi there!')
|
24
|
+
Now watch the log file.
|
25
|
+
|
26
|
+
DOC
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
@queue_name = "starlingdeamon"
|
31
|
+
@starling = Starling.new("127.0.0.1:22122")
|
32
|
+
@fetch_count = 0
|
33
|
+
end
|
34
|
+
|
35
|
+
daemonize(:log_prefix => false) do
|
36
|
+
puts "Trying to fetch from the '#{@queue_name}' queue. Dequeued #{@fetch_count} so far"
|
37
|
+
pp "GOT: ", @starling.get(@queue_name)
|
38
|
+
@fetch_count += 1
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,515 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module ICanDaemonize
|
5
|
+
class DieTime < StandardError; end
|
6
|
+
class TimeoutError < StandardError; end
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.extend ClassMethods
|
10
|
+
base.initialize_options
|
11
|
+
end
|
12
|
+
|
13
|
+
class Config
|
14
|
+
METHODS = [:script_path]
|
15
|
+
CONFIG = {}
|
16
|
+
def method_missing(name, *args)
|
17
|
+
name = name.to_s.upcase.to_sym
|
18
|
+
if name.to_s =~ /^(.*)=$/
|
19
|
+
name = $1.to_sym
|
20
|
+
CONFIG[name] = args.first
|
21
|
+
else
|
22
|
+
CONFIG[name]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
|
29
|
+
def initialize_options
|
30
|
+
@@config = Config.new
|
31
|
+
@@config.script_path = File.expand_path(File.dirname($0))
|
32
|
+
$0 = script_name
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_options
|
36
|
+
opts = OptionParser.new do |opt|
|
37
|
+
opt.banner = "Usage: #{script_name} [options] [start|stop]"
|
38
|
+
|
39
|
+
opt.on_tail('-h', '--help', 'Show this message') do
|
40
|
+
puts opt
|
41
|
+
exit(1)
|
42
|
+
end
|
43
|
+
|
44
|
+
opt.on('--loop-every=SECONDS', 'How long to sleep between each loop') do |value|
|
45
|
+
options[:loop_every] = value
|
46
|
+
end
|
47
|
+
|
48
|
+
opt.on('-t', '--ontop', 'Stay on top (does not daemonize)') do
|
49
|
+
options[:ontop] = true
|
50
|
+
end
|
51
|
+
|
52
|
+
opt.on('--instances=NUM', 'Allow multiple instances to run simultaneously? 0 for infinite. default: 1') do |value|
|
53
|
+
self.instances = value.to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
opt.on('--log-file=LOGFILE', 'Logfile to log to') do |value|
|
57
|
+
options[:log_file] = File.expand_path(value)
|
58
|
+
end
|
59
|
+
|
60
|
+
opt.on('--pid-file=PIDFILE', 'Location of pidfile') do |value|
|
61
|
+
options[:pid_file] = File.expand_path(value)
|
62
|
+
end
|
63
|
+
|
64
|
+
opt.on('--no-log-prefix', 'Do not prefix PID and date/time in log file.') do
|
65
|
+
options[:log_prefix] = false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
extra_args.each do |arg|
|
70
|
+
opts.on(*arg.first) do |value|
|
71
|
+
arg.last.call(value) if arg.last
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.parse!
|
76
|
+
|
77
|
+
if ARGV.include?('stop')
|
78
|
+
stop_daemons
|
79
|
+
elsif ARGV.include?('restart')
|
80
|
+
restart_daemons
|
81
|
+
elsif ARGV.include?('rotate')
|
82
|
+
rotate_daemons
|
83
|
+
elsif ARGV.include?('start') or ontop?
|
84
|
+
self.running = true
|
85
|
+
self.restarted = true if ARGV.include?('HUP')
|
86
|
+
else
|
87
|
+
puts opts.help
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def arg(*args, &block)
|
92
|
+
self.extra_args << [args, block]
|
93
|
+
end
|
94
|
+
|
95
|
+
def extra_args
|
96
|
+
@extra_args ||= []
|
97
|
+
end
|
98
|
+
|
99
|
+
def callbacks
|
100
|
+
@callbacks ||= {}
|
101
|
+
end
|
102
|
+
|
103
|
+
def options
|
104
|
+
@options ||= {}
|
105
|
+
end
|
106
|
+
|
107
|
+
def options=(options)
|
108
|
+
@options = options
|
109
|
+
end
|
110
|
+
|
111
|
+
def config
|
112
|
+
yield @@config
|
113
|
+
end
|
114
|
+
|
115
|
+
def before(&block)
|
116
|
+
callbacks[:before] = block
|
117
|
+
end
|
118
|
+
|
119
|
+
def after(&block)
|
120
|
+
callbacks[:after] = block
|
121
|
+
end
|
122
|
+
|
123
|
+
def sig(*signals, &block)
|
124
|
+
signals.each do |s|
|
125
|
+
callbacks["sig_#{s}".to_sym] = block
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def die_if(method=nil,&block)
|
130
|
+
options[:die_if] = method || block
|
131
|
+
end
|
132
|
+
|
133
|
+
def exit_if(method=nil,&block)
|
134
|
+
options[:exit_if] = method || block
|
135
|
+
end
|
136
|
+
|
137
|
+
def callback!(callback)
|
138
|
+
callbacks[callback].call if callbacks[callback]
|
139
|
+
end
|
140
|
+
|
141
|
+
# options may include:
|
142
|
+
#
|
143
|
+
# <tt>:loop_every</tt> Fixnum (DEFAULT 0)
|
144
|
+
# How many seconds to sleep between calls to your block
|
145
|
+
#
|
146
|
+
# <tt>:timeout</tt> Fixnum (DEFAULT 0)
|
147
|
+
# Timeout in if block does not execute withing passed number of seconds
|
148
|
+
#
|
149
|
+
# <tt>:kill_timeout</tt> Fixnum (DEFAULT 120)
|
150
|
+
# Wait number of seconds before using kill -9 on daemon
|
151
|
+
#
|
152
|
+
# <tt>:die_on_timeout</tt> BOOL (DEFAULT False)
|
153
|
+
# Should the daemon continue running if a block times out, or just run the block again
|
154
|
+
#
|
155
|
+
# <tt>:ontop</tt> BOOL (DEFAULT False)
|
156
|
+
# Do not daemonize. Run in current process
|
157
|
+
#
|
158
|
+
# <tt>:before</tt> BLOCK
|
159
|
+
# Run this block after daemonizing but before begining the daemonize loop.
|
160
|
+
# You can also define the before block by putting a before do/end block in your class.
|
161
|
+
#
|
162
|
+
# <tt>:after</tt> BLOCK
|
163
|
+
# Run this block before program exists.
|
164
|
+
# You can also define the after block by putting an after do/end block in your class.
|
165
|
+
#
|
166
|
+
# <tt>:die_if</tt> BLOCK
|
167
|
+
# Run this check after each iteration of the loop. If the block returns true, throw a DieTime exception and exit
|
168
|
+
# You can also define the after block by putting an die_if do/end block in your class.
|
169
|
+
#
|
170
|
+
# <tt>:exit_if</tt> BLOCK
|
171
|
+
# Run this check after each iteration of the loop. If the block returns true, exit gracefully
|
172
|
+
# You can also define the after block by putting an exit_if do/end block in your class.
|
173
|
+
#
|
174
|
+
# <tt>:log_prefix</tt> BOOL (DEFAULT true)
|
175
|
+
# Prefix log file entries with PID and timestamp
|
176
|
+
def daemonize(opts={}, &block)
|
177
|
+
self.options = opts
|
178
|
+
parse_options
|
179
|
+
return unless ok_to_start?
|
180
|
+
|
181
|
+
puts "Starting #{instances_to_start} #{script_name} #{pluralize('instance', instances_to_start)}..."
|
182
|
+
puts "Logging to: #{log_file}" unless ontop?
|
183
|
+
|
184
|
+
unless ontop?
|
185
|
+
instances_to_start.times do
|
186
|
+
safefork do
|
187
|
+
open(pid_file, 'a+') {|f| f << Process.pid << "\n"}
|
188
|
+
at_exit { remove_pid! }
|
189
|
+
|
190
|
+
|
191
|
+
trap('TERM') { callback!(:sig_term) ; self.running = false }
|
192
|
+
trap('INT') { callback!(:sig_int) ; Process.kill('TERM', $$) }
|
193
|
+
trap('HUP') { callback!(:sig_hup) ; restart_self }
|
194
|
+
trap('USR1') { callback!(:sig_usr1) ; reopen_filehandes }
|
195
|
+
|
196
|
+
sess_id = Process.setsid
|
197
|
+
reopen_filehandes
|
198
|
+
|
199
|
+
begin
|
200
|
+
at_exit { callback!(:after) }
|
201
|
+
callback!(:before)
|
202
|
+
run_block(&block)
|
203
|
+
rescue SystemExit
|
204
|
+
rescue Exception => e
|
205
|
+
$stdout.puts "Something bad happened #{e.inspect} #{e.backtrace.join("\n")}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
else
|
210
|
+
begin
|
211
|
+
callback!(:before)
|
212
|
+
run_block(&block)
|
213
|
+
rescue SystemExit, Interrupt
|
214
|
+
callback!(:after)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
|
221
|
+
def run_block(&block)
|
222
|
+
loop do
|
223
|
+
break unless running?
|
224
|
+
|
225
|
+
if options[:timeout]
|
226
|
+
begin
|
227
|
+
Timeout::timeout(options[:timeout].to_i) do
|
228
|
+
block.call if block
|
229
|
+
end
|
230
|
+
rescue Timeout::Error => e
|
231
|
+
if options[:die_on_timeout]
|
232
|
+
raise TimeoutError.new("#{self} timed out after #{options[:timeout]} seconds while executing block in loop")
|
233
|
+
else
|
234
|
+
$stderr.puts "#{self} timed out after #{options[:timeout]} seconds while executing block in loop #{e.backtrace.join("\n")}"
|
235
|
+
end
|
236
|
+
end
|
237
|
+
else
|
238
|
+
block.call if block
|
239
|
+
end
|
240
|
+
|
241
|
+
if options[:loop_every]
|
242
|
+
sleep options[:loop_every].to_i
|
243
|
+
elsif not block
|
244
|
+
sleep 0.1
|
245
|
+
end
|
246
|
+
|
247
|
+
break if should_exit?
|
248
|
+
raise DieTime.new('Die if conditions were met!') if should_die?
|
249
|
+
end
|
250
|
+
exit(0)
|
251
|
+
end
|
252
|
+
|
253
|
+
def should_die?
|
254
|
+
die_if = options[:die_if]
|
255
|
+
if die_if
|
256
|
+
if die_if.is_a?(Symbol) or die_if.is_a?(String)
|
257
|
+
self.send(die_if)
|
258
|
+
elsif die_if.is_a?(Proc)
|
259
|
+
die_if.call
|
260
|
+
end
|
261
|
+
else
|
262
|
+
false
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def should_exit?
|
267
|
+
exit_if = options[:exit_if]
|
268
|
+
if exit_if
|
269
|
+
if exit_if.is_a?(Symbol) or exit_if.is_a?(String)
|
270
|
+
self.send(exit_if.to_sym)
|
271
|
+
elsif exit_if.is_a?(Proc)
|
272
|
+
exit_if.call
|
273
|
+
end
|
274
|
+
else
|
275
|
+
false
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def instances_to_start
|
280
|
+
return 1 if restarted?
|
281
|
+
instances - pids.size
|
282
|
+
end
|
283
|
+
|
284
|
+
def ok_to_start?
|
285
|
+
return false unless running?
|
286
|
+
return true if restarted?
|
287
|
+
|
288
|
+
living_pids = []
|
289
|
+
if pids and pids.any?
|
290
|
+
pids.each do |pid|
|
291
|
+
if process_alive?(pid)
|
292
|
+
living_pids << pid
|
293
|
+
else
|
294
|
+
$stderr.puts "Removing stale pid: #{pid}..."
|
295
|
+
pids.delete(pid)
|
296
|
+
self.pids = pids
|
297
|
+
end
|
298
|
+
end
|
299
|
+
if instances > 0 and living_pids.size >= instances
|
300
|
+
$stderr.puts "#{script_name} is already running #{living_pids.size} out of #{instances} #{pluralize('instance', instances)}"
|
301
|
+
return false
|
302
|
+
end
|
303
|
+
end
|
304
|
+
return true
|
305
|
+
end
|
306
|
+
|
307
|
+
# stop the daemon, nicely at first, and then forcefully if necessary
|
308
|
+
def stop_daemons
|
309
|
+
self.running = false
|
310
|
+
pids_to_stop = @instances || pids.size
|
311
|
+
puts "Stopping #{pids_to_stop} #{script_name} #{pluralize('instance', pids_to_stop)}..."
|
312
|
+
if pids.empty?
|
313
|
+
$stderr.puts "#{script_name} doesn't appear to be running"
|
314
|
+
exit(1)
|
315
|
+
end
|
316
|
+
pids.each_with_index do |pid, ii|
|
317
|
+
kill_pid(pid)
|
318
|
+
break if ii == (pids_to_stop - 1)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def rotate_daemons
|
323
|
+
if pids.empty?
|
324
|
+
$stdout.puts "#{script_name} doesn't appear to be running!"
|
325
|
+
exit(1)
|
326
|
+
end
|
327
|
+
|
328
|
+
$stdout.puts "Sending SIGUSR1 to #{pids.join(', ')} ..."
|
329
|
+
pids.each do |pid|
|
330
|
+
begin
|
331
|
+
Process.kill('USR1', pid)
|
332
|
+
rescue Errno::ESRCH
|
333
|
+
$stdout.puts("Couldn't send USR1 #{pid} as it wasn't running")
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def restart_daemons
|
339
|
+
pids.each do |pid|
|
340
|
+
kill_pid(pid, 'HUP')
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def kill_pid(pid, signal='TERM')
|
345
|
+
$stdout.puts("Stopping pid #{pid} with #{signal}...")
|
346
|
+
begin
|
347
|
+
Process.kill(signal, pid)
|
348
|
+
if pid_running?(pid, options[:kill_timeout] || 120)
|
349
|
+
$stdout.puts("Using kill -9 #{pid}")
|
350
|
+
Process.kill(9, pid)
|
351
|
+
else
|
352
|
+
$stdout.puts("Process #{pid} stopped")
|
353
|
+
end
|
354
|
+
rescue Errno::ESRCH
|
355
|
+
$stdout.puts("Couldn't #{signal} #{pid} as it wasn't running")
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
def pid_running?(pid,time_to_wait=0)
|
360
|
+
times_to_check = 1
|
361
|
+
if time_to_wait > 0.5
|
362
|
+
times_to_check = (time_to_wait / 0.5).to_i
|
363
|
+
end
|
364
|
+
|
365
|
+
begin
|
366
|
+
times_to_check.times do
|
367
|
+
Process.kill(0, pid)
|
368
|
+
sleep 0.5
|
369
|
+
end
|
370
|
+
return true
|
371
|
+
rescue Errno::ESRCH
|
372
|
+
return false
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def restart_self
|
377
|
+
remove_pid!
|
378
|
+
cmd = "#{@@config.script_path}/#{script_name} "
|
379
|
+
cmd << 'HUP ' unless ARGV.include?('HUP')
|
380
|
+
cmd << ARGV.join(' ')
|
381
|
+
puts "Restarting #{cmd} pid: #{$$}..."
|
382
|
+
system(cmd)
|
383
|
+
Process.kill('TERM', $$)
|
384
|
+
end
|
385
|
+
|
386
|
+
def safefork(&block)
|
387
|
+
fork_tries ||= 0
|
388
|
+
fork(&block)
|
389
|
+
rescue Errno::EWOULDBLOCK
|
390
|
+
raise if fork_tries >= 20
|
391
|
+
fork_tries += 1
|
392
|
+
sleep 5
|
393
|
+
retry
|
394
|
+
end
|
395
|
+
|
396
|
+
def process_alive?(process_pid)
|
397
|
+
Process.kill(0, process_pid)
|
398
|
+
return true
|
399
|
+
rescue Errno::ESRCH => e
|
400
|
+
return false
|
401
|
+
end
|
402
|
+
|
403
|
+
LOG_FORMAT = '%-6d %-19s %s'
|
404
|
+
TIME_FORMAT = '%Y/%m/%d %H:%M:%S'
|
405
|
+
def reopen_filehandes
|
406
|
+
STDIN.reopen('/dev/null')
|
407
|
+
STDOUT.reopen(log_file, 'a')
|
408
|
+
STDOUT.sync = true
|
409
|
+
STDERR.reopen(STDOUT)
|
410
|
+
|
411
|
+
if log_prefix?
|
412
|
+
def STDOUT.write(string)
|
413
|
+
if @no_prefix
|
414
|
+
@no_prefix = false if string[-1, 1] == "\n"
|
415
|
+
else
|
416
|
+
string = LOG_FORMAT % [$$, Time.now.strftime(TIME_FORMAT), string]
|
417
|
+
@no_prefix = true
|
418
|
+
end
|
419
|
+
super(string)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def remove_pid!(pid=Process.pid)
|
425
|
+
pids.delete(pid)
|
426
|
+
self.pids = pids
|
427
|
+
end
|
428
|
+
|
429
|
+
def pids=(pids)
|
430
|
+
if pids.any?
|
431
|
+
open(pid_file, 'w') {|f| f << pids.join("\n") << "\n"}
|
432
|
+
else
|
433
|
+
File.unlink(pid_file) if File.exists?(pid_file)
|
434
|
+
end
|
435
|
+
@pids = pids
|
436
|
+
end
|
437
|
+
|
438
|
+
def pids
|
439
|
+
@pids ||= begin
|
440
|
+
if File.exist?(pid_file)
|
441
|
+
File.readlines(pid_file).collect {|p| p.to_i}
|
442
|
+
else
|
443
|
+
[]
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def instances=(num)
|
449
|
+
@instances = num
|
450
|
+
end
|
451
|
+
|
452
|
+
def instances
|
453
|
+
@instances || 1
|
454
|
+
end
|
455
|
+
|
456
|
+
def pluralize(name, num)
|
457
|
+
num == 1 ? name : "#{name}s"
|
458
|
+
end
|
459
|
+
|
460
|
+
def running?
|
461
|
+
@running || false
|
462
|
+
end
|
463
|
+
|
464
|
+
def running=(bool)
|
465
|
+
@running = bool
|
466
|
+
end
|
467
|
+
|
468
|
+
def restarted?
|
469
|
+
@restarted || false
|
470
|
+
end
|
471
|
+
|
472
|
+
def restarted=(bool)
|
473
|
+
@restarted = bool
|
474
|
+
end
|
475
|
+
|
476
|
+
def ontop?
|
477
|
+
options[:ontop]
|
478
|
+
end
|
479
|
+
|
480
|
+
def log_prefix?
|
481
|
+
options[:log_prefix] || true
|
482
|
+
end
|
483
|
+
|
484
|
+
LOG_PATHS = ['log/', 'logs/', '../log/', '../logs/', '../../log', '../../logs', '.']
|
485
|
+
LOG_PATHS.unshift("#{RAILS_ROOT}/log") if defined?(RAILS_ROOT)
|
486
|
+
def log_dir
|
487
|
+
options[:log_dir] ||= begin
|
488
|
+
LOG_PATHS.detect do |path|
|
489
|
+
File.exists?(File.expand_path(path))
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
def log_file
|
495
|
+
options[:log_file] ||= File.expand_path("#{log_dir}/#{script_name}.log")
|
496
|
+
end
|
497
|
+
|
498
|
+
def pid_dir
|
499
|
+
options[:pid_dir] ||= log_dir
|
500
|
+
end
|
501
|
+
|
502
|
+
def pid_file
|
503
|
+
options[:pid_file] ||= File.expand_path("#{pid_dir}/#{script_name}.pid")
|
504
|
+
end
|
505
|
+
|
506
|
+
def script_name
|
507
|
+
@script_name ||= File.basename($0).gsub('.rb', '')
|
508
|
+
end
|
509
|
+
|
510
|
+
def script_name=(script_name)
|
511
|
+
@script_name = script_name
|
512
|
+
end
|
513
|
+
|
514
|
+
end
|
515
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/i_can_daemonize'
|
3
|
+
|
4
|
+
class SimpleDaemon
|
5
|
+
include ICanDaemonize
|
6
|
+
|
7
|
+
arg '--test=VALUE', 'Test Arg' do |value|
|
8
|
+
@test = value
|
9
|
+
end
|
10
|
+
|
11
|
+
arg '-s', '--short-test=VALUE', 'Test arg with shortname' do |value|
|
12
|
+
@short_test = value
|
13
|
+
end
|
14
|
+
|
15
|
+
sig(:int, :term) do
|
16
|
+
end
|
17
|
+
|
18
|
+
counter = 0
|
19
|
+
daemonize do
|
20
|
+
if @options[:loop_every]
|
21
|
+
counter += 1
|
22
|
+
File.open(TEST_FILE, 'w'){|f| f << counter}
|
23
|
+
elsif @test
|
24
|
+
File.open(TEST_FILE, 'w'){|f| f << "#{@test}|#{@short_test}"}
|
25
|
+
else
|
26
|
+
File.open(TEST_FILE, 'w'){|f| f << "#{log_file}|#{pid_file}"}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
TEST_FILE = File.dirname(__FILE__) + '/test.txt' unless defined?(TEST_FILE)
|
5
|
+
|
6
|
+
unless Test::Unit::TestCase.respond_to?(:test)
|
7
|
+
class << Test::Unit::TestCase
|
8
|
+
def test(name, &block)
|
9
|
+
test_name = "test_#{name.gsub(/[\s\W]/,'_')}"
|
10
|
+
raise ArgumentError, "#{test_name} is already defined" if self.instance_methods.include? test_name
|
11
|
+
define_method test_name, &block
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class TestICanDaemonize < Test::Unit::TestCase
|
4
|
+
DEFAULT_LOG_FILE = File.dirname(__FILE__) + '/simple_daemon.log'
|
5
|
+
|
6
|
+
def setup
|
7
|
+
File.delete(TEST_FILE) if File.exist?(TEST_FILE)
|
8
|
+
@daemon = "#{File.dirname(__FILE__)}/simple_daemon.rb"
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
File.delete(TEST_FILE) if File.exist?(TEST_FILE)
|
13
|
+
File.delete(DEFAULT_LOG_FILE) if File.exist?(DEFAULT_LOG_FILE)
|
14
|
+
end
|
15
|
+
|
16
|
+
test "passing options" do
|
17
|
+
log_file = File.expand_path(File.join(File.dirname(__FILE__), 'test.log'))
|
18
|
+
pid_file = File.expand_path(File.join(File.dirname(__FILE__), 'test.pid'))
|
19
|
+
`ruby #{@daemon} --log-file #{log_file} --pid-file #{pid_file} start`
|
20
|
+
`ruby #{@daemon} --log-file #{log_file} --pid-file #{pid_file} stop`
|
21
|
+
File.delete(log_file)
|
22
|
+
assert_equal "#{log_file}|#{pid_file}", File.read(TEST_FILE)
|
23
|
+
end
|
24
|
+
|
25
|
+
test "loop every" do
|
26
|
+
`ruby #{@daemon} --loop-every 1 start`
|
27
|
+
sleep 5
|
28
|
+
`ruby #{@daemon} stop`
|
29
|
+
counter = File.read(TEST_FILE).to_i
|
30
|
+
assert counter > 5
|
31
|
+
end
|
32
|
+
|
33
|
+
test "arg class macro" do
|
34
|
+
`ruby #{@daemon} --test test -s short-test start`
|
35
|
+
`ruby #{@daemon} stop`
|
36
|
+
assert_equal "test|short-test", File.read(TEST_FILE)
|
37
|
+
end
|
38
|
+
|
39
|
+
test "delete stale pid" do
|
40
|
+
pidfile = File.dirname(__FILE__) + '/testpids.pid'
|
41
|
+
File.open(pidfile, 'w'){|f| f << '999999999'}
|
42
|
+
`ruby #{@daemon} start --pid-file=#{pidfile}`
|
43
|
+
`ruby #{@daemon} stop --pid-file=#{pidfile}`
|
44
|
+
assert !File.exist?(pidfile)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sa-i_can_daemonize
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Pisoni
|
8
|
+
- Amos Elliston
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-11-23 00:00:00 -02:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: ICanDaemonize makes it dead simple to create daemons of your own
|
18
|
+
email: wonko9@gmail.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files:
|
24
|
+
- README.txt
|
25
|
+
files:
|
26
|
+
- History.txt
|
27
|
+
- Manifest.txt
|
28
|
+
- README.txt
|
29
|
+
- Rakefile
|
30
|
+
- VERSION.yml
|
31
|
+
- lib/i_can_daemonize.rb
|
32
|
+
- test/simple_daemon.rb
|
33
|
+
- test/test_helper.rb
|
34
|
+
- test/test_i_can_daemonize.rb
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://github.com/sonian/i_can_daemonize
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- --charset=UTF-8
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.3.5
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: ICanDaemonize makes it dead simple to create daemons of your own
|
63
|
+
test_files:
|
64
|
+
- test/simple_daemon.rb
|
65
|
+
- test/test_helper.rb
|
66
|
+
- test/test_i_can_daemonize.rb
|
67
|
+
- examples/feature_demo.rb
|
68
|
+
- examples/rails_daemon.rb
|
69
|
+
- examples/simple_daemon.rb
|
70
|
+
- examples/starling_queue_daemon.rb
|