background_queue 0.3.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/TODO +13 -0
  2. data/VERSION +1 -1
  3. data/background_queue.gemspec +8 -2
  4. data/lib/background_queue/client.rb +31 -3
  5. data/lib/background_queue/client_lib/connection.rb +9 -0
  6. data/lib/background_queue/client_lib/job_handle.rb +34 -1
  7. data/lib/background_queue/config.rb +1 -11
  8. data/lib/background_queue/server_lib/balanced_queue.rb +22 -0
  9. data/lib/background_queue/server_lib/event_connection.rb +7 -1
  10. data/lib/background_queue/server_lib/job.rb +46 -7
  11. data/lib/background_queue/server_lib/priority_queue.rb +7 -0
  12. data/lib/background_queue/server_lib/queue_registry.rb +20 -5
  13. data/lib/background_queue/server_lib/task.rb +28 -0
  14. data/lib/background_queue/server_lib/task_registry.rb +7 -0
  15. data/lib/background_queue/server_lib/worker_client.rb +5 -2
  16. data/lib/background_queue/server_lib/worker_thread.rb +1 -1
  17. data/lib/background_queue/utils.rb +25 -1
  18. data/lib/background_queue/worker/base.rb +41 -0
  19. data/lib/background_queue/worker/calling.rb +24 -1
  20. data/lib/background_queue/worker/config.rb +20 -0
  21. data/lib/background_queue/worker/environment.rb +25 -1
  22. data/lib/background_queue/worker/logger.rb +114 -0
  23. data/lib/background_queue/worker/progress.rb +152 -0
  24. data/lib/background_queue/worker/worker_loader.rb +1 -1
  25. data/lib/background_queue_worker.rb +2 -0
  26. data/spec/background_queue/client_lib/connection_spec.rb +7 -1
  27. data/spec/background_queue/client_spec.rb +2 -1
  28. data/spec/background_queue/config_spec.rb +11 -23
  29. data/spec/background_queue/server_lib/integration/error_handling_spec.rb +85 -0
  30. data/spec/background_queue/server_lib/integration/full_test_spec.rb +76 -3
  31. data/spec/background_queue/server_lib/integration/queue_integration_spec.rb +6 -3
  32. data/spec/background_queue/server_lib/job_spec.rb +44 -3
  33. data/spec/background_queue/server_lib/worker_thread_spec.rb +2 -2
  34. data/spec/background_queue/utils_spec.rb +30 -0
  35. data/spec/background_queue/worker/calling_spec.rb +3 -1
  36. data/spec/background_queue/worker/environment_spec.rb +3 -1
  37. data/spec/background_queue/worker/logger_spec.rb +58 -0
  38. data/spec/background_queue/worker/progress_spec.rb +82 -0
  39. data/spec/background_queue/worker/worker_loader_spec.rb +1 -1
  40. data/spec/resources/summary_worker.rb +18 -0
  41. data/spec/shared/queue_registry_shared.rb +4 -3
  42. data/spec/support/simple_task.rb +24 -0
  43. metadata +33 -27
@@ -18,6 +18,7 @@ module BackgroundQueue::ServerLib
18
18
  @job_id = job_id
19
19
  @id = id
20
20
  @priority = priority
21
+ raise "Missing priority" if @priority.nil?
21
22
  @worker = worker
22
23
  @running = false
23
24
  @options = options
@@ -48,6 +49,10 @@ module BackgroundQueue::ServerLib
48
49
  @job = job
49
50
  end
50
51
 
52
+ def get_job
53
+ @job
54
+ end
55
+
51
56
  def is_excluded_from_count?
52
57
  @options[:exclude] == true
53
58
  end
@@ -68,6 +73,10 @@ module BackgroundQueue::ServerLib
68
73
  @options[:initial_progress_caption]
69
74
  end
70
75
 
76
+ def send_summary?
77
+ @options[:send_summary]
78
+ end
79
+
71
80
  def get_error_count
72
81
  if @error_count.nil?
73
82
  0
@@ -76,11 +85,30 @@ module BackgroundQueue::ServerLib
76
85
  end
77
86
  end
78
87
 
88
+ def set_error_status(e_status)
89
+ @error_status = e_status
90
+ end
91
+
92
+ def get_error_status
93
+ @error_status
94
+ end
95
+
96
+ def waiting_to_retry?
97
+ @error_status == :waiting_to_retry
98
+ end
99
+
100
+ def replaced_while_waiting_to_retry?
101
+ @error_status == :replaced_while_waiting_to_retry
102
+ end
79
103
 
80
104
  def increment_error_count
81
105
  @error_count = get_error_count + 1
82
106
  end
83
107
 
108
+ def step
109
+ @options[:step]
110
+ end
111
+
84
112
 
85
113
  def set_worker_status(status)
86
114
  raise "Task without job set" if @job.nil?
@@ -11,12 +11,19 @@ module BackgroundQueue::ServerLib
11
11
  def register(task)
12
12
  existing_task = @tasks[task.id]
13
13
  if existing_task.nil?
14
+ #puts "nil task"
14
15
  @tasks[task.id] = task
15
16
  [:new, nil]
16
17
  elsif existing_task.running?
18
+ #puts "task running"
17
19
  register_waiting_task(task)
18
20
  [:waiting, nil]
21
+ elsif existing_task.waiting_to_retry?
22
+ #puts "task waiting_to_retry"
23
+ @tasks[task.id] = task
24
+ [:waiting_to_retry, existing_task]
19
25
  else
26
+ #puts "task waiting"
20
27
  @tasks[task.id] = task
21
28
  [:existing, existing_task]
22
29
  end
@@ -21,7 +21,7 @@ module BackgroundQueue::ServerLib
21
21
  end
22
22
  return :ok
23
23
  rescue BackgroundQueue::ServerLib::WorkerError => we
24
- @server.logger.error("Worker Error sending request #{task.id} to worker: #{e.message}")
24
+ @server.logger.error("Worker Error sending request #{task.id} to worker: #{we.message}")
25
25
  return :worker_error
26
26
  rescue BackgroundQueue::ServerLib::ThreadManager::ForcedStop => fe
27
27
  @server.logger.error("Thread stop while sending request #{task.id} to worker: #{fe.message}")
@@ -37,7 +37,10 @@ module BackgroundQueue::ServerLib
37
37
 
38
38
  def build_request(uri, task, secret)
39
39
  req = Net::HTTP::Post.new(uri.path)
40
- req.set_form_data({:task=>task.to_json, :auth=>secret, :server_port=>@server.config.address.port})
40
+ form_data = {:task=>task.to_json, :auth=>secret, :server_port=>@server.config.address.port}
41
+ form_data[:summary] = task.get_job.summary.to_json if task.send_summary? && !task.get_job.summary.nil?
42
+ form_data[:step] = task.step unless task.step.nil?
43
+ req.set_form_data(form_data)
41
44
  req["host"] = task.domain
42
45
  req
43
46
  end
@@ -42,7 +42,7 @@ module BackgroundQueue::ServerLib
42
42
  else
43
43
  @server.logger.debug("failed calling worker for task #{task.id}")
44
44
  @server.workers.finish_using_worker(worker, result == :worker_error)
45
- @server.error_tasks.add_task(task)
45
+ @server.task_queue.add_task_to_error_list(task)
46
46
  return result != :stop
47
47
  #error_count += 1
48
48
  #if error_count > 5
@@ -2,6 +2,22 @@ module BackgroundQueue
2
2
  #Utility Module
3
3
  module Utils
4
4
 
5
+ def self.current_environment
6
+ if ENV.has_key?('RAILS_ENV')
7
+ ENV['RAILS_ENV']
8
+ elsif defined? Rails
9
+ Rails.env
10
+ end
11
+ end
12
+
13
+ def self.current_root
14
+ if defined? RAILS_ROOT
15
+ RAILS_ROOT
16
+ elsif defined? Rails
17
+ Rails.root
18
+ end
19
+ end
20
+
5
21
  #gets an entry from a hash regardless if the key is a string or symbol
6
22
  def self.get_hash_entry(hash, key)
7
23
  if hash.has_key?(key)
@@ -20,7 +36,11 @@ module BackgroundQueue
20
36
 
21
37
  #wrap a hash
22
38
  def initialize(hash)
23
- @hash = hash
39
+ if hash.kind_of?(Hash)
40
+ @hash = hash
41
+ else
42
+ raise "Invalid class used when initializing AnyKeyHash (#{hash.class.name})"
43
+ end
24
44
  end
25
45
 
26
46
  #get an entry by string or symbol
@@ -35,6 +55,10 @@ module BackgroundQueue
35
55
  def to_json(dummy=true)
36
56
  @hash.to_json
37
57
  end
58
+
59
+ def merge(other_map)
60
+ AnyKeyHash.new(@hash.clone.update(other_map))
61
+ end
38
62
  end
39
63
  end
40
64
  end
@@ -19,6 +19,7 @@ module BackgroundQueue::Worker
19
19
  def set_progress(caption, percent)
20
20
  #puts self
21
21
  #puts "env=#{self.environment}"
22
+ logger.debug("set_progress(#{caption}, #{percent})")
22
23
  self.environment.send_data({:caption=>caption, :percent=>percent}.to_json)
23
24
  end
24
25
 
@@ -26,14 +27,50 @@ module BackgroundQueue::Worker
26
27
  #key: :notice, :warning, :error, :meta
27
28
  #value: :notice/:warning/:error : String, :meta : any json compatible object
28
29
  def add_progress_meta(key, value)
30
+ logger.debug("add_progress_meta(#{key}, #{value.to_json})")
29
31
  self.environment.send_data({:meta=>{key=>value}}.to_json)
30
32
  end
31
33
 
34
+ def append_summary(type, data)
35
+ raise "Missing Type when appending summary" if type.nil?
36
+ self.environment.send_data({:summary=>"app", :type=>type.to_s, :data=>data}.to_json)
37
+ end
38
+
39
+ def set_summary(type, key, data)
40
+ raise "Missing Type when settind summary" if type.nil?
41
+ raise "Missing key when settind summary" if key.nil?
42
+ self.environment.send_data({:summary=>"set", :type=>type.to_s, :key=>key, :data=>data}.to_json)
43
+ end
44
+
45
+ def increment_summary(type, amount=1)
46
+ raise "Missing Type when incrementing summary" if type.nil?
47
+ self.environment.send_data({:summary=>"inc", :type=>type.to_s, :data=>amount}.to_json)
48
+ end
49
+
50
+ def decrement_sumary(type, amount=1)
51
+ raise "Missing Type when decrementing summary" if type.nil?
52
+ self.environment.send_data({:summary=>"dec", :type=>type.to_s, :data=>amount}.to_json)
53
+ end
54
+
55
+ def reset_summary(type)
56
+ self.environment.send_data({:summary=>"res", :type=>type.to_s}.to_json)
57
+ end
58
+
32
59
  #virtual function: called to process a worker request
33
60
  def run
34
61
  raise "run() Not Implemented on worker #{self.class.name}"
35
62
  end
36
63
 
64
+ #virtual function: called to process a worker request (step=start)
65
+ def start
66
+ raise "start() Not Implemented on worker #{self.class.name}"
67
+ end
68
+
69
+ #virtual function: called to process a worker request (step=finish)
70
+ def finish
71
+ raise "finish() Not Implemented on worker #{self.class.name}"
72
+ end
73
+
37
74
  def params
38
75
  self.environment.params
39
76
  end
@@ -42,5 +79,9 @@ module BackgroundQueue::Worker
42
79
  self.environment.logger
43
80
  end
44
81
 
82
+ def summary
83
+ self.environment.summary
84
+ end
85
+
45
86
  end
46
87
  end
@@ -34,17 +34,40 @@ module BackgroundQueue::Worker
34
34
  env
35
35
  end
36
36
 
37
+ def set_process_name(env)
38
+ unless BackgroundQueue::Worker::Config.process_name_prefix.nil?
39
+ $0 = "#{BackgroundQueue::Worker::Config.process_name_prefix}:#{env.worker}:#{env.owner_id}:#{env.job_id}:#{env.task_id}"
40
+ end
41
+ end
42
+
43
+ def revert_process_name
44
+ unless BackgroundQueue::Worker::Config.process_name_prefix.nil?
45
+ $0 = "#{BackgroundQueue::Worker::Config.process_name_prefix}:idle"
46
+ end
47
+ end
48
+
37
49
  def call_worker(worker, env)
38
50
  headers['X-Accel-Buffering'] = 'no' #passenger standalone uses nginx. This will turn buffering off in nginx
39
51
  render :text => lambda { |response,output|
40
52
  env.set_output(output)
53
+ set_process_name(env)
41
54
  begin
42
- worker.run
55
+ case env.step
56
+ when "start"
57
+ worker.start
58
+ when "finish"
59
+ worker.finish
60
+ else
61
+ worker.run
62
+ end
43
63
  rescue Exception=>e
44
64
  logger.error("Error calling worker: #{e.message}")
45
65
  logger.error(e.backtrace.join("\n"))
66
+ worker.logger.error("Error calling worker: #{e.message}")
67
+ worker.logger.error(e.backtrace.join("\n"))
46
68
  ensure
47
69
  worker.set_environment(nil)
70
+ revert_process_name
48
71
  end
49
72
  }, :type=>"text/text"
50
73
  end
@@ -2,6 +2,8 @@ module BackgroundQueue::Worker
2
2
  class Config
3
3
 
4
4
  @@worker_path = nil
5
+ @separate_logs = false
6
+ @@process_name_prefix = "bgq"
5
7
 
6
8
  def self.secret=(auth)
7
9
  @@secret = auth
@@ -33,5 +35,23 @@ module BackgroundQueue::Worker
33
35
  end
34
36
  end
35
37
 
38
+ def self.separate_logs=(use_separate_logs)
39
+ @separate_logs = use_separate_logs
40
+ end
41
+
42
+ #should the worker have its own log file per instance? (makes it easier to backtrack errors)
43
+ #if true, the log file is based on the job id. Each log line will have the task id.
44
+ def self.separate_logs?
45
+ @separate_logs
46
+ end
47
+
48
+
49
+ def self.process_name_prefix=(prefix)
50
+ @@process_name_prefix = prefix
51
+ end
52
+
53
+ def self.process_name_prefix
54
+ @@process_name_prefix
55
+ end
36
56
  end
37
57
  end
@@ -9,6 +9,8 @@ module BackgroundQueue::Worker
9
9
  attr_reader :owner_id
10
10
  attr_reader :worker
11
11
  attr_reader :logger
12
+ attr_reader :summary
13
+ attr_reader :step
12
14
 
13
15
  attr_reader :server_address
14
16
 
@@ -20,8 +22,12 @@ module BackgroundQueue::Worker
20
22
 
21
23
  def init_from_controller(controller)
22
24
  @controller = controller
23
- @logger = controller.logger
24
25
  init_params(controller.params)
26
+ if BackgroundQueue::Worker::Config.separate_logs?
27
+ @logger = BackgroundQueue::Worker::Logger.init_logger(@worker, @owner_id, @job_id , @task_id, controller.logger.level)
28
+ else
29
+ @logger = controller.logger
30
+ end
25
31
  init_server_address(controller)
26
32
  end
27
33
 
@@ -40,6 +46,20 @@ module BackgroundQueue::Worker
40
46
  @task_id = hash_data['id']
41
47
  @priority = hash_data['priority']
42
48
  @worker = hash_data['worker']
49
+
50
+
51
+ summary_data = nil
52
+ begin
53
+ summary_data = JSON.load(controller_params[:summary]) unless controller_params[:summary].nil?
54
+ rescue Exception=>e
55
+ raise "Invalid data format (should be json) when loading summary from buffer: #{e.message}"
56
+ end
57
+ if summary_data.nil?
58
+ @summary = {}
59
+ else
60
+ @summary = BackgroundQueue::Utils::AnyKeyHash.new(summary_data)
61
+ end
62
+ @step = controller_params[:step]
43
63
  end
44
64
 
45
65
  def set_output(out)
@@ -55,6 +75,10 @@ module BackgroundQueue::Worker
55
75
  @server_address = BackgroundQueue::Worker::Environment::Server.new(controller.request.remote_ip, controller.params[:server_port])
56
76
  end
57
77
 
78
+ def revert_environment
79
+
80
+ end
81
+
58
82
  class Server
59
83
  attr_accessor :host
60
84
  attr_accessor :port
@@ -0,0 +1,114 @@
1
+ module BackgroundQueue::Worker
2
+ class Logger < Logger
3
+
4
+ def initialize(logdev, task_key)
5
+ super(logdev, 0, 1048576)
6
+ @default_formatter = BackgroundQueue::Worker::LogFormatter.new(task_key)
7
+ end
8
+
9
+ def set_previous_state(previous_state)
10
+ @previous_state = previous_state
11
+ end
12
+
13
+ def format_message(severity, datetime, progname, msg)
14
+ (@formatter || @default_formatter).call(severity, datetime, progname, msg)
15
+ end
16
+
17
+ def self.init_logger(worker_name, owner_key, job_key, task_key, level)
18
+ logger = build_logger("#{BackgroundQueue::Utils.current_root}/log/workers/#{worker_name}-#{owner_key}-#{job_key}.log", task_key, level)
19
+
20
+ prev_state = {}
21
+
22
+ #we also want to have active record use this log file
23
+ if defined? ActiveRecord && defined? ActiveRecord::Base && ActiveRecord::Base.class.respond_to?(:logger)
24
+ begin
25
+ prev_state[:ar_base] = ActiveRecord::Base.logger
26
+ ActiveRecord::Base.logger = logger
27
+
28
+ if ActiveRecord::Base.respond_to?(:active_connections)
29
+ #set the logger for connections that are already loaded
30
+ ActiveRecord::Base.active_connections.each_value { |con| con.set_logger(ActiveRecord::Base.logger)}
31
+ end
32
+ rescue Exception=>e
33
+ logger.debug("Error setting up active record logger: #{e.message}")
34
+ end
35
+ end
36
+
37
+ #and RAILS_DEFAULT_LOGGER
38
+ if defined?(RAILS_DEFAULT_LOGGER)
39
+ prev_state[:rails_default_logger] = RAILS_DEFAULT_LOGGER
40
+ Object.redefine_const(
41
+ :RAILS_DEFAULT_LOGGER,
42
+ logger
43
+ )
44
+ end
45
+
46
+ logger.set_previous_state(prev_state) unless prev_state.empty?
47
+ logger
48
+ end
49
+
50
+ def self.build_logger(path, task_key, level)
51
+ logger = BackgroundQueue::Worker::Logger.new(path, task_key)
52
+ logger.level = level
53
+ logger
54
+ end
55
+
56
+ def revert_to_previous_state
57
+ unless @previous_state.nil?
58
+ if @previous_state[:ar_base]
59
+ ActiveRecord::Base.logger = @previous_state[:ar_base]
60
+ if ActiveRecord::Base.respond_to?(:active_connections)
61
+ #set the logger for connections that are already loaded
62
+ ActiveRecord::Base.active_connections.each_value { |con| con.set_logger(ActiveRecord::Base.logger)}
63
+ end
64
+ end
65
+ if @previous_state[:rails_default_logger]
66
+ Object.redefine_const(
67
+ :RAILS_DEFAULT_LOGGER,
68
+ @previous_state[:rails_default_logger]
69
+ )
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+
76
+ #custom log formatter
77
+ class LogFormatter < Logger::Formatter
78
+ Format = "%s %5s [%s] %s\n"
79
+
80
+ def initialize(task_key)
81
+ @datetime_format = "%Y-%m-%d %H:%M:%S"
82
+ @task_key = task_key
83
+ end
84
+
85
+ def call(severity, time, progname, msg)
86
+ Format % [@task_key, severity, format_datetime(time), msg2str(msg)]
87
+ end
88
+ end
89
+ end
90
+
91
+ #allow us to redifine constants (RAILS_DEFAULT_LOGGER)
92
+ unless Module.respond_to?(:redefine_const)
93
+ class Module
94
+ def redefine_const(name, value)
95
+ __send__(:remove_const, name) if const_defined?(name)
96
+ const_set(name, value)
97
+ end
98
+ end
99
+ end
100
+
101
+
102
+ #need to be able to set the current logger for existing connections...
103
+ if defined? ActiveRecord && defined? ActiveRecord::ConnectionAdapters && defined? ActiveRecord::ConnectionAdapters::AbstractAdapter
104
+ module ActiveRecord
105
+ module ConnectionAdapters # :nodoc:
106
+ class AbstractAdapter
107
+ def set_logger(logger)
108
+ @logger = logger
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+