background_queue 0.3.0 → 0.6.0

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 (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
+