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 ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
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
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'simple_daemon'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestSimpleDaemon < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
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