dante 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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