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 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