backgroundrb-rails3 1.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.
- data/.autotest +17 -0
- data/ChangeLog +50 -0
- data/Gemfile +11 -0
- data/LICENSE +4 -0
- data/MIT-LICENSE +20 -0
- data/README +22 -0
- data/Rakefile +128 -0
- data/TODO.org +5 -0
- data/app/controller/backgroundrb_status_controller.rb +6 -0
- data/backgroundrb-rails3.gemspec +219 -0
- data/config/backgroundrb.yml +11 -0
- data/doc/Rakefile +5 -0
- data/doc/config.yaml +2 -0
- data/doc/content/advanced/advanced.txt +76 -0
- data/doc/content/advanced/advanced.yaml +4 -0
- data/doc/content/bugs/bugs.txt +20 -0
- data/doc/content/bugs/bugs.yaml +5 -0
- data/doc/content/community/community.txt +36 -0
- data/doc/content/community/community.yaml +5 -0
- data/doc/content/content.txt +168 -0
- data/doc/content/content.yaml +5 -0
- data/doc/content/faq/faq.txt +41 -0
- data/doc/content/faq/faq.yaml +5 -0
- data/doc/content/rails/rails.txt +182 -0
- data/doc/content/rails/rails.yaml +5 -0
- data/doc/content/scheduling/scheduling.txt +166 -0
- data/doc/content/scheduling/scheduling.yaml +5 -0
- data/doc/content/workers/workers.txt +178 -0
- data/doc/content/workers/workers.yaml +5 -0
- data/doc/layouts/default/default.erb +56 -0
- data/doc/layouts/default/default.yaml +4 -0
- data/doc/lib/default.rb +7 -0
- data/doc/output/Assets/BG-Ad-Top.png +0 -0
- data/doc/output/Assets/BG-Body.png +0 -0
- data/doc/output/Assets/BG-Feed.png +0 -0
- data/doc/output/Assets/BG-Menu-Hover.png +0 -0
- data/doc/output/Assets/BG-Menu.png +0 -0
- data/doc/output/Assets/BG-Sidebar-Bottom.png +0 -0
- data/doc/output/Assets/Button-Feed.png +0 -0
- data/doc/output/images/bg-ad-top.png +0 -0
- data/doc/output/images/bg-body.png +0 -0
- data/doc/output/images/bg-feed.gif +0 -0
- data/doc/output/images/bg-footer.jpg +0 -0
- data/doc/output/images/bg-header.jpg +0 -0
- data/doc/output/images/bg-menu-hover.png +0 -0
- data/doc/output/images/bg-menu.png +0 -0
- data/doc/output/images/bg-sidebar-bottom.gif +0 -0
- data/doc/output/images/button-feed.png +0 -0
- data/doc/output/images/icon-comment.png +0 -0
- data/doc/output/images/more_icon.gif +0 -0
- data/doc/output/style.css +299 -0
- data/doc/page_defaults.yaml +13 -0
- data/doc/tasks/default.rake +3 -0
- data/doc/templates/default/default.txt +1 -0
- data/doc/templates/default/default.yaml +4 -0
- data/examples/backgroundrb.yml +25 -0
- data/examples/foo_controller.rb +48 -0
- data/examples/god_worker.rb +7 -0
- data/examples/worker_tests/god_worker_test.rb +8 -0
- data/examples/workers/error_worker.rb +17 -0
- data/examples/workers/foo_worker.rb +38 -0
- data/examples/workers/god_worker.rb +7 -0
- data/examples/workers/model_worker.rb +13 -0
- data/examples/workers/renewal_worker.rb +11 -0
- data/examples/workers/rss_worker.rb +26 -0
- data/examples/workers/server_worker.rb +31 -0
- data/examples/workers/world_worker.rb +12 -0
- data/examples/workers/xmpp_worker.rb +7 -0
- data/init.rb +7 -0
- data/install.rb +1 -0
- data/know_issues.org +5 -0
- data/lib/backgroundrb.rb +1 -0
- data/lib/backgroundrb/bdrb_client_helper.rb +8 -0
- data/lib/backgroundrb/bdrb_cluster_connection.rb +156 -0
- data/lib/backgroundrb/bdrb_config.rb +43 -0
- data/lib/backgroundrb/bdrb_conn_error.rb +29 -0
- data/lib/backgroundrb/bdrb_connection.rb +179 -0
- data/lib/backgroundrb/bdrb_job_queue.rb +79 -0
- data/lib/backgroundrb/bdrb_result.rb +19 -0
- data/lib/backgroundrb/bdrb_start_stop.rb +146 -0
- data/lib/backgroundrb/rails_worker_proxy.rb +181 -0
- data/lib/backgroundrb/railtie.rb +48 -0
- data/lib/generators/backgroundrb/bdrb_migration/USAGE +12 -0
- data/lib/generators/backgroundrb/bdrb_migration/bdrb_migration_generator.rb +15 -0
- data/lib/generators/backgroundrb/bdrb_migration/templates/migration.rb +27 -0
- data/lib/generators/backgroundrb/worker/USAGE +16 -0
- data/lib/generators/backgroundrb/worker/templates/unit_test.rb +12 -0
- data/lib/generators/backgroundrb/worker/templates/worker.rb +7 -0
- data/lib/generators/backgroundrb/worker/worker_generator.rb +14 -0
- data/lib/tasks/backgroundrb_tasks.rake +103 -0
- data/release_notes.org +48 -0
- data/release_points.org +46 -0
- data/script/backgroundrb +52 -0
- data/script/bdrb_test_helper.rb +99 -0
- data/script/load_worker_env.rb +31 -0
- data/script/monitrc +25 -0
- data/server/backgroundrb_server.rb +12 -0
- data/server/lib/bdrb_result_storage.rb +62 -0
- data/server/lib/bdrb_server_helper.rb +24 -0
- data/server/lib/bdrb_thread_pool.rb +127 -0
- data/server/lib/cron_trigger.rb +197 -0
- data/server/lib/invalid_dump_error.rb +4 -0
- data/server/lib/log_worker.rb +25 -0
- data/server/lib/master_proxy.rb +140 -0
- data/server/lib/master_worker.rb +187 -0
- data/server/lib/meta_worker.rb +432 -0
- data/server/lib/trigger.rb +34 -0
- data/test/bdrb_client_test_helper.rb +5 -0
- data/test/bdrb_test_helper.rb +35 -0
- data/test/client/backgroundrb.yml +17 -0
- data/test/client/test_bdrb_client_helper.rb +13 -0
- data/test/client/test_bdrb_cluster_connection.rb +162 -0
- data/test/client/test_bdrb_config.rb +20 -0
- data/test/client/test_bdrb_connection.rb +29 -0
- data/test/client/test_bdrb_job_queue.rb +63 -0
- data/test/client/test_worker_proxy.rb +130 -0
- data/test/server/test_cron_trigger.rb +281 -0
- data/test/server/test_master_proxy.rb +54 -0
- data/test/server/test_master_worker.rb +157 -0
- data/test/server/test_meta_worker.rb +281 -0
- data/test/server/test_result_storage.rb +14 -0
- data/test/socket_mocker.rb +34 -0
- data/test/workers/bar_worker.rb +10 -0
- data/test/workers/foo_worker.rb +10 -0
- data/uninstall.rb +1 -0
- metadata +345 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
RAILS_HOME = File.expand_path(File.join(File.dirname(__FILE__),".."))
|
|
4
|
+
|
|
5
|
+
require "rubygems"
|
|
6
|
+
require "active_support"
|
|
7
|
+
require "active_record"
|
|
8
|
+
|
|
9
|
+
require "yaml"
|
|
10
|
+
require "erb"
|
|
11
|
+
require "logger"
|
|
12
|
+
require "optparse"
|
|
13
|
+
|
|
14
|
+
require RAILS_HOME + "/config/boot"
|
|
15
|
+
require "backgroundrb"
|
|
16
|
+
|
|
17
|
+
BDRB_HOME = ::BackgrounDRb::BACKGROUNDRB_ROOT
|
|
18
|
+
|
|
19
|
+
["server","server/lib","lib","lib/backgroundrb"].each { |x| $LOAD_PATH.unshift(BDRB_HOME + "/#{x}")}
|
|
20
|
+
|
|
21
|
+
$LOAD_PATH.unshift(File.join(RAILS_HOME,"lib","workers"))
|
|
22
|
+
|
|
23
|
+
require "bdrb_config"
|
|
24
|
+
|
|
25
|
+
BDRB_CONFIG = BackgrounDRb::Config.read_config("#{RAILS_HOME}/config/backgroundrb.yml")
|
|
26
|
+
|
|
27
|
+
if !(::Packet::WorkerRunner::WORKER_OPTIONS[:worker_env] == false)
|
|
28
|
+
require RAILS_HOME + "/config/environment"
|
|
29
|
+
end
|
|
30
|
+
require "backgroundrb_server"
|
|
31
|
+
|
data/script/monitrc
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
set daemon 60
|
|
2
|
+
set mailserver localhost
|
|
3
|
+
set mail-format {
|
|
4
|
+
from: sample@foobar.com
|
|
5
|
+
subject: Alert from Backgroundrb
|
|
6
|
+
}
|
|
7
|
+
set alert hemant@gmail.com
|
|
8
|
+
|
|
9
|
+
set httpd port 3000 and
|
|
10
|
+
use address localhost # only accept connection from localhost
|
|
11
|
+
allow localhost # allow localhost to connect to the server and
|
|
12
|
+
|
|
13
|
+
check process backgroundrb
|
|
14
|
+
with pidfile /home/hemant/rails_sandbox/tmp/pids/backgroundrb_11008.pid
|
|
15
|
+
start program = "/usr/bin/ruby /home/hemant/rails_sandbox/script/backgroundrb start"
|
|
16
|
+
stop program = "/usr/bin/ruby /home/hemant/rails_sandbox/script/backgroundrb stop"
|
|
17
|
+
if totalmem > 50.0 MB for 5 cycles then restart
|
|
18
|
+
if cpu usage > 95% for 3 cycles then restart
|
|
19
|
+
|
|
20
|
+
if failed port 11008 type tcp
|
|
21
|
+
with timeout 30 seconds
|
|
22
|
+
for 5 cycles
|
|
23
|
+
then restart
|
|
24
|
+
group backgroundrb
|
|
25
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require "chronic"
|
|
2
|
+
require "lib/bdrb_result_storage"
|
|
3
|
+
require "lib/bdrb_thread_pool"
|
|
4
|
+
require "lib/bdrb_server_helper"
|
|
5
|
+
require "lib/master_worker"
|
|
6
|
+
require "lib/master_proxy"
|
|
7
|
+
require "lib/cron_trigger"
|
|
8
|
+
require "lib/invalid_dump_error"
|
|
9
|
+
require "lib/log_worker"
|
|
10
|
+
require "lib/trigger"
|
|
11
|
+
require "lib/meta_worker"
|
|
12
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module BackgrounDRb
|
|
2
|
+
class ResultStorage
|
|
3
|
+
attr_accessor :cache,:worker_name,:worker_key,:storage_type
|
|
4
|
+
def initialize(worker_name,worker_key,storage_type = nil)
|
|
5
|
+
@worker_name = worker_name
|
|
6
|
+
@worker_key = worker_key
|
|
7
|
+
@mutex = Mutex.new
|
|
8
|
+
@storage_type = storage_type
|
|
9
|
+
@cache = (@storage_type == 'memcache') ? memcache_instance : {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Initialize Memcache for result or object caching
|
|
13
|
+
def memcache_instance
|
|
14
|
+
require 'memcache'
|
|
15
|
+
memcache_options = {
|
|
16
|
+
:c_threshold => 10_000,
|
|
17
|
+
:compression => true,
|
|
18
|
+
:debug => false,
|
|
19
|
+
:namespace => 'backgroundrb_result_hash',
|
|
20
|
+
:readonly => false,
|
|
21
|
+
:urlencode => false
|
|
22
|
+
}
|
|
23
|
+
t_cache = MemCache.new(memcache_options)
|
|
24
|
+
t_cache.servers = BDRB_CONFIG[:memcache].split(',')
|
|
25
|
+
t_cache
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# generate key based on worker_name and worker_key
|
|
29
|
+
# for local cache, there is no need of unique key
|
|
30
|
+
def gen_key key
|
|
31
|
+
if storage_type == 'memcache'
|
|
32
|
+
key = [worker_name,worker_key,key].compact.join('_')
|
|
33
|
+
key
|
|
34
|
+
else
|
|
35
|
+
key
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# fetch object from cache
|
|
40
|
+
def [] key
|
|
41
|
+
@mutex.synchronize { @cache[gen_key(key)] }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def []= key,value
|
|
45
|
+
@mutex.synchronize { @cache[gen_key(key)] = value }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def delete key
|
|
49
|
+
@mutex.synchronize { @cache.delete(gen_key(key)) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def shift key
|
|
53
|
+
val = nil
|
|
54
|
+
@mutex.synchronize do
|
|
55
|
+
val = @cache[key]
|
|
56
|
+
@cache.delete(key)
|
|
57
|
+
end
|
|
58
|
+
return val
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module BackgrounDRb
|
|
2
|
+
module BdrbServerHelper
|
|
3
|
+
# Load data using Marshal.load, if load fails because of undefined constant
|
|
4
|
+
# try to load the constant. FIXME: regexp needs to handle all the cases.
|
|
5
|
+
def load_data data
|
|
6
|
+
begin
|
|
7
|
+
return Marshal.load(data)
|
|
8
|
+
rescue
|
|
9
|
+
error_msg = $!.message
|
|
10
|
+
if error_msg =~ /^undefined\ .+\ ([A-Z][^:]+)/
|
|
11
|
+
file_name = $1.underscore
|
|
12
|
+
begin
|
|
13
|
+
require file_name
|
|
14
|
+
return Marshal.load(data)
|
|
15
|
+
rescue
|
|
16
|
+
return nil
|
|
17
|
+
end
|
|
18
|
+
else
|
|
19
|
+
return nil
|
|
20
|
+
end
|
|
21
|
+
end # end of load_data method
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
module BackgrounDRb
|
|
2
|
+
|
|
3
|
+
class InterruptedException < RuntimeError ; end
|
|
4
|
+
|
|
5
|
+
class WorkData
|
|
6
|
+
attr_accessor :args,:block,:job_method,:persistent_job_id,:job_key
|
|
7
|
+
def initialize(args,job_key,job_method,persistent_job_id)
|
|
8
|
+
@args = args
|
|
9
|
+
@job_key = job_key
|
|
10
|
+
@job_method = job_method
|
|
11
|
+
@persistent_job_id = persistent_job_id
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class ThreadPool
|
|
16
|
+
attr_accessor :size,:threads,:work_queue,:logger
|
|
17
|
+
attr_accessor :result_queue,:master
|
|
18
|
+
|
|
19
|
+
def initialize(master,size,logger)
|
|
20
|
+
@master = master
|
|
21
|
+
@logger = logger
|
|
22
|
+
@size = size
|
|
23
|
+
@threads = []
|
|
24
|
+
@work_queue = []
|
|
25
|
+
@mutex = Monitor.new
|
|
26
|
+
@cv = @mutex.new_cond
|
|
27
|
+
@size.times { add_thread }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# can be used to make a call in threaded manner
|
|
31
|
+
# passed block runs in a thread from thread pool
|
|
32
|
+
# for example in a worker method you can do:
|
|
33
|
+
# def user_tags url
|
|
34
|
+
# thread_pool.defer(:fetch_url,url)
|
|
35
|
+
# end
|
|
36
|
+
# def fetch_url(url)
|
|
37
|
+
# begin
|
|
38
|
+
# data = Net::HTTP.get(url,'/')
|
|
39
|
+
# File.open("#{RAILS_ROOT}/log/pages.txt","w") do |fl|
|
|
40
|
+
# fl.puts(data)
|
|
41
|
+
# end
|
|
42
|
+
# rescue
|
|
43
|
+
# logger.info "Error downloading page"
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
# you can invoke above method from rails as:
|
|
47
|
+
# MiddleMan.worker(:rss_worker).async_user_tags(:arg => "en.wikipedia.org")
|
|
48
|
+
# assuming method is defined in rss_worker
|
|
49
|
+
|
|
50
|
+
def defer(method_name,args = nil)
|
|
51
|
+
@mutex.synchronize do
|
|
52
|
+
job_key = Thread.current[:job_key]
|
|
53
|
+
persistent_job_id = Thread.current[:persistent_job_id]
|
|
54
|
+
@cv.wait_while { @work_queue.size >= size }
|
|
55
|
+
@work_queue.push(WorkData.new(args,job_key,method_name,persistent_job_id))
|
|
56
|
+
@cv.broadcast
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Start worker threads
|
|
61
|
+
def add_thread
|
|
62
|
+
@threads << Thread.new do
|
|
63
|
+
Thread.current[:job_key] = nil
|
|
64
|
+
Thread.current[:persistent_job_id] = nil
|
|
65
|
+
while true
|
|
66
|
+
begin
|
|
67
|
+
task = nil
|
|
68
|
+
@mutex.synchronize do
|
|
69
|
+
@cv.wait_while { @work_queue.size == 0 }
|
|
70
|
+
task = @work_queue.pop
|
|
71
|
+
@cv.broadcast
|
|
72
|
+
end
|
|
73
|
+
if task
|
|
74
|
+
Thread.current[:job_key] = task.job_key
|
|
75
|
+
Thread.current[:persistent_job_id] = task.persistent_job_id
|
|
76
|
+
block_result = run_task(task)
|
|
77
|
+
end
|
|
78
|
+
rescue BackgrounDRb::InterruptedException
|
|
79
|
+
STDERR.puts("BackgrounDRb thread interrupted: #{Thread.current.inspect}")
|
|
80
|
+
STDERR.flush
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# run tasks popped out of queue
|
|
87
|
+
def run_task task
|
|
88
|
+
block_arity = master.method(task.job_method).arity
|
|
89
|
+
begin
|
|
90
|
+
check_db_connection
|
|
91
|
+
t_data = task.args
|
|
92
|
+
result = nil
|
|
93
|
+
if block_arity != 0
|
|
94
|
+
result = master.send(task.job_method,task.args)
|
|
95
|
+
else
|
|
96
|
+
result = master.send(task.job_method)
|
|
97
|
+
end
|
|
98
|
+
return result
|
|
99
|
+
rescue BackgrounDRb::InterruptedException => e
|
|
100
|
+
# Don't log, just re-raise
|
|
101
|
+
raise e
|
|
102
|
+
rescue Object => bdrb_error
|
|
103
|
+
log_exception(bdrb_error)
|
|
104
|
+
return nil
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def log_exception exception_object
|
|
109
|
+
STDERR.puts exception_object.to_s
|
|
110
|
+
STDERR.puts exception_object.backtrace.join("\n")
|
|
111
|
+
STDERR.flush
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Periodic check for lost database connections and closed connections
|
|
116
|
+
def check_db_connection
|
|
117
|
+
begin
|
|
118
|
+
ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord)
|
|
119
|
+
rescue Object => bdrb_error
|
|
120
|
+
log_exception(bdrb_error)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
end #end of class ThreadPool
|
|
126
|
+
end # end of module BackgrounDRb
|
|
127
|
+
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
module BackgrounDRb
|
|
2
|
+
class CronTrigger
|
|
3
|
+
WDAYS = { 0 => "Sunday",1 => "Monday",2 => "Tuesday",3 => "Wednesday", 4 => "Thursday", 5 => "Friday", 6 => "Saturday" }
|
|
4
|
+
LeapYearMonthDays = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
5
|
+
CommonYearMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
6
|
+
|
|
7
|
+
attr_reader :sec, :min, :hour, :day, :month, :wday, :year, :cron_expr
|
|
8
|
+
|
|
9
|
+
# initialize the Cron Trigger
|
|
10
|
+
def initialize(expr)
|
|
11
|
+
self.cron_expr = expr
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# create the cron expression and populate instance variables.
|
|
15
|
+
def cron_expr=(expr)
|
|
16
|
+
@cron_expr = expr
|
|
17
|
+
self.sec, self.min, self.hour, self.day, self.month, self.wday, self.year = @cron_expr.split(' ')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def fire_after_time(p_time)
|
|
21
|
+
@t_sec,@t_min,@t_hour,@t_day,@t_month,@t_year,@t_wday,@t_yday,@t_idst,@t_zone = p_time.to_a
|
|
22
|
+
@count = 0
|
|
23
|
+
loop do
|
|
24
|
+
@count += 1
|
|
25
|
+
|
|
26
|
+
if @year && !@year.include?(@t_year)
|
|
27
|
+
return nil if @t_year > @year.max
|
|
28
|
+
@t_year = @year.detect { |y| y > @t_year }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# if range of months doesn't include current month, find next month from the range
|
|
32
|
+
unless @month.include?(@t_month)
|
|
33
|
+
next_month = @month.detect { |m| m > @t_month } || @month.min
|
|
34
|
+
@t_day,@t_hour,@t_min,@t_sec = @day.min,@hour.min,@min.min,@sec.min
|
|
35
|
+
if next_month < @t_month
|
|
36
|
+
@t_month = next_month
|
|
37
|
+
@t_year += 1
|
|
38
|
+
retry
|
|
39
|
+
end
|
|
40
|
+
@t_month = next_month
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if !day_restricted? && wday_restricted?
|
|
44
|
+
unless @wday.include?(@t_wday)
|
|
45
|
+
next_wday = @wday.detect { |w| w > @t_wday} || @wday.min
|
|
46
|
+
@t_hour,@t_min,@t_sec = @hour.min,@min.min,@sec.min
|
|
47
|
+
t_time = Chronic.parse("next #{WDAYS[next_wday]}",:now => current_time)
|
|
48
|
+
@t_day,@t_month,@t_year = t_time.to_a[3..5]
|
|
49
|
+
@t_wday = next_wday
|
|
50
|
+
retry
|
|
51
|
+
end
|
|
52
|
+
elsif !wday_restricted? && day_restricted?
|
|
53
|
+
day_range = (1.. month_days(@t_year,@t_month))
|
|
54
|
+
# day array, that includes days which are present in current month
|
|
55
|
+
day_array = @day.select { |d| day_range === d }
|
|
56
|
+
unless day_array.include?(@t_day)
|
|
57
|
+
next_day = day_array.detect { |d| d > @t_day } || day_array.min
|
|
58
|
+
@t_hour,@t_min,@t_sec = @hour.min,@min.min,@sec.min
|
|
59
|
+
if !next_day || next_day < @t_day
|
|
60
|
+
t_time = Chronic.parse("next month",:now => current_time)
|
|
61
|
+
@t_day = next_day.nil? ? @day.min : next_day
|
|
62
|
+
@t_month,@t_year = t_time.month,t_time.year
|
|
63
|
+
retry
|
|
64
|
+
end
|
|
65
|
+
@t_day = next_day
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
# if both day and wday are restricted cron should give preference to one thats closer to current time
|
|
69
|
+
day_range = (1 .. month_days(@t_year,@t_month))
|
|
70
|
+
day_array = @day.select { |d| day_range === d }
|
|
71
|
+
if !day_array.include?(@t_day) && !@wday.include?(@t_wday)
|
|
72
|
+
next_day = day_array.detect { |d| d > @t_day } || day_array.min
|
|
73
|
+
next_wday = @wday.detect { |w| w > @t_wday } || @wday.min
|
|
74
|
+
@t_hour,@t_min,@t_sec = @hour.min,@min.min,@sec.min
|
|
75
|
+
|
|
76
|
+
# if next_day is nil or less than @t_day it means that it should run in next month
|
|
77
|
+
if !next_day || next_day < @t_day
|
|
78
|
+
next_time_mday = Chronic.parse("next month",:now => current_time)
|
|
79
|
+
else
|
|
80
|
+
@t_day = next_day
|
|
81
|
+
next_time_mday = current_time
|
|
82
|
+
end
|
|
83
|
+
next_time_wday = Chronic.parse("next #{WDAYS[next_wday]}",:now => current_time)
|
|
84
|
+
if next_time_mday < next_time_wday
|
|
85
|
+
@t_day,@t_month,@t_year = next_time_mday.to_a[3..5]
|
|
86
|
+
else
|
|
87
|
+
@t_day,@t_month,@t_year = next_time_wday.to_a[3..5]
|
|
88
|
+
end
|
|
89
|
+
retry
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
unless @hour.include?(@t_hour)
|
|
94
|
+
next_hour = @hour.detect { |h| h > @t_hour } || @hour.min
|
|
95
|
+
@t_min,@t_sec = @min.min,@sec.min
|
|
96
|
+
if next_hour < @t_hour
|
|
97
|
+
@t_hour = next_hour
|
|
98
|
+
next_day = Chronic.parse("next day",:now => current_time)
|
|
99
|
+
@t_day,@t_month,@t_year,@t_wday = next_day.to_a[3..6]
|
|
100
|
+
retry
|
|
101
|
+
end
|
|
102
|
+
@t_hour = next_hour
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
unless @min.include?(@t_min)
|
|
106
|
+
next_min = @min.detect { |m| m > @t_min } || @min.min
|
|
107
|
+
@t_sec = @sec.min
|
|
108
|
+
if next_min < @t_min
|
|
109
|
+
@t_min = next_min
|
|
110
|
+
next_hour = Chronic.parse("next hour",:now => current_time)
|
|
111
|
+
@t_hour,@t_day,@t_month,@t_year,@t_wday = next_hour.to_a[2..6]
|
|
112
|
+
retry
|
|
113
|
+
end
|
|
114
|
+
@t_min = next_min
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
unless @sec.include?(@t_sec)
|
|
118
|
+
next_sec = @sec.detect { |s| s > @t_sec } || @sec.min
|
|
119
|
+
if next_sec < @t_sec
|
|
120
|
+
@t_sec = next_sec
|
|
121
|
+
next_min = Chronic.parse("next minute",:now => current_time)
|
|
122
|
+
@t_min,@t_hour,@t_day,@t_month,@t_year,@t_wday = next_min.to_a[1..6]
|
|
123
|
+
retry
|
|
124
|
+
end
|
|
125
|
+
@t_sec = next_sec
|
|
126
|
+
end
|
|
127
|
+
break
|
|
128
|
+
end # end of loop do
|
|
129
|
+
current_time
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def current_time
|
|
133
|
+
Time.local(@t_sec,@t_min,@t_hour,@t_day,@t_month,@t_year,@t_wday,nil,@t_idst,@t_zone)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def day_restricted?
|
|
137
|
+
return !@day.eql?(1..31)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def wday_restricted?
|
|
141
|
+
return !@wday.eql?(0..6)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# TODO: mimic attr_reader to define all of these
|
|
145
|
+
def sec=(sec); @sec = parse_part(sec, 0 .. 59); end
|
|
146
|
+
|
|
147
|
+
def min=(min); @min = parse_part(min, 0 .. 59); end
|
|
148
|
+
|
|
149
|
+
def hour=(hour); @hour = parse_part(hour, 0 .. 23); end
|
|
150
|
+
|
|
151
|
+
def day=(day)
|
|
152
|
+
@day = parse_part(day, 1 .. 31)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def month=(month)
|
|
156
|
+
@month = parse_part(month, 1 .. 12)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def year=(year)
|
|
160
|
+
@year = parse_part(year)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def wday=(wday)
|
|
164
|
+
@wday = parse_part(wday, 0 .. 6)
|
|
165
|
+
end
|
|
166
|
+
private
|
|
167
|
+
def month_days(y, m)
|
|
168
|
+
if ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)
|
|
169
|
+
LeapYearMonthDays[m-1]
|
|
170
|
+
else
|
|
171
|
+
CommonYearMonthDays[m-1]
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# 0-5,8,10; 0-5; *; */5
|
|
176
|
+
def parse_part(part, range=nil)
|
|
177
|
+
return range if part.nil? or part == '*' or part =~ /^[*0]\/1$/
|
|
178
|
+
|
|
179
|
+
r = Array.new
|
|
180
|
+
part.split(',').each do |p|
|
|
181
|
+
if p =~ /-/ # 0-5
|
|
182
|
+
r << Range.new(*(p.scan(/\d+/).map { |x| x.to_i })).map { |x| x.to_i }
|
|
183
|
+
elsif p =~ /(\*|\d+)\/(\d+)/ && range # */5, 2/10
|
|
184
|
+
min = $1 == '*' ? 0 : $1.to_i
|
|
185
|
+
inc = $2.to_i
|
|
186
|
+
(min .. range.end).each_with_index do |x, i|
|
|
187
|
+
r << (range.begin == 1 ? x + 1 : x) if i % inc == 0
|
|
188
|
+
end
|
|
189
|
+
else
|
|
190
|
+
r << p.to_i
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
r.flatten
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|