simple_daemon 0.1.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.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/lib/simple_daemon.rb +221 -0
- data/simple_daemon.gemspec +57 -0
- data/test/helper.rb +10 -0
- data/test/test_simple_daemon.rb +7 -0
- metadata +107 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 watsonian
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
= simple_daemon
|
2
|
+
|
3
|
+
This is a simple library for daemonizing scripts. It was extracted from Kenneth
|
4
|
+
Kalmer's excellent daemon-kit project (http://github.com/kennethkalmer/daemon-kit).
|
5
|
+
The daemon-kit framework is amazing for bootstrapping your daemons, but if
|
6
|
+
you need something more lightweight (e.g., a single file daemon), then you're
|
7
|
+
out of luck (for now). There are a number of daemon gem libraries available, but
|
8
|
+
I was most impressed by the daemon-kit setup. This library lets you use the well
|
9
|
+
thought out daemonization process from daemon-kit for simpler daemon setups.
|
10
|
+
|
11
|
+
= Usage
|
12
|
+
|
13
|
+
You set configuration options as follows:
|
14
|
+
|
15
|
+
SimpleDaemon.setup do |config
|
16
|
+
config.daemon_name = "some_name"
|
17
|
+
config.pid_file = "/var/run/some_name.pid"
|
18
|
+
end
|
19
|
+
|
20
|
+
To actually daemonize your script, just run the code you want to run daemonized
|
21
|
+
in a block as follows:
|
22
|
+
|
23
|
+
SimpleDaemon.daemonized do
|
24
|
+
loop {
|
25
|
+
# do something useful here
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
The pid file is automatically created and the script is backgrounded when run. When
|
30
|
+
the process exits the pid file is cleaned up automatically. If the process is dies
|
31
|
+
or is forcibly killed with a KILL signal then the pid file will stay in place, but
|
32
|
+
the daemonized blocks check to see if the process in the pid file is running or not.
|
33
|
+
If it isn't, it will spin up a new process and replace the existing pid file. Keep
|
34
|
+
in mind that there's no facility (yet) to handle the case where the process is still
|
35
|
+
running, but no pid file exists.
|
36
|
+
|
37
|
+
== Copyright
|
38
|
+
|
39
|
+
Copyright (c) 2010 Joel Watson. Portions copyright Kenneth Kalmer. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "simple_daemon"
|
8
|
+
gem.summary = %Q{A simple library for daemonizing scripts.}
|
9
|
+
gem.description = %Q{A simple library for daemonizing scripts.}
|
10
|
+
gem.email = "watsonian@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/watsonian/simple_daemon"
|
12
|
+
gem.authors = ["watsonian"]
|
13
|
+
gem.add_dependency "simple_pid", ">=0.2.1"
|
14
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:test) do |test|
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.pattern = 'test/**/test_*.rb'
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |test|
|
32
|
+
test.libs << 'test'
|
33
|
+
test.pattern = 'test/**/test_*.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
task :test => :check_dependencies
|
43
|
+
|
44
|
+
task :default => :test
|
45
|
+
|
46
|
+
require 'rake/rdoctask'
|
47
|
+
Rake::RDocTask.new do |rdoc|
|
48
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
49
|
+
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = "simple_daemon #{version}"
|
52
|
+
rdoc.rdoc_files.include('README*')
|
53
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
54
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'simple_pid'
|
4
|
+
begin
|
5
|
+
require 'system_timer'
|
6
|
+
SimpleDaemonTimer = SystemTimer
|
7
|
+
rescue LoadError
|
8
|
+
puts "Falling back to the default timeout library. You should install the SystemTimer gem for better timeout support."
|
9
|
+
require 'timeout'
|
10
|
+
SimpleDaemonTimer = Timeout
|
11
|
+
end
|
12
|
+
|
13
|
+
class SimpleDaemon
|
14
|
+
@@config = OpenStruct.new
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
def setup(&block)
|
19
|
+
yield @@config
|
20
|
+
end
|
21
|
+
|
22
|
+
# Run block in the foreground
|
23
|
+
def foreground(&block)
|
24
|
+
self.set_prerun_config_defaults
|
25
|
+
self.set_process_name
|
26
|
+
self.chroot
|
27
|
+
self.clean_fd
|
28
|
+
self.redirect_io(true)
|
29
|
+
|
30
|
+
yield
|
31
|
+
end
|
32
|
+
|
33
|
+
# Run block as a daemon
|
34
|
+
def daemonized(&block)
|
35
|
+
self.set_prerun_config_defaults
|
36
|
+
self.set_process_name
|
37
|
+
self.drop_privileges
|
38
|
+
self.daemonize
|
39
|
+
self.chroot
|
40
|
+
self.clean_fd
|
41
|
+
self.redirect_io
|
42
|
+
|
43
|
+
yield
|
44
|
+
end
|
45
|
+
|
46
|
+
# def stop
|
47
|
+
# @pid_file = SimplePid.new(@@config.pid_file)
|
48
|
+
#
|
49
|
+
# unless @pid_file.running?
|
50
|
+
# @pid_file.cleanup
|
51
|
+
# puts "Nothing to stop"
|
52
|
+
# exit
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# target_pid = @pid_file.pid
|
56
|
+
#
|
57
|
+
# puts "Sending TERM to #{target_pid}"
|
58
|
+
# Process.kill( 'TERM', target_pid )
|
59
|
+
#
|
60
|
+
# if seconds = @@config.force_kill_wait
|
61
|
+
# begin
|
62
|
+
# SimpleDaemonTimer::timeout(seconds) do
|
63
|
+
# loop do
|
64
|
+
# puts "Waiting #{seconds} seconds for #{target_pid} before sending KILL"
|
65
|
+
#
|
66
|
+
# break unless @pid_file.running?
|
67
|
+
#
|
68
|
+
# seconds -= 1
|
69
|
+
# sleep 1
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
# rescue Timeout::Error
|
73
|
+
# Process.kill('KILL', target_pid)
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# if @pid_file.running?
|
78
|
+
# puts "Process still running, leaving pidfile behind! Consider using the :force_kill_wait configuration option."
|
79
|
+
# else
|
80
|
+
# @pid_file.cleanup
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
|
84
|
+
# Exit the daemon
|
85
|
+
# TODO: Make configurable callback chain
|
86
|
+
# TODO: Hook into at_exit()
|
87
|
+
# def exit!(code = 0)
|
88
|
+
# end
|
89
|
+
|
90
|
+
# http://gist.github.com/304739
|
91
|
+
#
|
92
|
+
# Stolen from Unicorn::Util
|
93
|
+
#
|
94
|
+
# This reopens ALL logfiles in the process that have been rotated
|
95
|
+
# using logrotate(8) (without copytruncate) or similar tools.
|
96
|
+
# A +File+ object is considered for reopening if it is:
|
97
|
+
# 1) opened with the O_APPEND and O_WRONLY flags
|
98
|
+
# 2) opened with an absolute path (starts with "/")
|
99
|
+
# 3) the current open file handle does not match its original open path
|
100
|
+
# 4) unbuffered (as far as userspace buffering goes, not O_SYNC)
|
101
|
+
# Returns the number of files reopened
|
102
|
+
# def reopen_logs
|
103
|
+
# nr = 0
|
104
|
+
# append_flags = File::WRONLY | File::APPEND
|
105
|
+
# @@logger.info "Rotating logs" if @@logger
|
106
|
+
#
|
107
|
+
# #logs = [STDOUT, STDERR]
|
108
|
+
# #logs.each do |fp|
|
109
|
+
# ObjectSpace.each_object(File) do |fp|
|
110
|
+
# next if fp.closed?
|
111
|
+
# next unless (fp.sync && fp.path[0..0] == "/")
|
112
|
+
# next unless (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
|
113
|
+
#
|
114
|
+
# begin
|
115
|
+
# a, b = fp.stat, File.stat(fp.path)
|
116
|
+
# next if a.ino == b.ino && a.dev == b.dev
|
117
|
+
# rescue Errno::ENOENT
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# open_arg = 'a'
|
121
|
+
# if fp.respond_to?(:external_encoding) && enc = fp.external_encoding
|
122
|
+
# open_arg << ":#{enc.to_s}"
|
123
|
+
# enc = fp.internal_encoding and open_arg << ":#{enc.to_s}"
|
124
|
+
# end
|
125
|
+
# @@logger.info "Rotating path: #{fp.path}" if @@logger
|
126
|
+
# fp.reopen(fp.path, open_arg)
|
127
|
+
# fp.sync = true
|
128
|
+
# nr += 1
|
129
|
+
# end # each_object
|
130
|
+
# nr
|
131
|
+
# end
|
132
|
+
|
133
|
+
protected
|
134
|
+
def set_process_name
|
135
|
+
$0 = @@config.daemon_name
|
136
|
+
end
|
137
|
+
|
138
|
+
# Set default options (called prior to yield blocks)
|
139
|
+
def set_prerun_config_defaults
|
140
|
+
@@config.daemon_name = File.basename(__FILE__) unless @@config.daemon_name
|
141
|
+
@@config.pid_file = "#{@@config.daemon_name}.pid" unless @@config.pid_file
|
142
|
+
@@config.force_kill_wait = nil unless @@config.force_kill_wait
|
143
|
+
@@config.group = nil unless @@config.group
|
144
|
+
@@config.user = nil unless @@config.user
|
145
|
+
end
|
146
|
+
|
147
|
+
# Daemonize the process
|
148
|
+
def daemonize
|
149
|
+
@pid_file = SimplePid.new(@@config.pid_file)
|
150
|
+
@pid_file.ensure_stopped!
|
151
|
+
|
152
|
+
if RUBY_VERSION < "1.9"
|
153
|
+
exit if fork
|
154
|
+
Process.setsid
|
155
|
+
exit if fork
|
156
|
+
else
|
157
|
+
Process.daemon( true, true )
|
158
|
+
end
|
159
|
+
|
160
|
+
@pid_file.write!
|
161
|
+
|
162
|
+
# TODO: Convert into shutdown hook
|
163
|
+
at_exit { @pid_file.cleanup }
|
164
|
+
end
|
165
|
+
|
166
|
+
# Release the old working directory and insure a sensible umask
|
167
|
+
# TODO: Make chroot directory configurable
|
168
|
+
def chroot
|
169
|
+
Dir.chdir '/'
|
170
|
+
File.umask 0000
|
171
|
+
end
|
172
|
+
|
173
|
+
# Make sure all file descriptors are closed (with the exception
|
174
|
+
# of STDIN, STDOUT & STDERR)
|
175
|
+
def clean_fd
|
176
|
+
ObjectSpace.each_object(IO) do |io|
|
177
|
+
unless [STDIN, STDOUT, STDERR].include?(io)
|
178
|
+
begin
|
179
|
+
unless io.closed?
|
180
|
+
io.close
|
181
|
+
end
|
182
|
+
rescue ::Exception
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Redirect our IO
|
189
|
+
# TODO: make this configurable
|
190
|
+
def redirect_io( simulate = false )
|
191
|
+
begin
|
192
|
+
STDIN.reopen '/dev/null'
|
193
|
+
rescue ::Exception
|
194
|
+
end
|
195
|
+
|
196
|
+
unless simulate
|
197
|
+
STDOUT.reopen '/dev/null', 'a'
|
198
|
+
STDERR.reopen '/dev/null', 'a'
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def drop_privileges
|
203
|
+
if @@config.group
|
204
|
+
begin
|
205
|
+
group = Etc.getgrnam(@@config.group)
|
206
|
+
Process::Sys.setgid(group.gid.to_i)
|
207
|
+
rescue => e
|
208
|
+
$stderr.puts "Caught exception while trying to drop group privileges: #{e.message}"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
if @@config.user
|
212
|
+
begin
|
213
|
+
user = Etc.getpwnam(@@config.user)
|
214
|
+
Process::Sys.setuid(user.uid.to_i)
|
215
|
+
rescue => e
|
216
|
+
$stderr.puts "Caught exception while trying to drop user privileges: #{e.message}"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{simple_daemon}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["watsonian"]
|
12
|
+
s.date = %q{2010-06-01}
|
13
|
+
s.description = %q{A simple library for daemonizing scripts.}
|
14
|
+
s.email = %q{watsonian@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/simple_daemon.rb",
|
27
|
+
"simple_daemon.gemspec",
|
28
|
+
"test/helper.rb",
|
29
|
+
"test/test_simple_daemon.rb"
|
30
|
+
]
|
31
|
+
s.homepage = %q{http://github.com/watsonian/simple_daemon}
|
32
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
33
|
+
s.require_paths = ["lib"]
|
34
|
+
s.rubygems_version = %q{1.3.7}
|
35
|
+
s.summary = %q{A simple library for daemonizing scripts.}
|
36
|
+
s.test_files = [
|
37
|
+
"test/helper.rb",
|
38
|
+
"test/test_simple_daemon.rb"
|
39
|
+
]
|
40
|
+
|
41
|
+
if s.respond_to? :specification_version then
|
42
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
43
|
+
s.specification_version = 3
|
44
|
+
|
45
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
46
|
+
s.add_runtime_dependency(%q<simple_pid>, [">= 0.2.1"])
|
47
|
+
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
48
|
+
else
|
49
|
+
s.add_dependency(%q<simple_pid>, [">= 0.2.1"])
|
50
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
51
|
+
end
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<simple_pid>, [">= 0.2.1"])
|
54
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
data/test/helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_daemon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- watsonian
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-06-01 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: simple_pid
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 21
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 2
|
33
|
+
- 1
|
34
|
+
version: 0.2.1
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: thoughtbot-shoulda
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
description: A simple library for daemonizing scripts.
|
52
|
+
email: watsonian@gmail.com
|
53
|
+
executables: []
|
54
|
+
|
55
|
+
extensions: []
|
56
|
+
|
57
|
+
extra_rdoc_files:
|
58
|
+
- LICENSE
|
59
|
+
- README.rdoc
|
60
|
+
files:
|
61
|
+
- .document
|
62
|
+
- .gitignore
|
63
|
+
- LICENSE
|
64
|
+
- README.rdoc
|
65
|
+
- Rakefile
|
66
|
+
- VERSION
|
67
|
+
- lib/simple_daemon.rb
|
68
|
+
- simple_daemon.gemspec
|
69
|
+
- test/helper.rb
|
70
|
+
- test/test_simple_daemon.rb
|
71
|
+
has_rdoc: true
|
72
|
+
homepage: http://github.com/watsonian/simple_daemon
|
73
|
+
licenses: []
|
74
|
+
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options:
|
77
|
+
- --charset=UTF-8
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
hash: 3
|
95
|
+
segments:
|
96
|
+
- 0
|
97
|
+
version: "0"
|
98
|
+
requirements: []
|
99
|
+
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 1.3.7
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: A simple library for daemonizing scripts.
|
105
|
+
test_files:
|
106
|
+
- test/helper.rb
|
107
|
+
- test/test_simple_daemon.rb
|