process-daemon 0.5.5 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5ece8e79345bfaabb606ac9b0b2be85dc176ff1d
4
- data.tar.gz: f199756be6f1625ee83a19795a111f67e8672406
3
+ metadata.gz: ce61de8f6a1e9c039282f457fed948aa8221d709
4
+ data.tar.gz: e44c53039d96fc318d894527010a5f378c4fe490
5
5
  SHA512:
6
- metadata.gz: 718589f9efa869b8256c065f74b8e6b234722a8e6862060f0fa1faf4f6b46fd92d3952f702e160d4ad9d1a3f0778c75d19e8609bb98ba7685af180b90d44eae2
7
- data.tar.gz: c5063b89bda0d750da4298b318d91cdd914cb6c225e06562a4ca6b3975f7fa5c507a6b81812ae2f4788119fc1c562075e5286caf6fb8ddd944e5944e756bca59
6
+ metadata.gz: 8530411f7ca42fc9726424da6fc39358d2f6ca32fae5e67e8a6ec94682d2f2f4f7dbd7094c8367ab772f4762fadb89d495266be281d1d84e8121cc4bbcef7cd9
7
+ data.tar.gz: 883851f2915344371a49de317a9d6fd057b7b70ec638a3a4a6a9c438baf17c3ad73fc17ecabe9896a8c0591c9b7e86e20d55ba786ea20e467fe0dd805475e4fd
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
@@ -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
@@ -1,5 +1,11 @@
1
1
  language: ruby
2
+ sudo: false
2
3
  rvm:
3
- - 1.9
4
- - 2.0
4
+ - 2.2
5
5
  - 2.1
6
+ - 2.0
7
+ - rbx-2
8
+ matrix:
9
+ allow_failures:
10
+ - rvm: rbx-2
11
+ env: COVERAGE=true
data/Gemfile CHANGED
@@ -4,5 +4,6 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :test do
7
- gem "minitest"
7
+ gem 'simplecov'
8
+ gem 'coveralls', require: false
8
9
  end
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
- [![Build Status](https://travis-ci.org/ioquatix/process-daemon.svg)](https://travis-ci.org/ioquatix/process-daemon)
5
+ [![Build Status](https://travis-ci.org/ioquatix/process-daemon.svg?branch=master)](https://travis-ci.org/ioquatix/process-daemon)
6
+ [![Code Climate](https://codeclimate.com/github/ioquatix/process-daemon.png)](https://codeclimate.com/github/ioquatix/process-daemon)
7
+ [![Coverage Status](https://coveralls.io/repos/ioquatix/process-daemon/badge.svg?branch=master)](https://coveralls.io/r/ioquatix/process-daemon?branch=master)
8
+ [![Documentation](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/process-daemon)
9
+ [![Code](http://img.shields.io/badge/github-code-blue.svg)](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
- gem 'process-daemon'
15
+ gem 'process-daemon'
12
16
 
13
17
  And then execute:
14
18
 
15
- $ bundle
19
+ $ bundle
16
20
 
17
21
  Or install it yourself as:
18
22
 
19
- $ gem install process-daemon
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 shutdown
61
- puts "Stopping the RPC server..."
62
- @rpc_server.stop
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, 2014, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
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
@@ -1,6 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
- RSpec::Core::RakeTask.new(:spec)
4
+ RSpec::Core::RakeTask.new(:spec) do |task|
5
+ task.rspec_opts = ["--require", "simplecov"] if ENV['COVERAGE']
6
+ end
5
7
 
6
8
  task :default => :spec
@@ -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
- # This class is the base daemon class. If you are writing a daemon, you should inherit from this class.
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
- # We freeze the working directory because it can't change after forking:
116
- @working_directory = File.expand_path(working_directory)
99
+ # Ignore any previously setup signal handler for SIGINT:
100
+ trap(:INT, :DEFAULT)
117
101
 
118
- def self.working_directory
119
- @working_directory
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
- trap("INT") do
154
- shutdown
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
- startup
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.run
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
- (@stop_timeout / STOP_PERIOD).to_i.times do
190
- sleep STOP_PERIOD if ProcessFile.running(@daemon)
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
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Process
22
22
  class Daemon
23
- VERSION = "0.5.5"
23
+ VERSION = "0.6.1"
24
24
  end
25
25
  end
@@ -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.0.0.rc1"
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 shutdown
42
- puts "Stopping the RPC server..."
43
- @rpc_server.stop
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
- #!/usr/bin/env ruby
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
- begin
60
- puts "Daemon starting..."
61
- @rpc_server.start
62
- puts "Daemon stopping..."
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
- end
70
-
71
- def shutdown
72
- puts "Stopping the RPC server..."
73
- @rpc_server.stop
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 startup
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.5.5
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: 2014-05-29 00:00:00.000000000 Z
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.0.0.rc1
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.0.0.rc1
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
- homepage: ''
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