basic_daemon 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,94 @@
1
+ A basic library for creating daemonized processes
2
+ (c) 2009 Samuel Mullen (samullen)
3
+
4
+ http://github.com/samullen/basic_daemon
5
+
6
+ This library works with Ruby 1.8, Ruby 1.9, and JRuby and is licensed under the MIT License.
7
+
8
+ Gettings Started
9
+
10
+ There are currently two ways of using the basic_daemon library: 1) Objected Oriented; 2) Functional.
11
+
12
+ Object Oriented
13
+
14
+ The basic idea here is to subclass BasicDaemon and overwrite the "run" method.
15
+
16
+ require 'basic_daemon'
17
+
18
+ class MyDaemon < BasicDaemon
19
+ def run
20
+ foo = open("/tmp/out", "w")
21
+
22
+ i = 1
23
+
24
+ while true do
25
+ foo.puts "loop: #{i}"
26
+ foo.flush
27
+ sleep 2
28
+
29
+ i += 1
30
+ end
31
+ end
32
+ end
33
+
34
+ daemon = MyDaemon.new
35
+
36
+ if ARGV[0] == 'start'
37
+ daemon.start
38
+ elsif ARGV[0] == 'stop'
39
+ daemon.stop
40
+ exit!
41
+ elsif ARGV[0] == 'restart'
42
+ daemon.restart
43
+ else
44
+ STDERR.puts "Usage: foo_daemon.rb <start|stop|restart>"
45
+ exit!
46
+ end
47
+
48
+ Functional
49
+
50
+ Rather than subclassing BasicDaemon, a block is just passed to the start method.
51
+
52
+ require 'basic_daemon'
53
+
54
+ daemon = BasicDaemon.new
55
+
56
+ if ARGV[0] == 'start'
57
+ daemon.start do
58
+ foo = open("/tmp/out", "w")
59
+
60
+ i = 1
61
+
62
+ while true do
63
+ foo.puts "loop: #{i}"
64
+ foo.flush
65
+ sleep 2
66
+
67
+ i += 1
68
+ end
69
+ end
70
+ elsif ARGV[0] == 'stop'
71
+ daemon.stop
72
+ exit!
73
+ elsif ARGV[0] == 'restart'
74
+ daemon.restart
75
+ else
76
+ STDERR.puts "Usage: foo_daemon.rb <start|stop|restart>"
77
+ exit!
78
+ end
79
+
80
+ Arguments
81
+
82
+ BasicDaemon creates file to store the process ID (PID). By default, this file is given the same name as the calling script sans the file extension and is stored in the /tmp directory. Also by default, the directory the calling script is working in is changed to the root directory ('/').
83
+
84
+ Each argument, :pidfile, :piddir, :workingdir, can be changed upon instantiation of the class.
85
+
86
+ Example:
87
+
88
+ Most Linux users will want to instantiate thusly:
89
+
90
+ BasicDaemon.new(:piddir => '/var/lock')
91
+
92
+ Installation
93
+
94
+ pending
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # STDERR.puts "this won't work until I get the block stuff working."
4
+ # exit
5
+
6
+ require File.join(File.dirname(__FILE__), '..', 'lib','basic_daemon')
7
+
8
+ basedir = "/tmp" # change this to where you want to deal with things
9
+ pidfile = File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME))
10
+
11
+ d = BasicDaemon.new({:pidfile => pidfile, :piddir => basedir, :workingdir => basedir})
12
+
13
+ if ARGV[0] == 'start'
14
+ puts "should print 'got here' on the next line"
15
+ d.start do
16
+ i = 1
17
+ foo = open(basedir + "/out", "w")
18
+
19
+ while true do
20
+ foo.puts "loop: #{i}"
21
+ foo.flush
22
+ sleep 5
23
+
24
+ i += 1
25
+ end
26
+ end
27
+ elsif ARGV[0] == 'stop'
28
+ d.stop
29
+ exit!
30
+ elsif ARGV[0] == 'restart'
31
+ d.restart
32
+ else
33
+ STDERR.puts "wrong! use start or stop."
34
+ exit!
35
+ end
36
+
37
+ puts 'got here'
38
+ exit
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib','basic_daemon')
4
+ require 'pp'
5
+
6
+ basedir = "/tmp" # change this to where you want to deal with things
7
+ pidfile = File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME))
8
+
9
+ class MyDaemon < BasicDaemon
10
+ def run
11
+ foo = open("/tmp/out", "w")
12
+
13
+ i = 1
14
+
15
+ while true do
16
+ foo.puts "loop: #{i}"
17
+ foo.flush
18
+ sleep 5
19
+
20
+ i += 1
21
+ end
22
+ end
23
+ end
24
+
25
+ d = MyDaemon.new(:pidfile => pidfile, :piddir => basedir, :workingdir => basedir)
26
+
27
+ if ARGV[0] == 'start'
28
+ puts "Should print 'got here' on the next line"
29
+ d.start
30
+ elsif ARGV[0] == 'stop'
31
+ d.stop
32
+ exit!
33
+ elsif ARGV[0] == 'restart'
34
+ d.restart
35
+ else
36
+ STDERR.puts "wrong! use start or stop."
37
+ exit!
38
+ end
39
+
40
+ puts 'got here'
41
+ exit
@@ -0,0 +1,187 @@
1
+ class BasicDaemon
2
+ attr_accessor :workingdir, :pidfile, :piddir, :process
3
+
4
+ VERSION = '0.9.0'
5
+
6
+ DEFAULT_OPTIONS = {
7
+ :pidfile => File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME)),
8
+ :piddir => '/tmp',
9
+ :workingdir => '/'
10
+ }
11
+
12
+ # Instantiate a new BasicDaemon
13
+ #
14
+ # Takes an optional hash with the following symbol keys:
15
+ # - :piddir = Directory to store the PID file in. Default is /tmp
16
+ # - :pidfile = name of the file to store the PID in. default is the script
17
+ # name sans extension.
18
+ # - :workingdir = Directory to work from. Default is "/" and should probably
19
+ # be left as such.
20
+ def initialize(*args)
21
+ opts = {}
22
+
23
+ case
24
+ when args.length == 0 then
25
+ when args.length == 1 && args[0].class == Hash then
26
+ arg = args.shift
27
+
28
+ if arg.class == Hash
29
+ opts = arg
30
+ end
31
+ else
32
+ raise ArgumentError, "new() expects hash or hashref as argument"
33
+ end
34
+
35
+ opts = DEFAULT_OPTIONS.merge opts
36
+
37
+ @piddir = opts[:piddir]
38
+ @pidfile = opts[:pidfile]
39
+ @workingdir = opts[:workingdir]
40
+ @pid = nil
41
+ @process = nil
42
+ end
43
+
44
+ # Returns the fullpath to the file containing the process ID (PID)
45
+ def pidpath
46
+ File.join(@piddir, @pidfile)
47
+ end
48
+
49
+ # Returns the PID of the currently running daemon
50
+ def pid
51
+ return @pid unless @pid.nil?
52
+
53
+ begin
54
+ @pid = open(self.pidpath, 'r').read.to_i
55
+ rescue Errno::EACCES => e
56
+ STDERR.puts "Error: unable to open file #{self.pidpath} for reading:\n\t"+
57
+ "(#{e.class}) #{e.message}"
58
+ exit!
59
+ rescue => e
60
+ end
61
+
62
+ @pid
63
+ end
64
+
65
+ # Starts the daemon by forking the supplied process either by block or by
66
+ # overridden run method.
67
+ def start(&block)
68
+ if pid
69
+ STDERR.puts "pidfile #{self.pidpath} with pid #{@pid} already exists. " +
70
+ "Make sure this daemon is not already running."
71
+ exit!
72
+ end
73
+
74
+ if block_given?
75
+ @process = block
76
+ end
77
+
78
+ #----- Fork off from the calling process -----#
79
+ begin
80
+ fork do
81
+ Process.setsid #----- make forked process session leader
82
+ fork && exit!
83
+
84
+ at_exit do
85
+ delpidfile
86
+ end
87
+
88
+ Dir::chdir(@workingdir) #----- chdir to working directory
89
+ File::umask(0) #----- clear out file mode creation mask
90
+
91
+ begin
92
+ open(self.pidpath, "w") do |f|
93
+ @pid = Process.pid
94
+ f.puts @pid
95
+ end
96
+ rescue => e
97
+ STDERR.puts "Error: Unable to open #{self.pidpath} for writing:\n\t" +
98
+ "(#{e.class}) #{e.message}"
99
+ exit!
100
+ end
101
+
102
+ STDIN.reopen("/dev/null", 'r')
103
+ STDOUT.reopen("/dev/null", "w")
104
+ STDERR.reopen("/dev/null", "w")
105
+
106
+ unless @process.nil?
107
+ @process.call
108
+ else
109
+ self.run
110
+ end
111
+ end
112
+ rescue => e
113
+ STDERR.puts "Error: Failed to fork properly: \n\t: " +
114
+ "(#{e.class}) #{e.message}"
115
+ exit!
116
+ end
117
+ end
118
+
119
+ # stops the daemon. It does this by retrieving the process ID (PID) of the
120
+ # currently running process from the pidfile and killing it till it's dead.
121
+ def stop
122
+ unless self.pid
123
+ STDERR.puts "pidfile #{self.pidpath} does not exist. Daemon not running?\n"
124
+ return # not an error in a restart
125
+ end
126
+
127
+ begin
128
+ while true do
129
+ Process.kill("TERM", self.pid)
130
+ sleep(0.1)
131
+ end
132
+ rescue Errno::ESRCH # gets here when there is no longer a process to kill
133
+ rescue => e
134
+ STDERR.puts "unable to terminate process: (#{e.class}) #{e.message}"
135
+ exit!
136
+ end
137
+ end
138
+
139
+ # restarts the daemon by first killing it and then restarting.
140
+ #
141
+ # Warning: does not work if block is initially passed to start.
142
+ def restart
143
+ self.stop
144
+ @pid = nil
145
+
146
+ unless @process.nil?
147
+ self.start
148
+ else
149
+ self.start &@process
150
+ end
151
+ end
152
+
153
+ # Boolean. Does the current process exist? True or false.
154
+ # Reads the PID from the pidfile and attempts to "kill 0" the process.
155
+ # Success results in true; failure, false.
156
+ def process_exists?
157
+ begin
158
+ Process.kill(0, self.pid)
159
+ true
160
+ rescue Errno::ESRCH, TypeError # "PID is NOT running or is zombied
161
+ false
162
+ rescue Errno::EPERM
163
+ STDERR.puts "No permission to query #{pid}!";
164
+ rescue => e
165
+ STDERR.puts "(#{e.class}) #{e.message}:\n\t" <<
166
+ "Unable to determine status for #{pid}."
167
+ end
168
+ end
169
+
170
+ # run should be overridden if using BasicDaemon Object Orientedly.
171
+ # See examples.
172
+ def run
173
+ end
174
+
175
+ private
176
+
177
+ #----------------------------------------------------------------------------#
178
+ def delpidfile
179
+ begin
180
+ File.unlink(self.pidpath)
181
+ rescue => e
182
+ STDERR.puts "ERROR: Unable to unlink #{self.pidpath}:\n\t" +
183
+ "(#{e.class}) #{e.message}"
184
+ exit
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,72 @@
1
+ require 'test/unit'
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib','basic_daemon')
4
+
5
+ class TestBasicDaemon < Test::Unit::TestCase
6
+ def setup
7
+ @daemon = BasicDaemon.new
8
+ end
9
+
10
+ def test_should_default_pidfile_to_program_name
11
+ file_sans_ext = File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME))
12
+ message = "#{@daemon.pidfile} and #{file_sans_ext} are not equal"
13
+
14
+ assert_equal @daemon.pidfile, file_sans_ext, message
15
+ end
16
+
17
+ def test_should_default_piddir_to_tmp
18
+ message = "PID Directory #{@daemon.piddir} should default to '/tmp'"
19
+
20
+ assert_equal @daemon.piddir, "/tmp", message
21
+ end
22
+
23
+ def test_should_default_working_directory_to_root
24
+ message = "Working Directory #{@daemon.workingdir} should default to '/'"
25
+
26
+ assert_equal @daemon.workingdir, "/", message
27
+ end
28
+
29
+ def test_update_of_pidfile
30
+ message = "PID File should be set to 'foo.txt'"
31
+ new_pidfile = 'foo.txt'
32
+
33
+ @daemon.pidfile = new_pidfile
34
+ assert_equal @daemon.pidfile, new_pidfile, message
35
+ assert_equal @daemon.pidpath, "/tmp/#{new_pidfile}", message
36
+ end
37
+
38
+ def test_pid_method_should_return_nil
39
+ assert_nil @daemon.pid
40
+ end
41
+ end
42
+
43
+ class TestWithSuppliedValues < Test::Unit::TestCase
44
+ def setup
45
+ @daemon = BasicDaemon.new({:pidfile => 'foo', :piddir => '/var/lock', :workingdir => '/var/lock'})
46
+ end
47
+
48
+ def test_should_default_pidfile_to_foo
49
+ message = "#{@daemon.pidfile} and 'foo' are not equal"
50
+
51
+ assert_equal @daemon.pidfile, "foo", message
52
+ end
53
+
54
+ def test_should_default_piddir_to_varlock
55
+ message = "PID Directory #{@daemon.piddir} should default to '/var/lock'"
56
+
57
+ assert_equal @daemon.piddir, "/var/lock", message
58
+ end
59
+
60
+ def test_should_default_piddir_to_varlockfoo
61
+ message = "PID Path #{@daemon.pidpath} should default to '/var/lock/foo'"
62
+
63
+ assert_equal @daemon.pidpath, "/var/lock/foo", message
64
+ end
65
+
66
+ def test_should_default_working_directory_to_varlock
67
+ message = "Working Directory #{@daemon.workingdir} should default to '/var/lock'"
68
+
69
+ assert_equal @daemon.workingdir, "/var/lock", message
70
+ end
71
+ end
72
+
@@ -0,0 +1,76 @@
1
+ require 'test/unit'
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib','basic_daemon')
4
+
5
+ class TestFunctionalBasicDaemon < Test::Unit::TestCase
6
+ def setup
7
+ @daemon = BasicDaemon.new
8
+
9
+ @process = Proc.new do
10
+ foo = open("/tmp/out", "w")
11
+
12
+ i = 1
13
+
14
+ while true do
15
+ foo.puts "loop: #{i}"
16
+ foo.flush
17
+ sleep 2
18
+
19
+ i += 1
20
+ end
21
+ end
22
+ end
23
+
24
+ def teardown
25
+ @daemon.process_exists? && @daemon.stop
26
+ end
27
+
28
+ def test_creation_deletion_of_pidfile
29
+ @daemon.start &@process
30
+
31
+ sleep 0.1 #----- give child proc time to create file
32
+ assert File.exists?(@daemon.pidpath), "PID file at #{@daemon.pidpath} should exist"
33
+ assert_match(/^\d+$/, File.open(@daemon.pidpath, 'r').read, "PID should be numeric")
34
+
35
+ @daemon.stop
36
+ assert File.exists?(@daemon.pidpath) == false, "PID file at #{@daemon.pidpath} should not exist"
37
+ assert @daemon.process_exists? == false, "Process should not exist"
38
+ end
39
+
40
+ def test_pidfile_removal_upon_termination
41
+ @daemon.start &@process
42
+ sleep 0.1 #----- give child proc time to create file
43
+ pid = File.open(@daemon.pidpath, 'r').read.to_i
44
+
45
+ begin
46
+ while true do
47
+ Process.kill("TERM", pid)
48
+ sleep(0.1)
49
+ end
50
+ rescue Errno::ESRCH
51
+ end
52
+ assert @daemon.process_exists? == false
53
+
54
+ assert File.exists?(@daemon.pidpath) == false
55
+ end
56
+
57
+ def test_backgrounding_of_subclassed_daemon
58
+ @daemon.start &@process
59
+ sleep 0.1 #----- give child proc time to create file
60
+ assert @daemon.process_exists?
61
+ @daemon.stop
62
+ assert @daemon.process_exists? == false
63
+ end
64
+
65
+ def test_restart
66
+ @daemon.start &@process
67
+ sleep 0.1
68
+ assert @daemon.process_exists?
69
+ previous_pid = @daemon.pid
70
+
71
+ @daemon.restart
72
+ sleep 0.1
73
+ assert @daemon.process_exists?
74
+ assert previous_pid != @daemon.pid
75
+ end
76
+ end
@@ -0,0 +1,89 @@
1
+ require 'test/unit'
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib','basic_daemon')
4
+
5
+ #------------------------------------------------------------------------------#
6
+ # OO Testing
7
+ #------------------------------------------------------------------------------#
8
+
9
+ #----- Really needs to be in a helper file -----#
10
+ class MyDaemon < BasicDaemon
11
+ def run
12
+ foo = open("/tmp/out", "w")
13
+
14
+ i = 1
15
+
16
+ while true do
17
+ foo.puts "loop: #{i}"
18
+ foo.flush
19
+ sleep 2
20
+
21
+ i += 1
22
+ end
23
+ end
24
+ end
25
+
26
+ class TestSubclassedBasicDaemon < Test::Unit::TestCase
27
+ def setup
28
+ @mydaemon = MyDaemon.new
29
+ end
30
+
31
+ def teardown
32
+ @mydaemon.process_exists? && @mydaemon.stop
33
+ end
34
+
35
+ #------------------------------------------------------------------------------#
36
+ def test_creation_deletion_of_pidfile
37
+ assert_nil @mydaemon.pid, "pidfile shouldn't exist so pid should be nil."
38
+
39
+ @mydaemon.start
40
+ sleep 0.1 #----- give child proc time to create file
41
+
42
+ assert File.exists?(@mydaemon.pidpath), "PID file at #{@mydaemon.pidpath} should exist"
43
+ assert_match(/^\d+$/, File.open(@mydaemon.pidpath, 'r').read, "PID should be numeric")
44
+
45
+ @mydaemon.stop
46
+ assert File.exists?(@mydaemon.pidpath) == false, "PID file at #{@mydaemon.pidpath} should no longer exist"
47
+ assert @mydaemon.process_exists? == false, "Process should no longer exist."
48
+ end
49
+
50
+ #------------------------------------------------------------------------------#
51
+ def test_pidfile_removal_upon_termination
52
+ @mydaemon.start
53
+ sleep 0.1 #----- give child proc time to create file
54
+
55
+ begin
56
+ while true do
57
+ Process.kill("TERM", @mydaemon.pid)
58
+ sleep(0.1)
59
+ end
60
+ rescue Errno::ESRCH
61
+ end
62
+
63
+ assert @mydaemon.process_exists? == false, "External termination of the process should register with the daemon."
64
+ assert File.exists?(@mydaemon.pidpath) == false, "pidfile should be deleted when the process is terminated externally"
65
+ end
66
+
67
+ #------------------------------------------------------------------------------#
68
+ def test_backgrounding_of_subclassed_daemon
69
+ @mydaemon.start
70
+ sleep 0.1 #----- give child proc time to create file
71
+ assert @mydaemon.process_exists?
72
+ @mydaemon.stop
73
+ assert @mydaemon.process_exists? == false
74
+ end
75
+
76
+ #------------------------------------------------------------------------------#
77
+ def test_restart
78
+ @mydaemon.start
79
+ sleep 0.1
80
+ assert @mydaemon.process_exists?
81
+ previous_pid = @mydaemon.pid
82
+
83
+ @mydaemon.restart
84
+ sleep 0.1
85
+ assert @mydaemon.process_exists?
86
+ assert previous_pid != @mydaemon.pid
87
+ end
88
+ end
89
+
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: basic_daemon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Mullen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-18 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: "false"
17
+ email: samullen@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - README
26
+ - examples/functional.rb
27
+ - examples/objectoriented.rb
28
+ - lib/basic_daemon.rb
29
+ - test/oo_basic_daemon_test.rb
30
+ - test/basic_daemon_settings_test.rb
31
+ - test/functional_basic_daemon_test.rb
32
+ has_rdoc: true
33
+ homepage: http://github.com/samullen/basic_daemon
34
+ licenses: []
35
+
36
+ post_install_message:
37
+ rdoc_options: []
38
+
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project:
56
+ rubygems_version: 1.3.5
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: A simple ruby library for daemonizing processes
60
+ test_files:
61
+ - test/oo_basic_daemon_test.rb
62
+ - test/basic_daemon_settings_test.rb
63
+ - test/functional_basic_daemon_test.rb