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