sidekiq-superworker 0.0.8 → 0.0.9
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/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
|