foreverb 0.2.6 → 0.3.0.a
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.md +6 -0
- data/README.md +63 -3
- data/Rakefile +18 -8
- data/examples/{sample → complex} +3 -11
- data/examples/simple +32 -0
- data/examples/stress +12 -0
- data/foreverb.gemspec +1 -1
- data/lib/forever.rb +8 -8
- data/lib/forever/base.rb +83 -37
- data/lib/forever/every.rb +23 -10
- data/lib/forever/version.rb +1 -1
- data/spec/cli_spec.rb +19 -19
- data/spec/foreverb_spec.rb +33 -33
- data/spec/spec_helper.rb +13 -25
- metadata +21 -17
data/CHANGES.md
CHANGED
data/README.md
CHANGED
@@ -14,6 +14,8 @@ In my servers I've several daemons and what I need is:
|
|
14
14
|
* easily manage exceptions
|
15
15
|
* easily see logs
|
16
16
|
* easily start/stop/restart daemon
|
17
|
+
* no blocking jobs
|
18
|
+
* no blocking queue
|
17
19
|
|
18
20
|
As like [sinatra](https://github.com/sinatra/sinatra) and [padrino](https://github.com/padrino/padrino-framework) I need a
|
19
21
|
**thin** framework to do these jobs in few seconds. This mean that:
|
@@ -61,7 +63,7 @@ Forever.run do
|
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
64
|
-
|
66
|
+
before :each do # or if you prefer before :all
|
65
67
|
require 'bundler/setup'
|
66
68
|
require 'foo'
|
67
69
|
Foo.start_loop
|
@@ -124,7 +126,7 @@ So looking our [example](https://github.com/DAddYE/foreverb/blob/master/examples
|
|
124
126
|
Forever.run do
|
125
127
|
dir File.expand_path('../', __FILE__) # Default is ../../__FILE__
|
126
128
|
|
127
|
-
|
129
|
+
before :all do
|
128
130
|
puts "All jobs will will wait me for 1 second"; sleep 1
|
129
131
|
end
|
130
132
|
|
@@ -146,6 +148,7 @@ Forever.run do
|
|
146
148
|
|
147
149
|
every 15.seconds do
|
148
150
|
puts "Every 15 seconds, but my task require 10 seconds"; sleep 10
|
151
|
+
# This doesn't block other jobs and your queue !!!!!!!
|
149
152
|
end
|
150
153
|
|
151
154
|
every 10.seconds, :at => [":#{Time.now.min+1}", ":#{Time.now.min+2}"] do
|
@@ -207,6 +210,30 @@ you should see:
|
|
207
210
|
[14/07 15:48:40] Bye bye
|
208
211
|
```
|
209
212
|
|
213
|
+
## Filters
|
214
|
+
|
215
|
+
In foreverb we have a couple of filters, `before` and `after`, like rspec you should be able to filter `before :all` or `before :each`.
|
216
|
+
|
217
|
+
``` rb
|
218
|
+
before :all do
|
219
|
+
puts "This will be ran only at start"
|
220
|
+
end
|
221
|
+
|
222
|
+
before :each do
|
223
|
+
puts "Do that before each job"
|
224
|
+
end
|
225
|
+
|
226
|
+
# ... here jobs ...
|
227
|
+
|
228
|
+
after :all do
|
229
|
+
puts "This will be ran only at shutdown"
|
230
|
+
end
|
231
|
+
|
232
|
+
after :each do
|
233
|
+
puts "Do that after each job"
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
210
237
|
## CLI
|
211
238
|
|
212
239
|
### Help:
|
@@ -307,6 +334,8 @@ as for stop we allow `--all` and `-y`
|
|
307
334
|
|
308
335
|
## HACKS
|
309
336
|
|
337
|
+
### Bundler
|
338
|
+
|
310
339
|
Bundler has the bad behavior to load `Gemfile` from your current path, so if your `daemons` (ex: [githubwatcher](https://github.com/daddye/githubwatcher))
|
311
340
|
is shipped with their own `Gemfile` to prevent errors you must insert that line:
|
312
341
|
|
@@ -314,6 +343,37 @@ is shipped with their own `Gemfile` to prevent errors you must insert that line:
|
|
314
343
|
ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__) # edit matching your Gemfile path
|
315
344
|
```
|
316
345
|
|
346
|
+
### Rails/Padrino prevent memory leaks
|
347
|
+
|
348
|
+
I highly suggest to use `fork` and `before` filters when you are using `forever` with frameworks, this since running same job on our ruby will eat a lot of
|
349
|
+
ram, so the better way that I found is that:
|
350
|
+
|
351
|
+
```rb
|
352
|
+
Forever.run :fork => true do
|
353
|
+
before :each do
|
354
|
+
require '/config/boot' # here the rails/padrino environment
|
355
|
+
end
|
356
|
+
|
357
|
+
every 10.seconds, :at => ['12:00', '00:00'] do
|
358
|
+
Project.all(&:perform_long_task)
|
359
|
+
end
|
360
|
+
|
361
|
+
every 1.minute do
|
362
|
+
Account.all.map(&:send_emails)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
```
|
366
|
+
|
367
|
+
This is similar to create a new process i.e.:
|
368
|
+
|
369
|
+
```rb
|
370
|
+
Process.fork do
|
371
|
+
require '/config/boot'
|
372
|
+
my_long_jobs
|
373
|
+
end
|
374
|
+
Process.waitall
|
375
|
+
```
|
376
|
+
|
317
377
|
## Extras
|
318
378
|
|
319
379
|
To see a most comprensive app running _foreverb_ + _growl_ see [githubwatcher gem](https://github.com/daddye/githubwatcher)
|
@@ -336,4 +396,4 @@ The above copyright notice and this permission notice shall be included in all c
|
|
336
396
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
337
397
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM,
|
338
398
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
339
|
-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
399
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rubygems' unless defined?(Gem)
|
2
2
|
require 'bundler/gem_tasks'
|
3
3
|
require 'rspec/core/rake_task'
|
4
|
+
require 'rake/testtask'
|
4
5
|
|
5
6
|
%w(install release).each do |task|
|
6
7
|
Rake::Task[task].enhance do
|
@@ -8,9 +9,9 @@ require 'rspec/core/rake_task'
|
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
11
|
-
desc
|
12
|
+
desc 'Bump version on github'
|
12
13
|
task :bump do
|
13
|
-
if `git status -s`.strip ==
|
14
|
+
if `git status -s`.strip == ''
|
14
15
|
puts "\e[31mNothing to commit (working directory clean)\e[0m"
|
15
16
|
else
|
16
17
|
version = Bundler.load_gemspec(Dir[File.expand_path('../*.gemspec', __FILE__)].first).version
|
@@ -18,13 +19,22 @@ task :bump do
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
|
-
|
22
|
+
Rake::TestTask.new(:spec) do |t|
|
23
|
+
t.test_files = Dir['spec/**/*_spec.rb']
|
24
|
+
t.verbose = true
|
25
|
+
end
|
22
26
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
namespace :example do
|
28
|
+
Dir['./examples/*'].each do |path|
|
29
|
+
next if File.directory?(path)
|
30
|
+
name = File.basename(path)
|
31
|
+
desc "Run example #{name}"
|
32
|
+
task name, :fork do |t, args|
|
33
|
+
ENV['FORK'] = args[:fork]
|
34
|
+
exec "#{Gem.ruby} #{path} && sleep 3 && tail -f -n 150 #{path}/../log/#{name}.log; #{path} stop"
|
35
|
+
end
|
36
|
+
end
|
28
37
|
end
|
29
38
|
|
39
|
+
task :release => :bump
|
30
40
|
task :default => :spec
|
data/examples/{sample → complex}
RENAMED
@@ -3,7 +3,7 @@ require 'rubygems' unless defined?(Gem)
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'forever'
|
5
5
|
|
6
|
-
Forever.run do
|
6
|
+
Forever.run :fork => ENV['FORK'] do
|
7
7
|
dir File.expand_path('../', __FILE__) # Default is ../../__FILE__
|
8
8
|
|
9
9
|
every 5.seconds do
|
@@ -14,19 +14,11 @@ Forever.run do
|
|
14
14
|
puts "All jobs will will wait me for 1 second"; sleep 1
|
15
15
|
end
|
16
16
|
|
17
|
-
every
|
18
|
-
puts "Every 5 seconds from start"
|
19
|
-
end
|
20
|
-
|
21
|
-
every 30.seconds, :last => Time.now do
|
17
|
+
every 30.seconds do
|
22
18
|
puts "Every 30 seconds from start with boom"
|
23
19
|
raise "woooooa"
|
24
20
|
end
|
25
21
|
|
26
|
-
every 10.seconds, :at => "#{Time.now.hour}:00" do
|
27
|
-
puts "Every 10 seconds but first call at #{Time.now.hour}:00"
|
28
|
-
end
|
29
|
-
|
30
22
|
every 1.seconds, :at => "#{Time.now.hour}:#{Time.now.min+1}" do
|
31
23
|
puts "Every one second but first call at #{Time.now.hour}:#{Time.now.min}"
|
32
24
|
end
|
@@ -40,7 +32,7 @@ Forever.run do
|
|
40
32
|
end
|
41
33
|
|
42
34
|
every 15.seconds do
|
43
|
-
puts "Every 15 seconds, but my task
|
35
|
+
puts "Every 15 seconds, but my task requires 10 seconds"; sleep 10
|
44
36
|
end
|
45
37
|
|
46
38
|
every 10.seconds, :at => [":#{Time.now.min+1}", ":#{Time.now.min+2}"] do
|
data/examples/simple
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'rubygems' unless defined?(Gem)
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'forever'
|
5
|
+
|
6
|
+
Forever.run :fork => ENV['FORK'] do
|
7
|
+
dir File.expand_path('../', __FILE__) # Default is ../../__FILE__
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
puts 'before all'
|
11
|
+
end
|
12
|
+
|
13
|
+
after :each do
|
14
|
+
puts 'after all'
|
15
|
+
end
|
16
|
+
|
17
|
+
every 1.seconds do
|
18
|
+
puts 'wait me 10 seconds'; sleep 10
|
19
|
+
end
|
20
|
+
|
21
|
+
every 2.seconds do
|
22
|
+
puts 'every 2 seconds'
|
23
|
+
end
|
24
|
+
|
25
|
+
on_ready do
|
26
|
+
puts "All jobs will will wait me for 1 second"; sleep 1
|
27
|
+
end
|
28
|
+
|
29
|
+
on_exit do
|
30
|
+
puts "Bye bye"
|
31
|
+
end
|
32
|
+
end
|
data/examples/stress
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'rubygems' unless defined?(Gem)
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'forever'
|
5
|
+
|
6
|
+
Forever.run :fork => ENV['FORK'] do
|
7
|
+
dir File.expand_path('../', __FILE__) # Default is ../../__FILE__
|
8
|
+
|
9
|
+
(1..40).each do |i|
|
10
|
+
every(i.seconds) { puts 'Every %d seconds' % i; sleep i }
|
11
|
+
end
|
12
|
+
end
|
data/foreverb.gemspec
CHANGED
@@ -18,5 +18,5 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = %w(lib)
|
20
20
|
s.add_dependency 'thor', '~>0.14.6'
|
21
|
-
s.add_development_dependency '
|
21
|
+
s.add_development_dependency 'minitest'
|
22
22
|
end
|
data/lib/forever.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'yaml' unless defined?(YAML)
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
2
|
+
require 'forever/extensions'
|
3
|
+
require 'forever/every'
|
4
|
+
require 'forever/base'
|
5
|
+
require 'forever/version'
|
6
6
|
|
7
|
-
YAML::ENGINE.yamler =
|
7
|
+
YAML::ENGINE.yamler = 'syck' if defined?(YAML::ENGINE)
|
8
8
|
|
9
9
|
FOREVER_PATH = ENV['FOREVER_PATH'] ||= File.expand_path("~/.foreverb") unless defined?(FOREVER_PATH)
|
10
10
|
path = File.dirname(FOREVER_PATH)
|
@@ -15,8 +15,8 @@ module Forever
|
|
15
15
|
|
16
16
|
def run(options={}, &block)
|
17
17
|
caller_file = caller(1).map { |line| line.split(/:(?=\d|in )/)[0,1] }.flatten.first
|
18
|
-
options[:
|
19
|
-
options[:
|
18
|
+
options[:file] ||= File.expand_path(caller_file)
|
19
|
+
options[:dir] ||= File.expand_path('../../', options[:file]) # => we presume we are calling it from a bin|script dir
|
20
20
|
Base.new(options, &block)
|
21
21
|
end # run
|
22
|
-
end # Forever
|
22
|
+
end # Forever
|
data/lib/forever/base.rb
CHANGED
@@ -4,12 +4,18 @@ module Forever
|
|
4
4
|
|
5
5
|
class Base
|
6
6
|
include Every
|
7
|
+
attr_reader :started_at
|
7
8
|
|
8
9
|
def initialize(options={}, &block)
|
10
|
+
forking = options.delete(:fork)
|
11
|
+
|
12
|
+
# Run others methods
|
9
13
|
options.each { |k,v| send(k, v) }
|
10
14
|
|
11
15
|
instance_eval(&block)
|
12
16
|
|
17
|
+
raise 'No jobs defined!' if jobs.empty?
|
18
|
+
|
13
19
|
Dir.chdir(dir) if exists?(dir)
|
14
20
|
Dir.mkdir(File.dirname(log)) if log && !File.exist?(File.dirname(log))
|
15
21
|
Dir.mkdir(File.dirname(pid)) if pid && !File.exist?(File.dirname(pid))
|
@@ -48,9 +54,13 @@ module Forever
|
|
48
54
|
exit
|
49
55
|
end
|
50
56
|
|
57
|
+
# Enable REE - http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
|
58
|
+
GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
|
59
|
+
|
51
60
|
fork do
|
52
61
|
$0 = "Forever: #{$0}"
|
53
|
-
print "[\e[90m%s\e[0m] Process demonized with pid \e[1m%d\e[0m with Forever v.%s\n" %
|
62
|
+
print "[\e[90m%s\e[0m] Process demonized with pid \e[1m%d\e[0m with \e[1m%s\e[0m and Forever v.%s\n" %
|
63
|
+
[name, Process.pid, forking ? :fork : :thread, Forever::VERSION]
|
54
64
|
|
55
65
|
%w(INT TERM KILL).each { |signal| trap(signal) { stop! } }
|
56
66
|
trap(:HUP) do
|
@@ -65,27 +75,34 @@ module Forever
|
|
65
75
|
STDOUT.reopen(stream)
|
66
76
|
STDERR.reopen(STDOUT)
|
67
77
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
78
|
+
@started_at = Time.now
|
79
|
+
|
80
|
+
# Invoke our before :all filters
|
81
|
+
before_filters[:all].each { |block| safe_call(block) }
|
82
|
+
|
83
|
+
# Start deamons
|
84
|
+
until stopping?
|
85
|
+
if forking
|
86
|
+
begin
|
87
|
+
jobs.select { |job| job.time?(Time.now) }.each do |job|
|
88
|
+
Process.fork { job_call(job) }
|
89
|
+
end
|
90
|
+
rescue Errno::EAGAIN
|
91
|
+
puts "\n\nWait all processes since os cannot create a new one\n\n"
|
92
|
+
Process.waitall
|
77
93
|
end
|
94
|
+
else
|
95
|
+
jobs.each { |job| Thread.new { job_call(job) } if job.time?(Time.now) }
|
78
96
|
end
|
97
|
+
sleep 0.5
|
79
98
|
end
|
80
99
|
|
81
|
-
# Launch our workers
|
82
|
-
threads.map(&:join)
|
83
|
-
|
84
100
|
# If we are here it means we are exiting so we can remove the pid and pending stop.txt
|
85
101
|
FileUtils.rm_f(pid)
|
86
102
|
FileUtils.rm_f(stop_txt)
|
87
103
|
|
88
|
-
|
104
|
+
# Invoke our after :all filters
|
105
|
+
after_filters[:all].each { |block| safe_call(block) }
|
89
106
|
end
|
90
107
|
|
91
108
|
self
|
@@ -141,7 +158,7 @@ module Forever
|
|
141
158
|
pid_was = File.read(pid).to_i
|
142
159
|
FileUtils.rm_f(pid)
|
143
160
|
print "[\e[90m%s\e[0m] Killing process \e[1m%d\e[0m...\n" % [name, pid_was]
|
144
|
-
|
161
|
+
after_filters[:all].each { |block| safe_call(block) }
|
145
162
|
Process.kill(:KILL, pid_was)
|
146
163
|
else
|
147
164
|
print "[\e[90m%s\e[0m] Process with \e[1mnot found\e[0m" % name
|
@@ -174,14 +191,14 @@ module Forever
|
|
174
191
|
# Callback raised when at exit
|
175
192
|
#
|
176
193
|
def on_exit(&block)
|
177
|
-
|
194
|
+
after(:all, &block)
|
178
195
|
end
|
179
196
|
|
180
197
|
##
|
181
198
|
# Callback to fire when the daemon start (blocking, not in thread)
|
182
199
|
#
|
183
200
|
def on_ready(&block)
|
184
|
-
|
201
|
+
before(:all, &block)
|
185
202
|
end
|
186
203
|
|
187
204
|
##
|
@@ -214,29 +231,58 @@ module Forever
|
|
214
231
|
{ :dir => dir, :file => file, :log => log, :pid => pid }
|
215
232
|
end
|
216
233
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
config_was << config
|
222
|
-
File.open(FOREVER_PATH, "w") { |f| f.write config_was.to_yaml }
|
223
|
-
end
|
234
|
+
def before(filter, &block)
|
235
|
+
raise "Filter #{filter.inspect} not supported, available options are: :each, :all" unless [:each, :all].include?(filter)
|
236
|
+
before_filters[filter] << block
|
237
|
+
end
|
224
238
|
|
225
|
-
|
226
|
-
|
227
|
-
|
239
|
+
def after(filter, &block)
|
240
|
+
raise "Filter #{filter.inspect} not supported, available options are: :each, :all" unless [:each, :all].include?(filter)
|
241
|
+
after_filters[filter] << block
|
242
|
+
end
|
228
243
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
puts "\n\n%s\n %s\n\n" % [e.message, e.backtrace.join("\n ")]
|
234
|
-
on_error[e] if on_error
|
235
|
-
end
|
236
|
-
end
|
244
|
+
private
|
245
|
+
def before_filters
|
246
|
+
@_before_filters ||= Hash.new { |hash, k| hash[k] = [] }
|
247
|
+
end
|
237
248
|
|
238
|
-
|
239
|
-
|
249
|
+
def after_filters
|
250
|
+
@_after_filters ||= Hash.new { |hash, k| hash[k] = [] }
|
251
|
+
end
|
252
|
+
|
253
|
+
def stopping?
|
254
|
+
File.exist?(stop_txt) && File.mtime(stop_txt) > started_at
|
255
|
+
end
|
256
|
+
|
257
|
+
def write_config!
|
258
|
+
config_was = File.exist?(FOREVER_PATH) ? YAML.load_file(FOREVER_PATH) : []
|
259
|
+
config_was.delete_if { |conf| conf[:file] == file }
|
260
|
+
config_was << config
|
261
|
+
File.open(FOREVER_PATH, "w") { |f| f.write config_was.to_yaml }
|
262
|
+
end
|
263
|
+
|
264
|
+
def exists?(*values)
|
265
|
+
values.all? { |value| value && File.exist?(value) }
|
266
|
+
end
|
267
|
+
|
268
|
+
def job_call(job)
|
269
|
+
return unless job.time?(Time.now)
|
270
|
+
before_filters[:each].each { |block| safe_call(block) }
|
271
|
+
safe_call(job)
|
272
|
+
after_filters[:each].each { |block| safe_call(block) }
|
273
|
+
end
|
274
|
+
|
275
|
+
def safe_call(block)
|
276
|
+
begin
|
277
|
+
block.call
|
278
|
+
rescue Exception => e
|
279
|
+
puts "\n\n%s\n %s\n\n" % [e.message, e.backtrace.join("\n ")]
|
280
|
+
on_error[e] if on_error
|
240
281
|
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def stop_txt
|
285
|
+
@_stop_txt ||= File.join(dir, 'stop.txt')
|
286
|
+
end
|
241
287
|
end # Base
|
242
288
|
end # Forever
|
data/lib/forever/every.rb
CHANGED
@@ -1,26 +1,39 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
|
1
3
|
module Forever
|
2
4
|
module Every
|
3
5
|
class Job
|
4
|
-
attr_accessor :period, :option, :last, :running
|
5
6
|
|
6
7
|
def initialize(period, options, block)
|
7
|
-
@period
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
8
|
+
@period = period
|
9
|
+
@at_start = options[:at] == :start && options.delete(:at) ? true : false
|
10
|
+
@at = options[:at] ? parse_at(*options[:at]) : []
|
11
|
+
@tmp = File.join(Dir.tmpdir, '%x' % rand(255**10))
|
12
|
+
@block = block
|
11
13
|
end
|
12
14
|
|
13
15
|
def call
|
14
|
-
@
|
16
|
+
File.open(@tmp, 'w') { |f| f.write('running') }
|
17
|
+
FileUtils.touch(@tmp)
|
15
18
|
@block.call
|
16
19
|
ensure
|
17
|
-
@
|
20
|
+
File.open(@tmp, 'w') { |f| f.write('idle') }
|
21
|
+
end
|
22
|
+
|
23
|
+
def running?
|
24
|
+
File.exist?(@tmp) && File.read(@tmp) == 'running'
|
25
|
+
end
|
26
|
+
|
27
|
+
def last
|
28
|
+
File.mtime(@tmp)
|
29
|
+
rescue Errno::ENOENT
|
30
|
+
0
|
18
31
|
end
|
19
32
|
|
20
33
|
def time?(t)
|
21
|
-
|
34
|
+
elapsed_ready = (t - last).to_i >= @period
|
22
35
|
time_ready = @at.empty? || @at.any? { |at| (at[0].empty? || t.hour == at[0].to_i) && (at[1].empty? || t.min == at[1].to_i) }
|
23
|
-
!running &&
|
36
|
+
!running? && elapsed_ready && time_ready
|
24
37
|
end
|
25
38
|
|
26
39
|
private
|
@@ -43,4 +56,4 @@ module Forever
|
|
43
56
|
@_jobs ||= []
|
44
57
|
end
|
45
58
|
end # Every
|
46
|
-
end # Forever
|
59
|
+
end # Forever
|
data/lib/forever/version.rb
CHANGED
data/spec/cli_spec.rb
CHANGED
@@ -1,34 +1,34 @@
|
|
1
|
-
require 'spec_helper'
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
2
|
|
3
|
-
describe
|
4
|
-
|
5
|
-
|
6
|
-
cli('list').should match(/Your config is empty/)
|
7
|
-
cli('list').should match(FOREVER_PATH)
|
8
|
-
cli('list -m').should match(/PID RSS CPU CMD/)
|
3
|
+
describe 'CLI' do
|
4
|
+
def cli(task)
|
5
|
+
`#{Gem.ruby} #{File.expand_path('../../bin/foreverb', __FILE__)} #{task}`
|
9
6
|
end
|
10
7
|
|
11
|
-
it
|
8
|
+
it 'should list daemons' do
|
9
|
+
cli('list').must_match(/Your config is empty/)
|
10
|
+
cli('list').must_match(FOREVER_PATH)
|
11
|
+
cli('list -m').must_match(/PID RSS CPU CMD/)
|
12
12
|
run_example
|
13
|
-
cli('list').
|
14
|
-
cli('list -m').
|
13
|
+
cli('list').must_match(/RUNNING/)
|
14
|
+
cli('list -m').must_match(/Forever:\s/)
|
15
15
|
end
|
16
16
|
|
17
17
|
it "should stop daemons" do
|
18
18
|
run_example
|
19
|
-
cli('list').
|
19
|
+
cli('list').must_match(/RUNNING/)
|
20
20
|
result = cli('stop -a -y')
|
21
|
-
result.
|
22
|
-
result.
|
23
|
-
cli('list').
|
21
|
+
result.must_match(/STOPPING/)
|
22
|
+
result.wont_match(/ERROR/)
|
23
|
+
cli('list').must_match(/NOT RUNNING/)
|
24
24
|
end
|
25
25
|
|
26
|
-
it
|
26
|
+
it 'should kill daemons' do
|
27
27
|
run_example
|
28
|
-
cli('list').
|
28
|
+
cli('list').must_match(/RUNNING/)
|
29
29
|
result = cli('kill -a -y')
|
30
|
-
result.
|
31
|
-
result.
|
32
|
-
cli('list').
|
30
|
+
result.must_match(/KILLING/)
|
31
|
+
result.wont_match(/ERROR/)
|
32
|
+
cli('list').must_match(/NOT RUNNING/)
|
33
33
|
end
|
34
34
|
end
|
data/spec/foreverb_spec.rb
CHANGED
@@ -1,48 +1,48 @@
|
|
1
|
-
require 'spec_helper'
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
2
|
|
3
3
|
describe Forever do
|
4
4
|
|
5
|
-
before :each do
|
6
|
-
$stdout = StringIO.new
|
7
|
-
ARGV << 'up'
|
8
|
-
end
|
9
|
-
|
10
5
|
it 'should set a basic config' do
|
11
|
-
|
12
|
-
@forever.dir.
|
13
|
-
@forever.log.
|
14
|
-
@forever.pid.
|
15
|
-
@forever.file.
|
6
|
+
run_example
|
7
|
+
@forever.dir.must_equal File.expand_path("../../", __FILE__)
|
8
|
+
@forever.log.must_equal File.join(@forever.dir, 'log', File.basename(example_filename, '.*') + '.log')
|
9
|
+
@forever.pid.must_equal File.join(@forever.dir, 'tmp', File.basename(example_filename, '.*') + '.pid')
|
10
|
+
@forever.file.must_equal example_filename
|
16
11
|
config = YAML.load_file(FOREVER_PATH)
|
17
|
-
config[0][:file].
|
18
|
-
config[0][:log].
|
19
|
-
config[0][:pid].
|
12
|
+
config[0][:file].must_equal example_filename
|
13
|
+
config[0][:log].must_equal @forever.log
|
14
|
+
config[0][:pid].must_equal @forever.pid
|
20
15
|
end
|
21
16
|
|
22
17
|
it 'should set a custom config' do
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
@forever.
|
27
|
-
@forever.
|
28
|
-
@forever.pid.should == File.join(@forever.dir, 'tmp', File.basename(__FILE__) + '.pid')
|
29
|
-
@forever.file.should == __FILE__
|
18
|
+
run_example(:dir => Dir.tmpdir)
|
19
|
+
@forever.dir.must_equal Dir.tmpdir
|
20
|
+
@forever.log.must_equal File.join(@forever.dir, 'log', File.basename(example_filename, '.*') + '.log')
|
21
|
+
@forever.pid.must_equal File.join(@forever.dir, 'tmp', File.basename(example_filename, '.*') + '.pid')
|
22
|
+
@forever.file.must_equal example_filename
|
30
23
|
config = YAML.load_file(FOREVER_PATH)
|
31
|
-
config[0][:file].
|
32
|
-
config[0][:log].
|
33
|
-
config[0][:pid].
|
24
|
+
config[0][:file].must_equal example_filename
|
25
|
+
config[0][:log].must_equal @forever.log
|
26
|
+
config[0][:pid].must_equal @forever.pid
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should launch a daemon with threads with soft stop' do
|
30
|
+
run_example
|
31
|
+
sleep 0.1 while !File.exist?(@forever.pid)
|
32
|
+
pid = File.read(@forever.pid).to_i
|
33
|
+
sleep 1
|
34
|
+
out, err = capture_io { @forever.stop }
|
35
|
+
out.must_match(/waiting the daemon's death/i)
|
36
|
+
out.must_match(/#{pid}/)
|
34
37
|
end
|
35
38
|
|
36
|
-
it 'should launch a daemon' do
|
37
|
-
|
38
|
-
stdout_was, $stdout = $stdout, StringIO.new
|
39
|
-
@forever = Forever.run do
|
40
|
-
on_ready { sleep 2 }
|
41
|
-
end
|
39
|
+
it 'should launch a daemon with threads with soft stop' do
|
40
|
+
run_example(:fork => true)
|
42
41
|
sleep 0.1 while !File.exist?(@forever.pid)
|
43
42
|
pid = File.read(@forever.pid).to_i
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
sleep 1
|
44
|
+
out, err = capture_io { @forever.stop }
|
45
|
+
out.must_match(/waiting the daemon's death/i)
|
46
|
+
out.must_match(/#{pid}/)
|
47
47
|
end
|
48
48
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,48 +1,36 @@
|
|
1
1
|
FOREVER_PATH = ENV['FOREVER_PATH'] ||= File.expand_path("../tmp/db.yaml", __FILE__)
|
2
2
|
require 'rubygems' unless defined?(Gem)
|
3
3
|
require 'bundler/setup'
|
4
|
-
require '
|
4
|
+
require 'minitest/autorun'
|
5
5
|
require 'forever'
|
6
6
|
require 'fileutils'
|
7
|
+
require 'tmpdir'
|
7
8
|
|
8
|
-
|
9
|
-
def capture_stdout(&block)
|
10
|
-
stdout_was, $stdout = $stdout, StringIO.new
|
11
|
-
block.call
|
12
|
-
return $stdout
|
13
|
-
ensure
|
14
|
-
$stdout = stdout_was
|
15
|
-
end
|
9
|
+
$dir = File.expand_path('.')
|
16
10
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|
11
|
+
class MiniTest::Spec
|
12
|
+
def run_example(options={}, &block)
|
13
|
+
block = proc { every(1.second) { puts 'foo' } } unless block_given?
|
14
|
+
capture_io { @forever = Forever.run(options, &block) }
|
23
15
|
end
|
24
16
|
|
25
|
-
|
26
|
-
output = `#{Gem.ruby} #{File.expand_path('../../bin/foreverb', __FILE__)} #{task}`
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
RSpec.configure do |config|
|
31
|
-
config.include(Helper)
|
17
|
+
let(:example_filename) { File.expand_path(__FILE__) }
|
32
18
|
|
33
|
-
|
19
|
+
before do
|
20
|
+
Dir.chdir($dir)
|
34
21
|
FileUtils.rm_rf File.dirname(FOREVER_PATH)
|
35
22
|
Dir.mkdir File.dirname(FOREVER_PATH)
|
36
23
|
ARGV.clear
|
37
24
|
end
|
38
25
|
|
39
|
-
|
26
|
+
after do
|
40
27
|
FileUtils.rm_rf(File.dirname(FOREVER_PATH))
|
41
28
|
if @forever
|
42
|
-
|
29
|
+
capture_io { @forever.stop! }
|
43
30
|
FileUtils.rm_rf(File.dirname(@forever.log)) if @forever.log
|
44
31
|
FileUtils.rm_rf(File.dirname(@forever.pid)) if @forever.pid
|
45
32
|
end
|
33
|
+
Dir.chdir($dir)
|
46
34
|
ARGV.clear
|
47
35
|
end
|
48
36
|
end
|
metadata
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreverb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 50
|
5
|
+
prerelease: 6
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
- a
|
11
|
+
version: 0.3.0.a
|
11
12
|
platform: ruby
|
12
13
|
authors:
|
13
14
|
- DAddYE
|
@@ -15,7 +16,7 @@ autorequire:
|
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
17
18
|
|
18
|
-
date: 2011-
|
19
|
+
date: 2011-10-04 00:00:00 +02:00
|
19
20
|
default_executable:
|
20
21
|
dependencies:
|
21
22
|
- !ruby/object:Gem::Dependency
|
@@ -35,19 +36,17 @@ dependencies:
|
|
35
36
|
type: :runtime
|
36
37
|
version_requirements: *id001
|
37
38
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
39
|
+
name: minitest
|
39
40
|
prerelease: false
|
40
41
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
42
|
none: false
|
42
43
|
requirements:
|
43
|
-
- -
|
44
|
+
- - ">="
|
44
45
|
- !ruby/object:Gem::Version
|
45
|
-
hash:
|
46
|
+
hash: 3
|
46
47
|
segments:
|
47
|
-
- 2
|
48
|
-
- 6
|
49
48
|
- 0
|
50
|
-
version:
|
49
|
+
version: "0"
|
51
50
|
type: :development
|
52
51
|
version_requirements: *id002
|
53
52
|
description: Small daemon framework for ruby, with logging, error handler, scheduling and much more.
|
@@ -66,7 +65,10 @@ files:
|
|
66
65
|
- README.md
|
67
66
|
- Rakefile
|
68
67
|
- bin/foreverb
|
69
|
-
- examples/
|
68
|
+
- examples/complex
|
69
|
+
- examples/simple
|
70
|
+
- examples/stress
|
71
|
+
- examples/tmp/complex.pid
|
70
72
|
- foreverb.gemspec
|
71
73
|
- lib/forever.rb
|
72
74
|
- lib/forever/base.rb
|
@@ -97,12 +99,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
99
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
100
|
none: false
|
99
101
|
requirements:
|
100
|
-
- - "
|
102
|
+
- - ">"
|
101
103
|
- !ruby/object:Gem::Version
|
102
|
-
hash:
|
104
|
+
hash: 25
|
103
105
|
segments:
|
104
|
-
-
|
105
|
-
|
106
|
+
- 1
|
107
|
+
- 3
|
108
|
+
- 1
|
109
|
+
version: 1.3.1
|
106
110
|
requirements: []
|
107
111
|
|
108
112
|
rubyforge_project: foreverb
|