dante 0.0.4 → 0.1.0

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.
data/README.md CHANGED
@@ -55,6 +55,8 @@ Be sure to properly make your bin executable:
55
55
  chmod +x bin/myapp
56
56
  ```
57
57
 
58
+ ### CLI
59
+
58
60
  This gives your binary several useful things for free:
59
61
 
60
62
  ```
@@ -81,7 +83,7 @@ will stop all daemonized processes for the specified pid file.
81
83
 
82
84
  Will return a useful help banner message explaining the simple usage.
83
85
 
84
- ## Customization
86
+ ### Advanced
85
87
 
86
88
  In many cases, you will need to add custom flags/options or a custom description to your executable. You can do this
87
89
  easily by using `Dante::Runner` more explicitly:
@@ -122,6 +124,19 @@ Now you would be able to do:
122
124
  and the `opts` would contain the `:test` option for use in your script. In addition, help will now contain
123
125
  your customized description in the banner.
124
126
 
127
+ You can also use dante programmatically to start, stop and restart arbitrary code:
128
+
129
+ ```ruby
130
+ # daemon start
131
+ Dante::Runner.new('gitdocs').execute(:daemonize => true, :pid_path => @pid) { something! }
132
+ # daemon stop
133
+ Dante::Runner.new('gitdocs').execute(:kill => true, :pid_path => @pid)
134
+ # daemon restart
135
+ Dante::Runner.new('gitdocs').execute(:daemonize => true, :restart => true, :pid_path => @pid) { something! }
136
+ ```
137
+
138
+ so you can use dante as part of a more complex CLI executable.
139
+
125
140
  ## God
126
141
 
127
142
  Dante can be used well in conjunction with the excellent God process manager. Simply, use Dante to daemonize a process
@@ -6,7 +6,7 @@ require "dante/runner"
6
6
  Dante.run("process-name") do
7
7
  begin
8
8
  # ...something here
9
- rescue Abort
9
+ rescue Interrupt
10
10
  # ...shutdown here
11
11
  end
12
12
  end
@@ -15,8 +15,7 @@ This is a utility for setting up a binary executable for a service.
15
15
 
16
16
  module Dante
17
17
  class Runner
18
- # Signal to application that the process is shutting down
19
- class Abort < Exception; end
18
+ MAX_START_TRIES = 5
20
19
 
21
20
  attr_accessor :options, :name, :description
22
21
 
@@ -29,6 +28,7 @@ module Dante
29
28
  def initialize(name, defaults={}, &block)
30
29
  @name = name
31
30
  @startup_command = block
31
+ @debug = defaults.delete(:debug) || true
32
32
  @options = {
33
33
  :host => '0.0.0.0',
34
34
  :pid_path => "/var/run/#{@name}.pid"
@@ -44,12 +44,14 @@ module Dante
44
44
  # Executes the runner based on options
45
45
  # @runner.execute
46
46
  # @runner.execute { ... }
47
- def execute(&block)
47
+ def execute(opts={}, &block)
48
48
  parse_options
49
+ self.options.merge!(opts)
49
50
 
50
51
  if options.include?(:kill)
51
- kill_pid(options[:kill] || '*')
52
+ self.stop
52
53
  else # create process
54
+ self.stop if options.include?(:restart)
53
55
  Process.euid = options[:user] if options[:user]
54
56
  Process.egid = options[:group] if options[:group]
55
57
  @startup_command = block if block_given?
@@ -57,23 +59,64 @@ module Dante
57
59
  end
58
60
  end
59
61
 
62
+ def daemonize
63
+ return log("Process is already started") if self.daemon_running? # daemon already started
64
+
65
+ # Start process
66
+ pid = fork do
67
+ exit if fork
68
+ Process.setsid
69
+ exit if fork
70
+ store_pid(Process.pid)
71
+ File.umask 0000
72
+ STDIN.reopen "/dev/null"
73
+ STDOUT.reopen "/dev/null", "a"
74
+ STDERR.reopen STDOUT
75
+ start
76
+ end
77
+ # Ensure process is running
78
+ if until_true(MAX_START_TRIES) { self.daemon_running? }
79
+ log "Daemon has started successfully"
80
+ true
81
+ else # Failed to start
82
+ log "Daemonized process couldn't be started"
83
+ false
84
+ end
85
+ end
86
+
60
87
  def start
61
- puts "Starting #{@name} service..."
88
+ log "Starting #{@name} service..."
62
89
 
63
90
  trap("INT") {
64
- stop
91
+ interrupt
65
92
  exit
66
93
  }
67
94
  trap("TERM"){
68
- stop
95
+ interrupt
69
96
  exit
70
97
  }
71
98
 
72
99
  @startup_command.call(self.options) if @startup_command
73
100
  end
74
101
 
75
- def stop
76
- raise Abort
102
+ # Stops a daemonized process
103
+ def stop(kill_arg=nil)
104
+ if self.daemon_running?
105
+ kill_pid(kill_arg || options[:kill])
106
+ until_true(MAX_START_TRIES) { self.daemon_stopped? }
107
+ else # not running
108
+ log "No #{@name} processes are running"
109
+ false
110
+ end
111
+ end
112
+
113
+ def restart
114
+ self.stop
115
+ self.start
116
+ end
117
+
118
+ def interrupt
119
+ raise Interrupt
77
120
  sleep(1)
78
121
  end
79
122
 
@@ -121,38 +164,54 @@ module Dante
121
164
  options
122
165
  end
123
166
 
124
- private
167
+ protected
125
168
 
126
169
  def store_pid(pid)
127
- FileUtils.mkdir_p(File.dirname(options[:pid_path]))
128
- File.open(options[:pid_path], 'w'){|f| f.write("#{pid}\n")}
170
+ FileUtils.mkdir_p(File.dirname(options[:pid_path]))
171
+ File.open(options[:pid_path], 'w'){|f| f.write("#{pid}\n")}
129
172
  end
130
173
 
131
- def kill_pid(k)
174
+ def kill_pid(k='*')
132
175
  Dir[options[:pid_path]].each do |f|
133
176
  begin
134
177
  pid = IO.read(f).chomp.to_i
135
178
  FileUtils.rm f
136
179
  Process.kill('INT', pid)
137
- puts "killed PID: #{pid} at #{f}"
180
+ log "Stopped PID: #{pid} at #{f}"
138
181
  rescue => e
139
- puts "Failed to kill! #{k}: #{e}"
182
+ log "Failed to stop! #{k}: #{e}"
140
183
  end
141
184
  end
142
185
  end
143
186
 
144
- def daemonize
145
- pid = fork do
146
- exit if fork
147
- Process.setsid
148
- exit if fork
149
- store_pid(Process.pid)
150
- File.umask 0000
151
- STDIN.reopen "/dev/null"
152
- STDOUT.reopen "/dev/null", "a"
153
- STDERR.reopen STDOUT
154
- start
187
+ # Runs until the block condition is met or the timeout_seconds is exceeded
188
+ # until_true(10) { ...return_condition... }
189
+ def until_true(timeout_seconds, interval=1, &block)
190
+ elapsed_seconds = 0
191
+ while elapsed_seconds < timeout_seconds && block.call != true
192
+ elapsed_seconds += interval
193
+ sleep(interval)
155
194
  end
195
+ elapsed_seconds < timeout_seconds
196
+ end
197
+
198
+ # Returns true if process is not running
199
+ def daemon_stopped?
200
+ ! self.daemon_running?
201
+ end
202
+
203
+ # Returns running for the daemonized process
204
+ # self.daemon_running?
205
+ def daemon_running?
206
+ return false unless File.exist?(options[:pid_path])
207
+ Process.kill 0, File.read(options[:pid_path]).to_i
208
+ true
209
+ rescue Errno::ESRCH
210
+ false
211
+ end
212
+
213
+ def log(message)
214
+ puts message if @debug
156
215
  end
157
216
 
158
217
  end
@@ -1,3 +1,3 @@
1
1
  module Dante
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -34,7 +34,7 @@ describe "dante runner" do
34
34
  sleep(1) # Wait to complete
35
35
  @output = File.read(@process.tmp_path)
36
36
  assert_match /Started on 8080!!/, @output
37
- assert_match /Abort!!/, @output
37
+ assert_match /Interrupt!!/, @output
38
38
  assert_match /Closing!!/, @output
39
39
  end
40
40
 
@@ -44,7 +44,7 @@ describe "dante runner" do
44
44
  sleep(1) # Wait to complete
45
45
  @output = File.read(@process.tmp_path)
46
46
  assert_match /Started on 8080!!/, @output
47
- assert_match /Abort!!/, @output
47
+ assert_match /Interrupt!!/, @output
48
48
  assert_match /Closing!!/, @output
49
49
  end
50
50
  end # daemonize
@@ -64,7 +64,7 @@ describe "dante runner" do
64
64
  sleep(1) # Wait to complete
65
65
  @output = File.read(@process.tmp_path)
66
66
  assert_match /Started on 8080!!/, @output
67
- assert_match /Abort!!/, @output
67
+ assert_match /Interrupt!!/, @output
68
68
  assert_match /Closing!!/, @output
69
69
  end
70
70
  end # execute with block
@@ -45,8 +45,8 @@ class TestingProcess
45
45
  @tmp = File.new(@tmp_path, 'w')
46
46
  @tmp.print "Started on #{port}!!"
47
47
  sleep(100)
48
- rescue Dante::Runner::Abort
49
- @tmp.print "Abort!!"
48
+ rescue Interrupt
49
+ @tmp.print "Interrupt!!"
50
50
  exit
51
51
  ensure
52
52
  @tmp.print "Closing!!"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dante
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 4
10
- version: 0.0.4
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Nathan Esquenazi
@@ -15,10 +15,13 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-12-01 00:00:00 Z
18
+ date: 2011-12-06 00:00:00 -08:00
19
+ default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
21
- version_requirements: &id001 !ruby/object:Gem::Requirement
22
+ name: rake
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
22
25
  none: false
23
26
  requirements:
24
27
  - - ">="
@@ -27,12 +30,12 @@ dependencies:
27
30
  segments:
28
31
  - 0
29
32
  version: "0"
30
- requirement: *id001
31
33
  type: :development
32
- prerelease: false
33
- name: rake
34
+ version_requirements: *id001
34
35
  - !ruby/object:Gem::Dependency
35
- version_requirements: &id002 !ruby/object:Gem::Requirement
36
+ name: minitest
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
36
39
  none: false
37
40
  requirements:
38
41
  - - ">="
@@ -41,12 +44,12 @@ dependencies:
41
44
  segments:
42
45
  - 0
43
46
  version: "0"
44
- requirement: *id002
45
47
  type: :development
46
- prerelease: false
47
- name: minitest
48
+ version_requirements: *id002
48
49
  - !ruby/object:Gem::Dependency
49
- version_requirements: &id003 !ruby/object:Gem::Requirement
50
+ name: mocha
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
50
53
  none: false
51
54
  requirements:
52
55
  - - ">="
@@ -55,10 +58,8 @@ dependencies:
55
58
  segments:
56
59
  - 0
57
60
  version: "0"
58
- requirement: *id003
59
61
  type: :development
60
- prerelease: false
61
- name: mocha
62
+ version_requirements: *id003
62
63
  description: Turn any process into a demon.
63
64
  email:
64
65
  - nesquena@gmail.com
@@ -81,6 +82,7 @@ files:
81
82
  - test/dante_test.rb
82
83
  - test/runner_test.rb
83
84
  - test/test_helper.rb
85
+ has_rdoc: true
84
86
  homepage: https://github.com/bazaarlabs/dante
85
87
  licenses: []
86
88
 
@@ -110,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
112
  requirements: []
111
113
 
112
114
  rubyforge_project: dante
113
- rubygems_version: 1.8.10
115
+ rubygems_version: 1.6.2
114
116
  signing_key:
115
117
  specification_version: 3
116
118
  summary: Turn any process into a demon