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