queue_dispatcher 1.1.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/.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
|