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.
@@ -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