process-daemon 0.5.5 → 0.6.1
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.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.simplecov +15 -0
- data/.travis.yml +8 -2
- data/Gemfile +2 -1
- data/README.md +80 -22
- data/Rakefile +3 -1
- data/lib/process/daemon.rb +50 -45
- data/lib/process/daemon/controller.rb +50 -22
- data/lib/process/daemon/log_file.rb +1 -0
- data/lib/process/daemon/notification.rb +59 -0
- data/lib/process/daemon/privileges.rb +2 -1
- data/lib/process/daemon/version.rb +1 -1
- data/process-daemon.gemspec +3 -2
- data/spec/process/daemon/daemon.rb +29 -11
- data/spec/process/daemon/daemon_spec.rb +13 -18
- data/spec/process/daemon/notification_spec.rb +96 -0
- data/spec/process/daemon/privileges_spec.rb +40 -0
- data/spec/process/daemon/process_file_spec.rb +62 -0
- metadata +15 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ce61de8f6a1e9c039282f457fed948aa8221d709
|
4
|
+
data.tar.gz: e44c53039d96fc318d894527010a5f378c4fe490
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8530411f7ca42fc9726424da6fc39358d2f6ca32fae5e67e8a6ec94682d2f2f4f7dbd7094c8367ab772f4762fadb89d495266be281d1d84e8121cc4bbcef7cd9
|
7
|
+
data.tar.gz: 883851f2915344371a49de317a9d6fd057b7b70ec638a3a4a6a9c438baf17c3ad73fc17ecabe9896a8c0591c9b7e86e20d55ba786ea20e467fe0dd805475e4fd
|
data/.rspec
ADDED
data/.simplecov
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter "/spec/"
|
4
|
+
end
|
5
|
+
|
6
|
+
# Work correctly across forks:
|
7
|
+
pid = Process.pid
|
8
|
+
SimpleCov.at_exit do
|
9
|
+
SimpleCov.result.format! if Process.pid == pid
|
10
|
+
end
|
11
|
+
|
12
|
+
if ENV['TRAVIS']
|
13
|
+
require 'coveralls'
|
14
|
+
Coveralls.wear!
|
15
|
+
end
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,24 +2,61 @@
|
|
2
2
|
|
3
3
|
`Process::Daemon` is a stable and helpful base class for long running tasks and daemons. Provides standard `start`, `stop`, `restart`, `status` operations.
|
4
4
|
|
5
|
-
[](https://travis-ci.org/ioquatix/process-daemon)
|
5
|
+
[](https://travis-ci.org/ioquatix/process-daemon)
|
6
|
+
[](https://codeclimate.com/github/ioquatix/process-daemon)
|
7
|
+
[](https://coveralls.io/r/ioquatix/process-daemon?branch=master)
|
8
|
+
[](http://www.rubydoc.info/gems/process-daemon)
|
9
|
+
[](https://github.com/ioquatix/process-daemon)
|
6
10
|
|
7
11
|
## Installation
|
8
12
|
|
9
13
|
Add this line to your application's Gemfile:
|
10
14
|
|
11
|
-
|
15
|
+
gem 'process-daemon'
|
12
16
|
|
13
17
|
And then execute:
|
14
18
|
|
15
|
-
|
19
|
+
$ bundle
|
16
20
|
|
17
21
|
Or install it yourself as:
|
18
22
|
|
19
|
-
|
23
|
+
$ gem install process-daemon
|
20
24
|
|
21
25
|
## Usage
|
22
26
|
|
27
|
+
A process daemon has a specific structure:
|
28
|
+
|
29
|
+
class MyDaemon < Process::Daemon
|
30
|
+
def startup
|
31
|
+
# Called when the daemon is initialized in it's own process. Should return quickly.
|
32
|
+
end
|
33
|
+
|
34
|
+
def run
|
35
|
+
# Do the actual work. Does not need to be implemented, e.g. if using threads or other background processing mechanisms.
|
36
|
+
end
|
37
|
+
|
38
|
+
def shutdown
|
39
|
+
# Stop everything that was setup in startup.
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Make this file executable and have a command line interface:
|
44
|
+
MyDaemon.daemonize
|
45
|
+
|
46
|
+
### Working directory
|
47
|
+
|
48
|
+
By default, daemons run in the current working directory. They setup paths according to the following logic:
|
49
|
+
|
50
|
+
working_directory = "."
|
51
|
+
log_directory = #{working_directory}/log
|
52
|
+
log_file_path = #{log_directory}/#{name}.log
|
53
|
+
runtime_directory = #{working_directory}/run
|
54
|
+
process_file_path = #{runtime_directory}/#{name}.pid
|
55
|
+
|
56
|
+
After calling `prefork`, the working directory is expanded to a full path and should not be changed.
|
57
|
+
|
58
|
+
### WEBRick Server
|
59
|
+
|
23
60
|
Create a file for your daemon, e.g. `daemon.rb`:
|
24
61
|
|
25
62
|
#!/usr/bin/env ruby
|
@@ -28,13 +65,7 @@ Create a file for your daemon, e.g. `daemon.rb`:
|
|
28
65
|
|
29
66
|
# Very simple XMLRPC daemon
|
30
67
|
class XMLRPCDaemon < Process::Daemon
|
31
|
-
def working_directory
|
32
|
-
File.expand_path("../tmp", __FILE__)
|
33
|
-
end
|
34
|
-
|
35
68
|
def startup
|
36
|
-
puts "Starting server..."
|
37
|
-
|
38
69
|
@rpc_server = WEBrick::HTTPServer.new(
|
39
70
|
:Port => 31337,
|
40
71
|
:BindAddress => "0.0.0.0"
|
@@ -47,19 +78,17 @@ Create a file for your daemon, e.g. `daemon.rb`:
|
|
47
78
|
end
|
48
79
|
|
49
80
|
@rpc_server.mount("/RPC2", @listener)
|
50
|
-
|
51
|
-
begin
|
52
|
-
@rpc_server.start
|
53
|
-
rescue Interrupt
|
54
|
-
puts "Daemon interrupted..."
|
55
|
-
ensure
|
56
|
-
@rpc_server.shutdown
|
57
|
-
end
|
58
81
|
end
|
59
82
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
83
|
+
def run
|
84
|
+
# This is the correct way to cleanly shutdown the server:
|
85
|
+
trap(:INT) do
|
86
|
+
@rpc_server.shutdown
|
87
|
+
end
|
88
|
+
|
89
|
+
@rpc_server.start
|
90
|
+
ensure
|
91
|
+
@rpc_server.shutdown
|
63
92
|
end
|
64
93
|
end
|
65
94
|
|
@@ -67,6 +96,35 @@ Create a file for your daemon, e.g. `daemon.rb`:
|
|
67
96
|
|
68
97
|
Then run `daemon.rb start`. To stop the daemon, run `daemon.rb stop`.
|
69
98
|
|
99
|
+
### Celluloid Actor
|
100
|
+
|
101
|
+
`Process::Daemon` is the perfect place to spawn your [celluloid](https://celluloid.io) actors.
|
102
|
+
|
103
|
+
require 'celluloid'
|
104
|
+
require 'process/daemon'
|
105
|
+
|
106
|
+
class MyActor
|
107
|
+
include Celluloid::Actor
|
108
|
+
|
109
|
+
def long_running
|
110
|
+
sleep 1000
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class MyDaemon < Process::Daemon
|
115
|
+
def startup
|
116
|
+
@actor = MyActor.new
|
117
|
+
|
118
|
+
@actor.async.long_running_process
|
119
|
+
end
|
120
|
+
|
121
|
+
def shutdown
|
122
|
+
@actor.terminate
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
MyDaemon.daemonize
|
127
|
+
|
70
128
|
## Contributing
|
71
129
|
|
72
130
|
1. Fork it
|
@@ -79,7 +137,7 @@ Then run `daemon.rb start`. To stop the daemon, run `daemon.rb stop`.
|
|
79
137
|
|
80
138
|
Released under the MIT license.
|
81
139
|
|
82
|
-
Copyright,
|
140
|
+
Copyright, 2015, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
|
83
141
|
|
84
142
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
85
143
|
of this software and associated documentation files (the "Software"), to deal
|
data/Rakefile
CHANGED
data/lib/process/daemon.rb
CHANGED
@@ -22,35 +22,19 @@ require 'fileutils'
|
|
22
22
|
|
23
23
|
require_relative 'daemon/controller'
|
24
24
|
|
25
|
+
require_relative 'daemon/notification'
|
26
|
+
|
25
27
|
require_relative 'daemon/log_file'
|
26
28
|
require_relative 'daemon/process_file'
|
27
29
|
|
28
30
|
module Process
|
29
|
-
#
|
30
|
-
#
|
31
|
-
# The basic structure of a daemon is as follows:
|
32
|
-
#
|
33
|
-
# class Server < Process::Daemon
|
34
|
-
# def startup
|
35
|
-
# # Long running process, e.g. web server, game server, etc.
|
36
|
-
# end
|
37
|
-
#
|
38
|
-
# def shutdown
|
39
|
-
# # Stop the process above, usually called on SIGINT.
|
40
|
-
# end
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# Server.daemonize
|
44
|
-
#
|
45
|
-
# The base directory specifies a path such that:
|
46
|
-
# working_directory = "."
|
47
|
-
# log_directory = #{working_directory}/log
|
48
|
-
# log_file_path = #{log_directory}/#{name}.log
|
49
|
-
# runtime_directory = #{working_directory}/run
|
50
|
-
# process_file_path = #{runtime_directory}/#{name}.pid
|
31
|
+
# Provides the infrastructure for spawning a daemon.
|
51
32
|
class Daemon
|
33
|
+
# Initialize the daemon in the given working root.
|
52
34
|
def initialize(working_directory = ".")
|
53
35
|
@working_directory = working_directory
|
36
|
+
|
37
|
+
@shutdown_notification = Notification.new
|
54
38
|
end
|
55
39
|
|
56
40
|
# Return the name of the daemon
|
@@ -109,30 +93,20 @@ module Process
|
|
109
93
|
|
110
94
|
return false
|
111
95
|
end
|
112
|
-
|
96
|
+
|
113
97
|
# The main function to setup any environment required by the daemon
|
114
98
|
def prefork
|
115
|
-
#
|
116
|
-
|
99
|
+
# Ignore any previously setup signal handler for SIGINT:
|
100
|
+
trap(:INT, :DEFAULT)
|
117
101
|
|
118
|
-
|
119
|
-
|
120
|
-
end
|
102
|
+
# We update the working directory to a full path:
|
103
|
+
@working_directory = File.expand_path(working_directory)
|
121
104
|
|
122
105
|
FileUtils.mkdir_p(log_directory)
|
123
106
|
FileUtils.mkdir_p(runtime_directory)
|
124
107
|
end
|
125
|
-
|
126
|
-
# The main function to start the daemon
|
127
|
-
def startup
|
128
|
-
end
|
129
|
-
|
130
|
-
# The main function to stop the daemon
|
131
|
-
def shutdown
|
132
|
-
# Interrupt all children processes, preferably to stop them so that they are not left behind.
|
133
|
-
Process.kill(:INT, 0)
|
134
|
-
end
|
135
108
|
|
109
|
+
# The process title of the daemon.
|
136
110
|
attr :title
|
137
111
|
|
138
112
|
# Set the process title - only works after daemon has forked.
|
@@ -146,15 +120,46 @@ module Process
|
|
146
120
|
end
|
147
121
|
end
|
148
122
|
|
123
|
+
# Request that the sleep_until_interrupted function call returns.
|
124
|
+
def request_shutdown
|
125
|
+
@shutdown_notification.signal
|
126
|
+
end
|
127
|
+
|
128
|
+
# Call this function to sleep until the daemon is sent SIGINT.
|
129
|
+
def sleep_until_interrupted
|
130
|
+
trap(:INT) do
|
131
|
+
self.request_shutdown
|
132
|
+
end
|
133
|
+
|
134
|
+
@shutdown_notification.wait
|
135
|
+
end
|
136
|
+
|
137
|
+
# This function must setup the daemon quickly and return.
|
138
|
+
def startup
|
139
|
+
end
|
140
|
+
|
141
|
+
# 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.
|
149
142
|
def run
|
150
|
-
|
143
|
+
sleep_until_interrupted
|
144
|
+
end
|
145
|
+
|
146
|
+
# This function should terminate any active processes in the daemon and return as quickly as possible.
|
147
|
+
def shutdown
|
148
|
+
end
|
149
|
+
|
150
|
+
# The entry point from the newly forked process.
|
151
|
+
def spawn
|
151
152
|
self.title = self.name
|
152
153
|
|
153
|
-
|
154
|
-
|
154
|
+
self.startup
|
155
|
+
|
156
|
+
begin
|
157
|
+
self.run
|
158
|
+
rescue Interrupt
|
159
|
+
$stderr.puts "Daemon interrupted, proceeding to shutdown."
|
155
160
|
end
|
156
161
|
|
157
|
-
|
162
|
+
self.shutdown
|
158
163
|
end
|
159
164
|
|
160
165
|
# A shared instance of the daemon.
|
@@ -176,17 +181,17 @@ module Process
|
|
176
181
|
controller(options).daemonize(argv)
|
177
182
|
end
|
178
183
|
|
179
|
-
# Start the daemon instance.
|
184
|
+
# Start the shared daemon instance.
|
180
185
|
def self.start
|
181
186
|
controller.start
|
182
187
|
end
|
183
188
|
|
184
|
-
# Stop the daemon instance.
|
189
|
+
# Stop the shared daemon instance.
|
185
190
|
def self.stop
|
186
191
|
controller.stop
|
187
192
|
end
|
188
193
|
|
189
|
-
# Check if the daemon is runnning or not.
|
194
|
+
# Check if the shared daemon instance is runnning or not.
|
190
195
|
def self.status
|
191
196
|
controller.status
|
192
197
|
end
|
@@ -81,7 +81,7 @@ module Process
|
|
81
81
|
$stderr.sync = true
|
82
82
|
|
83
83
|
begin
|
84
|
-
@daemon.
|
84
|
+
@daemon.spawn
|
85
85
|
rescue Exception => error
|
86
86
|
$stderr.puts "=== Daemon Exception Backtrace @ #{Time.now.to_s} ==="
|
87
87
|
$stderr.puts "#{error.class}: #{error.message}"
|
@@ -153,15 +153,22 @@ module Process
|
|
153
153
|
|
154
154
|
return daemon_state
|
155
155
|
end
|
156
|
-
|
156
|
+
|
157
|
+
# The pid of the daemon if it is available. The pid may be invalid if the daemon has crashed.
|
157
158
|
def pid
|
158
159
|
ProcessFile.recall(@daemon)
|
159
160
|
end
|
160
161
|
|
161
162
|
# How long to wait between checking the daemon process when shutting down:
|
162
163
|
STOP_PERIOD = 0.1
|
164
|
+
|
165
|
+
# The number of attempts to stop the daemon using SIGTERM. On the last attempt, SIGKILL is used.
|
166
|
+
STOP_ATTEMPTS = 5
|
167
|
+
|
168
|
+
# The factor which controls how long we sleep between attempts to kill the process. Only applies to processes which don't stop immediately.
|
169
|
+
STOP_WAIT_FACTOR = 3.0
|
163
170
|
|
164
|
-
# Stops the daemon process.
|
171
|
+
# Stops the daemon process. This function initially sends SIGINT. It waits STOP_PERIOD and checks if the daemon is still running. If it is, it sends SIGTERM, and then waits a bit longer. It tries STOP_ATTEMPTS times until it basically assumes the daemon is stuck and sends SIGKILL.
|
165
172
|
def stop
|
166
173
|
@output.puts Rainbow("Stopping #{@daemon.name} daemon...").blue
|
167
174
|
|
@@ -176,42 +183,63 @@ module Process
|
|
176
183
|
# Check if the @daemon is already stopped...
|
177
184
|
unless ProcessFile.running(@daemon)
|
178
185
|
@output.puts Rainbow("Pid #{pid} is not running. Has daemon crashed?").red
|
179
|
-
|
180
186
|
@daemon.tail_log($stderr)
|
181
|
-
|
182
187
|
return
|
183
188
|
end
|
184
189
|
|
185
|
-
# Interrupt the process group:
|
186
190
|
pgid = -Process.getpgid(pid)
|
187
|
-
Process.kill("INT", pgid)
|
188
191
|
|
189
|
-
(
|
190
|
-
|
191
|
-
end
|
192
|
-
|
193
|
-
# Kill/Term loop - if the @daemon didn't die easily, shoot
|
194
|
-
# it a few more times.
|
195
|
-
attempts = 5
|
196
|
-
while ProcessFile.running(@daemon) and attempts > 0
|
197
|
-
sig = (attempts <= 2) ? "KILL" : "TERM"
|
198
|
-
|
199
|
-
@output.puts Rainbow("Sending #{sig} to process group #{pgid}...").red
|
200
|
-
Process.kill(sig, pgid)
|
201
|
-
|
202
|
-
attempts -= 1
|
203
|
-
sleep 1
|
192
|
+
unless stop_by_interrupt(pgid)
|
193
|
+
stop_by_terminate_or_kill(pgid)
|
204
194
|
end
|
205
195
|
|
206
196
|
# If after doing our best the @daemon is still running (pretty odd)...
|
207
197
|
if ProcessFile.running(@daemon)
|
208
198
|
@output.puts Rainbow("Daemon appears to be still running!").red
|
209
199
|
return
|
200
|
+
else
|
201
|
+
@output.puts Rainbow("Daemon has left the building.").green
|
210
202
|
end
|
211
203
|
|
212
204
|
# Otherwise the @daemon has been stopped.
|
213
205
|
ProcessFile.clear(@daemon)
|
214
206
|
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def stop_by_interrupt(pgid)
|
211
|
+
running = true
|
212
|
+
|
213
|
+
# Interrupt the process group:
|
214
|
+
Process.kill("INT", pgid)
|
215
|
+
|
216
|
+
(@stop_timeout / STOP_PERIOD).ceil.times do
|
217
|
+
if running = ProcessFile.running(@daemon)
|
218
|
+
sleep STOP_PERIOD
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
return running
|
223
|
+
end
|
224
|
+
|
225
|
+
def stop_by_terminate_or_kill(pgid)
|
226
|
+
# TERM/KILL loop - if the daemon didn't die easily, shoot it a few more times.
|
227
|
+
(STOP_ATTEMPTS+1).times do |attempt|
|
228
|
+
break unless ProcessFile.running(@daemon)
|
229
|
+
|
230
|
+
# SIGKILL gets sent on the last attempt.
|
231
|
+
signal_name = (attempt < STOP_ATTEMPTS) ? "TERM" : "KILL"
|
232
|
+
|
233
|
+
@output.puts Rainbow("Sending #{signal_name} to process group #{pgid}...").red
|
234
|
+
|
235
|
+
Process.kill(signal_name, pgid)
|
236
|
+
|
237
|
+
# We iterate quickly to start with, and slow down if the process seems unresponsive.
|
238
|
+
timeout = STOP_PERIOD + (attempt.to_f / STOP_ATTEMPTS) * STOP_WAIT_FACTOR
|
239
|
+
@output.puts Rainbow("Waiting for #{timeout.round(1)}s for daemon to terminate...").blue
|
240
|
+
sleep(timeout)
|
241
|
+
end
|
242
|
+
end
|
215
243
|
end
|
216
244
|
end
|
217
245
|
end
|
@@ -20,6 +20,7 @@
|
|
20
20
|
|
21
21
|
module Process
|
22
22
|
class Daemon
|
23
|
+
# This is a special file instance which provides the ability to read a log file from the end backwards.
|
23
24
|
class LogFile < File
|
24
25
|
# Yields the lines of a log file in reverse order, once the yield statement returns true, stops, and returns the lines in order.
|
25
26
|
def tail_log
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Process
|
22
|
+
class Daemon
|
23
|
+
# This is a one shot cross-process notification mechanism using pipes. It can also be used in the same process if required, e.g. the self-pipe trick.
|
24
|
+
class Notification
|
25
|
+
def initialize
|
26
|
+
@output, @input = IO.pipe
|
27
|
+
|
28
|
+
@signalled = false
|
29
|
+
end
|
30
|
+
|
31
|
+
# Signal the notification.
|
32
|
+
def signal
|
33
|
+
@signalled = true
|
34
|
+
|
35
|
+
@input.puts
|
36
|
+
end
|
37
|
+
|
38
|
+
# Was this notification signalled?
|
39
|
+
def signalled?
|
40
|
+
@signalled
|
41
|
+
end
|
42
|
+
|
43
|
+
# Wait/block until a signal is received. Optional timeout.
|
44
|
+
# @param timeout [Integer] the time to wait in seconds.
|
45
|
+
def wait(timeout: nil)
|
46
|
+
if timeout
|
47
|
+
read_ready, _, _ = IO.select([@output], [], [], timeout)
|
48
|
+
|
49
|
+
return false unless read_ready and read_ready.any?
|
50
|
+
end
|
51
|
+
|
52
|
+
@signalled or @output.read(1)
|
53
|
+
|
54
|
+
# Just in case that this was split across multiple processes.
|
55
|
+
@signalled = true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -22,6 +22,7 @@ require 'etc'
|
|
22
22
|
|
23
23
|
module Process
|
24
24
|
class Daemon
|
25
|
+
# Provides functions for changing the current user.
|
25
26
|
module Privileges
|
26
27
|
# Set the user of the current process. Supply either a user ID
|
27
28
|
# or a user name.
|
@@ -41,4 +42,4 @@ module Process
|
|
41
42
|
end
|
42
43
|
end
|
43
44
|
end
|
44
|
-
end
|
45
|
+
end
|
data/process-daemon.gemspec
CHANGED
@@ -9,8 +9,9 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Samuel Williams"]
|
10
10
|
spec.email = ["samuel.williams@oriontransfer.co.nz"]
|
11
11
|
spec.summary = %q{`Process::Daemon` is a stable and helpful base class for long running tasks and daemons. Provides standard `start`, `stop`, `restart`, `status` operations.}
|
12
|
-
spec.homepage = ""
|
12
|
+
spec.homepage = "https://github.com/ioquatix/process-daemon"
|
13
13
|
spec.license = "MIT"
|
14
|
+
spec.has_rdoc = "yard"
|
14
15
|
|
15
16
|
spec.files = `git ls-files`.split($/)
|
16
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
@@ -22,6 +23,6 @@ Gem::Specification.new do |spec|
|
|
22
23
|
spec.add_dependency "rainbow", "~> 2.0"
|
23
24
|
|
24
25
|
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
-
spec.add_development_dependency "rspec", "~> 3.
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.3.0"
|
26
27
|
spec.add_development_dependency "rake"
|
27
28
|
end
|
@@ -1,4 +1,24 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
2
22
|
|
3
23
|
require 'process/daemon'
|
4
24
|
|
@@ -28,19 +48,17 @@ class XMLRPCDaemon < Process::Daemon
|
|
28
48
|
end
|
29
49
|
|
30
50
|
@rpc_server.mount("/RPC2", @listener)
|
31
|
-
|
32
|
-
begin
|
33
|
-
@rpc_server.start
|
34
|
-
rescue Interrupt
|
35
|
-
puts "Daemon interrupted..."
|
36
|
-
ensure
|
37
|
-
@rpc_server.shutdown
|
38
|
-
end
|
39
51
|
end
|
40
52
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
53
|
+
def run
|
54
|
+
# This is the correct way to cleanly shutdown the server:
|
55
|
+
trap(:INT) do
|
56
|
+
@rpc_server.shutdown
|
57
|
+
end
|
58
|
+
|
59
|
+
@rpc_server.start
|
60
|
+
ensure
|
61
|
+
@rpc_server.shutdown
|
44
62
|
end
|
45
63
|
end
|
46
64
|
|
@@ -1,6 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# Copyright (c) 2007, 2009, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
1
|
+
# Copyright, 2007, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
2
|
#
|
5
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
4
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -55,22 +53,19 @@ module Process::Daemon::DaemonSpec
|
|
55
53
|
end
|
56
54
|
|
57
55
|
@rpc_server.mount("/RPC2", @listener)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
rescue Interrupt
|
64
|
-
puts "Daemon interrupted..."
|
65
|
-
ensure
|
66
|
-
puts "Daemon shutdown..."
|
56
|
+
end
|
57
|
+
|
58
|
+
def run
|
59
|
+
# This is the correct way to cleanly shutdown the server, apparently:
|
60
|
+
trap(:INT) do
|
67
61
|
@rpc_server.shutdown
|
68
62
|
end
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
63
|
+
|
64
|
+
puts "RPC server starting..."
|
65
|
+
@rpc_server.start
|
66
|
+
ensure
|
67
|
+
puts "Stop accepting new connections to RPC server..."
|
68
|
+
@rpc_server.shutdown
|
74
69
|
end
|
75
70
|
end
|
76
71
|
|
@@ -79,7 +74,7 @@ module Process::Daemon::DaemonSpec
|
|
79
74
|
File.expand_path("../tmp", __FILE__)
|
80
75
|
end
|
81
76
|
|
82
|
-
def
|
77
|
+
def run
|
83
78
|
sleep 1 while true
|
84
79
|
end
|
85
80
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'process/daemon/notification'
|
22
|
+
|
23
|
+
module Process::Daemon::NotificationSpec
|
24
|
+
describe Process::Daemon::Notification do
|
25
|
+
it "can be signalled multiple times" do
|
26
|
+
notification = Process::Daemon::Notification.new
|
27
|
+
|
28
|
+
expect(notification.signalled?).to be_falsey
|
29
|
+
|
30
|
+
notification.signal
|
31
|
+
|
32
|
+
expect(notification.signalled?).to be_truthy
|
33
|
+
|
34
|
+
notification.signal
|
35
|
+
notification.signal
|
36
|
+
|
37
|
+
expect(notification.signalled?).to be_truthy
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can be signalled within trap context and across processes" do
|
41
|
+
notification = Process::Daemon::Notification.new
|
42
|
+
|
43
|
+
pid = fork do
|
44
|
+
trap(:INT) do
|
45
|
+
notification.signal
|
46
|
+
exit(0)
|
47
|
+
end
|
48
|
+
|
49
|
+
sleep
|
50
|
+
end
|
51
|
+
|
52
|
+
Process.kill(:INT, pid)
|
53
|
+
|
54
|
+
notification.wait(timeout: 1.0)
|
55
|
+
|
56
|
+
expect(notification.signalled?).to be_truthy
|
57
|
+
|
58
|
+
# Clean up zombie process
|
59
|
+
Process.waitpid(pid)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should receive signal in child process" do
|
63
|
+
notification = Process::Daemon::Notification.new
|
64
|
+
|
65
|
+
pid = fork do
|
66
|
+
if notification.wait(timeout: 60)
|
67
|
+
exit(0)
|
68
|
+
else
|
69
|
+
exit(1)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
notification.signal
|
74
|
+
|
75
|
+
Process.waitpid(pid)
|
76
|
+
|
77
|
+
expect($?.exitstatus).to be == 0
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should not receive signal in child process and time out" do
|
81
|
+
notification = Process::Daemon::Notification.new
|
82
|
+
|
83
|
+
pid = fork do
|
84
|
+
if notification.wait(timeout: 0.01)
|
85
|
+
exit(0)
|
86
|
+
else
|
87
|
+
exit(1)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
Process.waitpid(pid)
|
92
|
+
|
93
|
+
expect($?.exitstatus).to be == 1
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'process/daemon'
|
22
|
+
require 'process/daemon/privileges'
|
23
|
+
|
24
|
+
module Process::Daemon::PrivilegesSpec
|
25
|
+
describe Process::Daemon::Privileges do
|
26
|
+
let(:daemon) {SleepDaemon.instance}
|
27
|
+
|
28
|
+
it "should save report current user" do
|
29
|
+
expect(Process::Daemon::Privileges.current_user).to be == `whoami`.chomp
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should change current user" do
|
33
|
+
current_user = Process::Daemon::Privileges.current_user
|
34
|
+
|
35
|
+
Process::Daemon::Privileges.change_user(current_user)
|
36
|
+
|
37
|
+
expect(Process::Daemon::Privileges.current_user).to be == current_user
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'process/daemon'
|
22
|
+
require 'process/daemon/process_file'
|
23
|
+
|
24
|
+
module Process::Daemon::ProcessFileSpec
|
25
|
+
class SleepDaemon < Process::Daemon
|
26
|
+
def working_directory
|
27
|
+
File.expand_path("../tmp", __FILE__)
|
28
|
+
end
|
29
|
+
|
30
|
+
def startup
|
31
|
+
sleep 1 while true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe Process::Daemon::ProcessFile do
|
36
|
+
let(:daemon) {SleepDaemon.instance}
|
37
|
+
|
38
|
+
it "should save pid" do
|
39
|
+
Process::Daemon::ProcessFile.store(daemon, $$)
|
40
|
+
|
41
|
+
expect(Process::Daemon::ProcessFile.recall(daemon)).to be == $$
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should clear pid" do
|
45
|
+
Process::Daemon::ProcessFile.clear(daemon)
|
46
|
+
|
47
|
+
expect(Process::Daemon::ProcessFile.recall(daemon)).to be nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should be running" do
|
51
|
+
Process::Daemon::ProcessFile.store(daemon, $$)
|
52
|
+
|
53
|
+
expect(Process::Daemon::ProcessFile.status(daemon)).to be :running
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should not be running" do
|
57
|
+
Process::Daemon::ProcessFile.clear(daemon)
|
58
|
+
|
59
|
+
expect(Process::Daemon::ProcessFile.status(daemon)).to be :stopped
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: process-daemon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rainbow
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 3.
|
47
|
+
version: 3.3.0
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 3.
|
54
|
+
version: 3.3.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -74,6 +74,8 @@ extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
75
75
|
files:
|
76
76
|
- ".gitignore"
|
77
|
+
- ".rspec"
|
78
|
+
- ".simplecov"
|
77
79
|
- ".travis.yml"
|
78
80
|
- Gemfile
|
79
81
|
- README.md
|
@@ -81,13 +83,17 @@ files:
|
|
81
83
|
- lib/process/daemon.rb
|
82
84
|
- lib/process/daemon/controller.rb
|
83
85
|
- lib/process/daemon/log_file.rb
|
86
|
+
- lib/process/daemon/notification.rb
|
84
87
|
- lib/process/daemon/privileges.rb
|
85
88
|
- lib/process/daemon/process_file.rb
|
86
89
|
- lib/process/daemon/version.rb
|
87
90
|
- process-daemon.gemspec
|
88
91
|
- spec/process/daemon/daemon.rb
|
89
92
|
- spec/process/daemon/daemon_spec.rb
|
90
|
-
|
93
|
+
- spec/process/daemon/notification_spec.rb
|
94
|
+
- spec/process/daemon/privileges_spec.rb
|
95
|
+
- spec/process/daemon/process_file_spec.rb
|
96
|
+
homepage: https://github.com/ioquatix/process-daemon
|
91
97
|
licenses:
|
92
98
|
- MIT
|
93
99
|
metadata: {}
|
@@ -115,3 +121,7 @@ summary: "`Process::Daemon` is a stable and helpful base class for long running
|
|
115
121
|
test_files:
|
116
122
|
- spec/process/daemon/daemon.rb
|
117
123
|
- spec/process/daemon/daemon_spec.rb
|
124
|
+
- spec/process/daemon/notification_spec.rb
|
125
|
+
- spec/process/daemon/privileges_spec.rb
|
126
|
+
- spec/process/daemon/process_file_spec.rb
|
127
|
+
has_rdoc: yard
|