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.
@@ -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
 
@@ -13,6 +13,7 @@ class CreateSidekiqSuperworkerSubjobs < ActiveRecord::Migration
13
13
  t.text :arg_values
14
14
  t.string :status, null: false
15
15
  t.boolean :descendants_are_complete, default: false
16
+ t.text :meta
16
17
 
17
18
  t.timestamps
18
19
  end
@@ -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
- def self.enqueue(subjob)
5
- Superworker.debug "#{subjob.to_info}: Trying to enqueue"
6
- # Only enqueue subjobs that aren't running, complete, etc
7
- return unless subjob.status == 'initialized'
8
-
9
- Superworker.debug "#{subjob.to_info}: Enqueueing"
10
- # If this is a parallel subjob, enqueue all of its children
11
- if subjob.subworker_class == 'parallel'
12
- subjob.update_attribute(:status, 'running')
13
-
14
- Superworker.debug "#{subjob.to_info}: Enqueueing parallel children"
15
- jids = subjob.children.collect do |child|
16
- enqueue(child)
17
- end
18
- jid = jids.first
19
- elsif subjob.subworker_class == 'batch'
20
- subjob.update_attribute(:status, 'running')
21
-
22
- Superworker.debug "#{subjob.to_info}: Enqueueing batch children"
23
- jids = subjob.children.collect do |child|
24
- child.update_attribute(:status, 'running')
25
- enqueue(child.children.first)
26
- end
27
- jid = jids.first
28
- else
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
- # We need to explicitly set the job's JID, so that the ActiveRecord record can be updated before
37
- # the job fires off. If the job started first, it could finish before the ActiveRecord update
38
- # transaction completes, causing a race condition when finding the ActiveRecord record in
39
- # Processor#complete.
40
- jid = SecureRandom.hex(12)
41
- subjob.update_attributes(
42
- jid: jid,
43
- status: 'queued'
44
- )
45
- enqueue_in_sidekiq(subjob, klass, jid)
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
- # If sidekiq-unique-jobs is being used for this worker, a number of issues arise if the subjob isn't
55
- # queued, so we'll bypass the unique functionality of the worker while running the subjob.
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
- arg_values = subjob.arg_values
63
- # This is akin to perform_async, but it allows us to explicitly set the JID
64
- Sidekiq::Client.push('class' => klass, 'args' => arg_values, 'jid' => jid)
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
- if is_unique
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
- jid
72
- end
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
- def self.complete(subjob)
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
- def self.error(subjob, worker, item, exception)
91
- Superworker.debug "#{subjob.to_info}: Error"
92
- subjob.update_attribute(:status, 'failed')
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
- def self.descendants_are_complete(subjob)
97
- Superworker.debug "#{subjob.to_info}: Descendants are complete"
98
- subjob.update_attribute(:descendants_are_complete, true)
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
- if subjob.subworker_class == 'batch_child' || subjob.subworker_class == 'batch'
101
- complete(subjob)
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
- parent = subjob.parent
105
- is_child_of_parallel = parent && parent.subworker_class == 'parallel'
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
- # If a parent exists, check whether this subjob's siblings are all complete
108
- if parent
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
- unless is_child_of_parallel
118
- # If a next subjob is present, enqueue it
119
- next_subjob = subjob.next
120
- if next_subjob
121
- enqueue(next_subjob)
122
- return
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
- # If there isn't a parent, then, this is the final subjob of the superjob
126
- unless parent
127
- Superworker.debug "#{subjob.to_info}: Superjob is complete"
128
- SuperjobProcessor.complete(subjob.superjob_id)
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
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  module Superworker
3
- VERSION = '0.0.8'
3
+ VERSION = '0.0.9'
4
4
  end
5
5
  end
@@ -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.8
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-20 00:00:00.000000000 Z
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