background_queue 0.2.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/.document +5 -0
- data/.rspec +1 -0
- data/.rvmrc +48 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +20 -0
- data/README.md +69 -0
- data/Rakefile +42 -0
- data/TODO +2 -0
- data/VERSION +1 -0
- data/background_queue.gemspec +158 -0
- data/bin/bg_queue +26 -0
- data/lib/background_queue.rb +8 -0
- data/lib/background_queue/client.rb +96 -0
- data/lib/background_queue/client_lib/command.rb +36 -0
- data/lib/background_queue/client_lib/config.rb +109 -0
- data/lib/background_queue/client_lib/connection.rb +105 -0
- data/lib/background_queue/client_lib/job_handle.rb +19 -0
- data/lib/background_queue/command.rb +49 -0
- data/lib/background_queue/config.rb +118 -0
- data/lib/background_queue/server_lib/balanced_queue.rb +108 -0
- data/lib/background_queue/server_lib/config.rb +339 -0
- data/lib/background_queue/server_lib/event_connection.rb +133 -0
- data/lib/background_queue/server_lib/event_server.rb +35 -0
- data/lib/background_queue/server_lib/job.rb +252 -0
- data/lib/background_queue/server_lib/job_registry.rb +30 -0
- data/lib/background_queue/server_lib/lru.rb +193 -0
- data/lib/background_queue/server_lib/owner.rb +54 -0
- data/lib/background_queue/server_lib/priority_queue.rb +156 -0
- data/lib/background_queue/server_lib/queue_registry.rb +123 -0
- data/lib/background_queue/server_lib/server.rb +314 -0
- data/lib/background_queue/server_lib/sorted_workers.rb +52 -0
- data/lib/background_queue/server_lib/task.rb +79 -0
- data/lib/background_queue/server_lib/task_registry.rb +51 -0
- data/lib/background_queue/server_lib/thread_manager.rb +121 -0
- data/lib/background_queue/server_lib/worker.rb +18 -0
- data/lib/background_queue/server_lib/worker_balancer.rb +97 -0
- data/lib/background_queue/server_lib/worker_client.rb +94 -0
- data/lib/background_queue/server_lib/worker_thread.rb +70 -0
- data/lib/background_queue/utils.rb +40 -0
- data/lib/background_queue/worker/base.rb +46 -0
- data/lib/background_queue/worker/calling.rb +59 -0
- data/lib/background_queue/worker/config.rb +35 -0
- data/lib/background_queue/worker/environment.rb +70 -0
- data/lib/background_queue/worker/worker_loader.rb +94 -0
- data/lib/background_queue_server.rb +21 -0
- data/lib/background_queue_worker.rb +5 -0
- data/spec/background_queue/client_lib/command_spec.rb +75 -0
- data/spec/background_queue/client_lib/config_spec.rb +156 -0
- data/spec/background_queue/client_lib/connection_spec.rb +170 -0
- data/spec/background_queue/client_spec.rb +95 -0
- data/spec/background_queue/command_spec.rb +34 -0
- data/spec/background_queue/config_spec.rb +134 -0
- data/spec/background_queue/server_lib/balanced_queue_spec.rb +122 -0
- data/spec/background_queue/server_lib/config_spec.rb +443 -0
- data/spec/background_queue/server_lib/event_connection_spec.rb +190 -0
- data/spec/background_queue/server_lib/event_server_spec.rb +48 -0
- data/spec/background_queue/server_lib/integration/full_test_spec.rb +247 -0
- data/spec/background_queue/server_lib/integration/queue_integration_spec.rb +98 -0
- data/spec/background_queue/server_lib/integration/serialize_spec.rb +127 -0
- data/spec/background_queue/server_lib/job_registry_spec.rb +65 -0
- data/spec/background_queue/server_lib/job_spec.rb +525 -0
- data/spec/background_queue/server_lib/owner_spec.rb +33 -0
- data/spec/background_queue/server_lib/priority_queue_spec.rb +182 -0
- data/spec/background_queue/server_lib/server_spec.rb +353 -0
- data/spec/background_queue/server_lib/sorted_workers_spec.rb +122 -0
- data/spec/background_queue/server_lib/task_registry_spec.rb +69 -0
- data/spec/background_queue/server_lib/task_spec.rb +20 -0
- data/spec/background_queue/server_lib/thread_manager_spec.rb +106 -0
- data/spec/background_queue/server_lib/worker_balancer_spec.rb +127 -0
- data/spec/background_queue/server_lib/worker_client_spec.rb +196 -0
- data/spec/background_queue/server_lib/worker_thread_spec.rb +125 -0
- data/spec/background_queue/utils_spec.rb +27 -0
- data/spec/background_queue/worker/base_spec.rb +35 -0
- data/spec/background_queue/worker/calling_spec.rb +103 -0
- data/spec/background_queue/worker/environment_spec.rb +67 -0
- data/spec/background_queue/worker/worker_loader_spec.rb +103 -0
- data/spec/background_queue_spec.rb +7 -0
- data/spec/resources/config-client.yml +7 -0
- data/spec/resources/config-serialize.yml +12 -0
- data/spec/resources/config.yml +12 -0
- data/spec/resources/example_worker.rb +4 -0
- data/spec/resources/example_worker_with_error.rb +4 -0
- data/spec/resources/test_worker.rb +8 -0
- data/spec/shared/queue_registry_shared.rb +216 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/default_task.rb +9 -0
- data/spec/support/private.rb +23 -0
- data/spec/support/simple_server.rb +28 -0
- data/spec/support/simple_task.rb +58 -0
- data/spec/support/test_worker_server.rb +205 -0
- metadata +254 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
|
2
|
+
module BackgroundQueue::ServerLib
|
3
|
+
#A Thread that processes the task queue
|
4
|
+
class WorkerThread
|
5
|
+
|
6
|
+
def initialize(server)
|
7
|
+
@server = server
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_next_task
|
11
|
+
task = nil
|
12
|
+
while @server.running? && task.nil?
|
13
|
+
task = @server.task_queue.next_task
|
14
|
+
end
|
15
|
+
task = nil if task.nil? || !@server.running? #if the server isnt running, dont continue to process a task
|
16
|
+
task
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_client
|
20
|
+
BackgroundQueue::ServerLib::WorkerClient.new(@server)
|
21
|
+
end
|
22
|
+
|
23
|
+
def call_worker(task)
|
24
|
+
@server.change_stat(:tasks, -1)
|
25
|
+
@server.change_stat(:running, 1)
|
26
|
+
@server.logger.debug("calling worker for task #{task.id}")
|
27
|
+
error_count = 0
|
28
|
+
while @server.running?
|
29
|
+
worker = @server.workers.get_next_worker
|
30
|
+
if worker.nil?
|
31
|
+
Kernel.sleep(1) unless !@server.running?
|
32
|
+
else
|
33
|
+
client = build_client
|
34
|
+
if client.send_request(worker, task, @server.config.secret)
|
35
|
+
@server.logger.debug("called worker for task #{task.id}")
|
36
|
+
@server.workers.finish_using_worker(worker, true)
|
37
|
+
@server.task_queue.finish_task(task)
|
38
|
+
@server.change_stat(:running, -1)
|
39
|
+
@server.change_stat(:run_tasks, 1)
|
40
|
+
|
41
|
+
return true
|
42
|
+
else
|
43
|
+
@server.logger.debug("failed calling worker for task #{task.id}")
|
44
|
+
@server.workers.finish_using_worker(worker, false)
|
45
|
+
error_count += 1
|
46
|
+
if error_count > 5
|
47
|
+
@server.logger.debug("error count exceeded for task #{task.id}, returning to queue")
|
48
|
+
@server.task_queue.finish_task(task)
|
49
|
+
@server.task_queue.add_task(task)
|
50
|
+
return true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
#if we get here the server stopped before we could do the task... put it back so it can be saved to disk...
|
56
|
+
@server.logger.debug("returning task #{task.id} to queue because the server has stopped")
|
57
|
+
@server.task_queue.finish_task(task)
|
58
|
+
@server.task_queue.add_task(task)
|
59
|
+
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
def run
|
64
|
+
while @server.running?
|
65
|
+
task = get_next_task
|
66
|
+
call_worker(task) unless task.nil?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module BackgroundQueue
|
2
|
+
#Utility Module
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
#gets an entry from a hash regardless if the key is a string or symbol
|
6
|
+
def self.get_hash_entry(hash, key)
|
7
|
+
if hash.has_key?(key)
|
8
|
+
hash[key]
|
9
|
+
elsif key.kind_of?(String)
|
10
|
+
hash[key.intern]
|
11
|
+
else
|
12
|
+
hash[key.to_s]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
#class that wraps a hash so it can be accessed by key or symbol
|
17
|
+
class AnyKeyHash
|
18
|
+
#the wrapped hash
|
19
|
+
attr_accessor :hash
|
20
|
+
|
21
|
+
#wrap a hash
|
22
|
+
def initialize(hash)
|
23
|
+
@hash = hash
|
24
|
+
end
|
25
|
+
|
26
|
+
#get an entry by string or symbol
|
27
|
+
def [] (key)
|
28
|
+
BackgroundQueue::Utils.get_hash_entry(@hash, key)
|
29
|
+
end
|
30
|
+
|
31
|
+
def []=(key, value)
|
32
|
+
@hash[key] = value
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_json(dummy=true)
|
36
|
+
@hash.to_json
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module BackgroundQueue::Worker
|
2
|
+
#the base class of workers
|
3
|
+
class Base
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@environment = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def set_environment(env)
|
10
|
+
#puts "set_environment=#{env}"
|
11
|
+
@environment = env
|
12
|
+
end
|
13
|
+
|
14
|
+
def environment
|
15
|
+
@environment
|
16
|
+
end
|
17
|
+
|
18
|
+
#update the progress of the currently running task
|
19
|
+
def set_progress(caption, percent)
|
20
|
+
#puts self
|
21
|
+
#puts "env=#{self.environment}"
|
22
|
+
self.environment.send_data({:caption=>caption, :percent=>percent}.to_json)
|
23
|
+
end
|
24
|
+
|
25
|
+
#add meta data to the progress
|
26
|
+
#key: :notice, :warning, :error, :meta
|
27
|
+
#value: :notice/:warning/:error : String, :meta : any json compatible object
|
28
|
+
def add_progress_meta(key, value)
|
29
|
+
self.environment.send_data({:meta=>{key=>value}}.to_json)
|
30
|
+
end
|
31
|
+
|
32
|
+
#virtual function: called to process a worker request
|
33
|
+
def run
|
34
|
+
raise "run() Not Implemented on worker #{self.class.name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def params
|
38
|
+
self.environment.params
|
39
|
+
end
|
40
|
+
|
41
|
+
def logger
|
42
|
+
self.environment.logger
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module BackgroundQueue::Worker
|
2
|
+
#this module is mixed into the controller that will service the worker calls.
|
3
|
+
#the action that will recive the worker calls should only call run_worker
|
4
|
+
module Calling
|
5
|
+
|
6
|
+
#call this method from within the controller action that receives the worker calls.
|
7
|
+
#pass the shared secret in to validate the call: this should equal the secret in the server config.
|
8
|
+
def run_worker
|
9
|
+
return unless check_secret()
|
10
|
+
worker = nil
|
11
|
+
env = nil
|
12
|
+
begin
|
13
|
+
#setup worker environment
|
14
|
+
env = init_environment
|
15
|
+
#get worker
|
16
|
+
worker = BackgroundQueue::Worker::WorkerLoader.get_worker(env.worker)
|
17
|
+
worker.set_environment(env)
|
18
|
+
rescue Exception=>e
|
19
|
+
logger.error("Error initializing worker: #{e.message}")
|
20
|
+
logger.debug(e.backtrace.join("\n"))
|
21
|
+
render :text=>"Error initializing worker: #{e.message}", :status=>500
|
22
|
+
raise e
|
23
|
+
end
|
24
|
+
#puts worker
|
25
|
+
#call worker
|
26
|
+
call_worker(worker, env)
|
27
|
+
true
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def init_environment
|
32
|
+
env = BackgroundQueue::Worker::Environment.new
|
33
|
+
env.init_from_controller(self)
|
34
|
+
env
|
35
|
+
end
|
36
|
+
|
37
|
+
def call_worker(worker, env)
|
38
|
+
headers['X-Accel-Buffering'] = 'no' #passenger standalone uses nginx. This will turn buffering off in nginx
|
39
|
+
render :text => lambda { |response,output|
|
40
|
+
env.set_output(output)
|
41
|
+
begin
|
42
|
+
worker.run
|
43
|
+
rescue Exception=>e
|
44
|
+
logger.error("Error calling worker: #{e.message}")
|
45
|
+
logger.error(e.backtrace.join("\n"))
|
46
|
+
ensure
|
47
|
+
worker.set_environment(nil)
|
48
|
+
end
|
49
|
+
}, :type=>"text/text"
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_secret
|
53
|
+
return true if params[:auth] == BackgroundQueue::Worker::Config.secret
|
54
|
+
render :text=>"Invalid auth (#{params[:auth]})", :status=>401
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module BackgroundQueue::Worker
|
2
|
+
class Config
|
3
|
+
|
4
|
+
def self.secret=(auth)
|
5
|
+
@@secret = auth
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.secret
|
9
|
+
@@secret
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.worker_path=(path)
|
13
|
+
@@worker_path = path
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.worker_path
|
17
|
+
if @@worker_path.nil?
|
18
|
+
default_worker_path
|
19
|
+
else
|
20
|
+
@@worker_path
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.default_worker_path
|
25
|
+
if defined?(RAILS_ROOT)
|
26
|
+
File.join(RAILS_ROOT, "lib", "bgq_workers")
|
27
|
+
elsif defined?(Rails)
|
28
|
+
File.join(Rails.root, "lib", "bgq_workers")
|
29
|
+
else
|
30
|
+
raise "You must specify the BackgroundQueue::Worker::Config.worker_path"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module BackgroundQueue::Worker
|
2
|
+
#holds the params, controller and response
|
3
|
+
class Environment
|
4
|
+
attr_reader :params
|
5
|
+
attr_reader :owner_id
|
6
|
+
attr_reader :job_id
|
7
|
+
attr_reader :task_id
|
8
|
+
attr_reader :priority
|
9
|
+
attr_reader :owner_id
|
10
|
+
attr_reader :worker
|
11
|
+
attr_reader :logger
|
12
|
+
|
13
|
+
attr_reader :server_address
|
14
|
+
|
15
|
+
attr_reader :output
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@params = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def init_from_controller(controller)
|
22
|
+
@controller = controller
|
23
|
+
@logger = controller.logger
|
24
|
+
init_params(controller.params)
|
25
|
+
init_server_address(controller)
|
26
|
+
end
|
27
|
+
|
28
|
+
def init_params(controller_params)
|
29
|
+
hash_data = nil
|
30
|
+
begin
|
31
|
+
hash_data = JSON.load(controller_params[:task])
|
32
|
+
rescue Exception=>e
|
33
|
+
raise "Invalid data format (should be json) when loading task from buffer: #{e.message}"
|
34
|
+
end
|
35
|
+
raise 'Invalid json root object (should be hash)' unless hash_data.kind_of?(Hash)
|
36
|
+
|
37
|
+
@params = BackgroundQueue::Utils::AnyKeyHash.new(hash_data['params'])
|
38
|
+
@owner_id = hash_data['owner_id']
|
39
|
+
@job_id = hash_data['job_id']
|
40
|
+
@task_id = hash_data['id']
|
41
|
+
@priority = hash_data['priority']
|
42
|
+
@worker = hash_data['worker']
|
43
|
+
end
|
44
|
+
|
45
|
+
def set_output(out)
|
46
|
+
@output = out
|
47
|
+
end
|
48
|
+
|
49
|
+
def send_data(data)
|
50
|
+
@output.write("#{data}\n")
|
51
|
+
@output.flush
|
52
|
+
end
|
53
|
+
|
54
|
+
def init_server_address(controller)
|
55
|
+
@server_address = BackgroundQueue::Worker::Environment::Server.new(controller.request.remote_ip, controller.params[:server_port])
|
56
|
+
end
|
57
|
+
|
58
|
+
class Server
|
59
|
+
attr_accessor :host
|
60
|
+
attr_accessor :port
|
61
|
+
|
62
|
+
def initialize(host, port)
|
63
|
+
@host = host
|
64
|
+
@port = port
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module BackgroundQueue::Worker
|
2
|
+
#looks after loading and caching worker classes.
|
3
|
+
#worker classes sit in BackgroundQueue::Worker::Config.worker_path directory
|
4
|
+
class WorkerLoader
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@worker_entries = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.get_worker(worker_name)
|
11
|
+
@@worker_loader ||= BackgroundQueue::Worker::WorkerLoader.new
|
12
|
+
@@worker_loader.get_worker(worker_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_worker(worker_name)
|
16
|
+
worker_entry = get(worker_name)
|
17
|
+
if worker_entry.nil?
|
18
|
+
worker_entry = load_worker(worker_name)
|
19
|
+
set(worker_entry)
|
20
|
+
else
|
21
|
+
reload_if_updated(worker_entry)
|
22
|
+
end
|
23
|
+
worker_entry.worker
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_worker(worker_name)
|
27
|
+
path = worker_path(worker_name)
|
28
|
+
load_file(path)
|
29
|
+
datestamp = File.mtime(path)
|
30
|
+
worker = load_class(worker_name, path)
|
31
|
+
WorkerEntry.new(worker, path, datestamp, worker_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_file(path)
|
35
|
+
load(path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_class(worker_name, path)
|
39
|
+
class_name = worker_class_name(worker_name)
|
40
|
+
begin
|
41
|
+
eval("#{class_name}.new")
|
42
|
+
rescue NameError=>e
|
43
|
+
raise "#{path} did not define #{class_name}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def worker_class_name(worker_name)
|
48
|
+
worker_name.split('_').collect!{ |w| w.capitalize }.join
|
49
|
+
end
|
50
|
+
|
51
|
+
def worker_path(worker_name)
|
52
|
+
File.join(BackgroundQueue::Worker::Config.worker_path, "#{worker_name}.rb")
|
53
|
+
end
|
54
|
+
|
55
|
+
def reload_if_updated(worker_entry)
|
56
|
+
ds = File.mtime(worker_entry.path)
|
57
|
+
if ds != worker_entry.datestamp
|
58
|
+
load_file(worker_entry.path)
|
59
|
+
worker_entry.worker = load_class(worker_entry.name)
|
60
|
+
worker_entry.datestamp = ds
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
class WorkerEntry
|
67
|
+
|
68
|
+
attr_accessor :worker
|
69
|
+
attr_accessor :path
|
70
|
+
attr_accessor :datestamp
|
71
|
+
attr_accessor :name
|
72
|
+
|
73
|
+
def initialize(worker, path, datestamp, name)
|
74
|
+
@worker = worker
|
75
|
+
@path = path
|
76
|
+
@datestamp = datestamp
|
77
|
+
@name = name
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def set(worker_entry)
|
85
|
+
@worker_entries[worker_entry.name] = worker_entry
|
86
|
+
end
|
87
|
+
|
88
|
+
def get(worker_name)
|
89
|
+
@worker_entries[worker_name]
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "background_queue/utils"
|
2
|
+
require "background_queue/config"
|
3
|
+
require "background_queue/server_lib/lru"
|
4
|
+
require "background_queue/server_lib/config"
|
5
|
+
require "background_queue/server_lib/priority_queue"
|
6
|
+
require "background_queue/server_lib/queue_registry"
|
7
|
+
require "background_queue/server_lib/thread_manager"
|
8
|
+
require "background_queue/server_lib/balanced_queue"
|
9
|
+
require "background_queue/server_lib/owner"
|
10
|
+
require "background_queue/server_lib/job"
|
11
|
+
require "background_queue/server_lib/job_registry"
|
12
|
+
require "background_queue/server_lib/task"
|
13
|
+
require "background_queue/server_lib/task_registry"
|
14
|
+
require "background_queue/server_lib/sorted_workers"
|
15
|
+
require "background_queue/server_lib/worker"
|
16
|
+
require "background_queue/server_lib/worker_client"
|
17
|
+
require "background_queue/server_lib/worker_balancer"
|
18
|
+
require "background_queue/server_lib/worker_thread"
|
19
|
+
require "background_queue/server_lib/server"
|
20
|
+
require "background_queue/server_lib/event_server"
|
21
|
+
require "background_queue/server_lib/event_connection"
|