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,25 @@
|
|
|
1
|
+
class LogWorker < Packet::Worker
|
|
2
|
+
set_worker_name :log_worker
|
|
3
|
+
attr_accessor :log_file
|
|
4
|
+
def worker_init
|
|
5
|
+
@log_file = Logger.new("#{RAILS_HOME}/log/backgroundrb_#{BDRB_CONFIG[:backgroundrb][:port]}.log")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def receive_data p_data
|
|
9
|
+
case p_data[:type]
|
|
10
|
+
when :request: process_request(p_data)
|
|
11
|
+
when :response: process_response(p_data)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def process_request(p_data)
|
|
16
|
+
log_data = p_data[:data]
|
|
17
|
+
@log_file.info(log_data)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def process_response
|
|
21
|
+
puts "Not implemented and needed"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
module BackgrounDRb
|
|
2
|
+
class MasterProxy
|
|
3
|
+
attr_accessor :reloadable_workers,:worker_triggers,:reactor
|
|
4
|
+
def initialize
|
|
5
|
+
raise "Running old Ruby version, upgrade to Ruby >= 1.8.5" unless check_for_ruby_version
|
|
6
|
+
|
|
7
|
+
log_flag = BDRB_CONFIG[:backgroundrb][:debug_log].nil? ? true : BDRB_CONFIG[:backgroundrb][:debug_log]
|
|
8
|
+
debug_logger = DebugMaster.new(BDRB_CONFIG[:backgroundrb][:log],log_flag)
|
|
9
|
+
|
|
10
|
+
load_rails_env
|
|
11
|
+
|
|
12
|
+
find_reloadable_worker
|
|
13
|
+
|
|
14
|
+
Packet::Reactor.run do |t_reactor|
|
|
15
|
+
@reactor = t_reactor
|
|
16
|
+
t_reactor.start_worker(:worker => :log_worker,:worker_env => false) if log_flag
|
|
17
|
+
t_reactor.start_server(BDRB_CONFIG[:backgroundrb][:ip],
|
|
18
|
+
BDRB_CONFIG[:backgroundrb][:port],MasterWorker) do |conn|
|
|
19
|
+
conn.debug_logger = debug_logger
|
|
20
|
+
end
|
|
21
|
+
t_reactor.next_turn { reload_workers }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# FIXME: Method by same name exists in Packet::NbioHelper module
|
|
26
|
+
def gen_worker_key(worker_name,worker_key = nil)
|
|
27
|
+
return worker_name if worker_key.nil?
|
|
28
|
+
return "#{worker_name}_#{worker_key}".to_sym
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# method should find reloadable workers and load their schedule from config file
|
|
33
|
+
def find_reloadable_worker
|
|
34
|
+
t_workers = Dir["#{WORKER_ROOT}/**/*.rb"]
|
|
35
|
+
@reloadable_workers = t_workers.map do |x|
|
|
36
|
+
worker_name = File.basename(x,".rb")
|
|
37
|
+
require worker_name
|
|
38
|
+
worker_klass = Object.const_get(worker_name.classify)
|
|
39
|
+
worker_klass.reload_flag ? worker_klass : nil
|
|
40
|
+
end.compact
|
|
41
|
+
@worker_triggers = { }
|
|
42
|
+
@reloadable_workers.each do |t_worker|
|
|
43
|
+
schedule = load_reloadable_schedule(t_worker)
|
|
44
|
+
if schedule && !schedule.empty?
|
|
45
|
+
@worker_triggers[t_worker.worker_name.to_sym] = schedule
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# load schedule of workers which should be restarted on schedule
|
|
51
|
+
def load_reloadable_schedule(t_worker)
|
|
52
|
+
worker_method_triggers = { }
|
|
53
|
+
all_schedules = BDRB_CONFIG[:schedules]
|
|
54
|
+
return if all_schedules.nil? or all_schedules.empty?
|
|
55
|
+
worker_schedule = all_schedules[t_worker.worker_name.to_sym]
|
|
56
|
+
|
|
57
|
+
worker_schedule && worker_schedule.each do |key,value|
|
|
58
|
+
case value[:trigger_args]
|
|
59
|
+
when String
|
|
60
|
+
cron_args = value[:trigger_args] || "0 0 0 0 0"
|
|
61
|
+
trigger = BackgrounDRb::CronTrigger.new(cron_args)
|
|
62
|
+
worker_method_triggers[key] = {
|
|
63
|
+
:trigger => trigger,:data => value[:data],
|
|
64
|
+
:runtime => trigger.fire_after_time(Time.now).to_i
|
|
65
|
+
}
|
|
66
|
+
when Hash
|
|
67
|
+
trigger = BackgrounDRb::Trigger.new(value[:trigger_args])
|
|
68
|
+
worker_method_triggers[key] = {
|
|
69
|
+
:trigger => trigger,:data => value[:trigger_args][:data],
|
|
70
|
+
:runtime => trigger.fire_after_time(Time.now).to_i
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
worker_method_triggers
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Start the workers whose schedule has come
|
|
78
|
+
def reload_workers
|
|
79
|
+
return if worker_triggers.empty?
|
|
80
|
+
worker_triggers.each do |key,value|
|
|
81
|
+
value.delete_if { |key,value| value[:trigger].respond_to?(:end_time) && value[:trigger].end_time <= Time.now }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
worker_triggers.each do |worker_name,trigger|
|
|
85
|
+
trigger.each do |key,value|
|
|
86
|
+
time_now = Time.now.to_i
|
|
87
|
+
if value[:runtime] < time_now
|
|
88
|
+
load_and_invoke(worker_name,key,value)
|
|
89
|
+
t_time = value[:trigger].fire_after_time(Time.now)
|
|
90
|
+
value[:runtime] = t_time.to_i
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# method will load the worker and invoke worker method
|
|
97
|
+
def load_and_invoke(worker_name,p_method,data)
|
|
98
|
+
begin
|
|
99
|
+
require worker_name.to_s
|
|
100
|
+
worker_key = Packet::Guid.hexdigest
|
|
101
|
+
@reactor.start_worker(:worker => worker_name,:worker_key => worker_key,:disable_log => true)
|
|
102
|
+
worker_name_key = gen_worker_key(worker_name,worker_key)
|
|
103
|
+
data_request = {:data => { :worker_method => p_method,:arg => data[:data]},
|
|
104
|
+
:type => :request, :result => false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
exit_request = {:data => { :worker_method => :exit},
|
|
108
|
+
:type => :request, :result => false
|
|
109
|
+
}
|
|
110
|
+
t_worker = @reactor.live_workers[worker_name_key]
|
|
111
|
+
if t_worker
|
|
112
|
+
t_worker.send_request(data_request)
|
|
113
|
+
t_worker.send_request(exit_request)
|
|
114
|
+
end
|
|
115
|
+
rescue LoadError => e
|
|
116
|
+
puts "no such worker #{worker_name}"
|
|
117
|
+
puts e.backtrace.join("\n")
|
|
118
|
+
rescue MissingSourceFile => e
|
|
119
|
+
puts "no such worker #{worker_name}"
|
|
120
|
+
puts e.backtrace.join("\n")
|
|
121
|
+
return
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def load_rails_env
|
|
126
|
+
db_config_file = YAML.load(ERB.new(IO.read("#{RAILS_HOME}/config/database.yml")).result)
|
|
127
|
+
run_env = ENV["RAILS_ENV"]
|
|
128
|
+
ActiveRecord::Base.establish_connection(db_config_file[run_env])
|
|
129
|
+
|
|
130
|
+
if(Object.const_defined?(:Rails) && Rails.version < "2.2.2")
|
|
131
|
+
ActiveRecord::Base.allow_concurrency = true
|
|
132
|
+
elsif(Object.const_defined?(:RAILS_GEM_VERSION) && RAILS_GEM_VERSION < "2.2.2")
|
|
133
|
+
ActiveRecord::Base.allow_concurrency = true
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def check_for_ruby_version; RUBY_VERSION >= "1.8.5"; end
|
|
138
|
+
end # end of module BackgrounDRb
|
|
139
|
+
end
|
|
140
|
+
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
module BackgrounDRb
|
|
3
|
+
# Class wraps a logger object for debugging internal errors within server
|
|
4
|
+
class DebugMaster
|
|
5
|
+
attr_accessor :log_mode,:logger,:log_flag
|
|
6
|
+
def initialize(log_mode,log_flag = true)
|
|
7
|
+
@log_mode = log_mode
|
|
8
|
+
@log_flag = log_flag
|
|
9
|
+
if @log_mode == :foreground
|
|
10
|
+
@logger = ::Logger.new(STDOUT)
|
|
11
|
+
else
|
|
12
|
+
@logger = ::Logger.new("#{RAILS_HOME}/log/backgroundrb_debug_#{BDRB_CONFIG[:backgroundrb][:port]}.log")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def info(data)
|
|
17
|
+
return unless @log_flag
|
|
18
|
+
@logger.info(data)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def debug(data)
|
|
22
|
+
return unless @log_flag
|
|
23
|
+
@logger.debug(data)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class MasterWorker
|
|
28
|
+
attr_accessor :debug_logger
|
|
29
|
+
include BackgrounDRb::BdrbServerHelper
|
|
30
|
+
# receives requests from rails and based on request type invoke appropriate method
|
|
31
|
+
def receive_data p_data
|
|
32
|
+
@tokenizer.extract(p_data) do |b_data|
|
|
33
|
+
begin
|
|
34
|
+
t_data = load_data b_data
|
|
35
|
+
if t_data
|
|
36
|
+
case t_data[:type]
|
|
37
|
+
# async method invocation
|
|
38
|
+
when :async_invoke: async_method_invoke(t_data)
|
|
39
|
+
# get status/result
|
|
40
|
+
when :get_result: get_result_object(t_data)
|
|
41
|
+
# sync method invocation
|
|
42
|
+
when :sync_invoke: method_invoke(t_data)
|
|
43
|
+
when :start_worker: start_worker_request(t_data)
|
|
44
|
+
when :delete_worker: delete_drb_worker(t_data)
|
|
45
|
+
when :worker_info: pass_worker_info(t_data)
|
|
46
|
+
when :all_worker_info: all_worker_info(t_data)
|
|
47
|
+
else; debug_logger.info("Invalid request")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
rescue Object => bdrb_error
|
|
51
|
+
debug_logger.info(bdrb_error)
|
|
52
|
+
debug_logger.info(bdrb_error.backtrace.join("\n"))
|
|
53
|
+
send_object(nil)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Send worker info to the user
|
|
59
|
+
def pass_worker_info(t_data)
|
|
60
|
+
worker_name_key = gen_worker_key(t_data[:worker],t_data[:worker_key])
|
|
61
|
+
worker_instance = reactor.live_workers[worker_name_key]
|
|
62
|
+
info_response = { :worker => t_data[:worker],:worker_key => t_data[:worker_key]}
|
|
63
|
+
worker_instance ? (info_response[:status] = :running) : (info_response[:status] = :stopped)
|
|
64
|
+
send_object(info_response)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# collect all worker info in an array and send to the user
|
|
68
|
+
def all_worker_info(t_data)
|
|
69
|
+
info_response = []
|
|
70
|
+
reactor.live_workers.each do |key,value|
|
|
71
|
+
worker_key = (value.worker_key.to_s).gsub(/#{value.worker_name}_?/,"")
|
|
72
|
+
info_response << { :worker => value.worker_name,:worker_key => worker_key,:status => :running }
|
|
73
|
+
end
|
|
74
|
+
send_object(info_response)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Delete the worker. Sends TERM signal to the worker process and removes
|
|
78
|
+
# worker key from list of available workers
|
|
79
|
+
def delete_drb_worker(t_data)
|
|
80
|
+
worker_name = t_data[:worker]
|
|
81
|
+
worker_key = t_data[:worker_key]
|
|
82
|
+
worker_name_key = gen_worker_key(worker_name,worker_key)
|
|
83
|
+
begin
|
|
84
|
+
worker_instance = reactor.live_workers[worker_name_key]
|
|
85
|
+
raise Packet::InvalidWorker.new("Invalid worker with name #{worker_name} key #{worker_key}") unless worker_instance
|
|
86
|
+
Process.kill('TERM',worker_instance.pid)
|
|
87
|
+
# Warning: Change is temporary, may break things
|
|
88
|
+
reactor.live_workers.delete(worker_name_key)
|
|
89
|
+
rescue Packet::DisconnectError => sock_error
|
|
90
|
+
reactor.remove_worker(sock_error)
|
|
91
|
+
rescue
|
|
92
|
+
debug_logger.info($!.to_s)
|
|
93
|
+
debug_logger.info($!.backtrace.join("\n"))
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# start a new worker
|
|
98
|
+
def start_worker_request(p_data)
|
|
99
|
+
start_worker(p_data)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Invoke an asynchronous method on a worker
|
|
103
|
+
def async_method_invoke(t_data)
|
|
104
|
+
worker_name = t_data[:worker]
|
|
105
|
+
worker_name_key = gen_worker_key(worker_name,t_data[:worker_key])
|
|
106
|
+
|
|
107
|
+
unless worker_methods(worker_name_key).include?(t_data[:worker_method])
|
|
108
|
+
send_object(:result_flag => "error")
|
|
109
|
+
return
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
t_data.delete(:worker)
|
|
113
|
+
t_data.delete(:type)
|
|
114
|
+
begin
|
|
115
|
+
ask_worker(worker_name_key,:data => t_data, :type => :request, :result => false)
|
|
116
|
+
send_object(:result_flag => "ok")
|
|
117
|
+
rescue Packet::DisconnectError => sock_error
|
|
118
|
+
send_object(:result_flag => "error")
|
|
119
|
+
reactor.live_workers.delete(worker_name_key)
|
|
120
|
+
rescue
|
|
121
|
+
send_object(:result_flag => "error")
|
|
122
|
+
debug_logger.info($!.message)
|
|
123
|
+
debug_logger.info($!.backtrace.join("\n"))
|
|
124
|
+
return
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def worker_methods worker_name_key
|
|
129
|
+
reactor.live_workers[worker_name_key].invokable_worker_methods
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Given a cache key, ask the worker for result stored in it.
|
|
133
|
+
# If you are using Memcache for result storage, this method won't be
|
|
134
|
+
# called at all and bdrb client library will directly fetch
|
|
135
|
+
# the results from memcache and return
|
|
136
|
+
def get_result_object(t_data)
|
|
137
|
+
worker_name = t_data[:worker]
|
|
138
|
+
worker_name_key = gen_worker_key(worker_name,t_data[:worker_key])
|
|
139
|
+
t_data.delete(:worker)
|
|
140
|
+
t_data.delete(:type)
|
|
141
|
+
begin
|
|
142
|
+
ask_worker(worker_name_key,:data => t_data, :type => :get_result,:result => true)
|
|
143
|
+
rescue Packet::DisconnectError => sock_error
|
|
144
|
+
reactor.live_workers.delete(worker_name_key)
|
|
145
|
+
rescue
|
|
146
|
+
debug_logger.info($!.to_s)
|
|
147
|
+
debug_logger.info($!.backtrace.join("\n"))
|
|
148
|
+
return
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Invoke a synchronous/blocking method on a worker.
|
|
153
|
+
def method_invoke(t_data)
|
|
154
|
+
worker_name = t_data[:worker]
|
|
155
|
+
worker_name_key = gen_worker_key(worker_name,t_data[:worker_key])
|
|
156
|
+
t_data.delete(:worker)
|
|
157
|
+
t_data.delete(:type)
|
|
158
|
+
begin
|
|
159
|
+
ask_worker(worker_name_key,:data => t_data, :type => :request,:result => true)
|
|
160
|
+
rescue Packet::DisconnectError => sock_error
|
|
161
|
+
reactor.live_workers.delete(worker_name_key)
|
|
162
|
+
rescue
|
|
163
|
+
debug_logger.info($!.message)
|
|
164
|
+
debug_logger.info($!.backtrace.join("\n"))
|
|
165
|
+
return
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Receieve responses from workers and dispatch them back to the client
|
|
170
|
+
def worker_receive p_data
|
|
171
|
+
p_data[:result_flag] ||= "ok"
|
|
172
|
+
send_object(p_data)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def unbind; end
|
|
176
|
+
|
|
177
|
+
# called whenever a new connection is made.Initializes binary data parser
|
|
178
|
+
def post_init
|
|
179
|
+
@tokenizer = Packet::BinParser.new
|
|
180
|
+
end
|
|
181
|
+
def connection_completed; end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
module BackgrounDRb
|
|
2
|
+
# this class is a dummy class that implements things required for passing data to
|
|
3
|
+
# actual logger worker
|
|
4
|
+
class PacketLogger
|
|
5
|
+
def initialize(worker,log_flag = true)
|
|
6
|
+
@log_flag = log_flag
|
|
7
|
+
@worker = worker
|
|
8
|
+
@log_mutex = Mutex.new
|
|
9
|
+
end
|
|
10
|
+
[:info,:debug,:warn,:error,:fatal].each do |m|
|
|
11
|
+
define_method(m) do |log_data|
|
|
12
|
+
return unless @log_flag
|
|
13
|
+
@log_mutex.synchronize do
|
|
14
|
+
@worker.send_request(:worker => :log_worker, :data => log_data)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
# == MetaWorker class
|
|
20
|
+
# BackgrounDRb workers are asynchronous reactors which work using events
|
|
21
|
+
# You are free to use threads in your workers, but be reasonable with them.
|
|
22
|
+
# Following methods are available to all workers from parent classes.
|
|
23
|
+
# * BackgrounDRb::MetaWorker#connect
|
|
24
|
+
#
|
|
25
|
+
# Above method connects to an external tcp server and integrates the connection
|
|
26
|
+
# within reactor loop of worker. For example:
|
|
27
|
+
#
|
|
28
|
+
# class TimeClient
|
|
29
|
+
# def receive_data(p_data)
|
|
30
|
+
# worker.get_external_data(p_data)
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# def post_init
|
|
34
|
+
# p "***************** : connection completed"
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# class FooWorker < BackgrounDRb::MetaWorker
|
|
39
|
+
# set_worker_name :foo_worker
|
|
40
|
+
# def create(args = nil)
|
|
41
|
+
# external_connection = nil
|
|
42
|
+
# connect("localhost",11009,TimeClient) { |conn| conn = external_connection }
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# def get_external_data(p_data)
|
|
46
|
+
# puts "And external data is : #{p_data}"
|
|
47
|
+
# end
|
|
48
|
+
# end
|
|
49
|
+
# * BackgrounDRb::MetaWorker#start_server
|
|
50
|
+
#
|
|
51
|
+
# Above method allows you to start a tcp server from your worker, all the
|
|
52
|
+
# accepted connections are integrated with event loop of worker
|
|
53
|
+
# class TimeServer
|
|
54
|
+
#
|
|
55
|
+
# def receive_data(p_data)
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
# def post_init
|
|
59
|
+
# add_periodic_timer(2) { say_hello_world }
|
|
60
|
+
# end
|
|
61
|
+
#
|
|
62
|
+
# def connection_completed
|
|
63
|
+
# end
|
|
64
|
+
#
|
|
65
|
+
# def say_hello_world
|
|
66
|
+
# p "***************** : invoking hello world #{Time.now}"
|
|
67
|
+
# send_data("Hello World\n")
|
|
68
|
+
# end
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# class ServerWorker < BackgrounDRb::MetaWorker
|
|
72
|
+
# set_worker_name :server_worker
|
|
73
|
+
# def create(args = nil)
|
|
74
|
+
# # start the server when worker starts
|
|
75
|
+
# start_server("0.0.0.0",11009,TimeServer) do |client_connection|
|
|
76
|
+
# client_connection.say_hello_world
|
|
77
|
+
# end
|
|
78
|
+
# end
|
|
79
|
+
# end
|
|
80
|
+
|
|
81
|
+
class MetaWorker < Packet::Worker
|
|
82
|
+
include BackgrounDRb::BdrbServerHelper
|
|
83
|
+
attr_accessor :config_file, :my_schedule, :run_time, :trigger_type, :trigger
|
|
84
|
+
attr_accessor :logger, :thread_pool,:cache
|
|
85
|
+
iattr_accessor :pool_size
|
|
86
|
+
iattr_accessor :reload_flag
|
|
87
|
+
|
|
88
|
+
@pool_size = nil
|
|
89
|
+
@reload_flag = false
|
|
90
|
+
|
|
91
|
+
# set the thread pool size, default is 20
|
|
92
|
+
def self.pool_size(size = nil)
|
|
93
|
+
@pool_size = size if size
|
|
94
|
+
@pool_size
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# set auto restart flag on the worker
|
|
98
|
+
def self.reload_on_schedule(flag = nil)
|
|
99
|
+
if flag
|
|
100
|
+
self.no_auto_load = true
|
|
101
|
+
self.reload_flag = true
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# does initialization of worker stuff and invokes create method in
|
|
106
|
+
# user defined worker class
|
|
107
|
+
def worker_init
|
|
108
|
+
raise "Invalid worker name" if !worker_name
|
|
109
|
+
Thread.abort_on_exception = true
|
|
110
|
+
|
|
111
|
+
# stores the job key of currently running job
|
|
112
|
+
Thread.current[:job_key] = nil
|
|
113
|
+
initialize_logger
|
|
114
|
+
|
|
115
|
+
@thread_pool = ThreadPool.new(self,pool_size || 20,@logger)
|
|
116
|
+
t_worker_key = worker_options && worker_options[:worker_key]
|
|
117
|
+
|
|
118
|
+
@cache = ResultStorage.new(worker_name,t_worker_key,BDRB_CONFIG[:backgroundrb][:result_storage])
|
|
119
|
+
|
|
120
|
+
if(worker_options && worker_options[:schedule] && no_auto_load)
|
|
121
|
+
load_schedule_from_args
|
|
122
|
+
elsif(BDRB_CONFIG[:schedules] && BDRB_CONFIG[:schedules][worker_name.to_sym])
|
|
123
|
+
@my_schedule = BDRB_CONFIG[:schedules][worker_name.to_sym]
|
|
124
|
+
new_load_schedule if @my_schedule
|
|
125
|
+
end
|
|
126
|
+
if respond_to?(:create)
|
|
127
|
+
invoke_user_method(:create,worker_options[:data])
|
|
128
|
+
end
|
|
129
|
+
if run_persistent_jobs?
|
|
130
|
+
add_periodic_timer(persistent_delay.to_i) {
|
|
131
|
+
begin
|
|
132
|
+
check_for_enqueued_tasks
|
|
133
|
+
rescue Object => e
|
|
134
|
+
puts("Error while running persistent task : #{Time.now}")
|
|
135
|
+
log_exception(e.backtrace)
|
|
136
|
+
end
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
write_pid_file(t_worker_key)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def write_pid_file t_worker_key
|
|
143
|
+
key = [worker_name,t_worker_key].compact.join('_')
|
|
144
|
+
pid_file = "#{RAILS_HOME}/tmp/pids/backgroundrb_#{BDRB_CONFIG[:backgroundrb][:port]}_worker_#{key}.pid"
|
|
145
|
+
op = File.open(pid_file, "w")
|
|
146
|
+
op.write(Process.pid().to_s)
|
|
147
|
+
op.close
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def initialize_logger
|
|
151
|
+
log_flag = BDRB_CONFIG[:backgroundrb][:debug_log].nil? ? true : BDRB_CONFIG[:backgroundrb][:debug_load_rails_env]
|
|
152
|
+
if BDRB_CONFIG[:backgroundrb][:logging_logger].nil?
|
|
153
|
+
@logger = PacketLogger.new(self,log_flag)
|
|
154
|
+
else
|
|
155
|
+
log_config = BDRB_CONFIG[:backgroundrb][:logging_logger]
|
|
156
|
+
@logger = Logging::Logger[log_config[:name]]
|
|
157
|
+
@logger.trace = log_config[:trace]
|
|
158
|
+
@logger.additive = log_config[:additive]
|
|
159
|
+
|
|
160
|
+
log_config[:appenders].keys.each do |key|
|
|
161
|
+
appender_config = log_config[:appenders][key]
|
|
162
|
+
|
|
163
|
+
logger_options = {
|
|
164
|
+
:filename => "#{RAILS_HOME}/#{appender_config[:filename]}",
|
|
165
|
+
:age => appender_config[:age],
|
|
166
|
+
:size => appender_config[:size],
|
|
167
|
+
:keep => appender_config[:keep],
|
|
168
|
+
:safe => appender_config[:safe],
|
|
169
|
+
:layout => Logging::Layouts::Pattern.new(:pattern => appender_config[:layout_pattern])
|
|
170
|
+
}
|
|
171
|
+
appender = "Logging::Appenders::#{appender_config[:type]}".constantize.new("backgroundrb_#{key}",logger_options)
|
|
172
|
+
@logger.add_appenders(appender)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def puts msg
|
|
178
|
+
STDOUT.puts msg
|
|
179
|
+
STDOUT.flush
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Returns the persistent job queue check delay for this worker
|
|
183
|
+
def persistent_delay
|
|
184
|
+
get_config_value(:persistent_delay, 5)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Returns true if persistent jobs should be run for this worker.
|
|
188
|
+
def run_persistent_jobs?
|
|
189
|
+
!get_config_value(:persistent_disabled, false)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# return job key from thread global variable
|
|
193
|
+
def job_key; Thread.current[:job_key]; end
|
|
194
|
+
|
|
195
|
+
# if worker is running using a worker key, return it
|
|
196
|
+
def worker_key; worker_options && worker_options[:worker_key]; end
|
|
197
|
+
|
|
198
|
+
# fetch the persistent job id of job currently running, create AR object
|
|
199
|
+
# and return to the user.
|
|
200
|
+
def persistent_job
|
|
201
|
+
job_id = Thread.current[:persistent_job_id]
|
|
202
|
+
job_id ? BdrbJobQueue.find_by_id(job_id) : nil
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# loads workers schedule from options supplied from rails
|
|
206
|
+
# a user may pass trigger arguments to dynamically define the schedule
|
|
207
|
+
def load_schedule_from_args
|
|
208
|
+
@my_schedule = worker_options[:schedule]
|
|
209
|
+
new_load_schedule if @my_schedule
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Gets called, whenever master bdrb process sends any data to the worker
|
|
213
|
+
def receive_internal_data data
|
|
214
|
+
@tokenizer.extract(data) do |b_data|
|
|
215
|
+
data_obj = load_data(b_data)
|
|
216
|
+
receive_data(data_obj) if data_obj
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# receives requests/responses from master process or other workers
|
|
221
|
+
def receive_data p_data
|
|
222
|
+
if p_data[:data][:worker_method] == :exit
|
|
223
|
+
exit
|
|
224
|
+
end
|
|
225
|
+
case p_data[:type]
|
|
226
|
+
when :request: process_request(p_data)
|
|
227
|
+
when :response: process_response(p_data)
|
|
228
|
+
when :get_result: return_result_object(p_data)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def return_result_object p_data
|
|
233
|
+
user_input = p_data[:data]
|
|
234
|
+
user_job_key = user_input[:job_key]
|
|
235
|
+
send_response(p_data,cache[user_job_key])
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# method is responsible for invoking appropriate method in user
|
|
239
|
+
def process_request(p_data)
|
|
240
|
+
user_input = p_data[:data]
|
|
241
|
+
if (user_input[:worker_method]).nil? or !respond_to?(user_input[:worker_method])
|
|
242
|
+
result = nil
|
|
243
|
+
puts "Trying to invoke invalid worker method on worker #{worker_name}"
|
|
244
|
+
send_response(p_data,result,"error")
|
|
245
|
+
return
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
result = nil
|
|
249
|
+
|
|
250
|
+
Thread.current[:job_key] = user_input[:job_key]
|
|
251
|
+
|
|
252
|
+
result,result_flag = invoke_user_method(user_input[:worker_method],user_input[:arg])
|
|
253
|
+
|
|
254
|
+
if p_data[:result]
|
|
255
|
+
result = "dummy_result" if result.nil?
|
|
256
|
+
if can_dump?(result)
|
|
257
|
+
send_response(p_data,result,result_flag)
|
|
258
|
+
else
|
|
259
|
+
send_response(p_data,"dummy_result","error")
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# can the responses be dumped?
|
|
265
|
+
def can_dump?(p_object)
|
|
266
|
+
begin
|
|
267
|
+
Marshal.dump(p_object)
|
|
268
|
+
return true
|
|
269
|
+
rescue TypeError
|
|
270
|
+
return false
|
|
271
|
+
rescue
|
|
272
|
+
return false
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Load the schedule of worker from my_schedule instance variable
|
|
277
|
+
def new_load_schedule
|
|
278
|
+
@worker_method_triggers = { }
|
|
279
|
+
@my_schedule.each do |key,value|
|
|
280
|
+
case value[:trigger_args]
|
|
281
|
+
when String
|
|
282
|
+
cron_args = value[:trigger_args] || "0 0 0 0 0"
|
|
283
|
+
trigger = BackgrounDRb::CronTrigger.new(cron_args)
|
|
284
|
+
@worker_method_triggers[key] = { :trigger => trigger,:data => value[:data],:runtime => trigger.fire_after_time(Time.now).to_i }
|
|
285
|
+
when Hash
|
|
286
|
+
trigger = BackgrounDRb::Trigger.new(value[:trigger_args])
|
|
287
|
+
@worker_method_triggers[key] = { :trigger => trigger,:data => value[:trigger_args][:data],:runtime => trigger.fire_after_time(Time.now).to_i }
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# send the response back to master process and hence to the client
|
|
293
|
+
# if there is an error while dumping the object, send "invalid_result_dump_check_log"
|
|
294
|
+
def send_response input,output,result_flag = "ok"
|
|
295
|
+
input[:data] = output
|
|
296
|
+
input[:type] = :response
|
|
297
|
+
input[:result_flag] = result_flag
|
|
298
|
+
begin
|
|
299
|
+
send_data(input)
|
|
300
|
+
rescue Object => bdrb_error
|
|
301
|
+
log_exception(bdrb_error)
|
|
302
|
+
input[:data] = "invalid_result_dump_check_log"
|
|
303
|
+
input[:result_flag] = "error"
|
|
304
|
+
send_data(input)
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def log_exception exception_object
|
|
309
|
+
if exception_object.is_a?(Array)
|
|
310
|
+
STDERR.puts exception_object.each { |e| e << "\n" }
|
|
311
|
+
else
|
|
312
|
+
STDERR.puts exception_object.to_s
|
|
313
|
+
end
|
|
314
|
+
STDERR.flush
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def invoke_user_method user_method,args
|
|
318
|
+
if self.respond_to?(user_method)
|
|
319
|
+
called_method_arity = self.method(user_method).arity
|
|
320
|
+
t_result = nil
|
|
321
|
+
begin
|
|
322
|
+
if(called_method_arity != 0)
|
|
323
|
+
t_result = self.send(user_method,args)
|
|
324
|
+
else
|
|
325
|
+
t_result = self.send(user_method)
|
|
326
|
+
end
|
|
327
|
+
[t_result,"ok"]
|
|
328
|
+
rescue Object => bdrb_error
|
|
329
|
+
puts "Error calling method #{user_method} with #{args} on worker #{worker_name} at #{Time.now}"
|
|
330
|
+
log_exception(bdrb_error)
|
|
331
|
+
[t_result,"error"]
|
|
332
|
+
end
|
|
333
|
+
else
|
|
334
|
+
puts "Trying to invoke method #{user_method} with #{args} on worker #{worker_name} failed because no such method is defined on the worker at #{Time.now}"
|
|
335
|
+
[nil,"error"]
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# called when connection is closed
|
|
340
|
+
def unbind; end
|
|
341
|
+
|
|
342
|
+
def connection_completed; end
|
|
343
|
+
|
|
344
|
+
# Check for enqueued tasks and invoke appropriate methods
|
|
345
|
+
def check_for_enqueued_tasks
|
|
346
|
+
while (task = get_next_task)
|
|
347
|
+
if self.respond_to? task.worker_method
|
|
348
|
+
Thread.current[:persistent_job_id] = task[:id]
|
|
349
|
+
Thread.current[:job_key] = task[:job_key]
|
|
350
|
+
args = Marshal.load(task.args)
|
|
351
|
+
invoke_user_method(task.worker_method,args)
|
|
352
|
+
else
|
|
353
|
+
task.release_job
|
|
354
|
+
end
|
|
355
|
+
# Unless configured to loop on persistent tasks, run only
|
|
356
|
+
# once, and then break
|
|
357
|
+
break unless BDRB_CONFIG[:backgroundrb][:persistent_multi]
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Get the next enqueued job
|
|
362
|
+
def get_next_task
|
|
363
|
+
if worker_key && !worker_key.empty?
|
|
364
|
+
BdrbJobQueue.find_next(worker_name.to_s,worker_key.to_s)
|
|
365
|
+
else
|
|
366
|
+
BdrbJobQueue.find_next(worker_name.to_s)
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Check for timer events and invoke scheduled methods in timer and scheduler
|
|
371
|
+
def check_for_timer_events
|
|
372
|
+
super
|
|
373
|
+
return if @worker_method_triggers.nil? or @worker_method_triggers.empty?
|
|
374
|
+
@worker_method_triggers.delete_if { |key,value| value[:trigger].respond_to?(:end_time) && value[:trigger].end_time <= Time.now }
|
|
375
|
+
|
|
376
|
+
@worker_method_triggers.each do |key,value|
|
|
377
|
+
time_now = Time.now.to_i
|
|
378
|
+
if value[:runtime] < time_now
|
|
379
|
+
check_db_connection
|
|
380
|
+
invoke_user_method(key,value[:data])
|
|
381
|
+
t_time = value[:trigger].fire_after_time(Time.now)
|
|
382
|
+
value[:runtime] = t_time.to_i
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Periodic check for lost database connections and closed connections
|
|
388
|
+
def check_db_connection
|
|
389
|
+
begin
|
|
390
|
+
ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord)
|
|
391
|
+
rescue Object => bdrb_error
|
|
392
|
+
log_exception(bdrb_error)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
private
|
|
397
|
+
|
|
398
|
+
# Returns the local configuration hash for this worker. Returns an
|
|
399
|
+
# empty hash if no local config exists.
|
|
400
|
+
def worker_config
|
|
401
|
+
if BDRB_CONFIG[:workers] && BDRB_CONFIG[:workers][worker_name.to_sym]
|
|
402
|
+
BDRB_CONFIG[:workers][worker_name.to_sym]
|
|
403
|
+
else
|
|
404
|
+
{}
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Returns the appropriate configuration value, based on both the
|
|
409
|
+
# global config and the per-worker configuration for this worker.
|
|
410
|
+
def get_config_value(key_sym, default)
|
|
411
|
+
if !worker_config[key_sym].nil?
|
|
412
|
+
worker_config[key_sym]
|
|
413
|
+
elsif !BDRB_CONFIG[:backgroundrb][key_sym].nil?
|
|
414
|
+
BDRB_CONFIG[:backgroundrb][key_sym]
|
|
415
|
+
else
|
|
416
|
+
default
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def load_rails_env
|
|
421
|
+
db_config_file = YAML.load(ERB.new(IO.read("#{RAILS_HOME}/config/database.yml")).result)
|
|
422
|
+
run_env = ENV["RAILS_ENV"]
|
|
423
|
+
ActiveRecord::Base.establish_connection(db_config_file[run_env])
|
|
424
|
+
if(Object.const_defined?(:Rails) && Rails.version < "2.2.2")
|
|
425
|
+
ActiveRecord::Base.allow_concurrency = true
|
|
426
|
+
elsif(Object.const_defined?(:RAILS_GEM_VERSION) && RAILS_GEM_VERSION < "2.2.2")
|
|
427
|
+
ActiveRecord::Base.allow_concurrency = true
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
end # end of class MetaWorker
|
|
432
|
+
end # end of module BackgrounDRb
|