sidekiq-superworker 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/app/models/sidekiq/superworker/subjob.rb +4 -2
- data/lib/generators/sidekiq/superworker/install/templates/create_sidekiq_superworker_subjobs.rb +1 -0
- data/lib/sidekiq/superworker/dsl_hash.rb +3 -1
- data/lib/sidekiq/superworker/subjob_processor.rb +113 -106
- data/lib/sidekiq/superworker/superjob_processor.rb +2 -0
- data/lib/sidekiq/superworker/version.rb +1 -1
- data/lib/sidekiq/superworker/worker.rb +1 -48
- data/lib/sidekiq/superworker/worker_class.rb +59 -0
- metadata +3 -2
@@ -2,11 +2,13 @@ module Sidekiq
|
|
2
2
|
module Superworker
|
3
3
|
class Subjob < ActiveRecord::Base
|
4
4
|
attr_accessible :jid, :subjob_id, :superjob_id, :parent_id, :children_ids, :next_id,
|
5
|
-
:subworker_class, :superworker_class, :arg_keys, :arg_values, :status
|
5
|
+
:subworker_class, :superworker_class, :arg_keys, :arg_values, :status, :descendants_are_complete,
|
6
|
+
:meta if ActiveRecord::VERSION::MAJOR < 4
|
6
7
|
|
7
|
-
serialize :children_ids
|
8
8
|
serialize :arg_keys
|
9
9
|
serialize :arg_values
|
10
|
+
serialize :children_ids
|
11
|
+
serialize :meta
|
10
12
|
|
11
13
|
validates_presence_of :subjob_id, :subworker_class, :superworker_class, :status
|
12
14
|
|
@@ -113,6 +113,7 @@ module Sidekiq
|
|
113
113
|
@records[batch_child_id] = batch_child
|
114
114
|
|
115
115
|
@record_id += 1
|
116
|
+
last_subjob_id = nil
|
116
117
|
subjobs.values.each_with_index do |subjob, index|
|
117
118
|
subjob_id = @record_id
|
118
119
|
@record_id += 1
|
@@ -122,8 +123,9 @@ module Sidekiq
|
|
122
123
|
subjob[:parent_id] = batch_child_id
|
123
124
|
subjob[:arg_values] = iteration_args.values
|
124
125
|
@records[subjob_id] = subjob
|
126
|
+
@records[last_subjob_id][:next_id] = subjob_id if last_subjob_id
|
127
|
+
last_subjob_id = subjob_id
|
125
128
|
nested_hash_to_records_recursive(children, parent_id: subjob_id, scoped_args: iteration_args)
|
126
|
-
subjob[:next_id] = subjob_id + 1 if index < (subjobs.values.length - 1)
|
127
129
|
end
|
128
130
|
|
129
131
|
children_ids << batch_child_id
|
@@ -1,133 +1,140 @@
|
|
1
1
|
module Sidekiq
|
2
2
|
module Superworker
|
3
3
|
class SubjobProcessor
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
subjob.
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
subjob.
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
klass = "::#{subjob.subworker_class}".constantize
|
30
|
-
|
31
|
-
# If this is a superworker, mark it as complete, which will queue its children or its next subjob
|
32
|
-
if klass.respond_to?(:is_a_superworker?) && klass.is_a_superworker?
|
33
|
-
complete(subjob)
|
34
|
-
# Otherwise, enqueue it in Sidekiq
|
4
|
+
class << self
|
5
|
+
def enqueue(subjob)
|
6
|
+
Superworker.debug "#{subjob.to_info}: Trying to enqueue"
|
7
|
+
# Only enqueue subjobs that aren't running, complete, etc
|
8
|
+
return unless subjob.status == 'initialized'
|
9
|
+
|
10
|
+
Superworker.debug "#{subjob.to_info}: Enqueueing"
|
11
|
+
# If this is a parallel subjob, enqueue all of its children
|
12
|
+
if subjob.subworker_class == 'parallel'
|
13
|
+
subjob.update_attribute(:status, 'running')
|
14
|
+
|
15
|
+
Superworker.debug "#{subjob.to_info}: Enqueueing parallel children"
|
16
|
+
jids = subjob.children.collect do |child|
|
17
|
+
enqueue(child)
|
18
|
+
end
|
19
|
+
jid = jids.first
|
20
|
+
elsif subjob.subworker_class == 'batch'
|
21
|
+
subjob.update_attribute(:status, 'running')
|
22
|
+
|
23
|
+
Superworker.debug "#{subjob.to_info}: Enqueueing batch children"
|
24
|
+
jids = subjob.children.collect do |child|
|
25
|
+
child.update_attribute(:status, 'running')
|
26
|
+
enqueue(child.children.first)
|
27
|
+
end
|
28
|
+
jid = jids.first
|
35
29
|
else
|
36
|
-
|
37
|
-
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
30
|
+
klass = "::#{subjob.subworker_class}".constantize
|
31
|
+
|
32
|
+
# If this is a superworker, mark it as complete, which will queue its children or its next subjob
|
33
|
+
if klass.respond_to?(:is_a_superworker?) && klass.is_a_superworker?
|
34
|
+
complete(subjob)
|
35
|
+
# Otherwise, enqueue it in Sidekiq
|
36
|
+
else
|
37
|
+
# We need to explicitly set the job's JID, so that the ActiveRecord record can be updated before
|
38
|
+
# the job fires off. If the job started first, it could finish before the ActiveRecord update
|
39
|
+
# transaction completes, causing a race condition when finding the ActiveRecord record in
|
40
|
+
# Processor#complete.
|
41
|
+
jid = SecureRandom.hex(12)
|
42
|
+
subjob.update_attributes(
|
43
|
+
jid: jid,
|
44
|
+
status: 'queued'
|
45
|
+
)
|
46
|
+
enqueue_in_sidekiq(subjob, klass, jid)
|
47
|
+
end
|
46
48
|
end
|
49
|
+
jid
|
47
50
|
end
|
48
|
-
jid
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.enqueue_in_sidekiq(subjob, klass, jid)
|
52
|
-
Superworker.debug "#{subjob.to_info}: Enqueueing in Sidekiq"
|
53
51
|
|
54
|
-
|
55
|
-
|
56
|
-
is_unique = klass.respond_to?(:sidekiq_options_hash) && !!klass.sidekiq_options_hash['unique']
|
57
|
-
if is_unique
|
58
|
-
unique_value = klass.sidekiq_options_hash.delete('unique')
|
59
|
-
unique_job_expiration_value = klass.sidekiq_options_hash.delete('unique_job_expiration')
|
60
|
-
end
|
52
|
+
def enqueue_in_sidekiq(subjob, klass, jid)
|
53
|
+
Superworker.debug "#{subjob.to_info}: Enqueueing in Sidekiq"
|
61
54
|
|
62
|
-
|
63
|
-
|
64
|
-
|
55
|
+
# If sidekiq-unique-jobs is being used for this worker, a number of issues arise if the subjob isn't
|
56
|
+
# queued, so we'll bypass the unique functionality of the worker while running the subjob.
|
57
|
+
is_unique = klass.respond_to?(:sidekiq_options_hash) && !!(klass.sidekiq_options_hash || {})['unique']
|
58
|
+
if is_unique
|
59
|
+
unique_value = klass.sidekiq_options_hash.delete('unique')
|
60
|
+
unique_job_expiration_value = klass.sidekiq_options_hash.delete('unique_job_expiration')
|
61
|
+
end
|
65
62
|
|
66
|
-
|
67
|
-
klass.sidekiq_options_hash['unique'] = unique_value
|
68
|
-
klass.sidekiq_options_hash['unique_job_expiration'] = unique_job_expiration_value
|
69
|
-
end
|
63
|
+
sidekiq_push(subjob, klass, jid)
|
70
64
|
|
71
|
-
|
72
|
-
|
65
|
+
if is_unique
|
66
|
+
klass.sidekiq_options_hash['unique'] = unique_value
|
67
|
+
klass.sidekiq_options_hash['unique_job_expiration'] = unique_job_expiration_value
|
68
|
+
end
|
73
69
|
|
74
|
-
|
75
|
-
Superworker.debug "#{subjob.to_info}: Complete"
|
76
|
-
subjob.update_attribute(:status, 'complete')
|
77
|
-
|
78
|
-
# If children are present, enqueue the first one
|
79
|
-
children = subjob.children
|
80
|
-
if children.present?
|
81
|
-
Superworker.debug "#{subjob.to_info}: Enqueueing children"
|
82
|
-
enqueue(children.first)
|
83
|
-
return
|
84
|
-
# Otherwise, set this as having its descendants complete
|
85
|
-
else
|
86
|
-
descendants_are_complete(subjob)
|
70
|
+
jid
|
87
71
|
end
|
88
|
-
end
|
89
72
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
SuperjobProcessor.error(subjob.superjob_id, worker, item, exception)
|
94
|
-
end
|
73
|
+
def complete(subjob)
|
74
|
+
Superworker.debug "#{subjob.to_info}: Complete"
|
75
|
+
subjob.update_attribute(:status, 'complete')
|
95
76
|
|
96
|
-
|
97
|
-
|
98
|
-
|
77
|
+
# If children are present, enqueue the first one
|
78
|
+
children = subjob.children
|
79
|
+
if children.present?
|
80
|
+
Superworker.debug "#{subjob.to_info}: Enqueueing children"
|
81
|
+
enqueue(children.first)
|
82
|
+
return
|
83
|
+
# Otherwise, set this as having its descendants complete
|
84
|
+
else
|
85
|
+
descendants_are_complete(subjob)
|
86
|
+
end
|
87
|
+
end
|
99
88
|
|
100
|
-
|
101
|
-
|
89
|
+
def error(subjob, worker, item, exception)
|
90
|
+
Superworker.debug "#{subjob.to_info}: Error"
|
91
|
+
subjob.update_attribute(:status, 'failed')
|
92
|
+
SuperjobProcessor.error(subjob.superjob_id, worker, item, exception)
|
102
93
|
end
|
103
94
|
|
104
|
-
|
105
|
-
|
95
|
+
protected
|
96
|
+
|
97
|
+
def descendants_are_complete(subjob)
|
98
|
+
Superworker.debug "#{subjob.to_info}: Descendants are complete"
|
99
|
+
subjob.update_attribute(:descendants_are_complete, true)
|
106
100
|
|
107
|
-
|
108
|
-
|
109
|
-
siblings_descendants_are_complete = parent.children.all? { |child| child.descendants_are_complete }
|
110
|
-
if siblings_descendants_are_complete
|
111
|
-
Superworker.debug "#{subjob.to_info}: Parent (#{parent.to_info}) is complete"
|
112
|
-
descendants_are_complete(parent)
|
113
|
-
parent.update_attribute(:status, 'complete') if is_child_of_parallel
|
101
|
+
if subjob.subworker_class == 'batch_child' || subjob.subworker_class == 'batch'
|
102
|
+
complete(subjob)
|
114
103
|
end
|
115
|
-
end
|
116
104
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
105
|
+
parent = subjob.parent
|
106
|
+
is_child_of_parallel = parent && parent.subworker_class == 'parallel'
|
107
|
+
|
108
|
+
# If a parent exists, check whether this subjob's siblings are all complete
|
109
|
+
if parent
|
110
|
+
siblings_descendants_are_complete = parent.children.all? { |child| child.descendants_are_complete }
|
111
|
+
if siblings_descendants_are_complete
|
112
|
+
Superworker.debug "#{subjob.to_info}: Parent (#{parent.to_info}) is complete"
|
113
|
+
descendants_are_complete(parent)
|
114
|
+
parent.update_attribute(:status, 'complete') if is_child_of_parallel
|
115
|
+
end
|
123
116
|
end
|
124
117
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
118
|
+
unless is_child_of_parallel
|
119
|
+
# If a next subjob is present, enqueue it
|
120
|
+
next_subjob = subjob.next
|
121
|
+
if next_subjob
|
122
|
+
enqueue(next_subjob)
|
123
|
+
return
|
124
|
+
end
|
125
|
+
|
126
|
+
# If there isn't a parent, then, this is the final subjob of the superjob
|
127
|
+
unless parent
|
128
|
+
Superworker.debug "#{subjob.to_info}: Superjob is complete"
|
129
|
+
SuperjobProcessor.complete(subjob.superjob_id)
|
130
|
+
end
|
129
131
|
end
|
130
132
|
end
|
133
|
+
|
134
|
+
def sidekiq_push(subjob, klass, jid)
|
135
|
+
# This is akin to perform_async, but it allows us to explicitly set the JID
|
136
|
+
Sidekiq::Client.push('class' => klass, 'args' => subjob.arg_values, 'jid' => jid)
|
137
|
+
end
|
131
138
|
end
|
132
139
|
end
|
133
140
|
end
|
@@ -8,6 +8,8 @@ module Sidekiq
|
|
8
8
|
def self.create(superjob_id, superworker_class_name, args, subjobs, options={})
|
9
9
|
Superworker.debug "Superworker ##{superjob_id}: Create"
|
10
10
|
|
11
|
+
options ||= {}
|
12
|
+
|
11
13
|
# If sidekiq_monitor is being used, create a Sidekiq::Monitor::Job for the superjob
|
12
14
|
if defined?(Sidekiq::Monitor)
|
13
15
|
now = Time.now
|
@@ -10,58 +10,11 @@ module Sidekiq
|
|
10
10
|
protected
|
11
11
|
|
12
12
|
def self.create_class(class_name, arg_keys, nested_hash)
|
13
|
-
klass = Class.new do
|
13
|
+
klass = Class.new(Sidekiq::Superworker::WorkerClass) do
|
14
14
|
@class_name = class_name
|
15
15
|
@arg_keys = arg_keys
|
16
16
|
@nested_hash = nested_hash
|
17
17
|
@dsl_hash = DSLHash.new
|
18
|
-
|
19
|
-
class << self
|
20
|
-
attr_reader :nested_hash
|
21
|
-
|
22
|
-
def is_a_superworker?
|
23
|
-
true
|
24
|
-
end
|
25
|
-
|
26
|
-
def perform_async(*arg_values)
|
27
|
-
superjob_options = {}
|
28
|
-
|
29
|
-
# If an additional argument value is given, it's the superjob's options
|
30
|
-
if (arg_values.length == @arg_keys.length + 1) && arg_values.last.is_a?(Hash)
|
31
|
-
superjob_options = arg_values.last
|
32
|
-
elsif @arg_keys.length != arg_values.length
|
33
|
-
raise "Wrong number of arguments for #{name}.perform_async (#{arg_values.length} for #{@arg_keys.length})"
|
34
|
-
end
|
35
|
-
|
36
|
-
@args = Hash[@arg_keys.zip(arg_values)]
|
37
|
-
@superjob_id = SecureRandom.hex(12)
|
38
|
-
subjobs = create_subjobs(arg_values)
|
39
|
-
SuperjobProcessor.create(@superjob_id, @class_name, arg_values, subjobs, superjob_options)
|
40
|
-
end
|
41
|
-
|
42
|
-
protected
|
43
|
-
|
44
|
-
def create_subjobs(arg_values)
|
45
|
-
records = @dsl_hash.nested_hash_to_records(@nested_hash, @args)
|
46
|
-
subjobs = records.collect do |id, record|
|
47
|
-
record[:status] = 'initialized'
|
48
|
-
record[:superjob_id] = @superjob_id
|
49
|
-
record[:superworker_class] = @class_name
|
50
|
-
unless record.key?(:arg_values)
|
51
|
-
record[:arg_values] = record[:arg_keys].collect do |arg_key|
|
52
|
-
# Allow for subjob arg_values to be set within the superworker definition; if a symbol is
|
53
|
-
# used in the DSL, use @args[arg_key], and otherwise use arg_key as the value
|
54
|
-
arg_key.is_a?(Symbol) ? @args[arg_key] : arg_key
|
55
|
-
end
|
56
|
-
end
|
57
|
-
record
|
58
|
-
end
|
59
|
-
# Perform the inserts in a single transaction to improve the performance of large mass inserts
|
60
|
-
Sidekiq::Superworker::Subjob.transaction do
|
61
|
-
Sidekiq::Superworker::Subjob.create(subjobs)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
18
|
end
|
66
19
|
|
67
20
|
Object.const_set(class_name, klass)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Superworker
|
3
|
+
class WorkerClass
|
4
|
+
class << self
|
5
|
+
attr_reader :nested_hash
|
6
|
+
|
7
|
+
def is_a_superworker?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform_async(*arg_values)
|
12
|
+
options = initialize_superjob(arg_values)
|
13
|
+
subjobs = create_subjobs(arg_values)
|
14
|
+
SuperjobProcessor.create(@superjob_id, @class_name, arg_values, subjobs, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def initialize_superjob(arg_values)
|
20
|
+
options = nil
|
21
|
+
|
22
|
+
# If an additional argument value is given, it's the superjob's options
|
23
|
+
if (arg_values.length == @arg_keys.length + 1) && arg_values.last.is_a?(Hash)
|
24
|
+
options = arg_values.last
|
25
|
+
elsif @arg_keys.length != arg_values.length
|
26
|
+
raise "Wrong number of arguments for #{name}.perform_async (#{arg_values.length} for #{@arg_keys.length})"
|
27
|
+
end
|
28
|
+
|
29
|
+
@args = Hash[@arg_keys.zip(arg_values)]
|
30
|
+
@superjob_id = SecureRandom.hex(12)
|
31
|
+
|
32
|
+
options
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_subjobs(arg_values, options={})
|
36
|
+
records = @dsl_hash.nested_hash_to_records(@nested_hash, @args)
|
37
|
+
subjobs = records.collect do |id, record|
|
38
|
+
record[:status] = 'initialized'
|
39
|
+
record[:superjob_id] = @superjob_id
|
40
|
+
record[:superworker_class] = @class_name
|
41
|
+
record[:meta] = options[:meta] if options[:meta]
|
42
|
+
unless record.key?(:arg_values)
|
43
|
+
record[:arg_values] = record[:arg_keys].collect do |arg_key|
|
44
|
+
# Allow for subjob arg_values to be set within the superworker definition; if a symbol is
|
45
|
+
# used in the DSL, use @args[arg_key], and otherwise use arg_key as the value
|
46
|
+
arg_key.is_a?(Symbol) ? @args[arg_key] : arg_key
|
47
|
+
end
|
48
|
+
end
|
49
|
+
record
|
50
|
+
end
|
51
|
+
# Perform the inserts in a single transaction to improve the performance of large mass inserts
|
52
|
+
Sidekiq::Superworker::Subjob.transaction do
|
53
|
+
Sidekiq::Superworker::Subjob.create(subjobs)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-superworker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sidekiq
|
@@ -113,6 +113,7 @@ files:
|
|
113
113
|
- lib/sidekiq/superworker/superjob_processor.rb
|
114
114
|
- lib/sidekiq/superworker/version.rb
|
115
115
|
- lib/sidekiq/superworker/worker.rb
|
116
|
+
- lib/sidekiq/superworker/worker_class.rb
|
116
117
|
- lib/sidekiq/superworker.rb
|
117
118
|
- lib/sidekiq-superworker.rb
|
118
119
|
- MIT-LICENSE
|