File: daemon.rb

package info (click to toggle)
ruby-process-daemon 1.0.1-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 196 kB
  • sloc: ruby: 761; makefile: 7
file content (199 lines) | stat: -rw-r--r-- 5,355 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require 'fileutils'

require_relative 'daemon/controller'

require_relative 'daemon/notification'

require_relative 'daemon/log_file'
require_relative 'daemon/process_file'

module Process
	# Provides the infrastructure for spawning a daemon.
	class Daemon
		# Initialize the daemon in the given working root.
		def initialize(working_directory = ".")
			@working_directory = working_directory
			
			@shutdown_notification = Notification.new
		end
		
		# Return the name of the daemon
		def name
			return self.class.name.gsub(/[^a-zA-Z0-9]+/, '-')
		end

		# The directory the daemon will run in.
		attr :working_directory

		# Return the directory to store log files in.
		def log_directory
			File.join(working_directory, "log")
		end

		# Standard log file for stdout and stderr.
		def log_file_path
			File.join(log_directory, "#{name}.log")
		end

		# Runtime data directory for the daemon.
		def runtime_directory
			File.join(working_directory, "run")
		end

		# Standard location of process pid file.
		def process_file_path
			File.join(runtime_directory, "#{name}.pid")
		end

		# Mark the output log.
		def mark_log
			File.open(log_file_path, "a") do |log_file|
				log_file.puts "=== Log Marked @ #{Time.now.to_s} [#{Process.pid}] ==="
			end
		end

		# Prints some information relating to daemon startup problems.
		def tail_log(output)
			lines = LogFile.open(log_file_path).tail_log do |line|
				line.match("=== Log Marked") || line.match("=== Daemon Exception Backtrace")
			end
			
			output.puts lines
		end

		# Check the last few lines of the log file to find out if the daemon crashed.
		def crashed?
			count = 3
			
			LogFile.open(log_file_path).tail_log do |line|
				return true if line.match("=== Daemon Crashed")

				break if (count -= 1) == 0
			end

			return false
		end
		
		# The main function to setup any environment required by the daemon
		def prefork
			# Ignore any previously setup signal handler for SIGINT:
			trap(:INT, :DEFAULT)
			
			# We update the working directory to a full path:
			@working_directory = File.expand_path(working_directory)
			
			FileUtils.mkdir_p(log_directory)
			FileUtils.mkdir_p(runtime_directory)
		end
		
		# The process title of the daemon.
		attr :title
		
		# Set the process title - only works after daemon has forked.
		def title= title
			@title = title
			
			if Process.respond_to? :setproctitle
				Process.setproctitle(@title)
			else
				$0 = @title
			end
		end
		
		# Request that the sleep_until_interrupted function call returns.
		def request_shutdown
			@shutdown_notification.signal
		end
		
		# Call this function to sleep until the daemon is sent SIGINT.
		def sleep_until_interrupted
			trap(:INT) do
				self.request_shutdown
			end

			@shutdown_notification.wait
		end
		
		# This function must setup the daemon quickly and return.
		def startup
		end
		
		# If you want to implement a long running process you override this method. You may like to call super but it is not necessary to use the supplied interruption machinery.
		def run
			sleep_until_interrupted
		end
		
		# This function should terminate any active processes in the daemon and return as quickly as possible.
		def shutdown
		end
		
		# The entry point from the newly forked process.
		def spawn
			self.title = self.name
			
			self.startup
			
			begin
				self.run
			rescue Interrupt
				$stderr.puts "Daemon interrupted, proceeding to shutdown."
			end
			
			self.shutdown
		end
		
		class << self
			# A shared instance of the daemon.
			def instance
				@instance ||= self.new
			end
			
			# The process controller, responsible for managing the daemon process start, stop, restart, etc.
			def controller(options = {})
				@controller ||= Controller.new(instance, options)
			end
			
			# The main entry point for daemonized scripts.
			def daemonize(*args, **options)
				args = ARGV if args.empty?
				
				controller(options).daemonize(args)
			end
			
			# Start the shared daemon instance.
			def start
				controller.start
			end
			
			# Stop the shared daemon instance.
			def stop
				controller.stop
			end
			
			# Check if the shared daemon instance is runnning or not.
			def status
				controller.status
			end
		end
	end
end