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.
- data/TODO +13 -0
- data/VERSION +1 -1
- data/background_queue.gemspec +8 -2
- data/lib/background_queue/client.rb +31 -3
- data/lib/background_queue/client_lib/connection.rb +9 -0
- data/lib/background_queue/client_lib/job_handle.rb +34 -1
- data/lib/background_queue/config.rb +1 -11
- data/lib/background_queue/server_lib/balanced_queue.rb +22 -0
- data/lib/background_queue/server_lib/event_connection.rb +7 -1
- data/lib/background_queue/server_lib/job.rb +46 -7
- data/lib/background_queue/server_lib/priority_queue.rb +7 -0
- data/lib/background_queue/server_lib/queue_registry.rb +20 -5
- data/lib/background_queue/server_lib/task.rb +28 -0
- data/lib/background_queue/server_lib/task_registry.rb +7 -0
- data/lib/background_queue/server_lib/worker_client.rb +5 -2
- data/lib/background_queue/server_lib/worker_thread.rb +1 -1
- data/lib/background_queue/utils.rb +25 -1
- data/lib/background_queue/worker/base.rb +41 -0
- data/lib/background_queue/worker/calling.rb +24 -1
- data/lib/background_queue/worker/config.rb +20 -0
- data/lib/background_queue/worker/environment.rb +25 -1
- data/lib/background_queue/worker/logger.rb +114 -0
- data/lib/background_queue/worker/progress.rb +152 -0
- data/lib/background_queue/worker/worker_loader.rb +1 -1
- data/lib/background_queue_worker.rb +2 -0
- data/spec/background_queue/client_lib/connection_spec.rb +7 -1
- data/spec/background_queue/client_spec.rb +2 -1
- data/spec/background_queue/config_spec.rb +11 -23
- data/spec/background_queue/server_lib/integration/error_handling_spec.rb +85 -0
- data/spec/background_queue/server_lib/integration/full_test_spec.rb +76 -3
- data/spec/background_queue/server_lib/integration/queue_integration_spec.rb +6 -3
- data/spec/background_queue/server_lib/job_spec.rb +44 -3
- data/spec/background_queue/server_lib/worker_thread_spec.rb +2 -2
- data/spec/background_queue/utils_spec.rb +30 -0
- data/spec/background_queue/worker/calling_spec.rb +3 -1
- data/spec/background_queue/worker/environment_spec.rb +3 -1
- data/spec/background_queue/worker/logger_spec.rb +58 -0
- data/spec/background_queue/worker/progress_spec.rb +82 -0
- data/spec/background_queue/worker/worker_loader_spec.rb +1 -1
- data/spec/resources/summary_worker.rb +18 -0
- data/spec/shared/queue_registry_shared.rb +4 -3
- data/spec/support/simple_task.rb +24 -0
- 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: #{
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
+
|