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,35 @@
1
+ require 'eventmachine'
2
+ require 'rubygems'
3
+ require 'rufus/scheduler'
4
+
5
+ module BackgroundQueue::ServerLib
6
+ class EventServer
7
+
8
+ attr_reader :running
9
+
10
+ def initialize(server)
11
+ @server = server
12
+ @running = false
13
+ end
14
+
15
+ def start
16
+ EventMachine.run do
17
+ EventMachine::start_server(@server.config.address.host, @server.config.address.port, BackgroundQueue::ServerLib::EventConnection) do |conn|
18
+ conn.server = @server
19
+ end
20
+
21
+ @scheduler = Rufus::Scheduler::EmScheduler.new
22
+ @scheduler.start
23
+ for job in @server.config.jobs
24
+ job.schedule(@scheduler, @server)
25
+ end
26
+ @running = true
27
+ end
28
+ @running = false
29
+ end
30
+
31
+ def stop
32
+ EventMachine::stop if EventMachine::reactor_running?
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,252 @@
1
+ module BackgroundQueue::ServerLib
2
+ class Job < PriorityQueue
3
+
4
+ attr_accessor :id
5
+ attr_reader :running_status
6
+ attr_reader :running_ordered_status
7
+ attr_reader :total_tasks
8
+ attr_reader :total_counted_tasks
9
+ attr_reader :completed_tasks
10
+ attr_reader :completed_counted_tasks
11
+ attr_reader :running_percent
12
+ attr_reader :running_percent_counted
13
+ attr_reader :current_running_status
14
+
15
+ attr_reader :total_weighted_tasks
16
+ attr_reader :total_weighted_percent
17
+ attr_reader :completed_weighted_percent
18
+ attr_reader :completed_weighted_tasks
19
+ attr_reader :running_percent_weighted
20
+
21
+ #attr_reader :current_running_excluded_status
22
+
23
+ def initialize(id, owner)
24
+ @id = id
25
+ @owner = owner
26
+ @stalled = false
27
+ @total_tasks = 0
28
+ @total_counted_tasks = 0
29
+ @completed_tasks = 0
30
+ @completed_counted_tasks = 0
31
+ @running_status = {}
32
+ @running_ordered_status = []
33
+ @running_percent = 0
34
+ @current_running_status = nil
35
+ @current_progress = {:percent=>0.0, :caption=>""}
36
+ @current_caption = ""
37
+ @synchronous_count = 0
38
+ @total_weighted_tasks = 0
39
+ @total_weighted_percent = 0.0
40
+ @completed_weighted_percent = 0.0
41
+ @completed_weighted_tasks = 0
42
+ @running_percent_weighted = 0.0
43
+ @status_meta = {}
44
+ @mutex = Mutex.new
45
+ #@current_running_excluded_status = nil
46
+ super()
47
+ end
48
+
49
+ def ==(other)
50
+ @id == other.id
51
+ end
52
+
53
+ def inspect
54
+ "#{self.id}:#{@queues.inspect}"
55
+ end
56
+
57
+ def server
58
+ @owner.server
59
+ end
60
+
61
+ def add_item(task)
62
+ task.set_job(self)
63
+ @total_tasks += 1
64
+ unless task.is_excluded_from_count?
65
+ @total_counted_tasks += 1
66
+ end
67
+ if task.weighted?
68
+ @total_weighted_tasks += 1
69
+ @total_weighted_percent += task.weighted_percent
70
+ end
71
+ @synchronous_count+=1 if task.synchronous?
72
+ unless task.initial_progress_caption.nil? || task.initial_progress_caption.length == 0 || @current_progress[:percent] > 0
73
+ @current_progress[:caption] = task.initial_progress_caption
74
+ end
75
+ push(task)
76
+ end
77
+
78
+ def next_item
79
+ pop
80
+ end
81
+
82
+ def finish_item(item)
83
+ @synchronous_count-=1 if item.synchronous?
84
+ end
85
+
86
+ def synchronous?
87
+ @synchronous_count > 0
88
+ end
89
+
90
+ def set_worker_status(status)
91
+ if status[:meta]
92
+ update_status_meta(status[:meta])
93
+ else
94
+ running_status = get_running_status(status)
95
+ if status[:percent] >= 100
96
+ update_finished_status(status)
97
+ else
98
+ update_running_status(running_status, status)
99
+ end
100
+ end
101
+ end
102
+
103
+ def get_running_status(status)
104
+ rstatus = @running_status[status[:task_id]]
105
+ rstatus = register_running_status(status) if rstatus.nil?
106
+ rstatus
107
+ end
108
+
109
+ def register_running_status(status)
110
+ rstatus = {:task_id=>status[:task_id], :caption=>status[:caption], :percent=>0, :exclude=>status[:exclude], :weight=>status[:weight] }
111
+ @running_status[status[:task_id]] = rstatus
112
+ #if status[:exclude]
113
+ # @current_running_excluded_status = rstatus
114
+ #else
115
+ @running_ordered_status << rstatus
116
+ #end
117
+ rstatus
118
+ end
119
+
120
+ def deregister_running_status(task_id)
121
+ rstatus = @running_status.delete(task_id)
122
+ unless rstatus.nil?
123
+ @running_ordered_status.delete(rstatus)
124
+ end
125
+ rstatus
126
+ end
127
+
128
+ def update_running_status(running_status, status)
129
+ running_status[:percent] = status[:percent]
130
+ running_status[:caption] = status[:caption]
131
+ update_running_percent
132
+ end
133
+
134
+
135
+ def update_status_meta(meta)
136
+ [:notice, :warning, :error].each { |key|
137
+ if meta[key]
138
+ @status_meta[key] = [] if @status_meta[key].nil?
139
+ @status_meta[key] << meta[key]
140
+ end
141
+ }
142
+ if meta[:meta]
143
+ @status_meta[:meta] = {} if @status_meta[:meta].nil?
144
+ @status_meta[:meta] = @status_meta[:meta].update(meta[:meta])
145
+ end
146
+ update_current_progress
147
+ end
148
+
149
+ def update_finished_status(status)
150
+ rstatus = deregister_running_status(status[:task_id])
151
+ unless rstatus.nil?
152
+ @completed_tasks += 1
153
+ @completed_counted_tasks += 1 unless rstatus[:exclude]
154
+ unless rstatus[:weight].nil?
155
+ @completed_weighted_percent += rstatus[:weight]
156
+ @completed_weighted_tasks += 1
157
+ end
158
+ if self.current_running_status.nil? || @current_running_status == rstatus
159
+ #sometimes the status is finished straight away...
160
+ update_current_caption(status)
161
+ end
162
+ update_running_percent()
163
+ end
164
+ end
165
+
166
+ def update_running_percent
167
+ total_percent = 0.0
168
+ total_task_percent = 0.0
169
+ total_percent_counted = 0.0
170
+ total_weighted_percent = 0.0
171
+ for status in @running_ordered_status
172
+ if status[:weight] && status[:weight] > 0
173
+ total_weighted_percent += (status[:percent] * status[:weight] / 100.0)
174
+ else
175
+ total_percent_counted += status[:percent] unless status[:exclude]
176
+ total_percent += status[:percent]
177
+ end
178
+ total_task_percent += status[:percent]
179
+ end
180
+ set_running_percent(total_percent_counted.to_f / 100.0, total_percent.to_f / 100.0, total_task_percent / 100.0, total_weighted_percent.to_f / 100.0)
181
+ self.update_current_progress
182
+ end
183
+
184
+ def set_running_percent(pcent_counted, pcent, running_task_pcent, weighted_percent)
185
+ @running_percent_counted = pcent_counted
186
+ @running_percent = pcent
187
+ @running_percent_weighted = weighted_percent
188
+ idx = running_task_pcent.to_i
189
+ if @running_ordered_status.length <= idx
190
+ @current_running_status = @running_ordered_status.last
191
+ else
192
+ @current_running_status = @running_ordered_status[idx]
193
+ end
194
+ end
195
+
196
+ def get_current_progress_percent
197
+ unweighted_percent = (100.0 - self.total_weighted_percent) / 100.0
198
+
199
+ total_unweighted_tasks = self.total_tasks - self.total_weighted_tasks
200
+ completed_unweighted_tasks = self.completed_tasks - self.completed_weighted_tasks
201
+ total_finished_percent = total_unweighted_tasks == 0 ? 0 : (completed_unweighted_tasks.to_f / total_unweighted_tasks.to_f) * 100.0
202
+ running_fraction = total_unweighted_tasks == 0 ? 1.0 : (1.0 / total_unweighted_tasks.to_f)
203
+
204
+
205
+ total_running_percent = self.running_percent.to_f * running_fraction * 100.0
206
+
207
+ total_unweighted_percent = (total_finished_percent + total_running_percent) * unweighted_percent
208
+
209
+ total_percent = total_unweighted_percent.to_f + (running_percent_weighted * 100.0) + completed_weighted_percent
210
+
211
+ total_percent
212
+ end
213
+
214
+ def get_current_progress_caption
215
+ if self.current_running_status
216
+ update_current_caption(self.current_running_status)
217
+ end
218
+ @current_caption
219
+ end
220
+
221
+ def update_current_caption(status)
222
+ caption = status[:caption]
223
+ caption = "" if caption.nil?
224
+ if total_counted_tasks > 1 && status[:exclude] != true
225
+ caption = "#{caption} (#{self.get_current_counted_tasks}/#{self.total_counted_tasks})"
226
+ end
227
+ @current_caption = caption
228
+ end
229
+
230
+ def get_current_counted_tasks
231
+ cnt = self.completed_counted_tasks + self.running_percent_counted.to_i
232
+ if cnt < self.total_counted_tasks
233
+ cnt += 1
234
+ end
235
+ cnt
236
+ end
237
+
238
+ def update_current_progress
239
+ @current_progress = {
240
+ :percent=>get_current_progress_percent,
241
+ :caption=>get_current_progress_caption
242
+ }.update(@status_meta)
243
+ #puts "set status to #{@current_progress.inspect}"
244
+ end
245
+
246
+ def get_current_progress
247
+ @current_progress
248
+ end
249
+
250
+
251
+ end
252
+ end
@@ -0,0 +1,30 @@
1
+ module BackgroundQueue::ServerLib
2
+ #keep track of jobs, even after they have finished.
3
+ #this allows us to query the status of the job after its finished.
4
+ #It is used to get the status of the job
5
+ class JobRegistry
6
+ def initialize(size = 1000)
7
+ @lru = Cache::LRU.new(:max_elements=>size)
8
+ @mutex = Mutex.new
9
+
10
+ end
11
+
12
+ def register(job)
13
+ @mutex.synchronize {
14
+ @lru[job.id] = job
15
+ }
16
+ end
17
+
18
+ def get_job(id)
19
+ @mutex.synchronize {
20
+ @lru[id]
21
+ }
22
+ end
23
+
24
+ private
25
+
26
+ def lru
27
+ @lru
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,193 @@
1
+ # Copyright (c) 2007 Michael Bryzek
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+ #
21
+ module Cache
22
+
23
+ # Defines a local LRU cache. All operations are maintained in
24
+ # constant size regardless of the size of the Cache. When creating
25
+ # the cache, you specify the max size of the cache.
26
+ #
27
+ # Example:
28
+ # cache = Cache::LRU.new(:max_elements => 5)
29
+ # cache.put(:a, 1)
30
+ # cache[:a] = 2
31
+ # cache.get(:b) { 1 }
32
+ # cache[:b]
33
+ class LRU
34
+
35
+ include Enumerable
36
+
37
+ attr_reader :size
38
+ attr_reader :keys
39
+ # opts:
40
+ # - max_elements - maximum number of elements to keep in the
41
+ # cache at any time. The default is 100 elements
42
+ def initialize(opts = {})
43
+ opts = { :max_elements => 100 }.merge(opts)
44
+ @max_elements = opts.delete(:max_elements)
45
+ raise "Invalid options: #{opts.keys.join(' ')}" if opts.keys.size > 0
46
+ @keys = LinkedList.new
47
+ @map = {}
48
+ @size = 0
49
+ end
50
+
51
+ def clear!
52
+ initialize( :max_elements => @max_elements )
53
+ end
54
+
55
+ # Iterates through all of the key/value pairs added to the cache,
56
+ # in random order. Accepts a block that is yielded to with the key
57
+ # and value for each entry in the cache.
58
+ def each
59
+ @map.each do |k, el|
60
+ yield k, el.value
61
+ end
62
+ end
63
+
64
+ def [](key)
65
+ get(key)
66
+ end
67
+
68
+ # Fetches the value of the element with the given key. If this key
69
+ # does not exist in the cache, you can provide an optional code
70
+ # block that we'll yield to to repopulate the value
71
+ def get(key)
72
+ if el = @map[key]
73
+ @keys.move_to_head(el)
74
+ return el.value
75
+ elsif block_given?
76
+ return put(key, yield)
77
+ end
78
+ return nil
79
+ end
80
+
81
+ def []=(key, value)
82
+ put(key, value)
83
+ end
84
+
85
+ def put(key, value)
86
+ el = @map[key]
87
+ if el
88
+ el.value = value
89
+ @keys.move_to_head(el)
90
+ else
91
+ el = @keys.add(key, value)
92
+ @size += 1
93
+ end
94
+ @map[key] = el
95
+
96
+ if @size > @max_elements
97
+ delete_element(@keys.last)
98
+ @size -= 1
99
+ end
100
+ value
101
+ end
102
+
103
+ def delete(key)
104
+ if el = @map[key]
105
+ delete_element(el)
106
+ @size -= 1
107
+ else
108
+ nil
109
+ end
110
+ end
111
+
112
+ private
113
+ def delete_element(el)
114
+ @keys.remove_element(el)
115
+ @map.delete(el.key)
116
+ el.value
117
+ end
118
+
119
+ end
120
+
121
+ class LinkedList
122
+
123
+ attr_reader :last
124
+ def initialize
125
+ @head = @last = nil
126
+ end
127
+
128
+ def add(key, value)
129
+ add_element(Element.new(key, value, @head))
130
+ end
131
+
132
+ def add_element(el)
133
+ @head.previous_element = el if @head
134
+
135
+ el.next_element = @head
136
+ el.previous_element = nil
137
+ @head = el
138
+ @last = el unless @last
139
+ el
140
+ end
141
+
142
+ def remove_element(el)
143
+ el.previous_element.next_element = el.next_element if el.previous_element
144
+ el.next_element.previous_element = el.previous_element if el.next_element
145
+
146
+ @last = el.previous_element if el == @last
147
+ @head = el.next_element if el == @head
148
+ end
149
+
150
+ def move_to_head(el)
151
+ remove_element(el)
152
+ add_element(el)
153
+ end
154
+
155
+ # Returns a nicely formatted stirng of all elements in the linked
156
+ # list. First element is most recently used, last element is least
157
+ # recently used.
158
+ def pp
159
+ s = ''
160
+ el = @head
161
+ while el
162
+ s << ', ' if s.size > 0
163
+ s << el.to_s
164
+ el = el.next_element
165
+ end
166
+ s
167
+ end
168
+
169
+ class Element
170
+
171
+ attr_accessor :key, :value, :previous_element, :next_element
172
+ def initialize(key, value, next_element)
173
+ @key = key
174
+ @value = value
175
+ @next_element = next_element
176
+ @previous_element = nil
177
+ end
178
+
179
+ def inspect
180
+ to_s
181
+ end
182
+
183
+ def to_s
184
+ p = @previous_element ? @previous_element.key : 'nil'
185
+ n = @next_element ? @next_element.key : 'nil'
186
+ "[#{@key}: #{@value.inspect}, previous: #{p}, next: #{n}]"
187
+ end
188
+
189
+ end
190
+
191
+ end
192
+
193
+ end