backgroundrb-rails3 1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. data/.autotest +17 -0
  2. data/ChangeLog +50 -0
  3. data/Gemfile +11 -0
  4. data/LICENSE +4 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README +22 -0
  7. data/Rakefile +128 -0
  8. data/TODO.org +5 -0
  9. data/app/controller/backgroundrb_status_controller.rb +6 -0
  10. data/backgroundrb-rails3.gemspec +219 -0
  11. data/config/backgroundrb.yml +11 -0
  12. data/doc/Rakefile +5 -0
  13. data/doc/config.yaml +2 -0
  14. data/doc/content/advanced/advanced.txt +76 -0
  15. data/doc/content/advanced/advanced.yaml +4 -0
  16. data/doc/content/bugs/bugs.txt +20 -0
  17. data/doc/content/bugs/bugs.yaml +5 -0
  18. data/doc/content/community/community.txt +36 -0
  19. data/doc/content/community/community.yaml +5 -0
  20. data/doc/content/content.txt +168 -0
  21. data/doc/content/content.yaml +5 -0
  22. data/doc/content/faq/faq.txt +41 -0
  23. data/doc/content/faq/faq.yaml +5 -0
  24. data/doc/content/rails/rails.txt +182 -0
  25. data/doc/content/rails/rails.yaml +5 -0
  26. data/doc/content/scheduling/scheduling.txt +166 -0
  27. data/doc/content/scheduling/scheduling.yaml +5 -0
  28. data/doc/content/workers/workers.txt +178 -0
  29. data/doc/content/workers/workers.yaml +5 -0
  30. data/doc/layouts/default/default.erb +56 -0
  31. data/doc/layouts/default/default.yaml +4 -0
  32. data/doc/lib/default.rb +7 -0
  33. data/doc/output/Assets/BG-Ad-Top.png +0 -0
  34. data/doc/output/Assets/BG-Body.png +0 -0
  35. data/doc/output/Assets/BG-Feed.png +0 -0
  36. data/doc/output/Assets/BG-Menu-Hover.png +0 -0
  37. data/doc/output/Assets/BG-Menu.png +0 -0
  38. data/doc/output/Assets/BG-Sidebar-Bottom.png +0 -0
  39. data/doc/output/Assets/Button-Feed.png +0 -0
  40. data/doc/output/images/bg-ad-top.png +0 -0
  41. data/doc/output/images/bg-body.png +0 -0
  42. data/doc/output/images/bg-feed.gif +0 -0
  43. data/doc/output/images/bg-footer.jpg +0 -0
  44. data/doc/output/images/bg-header.jpg +0 -0
  45. data/doc/output/images/bg-menu-hover.png +0 -0
  46. data/doc/output/images/bg-menu.png +0 -0
  47. data/doc/output/images/bg-sidebar-bottom.gif +0 -0
  48. data/doc/output/images/button-feed.png +0 -0
  49. data/doc/output/images/icon-comment.png +0 -0
  50. data/doc/output/images/more_icon.gif +0 -0
  51. data/doc/output/style.css +299 -0
  52. data/doc/page_defaults.yaml +13 -0
  53. data/doc/tasks/default.rake +3 -0
  54. data/doc/templates/default/default.txt +1 -0
  55. data/doc/templates/default/default.yaml +4 -0
  56. data/examples/backgroundrb.yml +25 -0
  57. data/examples/foo_controller.rb +48 -0
  58. data/examples/god_worker.rb +7 -0
  59. data/examples/worker_tests/god_worker_test.rb +8 -0
  60. data/examples/workers/error_worker.rb +17 -0
  61. data/examples/workers/foo_worker.rb +38 -0
  62. data/examples/workers/god_worker.rb +7 -0
  63. data/examples/workers/model_worker.rb +13 -0
  64. data/examples/workers/renewal_worker.rb +11 -0
  65. data/examples/workers/rss_worker.rb +26 -0
  66. data/examples/workers/server_worker.rb +31 -0
  67. data/examples/workers/world_worker.rb +12 -0
  68. data/examples/workers/xmpp_worker.rb +7 -0
  69. data/init.rb +7 -0
  70. data/install.rb +1 -0
  71. data/know_issues.org +5 -0
  72. data/lib/backgroundrb.rb +1 -0
  73. data/lib/backgroundrb/bdrb_client_helper.rb +8 -0
  74. data/lib/backgroundrb/bdrb_cluster_connection.rb +156 -0
  75. data/lib/backgroundrb/bdrb_config.rb +43 -0
  76. data/lib/backgroundrb/bdrb_conn_error.rb +29 -0
  77. data/lib/backgroundrb/bdrb_connection.rb +179 -0
  78. data/lib/backgroundrb/bdrb_job_queue.rb +79 -0
  79. data/lib/backgroundrb/bdrb_result.rb +19 -0
  80. data/lib/backgroundrb/bdrb_start_stop.rb +146 -0
  81. data/lib/backgroundrb/rails_worker_proxy.rb +181 -0
  82. data/lib/backgroundrb/railtie.rb +48 -0
  83. data/lib/generators/backgroundrb/bdrb_migration/USAGE +12 -0
  84. data/lib/generators/backgroundrb/bdrb_migration/bdrb_migration_generator.rb +15 -0
  85. data/lib/generators/backgroundrb/bdrb_migration/templates/migration.rb +27 -0
  86. data/lib/generators/backgroundrb/worker/USAGE +16 -0
  87. data/lib/generators/backgroundrb/worker/templates/unit_test.rb +12 -0
  88. data/lib/generators/backgroundrb/worker/templates/worker.rb +7 -0
  89. data/lib/generators/backgroundrb/worker/worker_generator.rb +14 -0
  90. data/lib/tasks/backgroundrb_tasks.rake +103 -0
  91. data/release_notes.org +48 -0
  92. data/release_points.org +46 -0
  93. data/script/backgroundrb +52 -0
  94. data/script/bdrb_test_helper.rb +99 -0
  95. data/script/load_worker_env.rb +31 -0
  96. data/script/monitrc +25 -0
  97. data/server/backgroundrb_server.rb +12 -0
  98. data/server/lib/bdrb_result_storage.rb +62 -0
  99. data/server/lib/bdrb_server_helper.rb +24 -0
  100. data/server/lib/bdrb_thread_pool.rb +127 -0
  101. data/server/lib/cron_trigger.rb +197 -0
  102. data/server/lib/invalid_dump_error.rb +4 -0
  103. data/server/lib/log_worker.rb +25 -0
  104. data/server/lib/master_proxy.rb +140 -0
  105. data/server/lib/master_worker.rb +187 -0
  106. data/server/lib/meta_worker.rb +432 -0
  107. data/server/lib/trigger.rb +34 -0
  108. data/test/bdrb_client_test_helper.rb +5 -0
  109. data/test/bdrb_test_helper.rb +35 -0
  110. data/test/client/backgroundrb.yml +17 -0
  111. data/test/client/test_bdrb_client_helper.rb +13 -0
  112. data/test/client/test_bdrb_cluster_connection.rb +162 -0
  113. data/test/client/test_bdrb_config.rb +20 -0
  114. data/test/client/test_bdrb_connection.rb +29 -0
  115. data/test/client/test_bdrb_job_queue.rb +63 -0
  116. data/test/client/test_worker_proxy.rb +130 -0
  117. data/test/server/test_cron_trigger.rb +281 -0
  118. data/test/server/test_master_proxy.rb +54 -0
  119. data/test/server/test_master_worker.rb +157 -0
  120. data/test/server/test_meta_worker.rb +281 -0
  121. data/test/server/test_result_storage.rb +14 -0
  122. data/test/socket_mocker.rb +34 -0
  123. data/test/workers/bar_worker.rb +10 -0
  124. data/test/workers/foo_worker.rb +10 -0
  125. data/uninstall.rb +1 -0
  126. metadata +345 -0
@@ -0,0 +1,4 @@
1
+ module BackgrounDRb
2
+ class InvalidDumpError < RuntimeError
3
+ end
4
+ end
@@ -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