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.
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