queue_dispatcher 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.rdoc +55 -0
- data/Rakefile +2 -0
- data/app/assets/images/icon_acquire_lock.png +0 -0
- data/app/assets/images/icon_error.png +0 -0
- data/app/assets/images/icon_init_queue.gif +0 -0
- data/app/assets/images/icon_pending.gif +0 -0
- data/app/assets/images/icon_running.gif +0 -0
- data/app/assets/images/icon_successful.png +0 -0
- data/app/assets/images/icon_warning.gif +0 -0
- data/app/assets/images/lock.png +0 -0
- data/app/assets/javascript/tasks.js.coffee +8 -0
- data/app/helpers/tasks_helper.rb +28 -0
- data/app/models/task_dependency.rb +2 -0
- data/app/views/queue_dispatcher_views/_search_results_events.html.haml +4 -0
- data/app/views/queue_dispatcher_views/_search_results_my_events.html.haml +4 -0
- data/app/views/queue_dispatcher_views/_task_event.html.haml +24 -0
- data/app/views/queue_dispatcher_views/_task_footer.html.erb +1 -0
- data/app/views/queue_dispatcher_views/_task_header.html.erb +12 -0
- data/app/views/queue_dispatcher_views/_task_my_event.html.haml +24 -0
- data/app/views/queue_dispatcher_views/expand_event.js.rjs +1 -0
- data/app/views/queue_dispatcher_views/my_events.html.haml +7 -0
- data/app/views/queue_dispatcher_views/my_events.js.erb +6 -0
- data/app/views/queue_dispatcher_views/update_events.js.erb +15 -0
- data/lib/generators/queue_dispatcher/migration/migration_generator.rb +51 -0
- data/lib/generators/queue_dispatcher/migration/templates/task_dependencies.rb +16 -0
- data/lib/generators/queue_dispatcher/migration/templates/task_queues.rb +18 -0
- data/lib/generators/queue_dispatcher/migration/templates/tasks.rb +25 -0
- data/lib/queue_dispatcher/acts_as_task.rb +194 -0
- data/lib/queue_dispatcher/acts_as_task_controller.rb +95 -0
- data/lib/queue_dispatcher/acts_as_task_queue.rb +447 -0
- data/lib/queue_dispatcher/deserialization_error.rb +4 -0
- data/lib/queue_dispatcher/message_sending.rb +31 -0
- data/lib/queue_dispatcher/psych_ext.rb +127 -0
- data/lib/queue_dispatcher/qd_logger.rb +15 -0
- data/lib/queue_dispatcher/rc_and_msg.rb +60 -0
- data/lib/queue_dispatcher/serialization/active_record.rb +20 -0
- data/lib/queue_dispatcher/syck_ext.rb +34 -0
- data/lib/queue_dispatcher/version.rb +3 -0
- data/lib/queue_dispatcher/yaml_ext.rb +10 -0
- data/lib/queue_dispatcher.rb +20 -0
- data/lib/tasks/queue_dispatcher.rake +7 -0
- data/queue_dispatcher.gemspec +26 -0
- data/script/queue_worker_dispatcher +259 -0
- metadata +187 -0
@@ -0,0 +1,447 @@
|
|
1
|
+
require 'sys/proctable'
|
2
|
+
require 'queue_dispatcher/rc_and_msg'
|
3
|
+
require 'spawn'
|
4
|
+
|
5
|
+
module QueueDispatcher
|
6
|
+
module ActsAsTaskQueue
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
class Config
|
13
|
+
attr_reader :task_class_name
|
14
|
+
attr_reader :leave_running_tasks_in_queue
|
15
|
+
attr_reader :leave_finished_tasks_in_queue
|
16
|
+
attr_reader :idle_wait_time
|
17
|
+
attr_reader :poll_time
|
18
|
+
|
19
|
+
def initialize(args)
|
20
|
+
@task_class_name = (args[:task_model] || :task).to_s.underscore
|
21
|
+
@leave_finished_tasks_in_queue = args[:leave_finished_tasks_in_queue].nil? ? false : args[:leave_finished_tasks_in_queue]
|
22
|
+
@leave_running_tasks_in_queue = args[:leave_running_tasks_in_queue].nil? ? false : args[:leave_running_tasks_in_queue]
|
23
|
+
@leave_running_tasks_in_queue = true if @leave_finished_tasks_in_queue
|
24
|
+
@idle_wait_time = args[:idle_wait_time] || 0
|
25
|
+
@poll_time = args[:poll_time] || 2.seconds
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
def acts_as_task_queue args = {}
|
32
|
+
include Spawn
|
33
|
+
include ActionView::Helpers::UrlHelper
|
34
|
+
include QdLogger
|
35
|
+
|
36
|
+
include QueueDispatcher::ActsAsTaskQueue::InstanceMethods
|
37
|
+
extend QueueDispatcher::ActsAsTaskQueue::SingletonMethods
|
38
|
+
|
39
|
+
@acts_as_task_queue_config = QueueDispatcher::ActsAsTaskQueue::Config.new(args)
|
40
|
+
|
41
|
+
has_many acts_as_task_queue_config.task_class_name.pluralize, :order => [:priority, :id]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
module SingletonMethods
|
47
|
+
def acts_as_task_queue_config
|
48
|
+
@acts_as_task_queue_config
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# Are there any running task_queues?
|
53
|
+
def any_running?
|
54
|
+
running = false
|
55
|
+
all.each{ |tq| running = true if tq.running? || tq.brand_new? }
|
56
|
+
running
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Get next pending task_queue
|
61
|
+
def get_next_pending
|
62
|
+
task_queue = nil
|
63
|
+
|
64
|
+
transaction do
|
65
|
+
# Find next task_queue which is not running and not in state error
|
66
|
+
order(:id).lock(true).all.each { |tq| task_queue = tq unless task_queue || tq.pid_running? || tq.state == 'error' }
|
67
|
+
|
68
|
+
# Update pid inside the atomic transaction to be sure, the next call of this method will not give the same queue a second time
|
69
|
+
task_queue.update_attribute :pid, $$ if task_queue
|
70
|
+
end
|
71
|
+
|
72
|
+
task_queue
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# Find or create a task_queue by its name which is not in state 'error'. Create one, if there does not exists one
|
77
|
+
def find_or_create_by_name name, options = {}
|
78
|
+
transaction do
|
79
|
+
self.where(:name => name).where('state != "error"').first || self.create(:name => name, :state => 'new', terminate_immediately: options[:terminate_immediately])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
module InstanceMethods
|
86
|
+
def acts_as_task_queue_tasks
|
87
|
+
self.send(acts_as_task_queue_config.task_class_name.pluralize)
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
# Put a new task into the queue
|
92
|
+
def push task
|
93
|
+
acts_as_task_queue_tasks << task
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
# Get the next ready to run task out of the queue. Consider the priority and the dependent tasks, which is defined in the association defined on
|
98
|
+
# top of this model.
|
99
|
+
def pop args = {}
|
100
|
+
task = nil
|
101
|
+
|
102
|
+
transaction do
|
103
|
+
# Find next pending task, where all dependent tasks are executed
|
104
|
+
all_tasks = acts_as_task_queue_tasks.lock(true).all
|
105
|
+
i = 0
|
106
|
+
while task.nil? && i < all_tasks.count do
|
107
|
+
t = all_tasks[i]
|
108
|
+
if t.dependent_tasks_executed?
|
109
|
+
task = t if t.state == 'new'
|
110
|
+
else
|
111
|
+
log :msg => "Task #{t.id}: Waiting for dependent tasks #{t.dependent_tasks.map{|dt| dt.id}.join ','}...",
|
112
|
+
:sev => :debug
|
113
|
+
end
|
114
|
+
i += 1
|
115
|
+
end
|
116
|
+
|
117
|
+
# Remove task from current queue
|
118
|
+
if task
|
119
|
+
if args[:remove_task].nil? || args[:remove_task]
|
120
|
+
task.update_attribute :task_queue_id, nil
|
121
|
+
else
|
122
|
+
task.update_attribute :state, 'new_popped'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
task
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
# Returns the state of this task list (:stopped or :running)
|
132
|
+
def task_states
|
133
|
+
states = determine_state_of_task_array acts_as_task_queue_tasks
|
134
|
+
|
135
|
+
if states[:empty]
|
136
|
+
nil
|
137
|
+
elsif states[:running]
|
138
|
+
:running
|
139
|
+
elsif states[:init_queue]
|
140
|
+
:init_queue
|
141
|
+
elsif states[:pending]
|
142
|
+
:pending
|
143
|
+
elsif states[:acquire_lock]
|
144
|
+
:acquire_lock
|
145
|
+
elsif states[:error]
|
146
|
+
:error
|
147
|
+
elsif states[:new]
|
148
|
+
:new
|
149
|
+
elsif states[:successful]
|
150
|
+
:successful
|
151
|
+
else
|
152
|
+
:unknown
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
# Return true, if the command of the process with pid 'self.pid' is 'ruby'
|
158
|
+
def pid_running?
|
159
|
+
ps = self.pid ? Sys::ProcTable.ps(self.pid) : nil
|
160
|
+
if ps
|
161
|
+
# Asume, that if the command of the 'ps'-output is 'ruby', the process is still running
|
162
|
+
ps.comm == 'ruby'
|
163
|
+
else
|
164
|
+
false
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
# Return true, if the task_queue is still running
|
170
|
+
def running?
|
171
|
+
state == 'running' && pid_running?
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
# Return true, if the task_queue is in state new and is not older 30 seconds
|
176
|
+
def brand_new?
|
177
|
+
state == 'new' && (Time.now - created_at) < 30.seconds
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
# Return true if there are no tasks in this taskqueue
|
182
|
+
def empty?
|
183
|
+
acts_as_task_queue_tasks.empty?
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
# Are there any running or pending tasks in the queue?
|
188
|
+
def pending_tasks?
|
189
|
+
transaction do
|
190
|
+
queue = TaskQueue.where(:id => self.id).lock(true).first
|
191
|
+
states = determine_state_of_task_array queue.acts_as_task_queue_tasks.lock(true)
|
192
|
+
states[:running] || states[:pending] || states[:acquire_lock] || states[:init_queue]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
# Are all tasks executed?
|
198
|
+
def all_done?
|
199
|
+
! pending_tasks? || empty?
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
# Return true, if the task_queue is working or has pending jobs
|
204
|
+
def working?
|
205
|
+
self.task_states == :running && self.running?
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
# Return true, if the task_queue has pending jobs and is running but no job is running
|
210
|
+
def pending?
|
211
|
+
ts = task_states
|
212
|
+
(ts == :new || ts == :pending || ts == :acquire_lock) && self.running?
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
# Kill a task_queue
|
217
|
+
def kill
|
218
|
+
Process.kill('HUP', pid) if pid
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
# Destroy the queue if it has no pending jobs
|
223
|
+
def destroy_if_all_done!
|
224
|
+
transaction do
|
225
|
+
queue = TaskQueue.where(:id => self.id).lock(true).first
|
226
|
+
queue.destroy if queue && queue.all_done?
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
# Remove finished tasks from queue
|
232
|
+
def remove_finished_tasks!
|
233
|
+
trasnaction do
|
234
|
+
tasks.each{ |t| t.update_attribute(:task_queue_id, nil) if t.executed? }
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
# Execute all tasks in the queue
|
240
|
+
def run! args = {}
|
241
|
+
task = nil
|
242
|
+
@logger = args[:logger] || Logger.new("#{File.expand_path(Rails.root)}/log/task_queue.log")
|
243
|
+
finish_state = 'aborted'
|
244
|
+
task_queue = self
|
245
|
+
print_log = args[:print_log]
|
246
|
+
|
247
|
+
task_queue.update_attribute :state, 'running'
|
248
|
+
|
249
|
+
# Set logger in engine
|
250
|
+
@engine.logger = @logger if defined? @engine
|
251
|
+
log :msg => "#{name}: Starting TaskQueue #{task_queue.id}...", :print_log => print_log
|
252
|
+
|
253
|
+
# Init. Pop first task from queue, to show init_queue-state
|
254
|
+
task = task_queue.pop(:remove_task => false)
|
255
|
+
task.update_attribute :state, 'init_queue' if task
|
256
|
+
init
|
257
|
+
|
258
|
+
# Put task, which was used for showing the init_queue-state, back into the task_queue
|
259
|
+
task.update_attributes :state => 'new', :task_queue_id => task_queue.id if task
|
260
|
+
task_queue.reload
|
261
|
+
|
262
|
+
# Ensure, that each task_queue is executed at least once, even if there are no tasks inside at the time it is started (this
|
263
|
+
# can happen, if there are a lot of DB activities...)
|
264
|
+
first_run = true
|
265
|
+
# Loop as long as the task_queue exists with states 'running' and until the task_queue has pending tasks
|
266
|
+
while task_queue && task_queue.state == 'running' && (task_queue.pending_tasks? || first_run) do
|
267
|
+
first_run = false
|
268
|
+
|
269
|
+
# Pop next task from queue
|
270
|
+
task = task_queue.pop(:remove_task => (! acts_as_task_queue_config.leave_running_tasks_in_queue))
|
271
|
+
|
272
|
+
if task
|
273
|
+
if task.new?
|
274
|
+
# Start
|
275
|
+
task.update_attributes :state => 'acquire_lock', :perc_finished => 0
|
276
|
+
get_lock_for task
|
277
|
+
log :msg => "#{name}: Starting task #{task.id} (#{task.target.class.name}.#{task.method_name})...", :print_log => print_log
|
278
|
+
task.update_attributes :state => 'running'
|
279
|
+
|
280
|
+
# Execute the method defined in task.method
|
281
|
+
if task.target.methods.include?(task.method_name) || task.target.methods.include?(task.method_name.to_sym)
|
282
|
+
if task.dependent_tasks_had_errors
|
283
|
+
error_msg = "Dependent tasks had errors!"
|
284
|
+
log :msg => error_msg,
|
285
|
+
:sev => :warn,
|
286
|
+
:print_log => print_log
|
287
|
+
rc_and_msg = QueueDispatcher::RcAndMsg.bad_rc error_msg
|
288
|
+
else
|
289
|
+
target = task.target
|
290
|
+
target.logger = @logger if target.methods.include?(:logger=) || target.methods.include?('logger=')
|
291
|
+
rc_and_msg = task.execute!
|
292
|
+
end
|
293
|
+
else
|
294
|
+
error_msg = "unknown method '#{task.method_name}' for #{task.target.class.name}!"
|
295
|
+
log :msg => error_msg,
|
296
|
+
:sev => :warn,
|
297
|
+
:print_log => print_log
|
298
|
+
rc_and_msg = QueueDispatcher::RcAndMsg.bad_rc error_msg
|
299
|
+
end
|
300
|
+
|
301
|
+
# Change task state according to the return code and remove it from the queue
|
302
|
+
task.update_state rc_and_msg
|
303
|
+
cleanup_locks_after_error_for task
|
304
|
+
task.update_attribute :task_queue_id, nil unless acts_as_task_queue_config.leave_finished_tasks_in_queue
|
305
|
+
log :msg => "#{name}: Task #{task.id} (#{task.target.class.name}.#{task.method_name}) finished with state '#{task.state}'.", :print_log => print_log
|
306
|
+
end
|
307
|
+
else
|
308
|
+
# We couldn't fetch a task out of the queue but there should still exists some. Maybe some are waiting for dependent tasks.
|
309
|
+
# Sleep some time before trying it again.
|
310
|
+
sleep acts_as_task_queue_config.poll_time
|
311
|
+
end
|
312
|
+
|
313
|
+
# Reload task_queue to get all updates
|
314
|
+
task_queue = TaskQueue.find_by_id task_queue.id
|
315
|
+
|
316
|
+
# If all tasks are finished, a config reload will be executed at the end of this method. To avoid too much config reloads,
|
317
|
+
# wait some time before continuing. Maybe, some more tasks will added to the queue?!
|
318
|
+
wait_time = 0
|
319
|
+
unless task_queue.nil? || task_queue.terminate_immediately
|
320
|
+
until task_queue.nil? || task_queue.pending_tasks? || wait_time >= acts_as_task_queue_config.idle_wait_time || task_queue.state != 'running' do
|
321
|
+
sleep acts_as_task_queue_config.poll_time
|
322
|
+
wait_time += acts_as_task_queue_config.poll_time
|
323
|
+
task_queue = TaskQueue.find_by_id task_queue.id
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# Reset logger since this got lost by reloading the task_queue
|
328
|
+
task_queue.logger = @logger if task_queue
|
329
|
+
end
|
330
|
+
|
331
|
+
# Reload config if last task was not a config reload
|
332
|
+
config_reload_required = cleanup_before_auto_reload
|
333
|
+
if config_reload_required
|
334
|
+
task_queue.update_attributes :state => 'reloading_config' if task_queue
|
335
|
+
reload_config task, print_log: print_log
|
336
|
+
end
|
337
|
+
|
338
|
+
# Delete task_queue
|
339
|
+
task_queue.destroy_if_all_done! if task_queue
|
340
|
+
|
341
|
+
# Loop has ended
|
342
|
+
log :msg => "#{name}: TaskQueue has ended!", :print_log => print_log
|
343
|
+
finish_state = 'stopped'
|
344
|
+
rescue => exception
|
345
|
+
# Error handler
|
346
|
+
backtrace = exception.backtrace.join("\n ")
|
347
|
+
log :msg => "Fatal error in method 'run!': #{$!}\n #{backtrace}", :sev => :error, :print_log => print_log
|
348
|
+
puts "Fatal error in method 'run!': #{$!}\n#{backtrace}"
|
349
|
+
task.update_state QueueDispatcher::RcAndMsg.bad_rc("Fatal error: #{$!}") if task
|
350
|
+
cleanup_locks_after_error_for task if task
|
351
|
+
finish_state = 'error'
|
352
|
+
ensure
|
353
|
+
# Reload task and task_queue, to ensure the objects are up to date
|
354
|
+
task_queue = TaskQueue.find_by_id task_queue.id if task_queue
|
355
|
+
task = Task.find_by_id task.id if task
|
356
|
+
|
357
|
+
# Update states of task and task_queue
|
358
|
+
task.update_attributes :state => 'aborted' if task && task.state == 'running'
|
359
|
+
task_queue.update_attributes :state => finish_state, :pid => nil if task_queue
|
360
|
+
|
361
|
+
# Clean up
|
362
|
+
deinit
|
363
|
+
end
|
364
|
+
|
365
|
+
|
366
|
+
#----------------------------------------------------------------------------------------------------------------
|
367
|
+
private
|
368
|
+
#----------------------------------------------------------------------------------------------------------------
|
369
|
+
|
370
|
+
|
371
|
+
def acts_as_task_queue_config
|
372
|
+
self.class.acts_as_task_queue_config
|
373
|
+
end
|
374
|
+
|
375
|
+
|
376
|
+
def determine_state_of_task_array task_array
|
377
|
+
successful = true
|
378
|
+
new = true
|
379
|
+
pending = false
|
380
|
+
error = false
|
381
|
+
running = false
|
382
|
+
acquire_lock = false
|
383
|
+
init_queue = false
|
384
|
+
|
385
|
+
task_array.each do |task|
|
386
|
+
running = true if task.state == 'running'
|
387
|
+
acquire_lock = true if task.state == 'acquire_lock'
|
388
|
+
successful = false unless task.state == 'finished' || task.state == 'successful'
|
389
|
+
new = false unless task.state == 'new' || task.state == 'new_popped' || task.state == 'build'
|
390
|
+
pending = true if (task.state == 'new' || task.state == 'new_popped' || task.state == 'build' || task.state == 'pending')
|
391
|
+
error = true if task.state == 'error'
|
392
|
+
init_queue = true if task.state == 'init_queue'
|
393
|
+
end
|
394
|
+
|
395
|
+
{:running => running,
|
396
|
+
:acquire_lock => acquire_lock,
|
397
|
+
:successful => successful,
|
398
|
+
:new => new,
|
399
|
+
:pending => pending,
|
400
|
+
:error => error,
|
401
|
+
:empty => task_array.empty?,
|
402
|
+
:init_queu => init_queue}
|
403
|
+
end
|
404
|
+
|
405
|
+
|
406
|
+
# Get Lock
|
407
|
+
def get_lock_for task
|
408
|
+
end
|
409
|
+
|
410
|
+
|
411
|
+
# Release Lock
|
412
|
+
def release_lock_for task
|
413
|
+
end
|
414
|
+
|
415
|
+
|
416
|
+
# Clean up locks after an error occured
|
417
|
+
def cleanup_locks_after_error_for task
|
418
|
+
release_lock_for task
|
419
|
+
Lock.release_for_task task
|
420
|
+
end
|
421
|
+
|
422
|
+
|
423
|
+
# Here you can add clean up tasks which will be executed before the auto-reload at the end of the task-queue execution. This is handy
|
424
|
+
# if you want to remove the virtual-flag from objects for example. Return true, when a config reload is needed.
|
425
|
+
def cleanup_before_auto_reload
|
426
|
+
true
|
427
|
+
end
|
428
|
+
|
429
|
+
|
430
|
+
# Initialize
|
431
|
+
def init
|
432
|
+
end
|
433
|
+
|
434
|
+
|
435
|
+
# Deinitialize
|
436
|
+
def deinit
|
437
|
+
end
|
438
|
+
|
439
|
+
|
440
|
+
# Reload config
|
441
|
+
def reload_config(last_task, args = {})
|
442
|
+
#log :msg => "#{name}: Reloading config...", :print_log => args[:print_log]
|
443
|
+
end
|
444
|
+
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'active_support/basic_object'
|
2
|
+
|
3
|
+
module QueueDispatcher
|
4
|
+
class QueueDispatcherProxy < ActiveSupport::BasicObject
|
5
|
+
def initialize(target, options = {})
|
6
|
+
@target = target
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(method, *args)
|
11
|
+
# Find or create the task_queue
|
12
|
+
terminate_immediately = @options.delete(:terminate_immediately)
|
13
|
+
terminate_immediately = terminate_immediately.nil? ? false : terminate_immediately
|
14
|
+
terminate_immediately = @options[:queue].nil? ? true : terminate_immediately
|
15
|
+
task_queue_name = @options.delete(:queue) || "#{@target.to_s}_#{::Time.now.to_f}"
|
16
|
+
task_queue = ::TaskQueue.find_or_create_by_name task_queue_name, terminate_immediately: terminate_immediately
|
17
|
+
|
18
|
+
# Create Task
|
19
|
+
default_values = {priority: 100}
|
20
|
+
mandatory_values = {target: @target, method_name: method, args: args, state: 'new', task_queue: task_queue}
|
21
|
+
::Task.create default_values.merge(@options).merge(mandatory_values)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
module MessageSending
|
27
|
+
def enqueue(options = {})
|
28
|
+
QueueDispatcherProxy.new(self, options)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
if defined?(ActiveRecord)
|
2
|
+
class ActiveRecord::Base
|
3
|
+
# serialize to YAML
|
4
|
+
def encode_with(coder)
|
5
|
+
coder["attributes"] = @attributes
|
6
|
+
coder.tag = ['!ruby/ActiveRecord', self.class.name].join(':')
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Psych
|
12
|
+
module Visitors
|
13
|
+
class YAMLTree
|
14
|
+
def visit_Class(klass)
|
15
|
+
@emitter.scalar klass.name, nil, '!ruby/class', false, false, Nodes::Scalar::SINGLE_QUOTED
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ToRuby
|
20
|
+
def visit_Psych_Nodes_Scalar(o)
|
21
|
+
@st[o.anchor] = o.value if o.anchor
|
22
|
+
|
23
|
+
if klass = Psych.load_tags[o.tag]
|
24
|
+
instance = klass.allocate
|
25
|
+
|
26
|
+
if instance.respond_to?(:init_with)
|
27
|
+
coder = Psych::Coder.new(o.tag)
|
28
|
+
coder.scalar = o.value
|
29
|
+
instance.init_with coder
|
30
|
+
end
|
31
|
+
|
32
|
+
return instance
|
33
|
+
end
|
34
|
+
|
35
|
+
return o.value if o.quoted
|
36
|
+
return @ss.tokenize(o.value) unless o.tag
|
37
|
+
|
38
|
+
case o.tag
|
39
|
+
when '!binary', 'tag:yaml.org,2002:binary'
|
40
|
+
o.value.unpack('m').first
|
41
|
+
when '!str', 'tag:yaml.org,2002:str'
|
42
|
+
o.value
|
43
|
+
when "!ruby/object:DateTime"
|
44
|
+
require 'date'
|
45
|
+
@ss.parse_time(o.value).to_datetime
|
46
|
+
when "!ruby/object:Complex"
|
47
|
+
Complex(o.value)
|
48
|
+
when "!ruby/object:Rational"
|
49
|
+
Rational(o.value)
|
50
|
+
when "!ruby/class", "!ruby/module"
|
51
|
+
resolve_class o.value
|
52
|
+
when "tag:yaml.org,2002:float", "!float"
|
53
|
+
Float(@ss.tokenize(o.value))
|
54
|
+
when "!ruby/regexp"
|
55
|
+
o.value =~ /^\/(.*)\/([mixn]*)$/
|
56
|
+
source = $1
|
57
|
+
options = 0
|
58
|
+
lang = nil
|
59
|
+
($2 || '').split('').each do |option|
|
60
|
+
case option
|
61
|
+
when 'x' then options |= Regexp::EXTENDED
|
62
|
+
when 'i' then options |= Regexp::IGNORECASE
|
63
|
+
when 'm' then options |= Regexp::MULTILINE
|
64
|
+
when 'n' then options |= Regexp::NOENCODING
|
65
|
+
else lang = option
|
66
|
+
end
|
67
|
+
end
|
68
|
+
Regexp.new(*[source, options, lang].compact)
|
69
|
+
when "!ruby/range"
|
70
|
+
args = o.value.split(/([.]{2,3})/, 2).map { |s|
|
71
|
+
accept Nodes::Scalar.new(s)
|
72
|
+
}
|
73
|
+
args.push(args.delete_at(1) == '...')
|
74
|
+
Range.new(*args)
|
75
|
+
when /^!ruby\/sym(bol)?:?(.*)?$/
|
76
|
+
o.value.to_sym
|
77
|
+
else
|
78
|
+
@ss.tokenize o.value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def visit_Psych_Nodes_Mapping_with_class(object)
|
83
|
+
return revive(Psych.load_tags[object.tag], object) if Psych.load_tags[object.tag]
|
84
|
+
|
85
|
+
case object.tag
|
86
|
+
when /^!ruby\/ActiveRecord:(.+)$/
|
87
|
+
klass = resolve_class($1)
|
88
|
+
payload = Hash[*object.children.map { |c| accept c }]
|
89
|
+
id = payload["attributes"][klass.primary_key]
|
90
|
+
begin
|
91
|
+
if ActiveRecord::VERSION::MAJOR == 3
|
92
|
+
klass.unscoped.find(id)
|
93
|
+
else # Rails 2
|
94
|
+
klass.with_exclusive_scope { klass.find(id) }
|
95
|
+
end
|
96
|
+
rescue ActiveRecord::RecordNotFound
|
97
|
+
# raise QueueDispatcher::DeserializationError
|
98
|
+
|
99
|
+
# Return ActiveRecord without reload
|
100
|
+
revive(klass, object)
|
101
|
+
end
|
102
|
+
when /^!ruby\/Mongoid:(.+)$/
|
103
|
+
klass = resolve_class($1)
|
104
|
+
payload = Hash[*object.children.map { |c| accept c }]
|
105
|
+
begin
|
106
|
+
klass.find(payload["attributes"]["_id"])
|
107
|
+
rescue Mongoid::Errors::DocumentNotFound
|
108
|
+
# raise QueueDispatcher::DeserializationError
|
109
|
+
|
110
|
+
# Return ActiveRecord without reload
|
111
|
+
revive(klass, object)
|
112
|
+
end
|
113
|
+
else
|
114
|
+
visit_Psych_Nodes_Mapping_without_class(object)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
alias_method_chain :visit_Psych_Nodes_Mapping, :class
|
118
|
+
|
119
|
+
def resolve_class_with_constantize(klass_name)
|
120
|
+
klass_name.constantize
|
121
|
+
rescue
|
122
|
+
resolve_class_without_constantize(klass_name)
|
123
|
+
end
|
124
|
+
alias_method_chain :resolve_class, :constantize
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module QdLogger
|
2
|
+
attr_accessor :logger
|
3
|
+
|
4
|
+
def initialize_logger(logger = nil)
|
5
|
+
@logger = logger || Logger.new("#{File.expand_path(Rails.root)}/log/queue_dispatcher.log")
|
6
|
+
end
|
7
|
+
|
8
|
+
# Write a standart log message
|
9
|
+
def log(args = {})
|
10
|
+
sev = args[:sev] || :info
|
11
|
+
msg = Time.now.to_s + " #{sev.to_s.upcase} #{$$} (#{self.class.name}): " + args[:msg]
|
12
|
+
logger.send(sev, msg) if logger
|
13
|
+
puts "#{sev.to_s.upcase}: #{args[:msg]}" if logger.nil? || args[:print_log]
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module QueueDispatcher
|
2
|
+
class RcAndMsg
|
3
|
+
attr_accessor :rc, :output, :error_msg
|
4
|
+
|
5
|
+
|
6
|
+
def self.good_rc(output, args = {})
|
7
|
+
rc_and_msg = new
|
8
|
+
rc_and_msg.good_rc(output, args)
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def self.bad_rc(error_msg, args = {})
|
13
|
+
rc_and_msg = new
|
14
|
+
rc_and_msg.bad_rc(error_msg, args)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
# Initializer
|
19
|
+
def initialize(args = {})
|
20
|
+
@rc = args[:rc].to_i if args[:rc]
|
21
|
+
@output = args[:output]
|
22
|
+
@error_msg = args[:error_msg]
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# Fake a good RC
|
28
|
+
def good_rc(output, args = {})
|
29
|
+
@rc = 0
|
30
|
+
@output = output
|
31
|
+
@error_msg = args[:error_msg]
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# Fake a bad RC
|
37
|
+
def bad_rc(error_msg, args = {})
|
38
|
+
@rc = 999
|
39
|
+
@output = args[:output]
|
40
|
+
@error_msg = error_msg
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# Addition
|
46
|
+
def +(other)
|
47
|
+
rc_and_msg = self.clone
|
48
|
+
rc_and_msg.rc += other.rc
|
49
|
+
rc_and_msg.output += "\n#{other.output}" if other.output.present?
|
50
|
+
rc_and_msg.error_msg += "\n#{other.error_msg}" if other.error_msg.present?
|
51
|
+
rc_and_msg
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# Return hash
|
56
|
+
def to_hash
|
57
|
+
{ :rc => @rc, :output => @output, :error_msg => @error_msg }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|