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.
Files changed (91) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +48 -0
  4. data/Gemfile +19 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +69 -0
  7. data/Rakefile +42 -0
  8. data/TODO +2 -0
  9. data/VERSION +1 -0
  10. data/background_queue.gemspec +158 -0
  11. data/bin/bg_queue +26 -0
  12. data/lib/background_queue.rb +8 -0
  13. data/lib/background_queue/client.rb +96 -0
  14. data/lib/background_queue/client_lib/command.rb +36 -0
  15. data/lib/background_queue/client_lib/config.rb +109 -0
  16. data/lib/background_queue/client_lib/connection.rb +105 -0
  17. data/lib/background_queue/client_lib/job_handle.rb +19 -0
  18. data/lib/background_queue/command.rb +49 -0
  19. data/lib/background_queue/config.rb +118 -0
  20. data/lib/background_queue/server_lib/balanced_queue.rb +108 -0
  21. data/lib/background_queue/server_lib/config.rb +339 -0
  22. data/lib/background_queue/server_lib/event_connection.rb +133 -0
  23. data/lib/background_queue/server_lib/event_server.rb +35 -0
  24. data/lib/background_queue/server_lib/job.rb +252 -0
  25. data/lib/background_queue/server_lib/job_registry.rb +30 -0
  26. data/lib/background_queue/server_lib/lru.rb +193 -0
  27. data/lib/background_queue/server_lib/owner.rb +54 -0
  28. data/lib/background_queue/server_lib/priority_queue.rb +156 -0
  29. data/lib/background_queue/server_lib/queue_registry.rb +123 -0
  30. data/lib/background_queue/server_lib/server.rb +314 -0
  31. data/lib/background_queue/server_lib/sorted_workers.rb +52 -0
  32. data/lib/background_queue/server_lib/task.rb +79 -0
  33. data/lib/background_queue/server_lib/task_registry.rb +51 -0
  34. data/lib/background_queue/server_lib/thread_manager.rb +121 -0
  35. data/lib/background_queue/server_lib/worker.rb +18 -0
  36. data/lib/background_queue/server_lib/worker_balancer.rb +97 -0
  37. data/lib/background_queue/server_lib/worker_client.rb +94 -0
  38. data/lib/background_queue/server_lib/worker_thread.rb +70 -0
  39. data/lib/background_queue/utils.rb +40 -0
  40. data/lib/background_queue/worker/base.rb +46 -0
  41. data/lib/background_queue/worker/calling.rb +59 -0
  42. data/lib/background_queue/worker/config.rb +35 -0
  43. data/lib/background_queue/worker/environment.rb +70 -0
  44. data/lib/background_queue/worker/worker_loader.rb +94 -0
  45. data/lib/background_queue_server.rb +21 -0
  46. data/lib/background_queue_worker.rb +5 -0
  47. data/spec/background_queue/client_lib/command_spec.rb +75 -0
  48. data/spec/background_queue/client_lib/config_spec.rb +156 -0
  49. data/spec/background_queue/client_lib/connection_spec.rb +170 -0
  50. data/spec/background_queue/client_spec.rb +95 -0
  51. data/spec/background_queue/command_spec.rb +34 -0
  52. data/spec/background_queue/config_spec.rb +134 -0
  53. data/spec/background_queue/server_lib/balanced_queue_spec.rb +122 -0
  54. data/spec/background_queue/server_lib/config_spec.rb +443 -0
  55. data/spec/background_queue/server_lib/event_connection_spec.rb +190 -0
  56. data/spec/background_queue/server_lib/event_server_spec.rb +48 -0
  57. data/spec/background_queue/server_lib/integration/full_test_spec.rb +247 -0
  58. data/spec/background_queue/server_lib/integration/queue_integration_spec.rb +98 -0
  59. data/spec/background_queue/server_lib/integration/serialize_spec.rb +127 -0
  60. data/spec/background_queue/server_lib/job_registry_spec.rb +65 -0
  61. data/spec/background_queue/server_lib/job_spec.rb +525 -0
  62. data/spec/background_queue/server_lib/owner_spec.rb +33 -0
  63. data/spec/background_queue/server_lib/priority_queue_spec.rb +182 -0
  64. data/spec/background_queue/server_lib/server_spec.rb +353 -0
  65. data/spec/background_queue/server_lib/sorted_workers_spec.rb +122 -0
  66. data/spec/background_queue/server_lib/task_registry_spec.rb +69 -0
  67. data/spec/background_queue/server_lib/task_spec.rb +20 -0
  68. data/spec/background_queue/server_lib/thread_manager_spec.rb +106 -0
  69. data/spec/background_queue/server_lib/worker_balancer_spec.rb +127 -0
  70. data/spec/background_queue/server_lib/worker_client_spec.rb +196 -0
  71. data/spec/background_queue/server_lib/worker_thread_spec.rb +125 -0
  72. data/spec/background_queue/utils_spec.rb +27 -0
  73. data/spec/background_queue/worker/base_spec.rb +35 -0
  74. data/spec/background_queue/worker/calling_spec.rb +103 -0
  75. data/spec/background_queue/worker/environment_spec.rb +67 -0
  76. data/spec/background_queue/worker/worker_loader_spec.rb +103 -0
  77. data/spec/background_queue_spec.rb +7 -0
  78. data/spec/resources/config-client.yml +7 -0
  79. data/spec/resources/config-serialize.yml +12 -0
  80. data/spec/resources/config.yml +12 -0
  81. data/spec/resources/example_worker.rb +4 -0
  82. data/spec/resources/example_worker_with_error.rb +4 -0
  83. data/spec/resources/test_worker.rb +8 -0
  84. data/spec/shared/queue_registry_shared.rb +216 -0
  85. data/spec/spec_helper.rb +15 -0
  86. data/spec/support/default_task.rb +9 -0
  87. data/spec/support/private.rb +23 -0
  88. data/spec/support/simple_server.rb +28 -0
  89. data/spec/support/simple_task.rb +58 -0
  90. data/spec/support/test_worker_server.rb +205 -0
  91. 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"