sidekiq-superworker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 SocialPandas
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ Sidekiq Superworker
2
+ ===================
3
+ Chain together Sidekiq workers in parallel and/or serial configurations
4
+
5
+ Overview
6
+ --------
7
+
8
+ Sidekiq Superworker lets you create superworkers, which are simple or complex chains of Sidekiq workers.
9
+
10
+ For example, you can define complex chains of workers, and even use parallel blocks:
11
+
12
+ [![](https://raw.github.com/socialpandas/sidekiq-superworker/master/doc/diagram-complex.png)](https://raw.github.com/socialpandas/sidekiq-superworker/master/doc/diagram-complex.png)
13
+
14
+ *(Worker10 will run after Worker5, Worker7, Worker8, and Worker9 have all completed.)*
15
+
16
+ ```ruby
17
+ Superworker.create(:MySuperworker, :user_id, :comment_id) do
18
+ Worker1 :user_id
19
+ Worker2 :user_id do
20
+ parallel do
21
+ Worker3 :comment_id do
22
+ Worker4 :comment_id
23
+ Worker5 :comment_id
24
+ end
25
+ Worker6 :user_id do
26
+ parallel do
27
+ Worker7 :user_id
28
+ Worker8 :user_id
29
+ Worker9 :user_id
30
+ end
31
+ end
32
+ end
33
+ Worker10 :comment_id
34
+ end
35
+ end
36
+ ```
37
+
38
+ And you can run it like any other worker:
39
+
40
+ ```ruby
41
+ MySuperworker.perform_async(23, 852)
42
+ ```
43
+
44
+ You can also define simple serial chains of workers:
45
+
46
+ [![](https://raw.github.com/socialpandas/sidekiq-superworker/master/doc/diagram-simple.png)](https://raw.github.com/socialpandas/sidekiq-superworker/master/doc/diagram-simple.png)
47
+
48
+ ```ruby
49
+ Superworker.create(:MySuperworker, :user_id, :comment_id) do
50
+ Worker1 :user_id, :comment_id
51
+ Worker2 :comment_id
52
+ Worker3 :user_id
53
+ end
54
+ ```
55
+
56
+ If you're also using [sidekiq_monitor](https://github.com/socialpandas/sidekiq_monitor), you can easily monitor when a superworker is running and when it has finished.
57
+
58
+ Installation
59
+ ------------
60
+
61
+ Include it in your Gemfile:
62
+
63
+ gem 'sidekiq-superworker'
64
+
65
+ Install and run the migration:
66
+
67
+ rails g sidekiq:superworker:install
68
+ rake db:migrate
69
+
70
+ Usage
71
+ -----
72
+
73
+ ### Arguments
74
+
75
+ You can define any number of arguments for the superworker and pass them to different subworkers as you see fit:
76
+
77
+ ```ruby
78
+ Superworker.create(:MySuperworker, :user_id, :comment_id) do
79
+ Worker1 :user_id, :comment_id
80
+ Worker2 :comment_id
81
+ Worker3 :user_id
82
+ end
83
+ ```
84
+
85
+ If you want to set any static arguments for the subworkers, you can do that by using any values that are not symbols (e.g. strings, integers, etc):
86
+
87
+ ```ruby
88
+ Superworker.create(:MySuperworker, :user_id, :comment_id) do
89
+ Worker1 100, :user_id, :comment_id
90
+ Worker2 'all'
91
+ end
92
+ ```
93
+
94
+ If a subworker doesn't take any arguments, you'll need to include parentheses after it:
95
+
96
+ ```ruby
97
+ Superworker.create(:MySuperworker, :user_id, :comment_id) do
98
+ Worker1 :user_id, :comment_id
99
+ Worker2()
100
+ end
101
+ ```
102
+
103
+ License
104
+ -------
105
+
106
+ Sidekiq Superworker is released under the MIT License. Please see the MIT-LICENSE file for details.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ task :default => :test
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.libs << "lib"
9
+ t.libs << "test"
10
+ t.test_files = FileList["test/**/*_test.rb"]
11
+ t.verbose = true
12
+ end
@@ -0,0 +1,31 @@
1
+ module Sidekiq
2
+ module Superworker
3
+ class Subjob < ActiveRecord::Base
4
+ attr_accessible :jid, :subjob_id, :superjob_id, :parent_id, :children_ids, :next_id,
5
+ :subworker_class, :superworker_class, :arg_keys, :arg_values, :status
6
+
7
+ serialize :children_ids
8
+ serialize :arg_keys
9
+ serialize :arg_values
10
+
11
+ validates_presence_of :subjob_id, :subworker_class, :superworker_class, :status
12
+
13
+ def relatives
14
+ self.class.where(superjob_id: superjob_id)
15
+ end
16
+
17
+ def parent
18
+ return nil if parent_id.nil?
19
+ relatives.where(subjob_id: parent_id).first
20
+ end
21
+
22
+ def children
23
+ relatives.where(parent_id: subjob_id).order(:subjob_id)
24
+ end
25
+
26
+ def next
27
+ relatives.where(subjob_id: next_id).first
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/base'
3
+
4
+ module Sidekiq
5
+ module Superworker
6
+ module Generators
7
+ class InstallGenerator < ::Rails::Generators::Base
8
+ include ::Rails::Generators::Migration
9
+ source_root File.expand_path('../templates', __FILE__)
10
+ desc "Install the migrations"
11
+
12
+ def self.next_migration_number(path)
13
+ unless @prev_migration_nr
14
+ @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
15
+ else
16
+ @prev_migration_nr += 1
17
+ end
18
+ @prev_migration_nr.to_s
19
+ end
20
+
21
+ def install_migrations
22
+ migration_template "create_sidekiq_superworker_subjobs.rb", "db/migrate/create_sidekiq_superworker_subjobs.rb"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ class CreateSidekiqSuperworkerSubjobs < ActiveRecord::Migration
2
+ def change
3
+ create_table :sidekiq_superworker_subjobs do |t|
4
+ t.string :jid
5
+ t.string :superjob_id, null: false
6
+ t.integer :subjob_id, null: false
7
+ t.integer :parent_id
8
+ t.text :children_ids
9
+ t.integer :next_id
10
+ t.string :superworker_class, null: false
11
+ t.string :subworker_class, null: false
12
+ t.text :arg_keys
13
+ t.text :arg_values
14
+ t.string :status, null: false
15
+ t.boolean :descendants_are_complete, default: false
16
+
17
+ t.timestamps
18
+ end
19
+
20
+ add_index :sidekiq_superworker_subjobs, :jid
21
+ add_index :sidekiq_superworker_subjobs, :subjob_id
22
+ add_index :sidekiq_superworker_subjobs, [:superjob_id, :subjob_id]
23
+ add_index :sidekiq_superworker_subjobs, [:superjob_id, :parent_id]
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ module Sidekiq
2
+ module Superworker
3
+ class DSLEvaluator
4
+ def method_missing(method, *args, &block)
5
+ Fiber.yield([method, args, block])
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,78 @@
1
+ module Sidekiq
2
+ module Superworker
3
+ class DSLParser
4
+ def self.parse(block)
5
+ @dsl_evaluator = DSLEvaluator.new
6
+ set_records_from_block(block)
7
+ {
8
+ records: @records,
9
+ nested_records: @nested_records
10
+ }
11
+ end
12
+
13
+ def self.set_records_from_block(block)
14
+ @record_id = 0
15
+ @records = {}
16
+ @nested_records = block_to_nested_records(block)
17
+ set_records_from_nested_records(@nested_records)
18
+ end
19
+
20
+ def self.set_records_from_nested_records(nested_records, parent_id=nil)
21
+ last_id = nil
22
+ nested_records.each do |id, value|
23
+ @records[id] = {
24
+ subjob_id: id,
25
+ subworker_class: value[:subworker_class].to_s,
26
+ arg_keys: value[:arg_keys],
27
+ parent_id: parent_id,
28
+ children_ids: value[:children] ? value[:children].keys : nil
29
+ }
30
+ @records[last_id][:next_id] = id if @records[last_id]
31
+ last_id = id
32
+ set_records_from_nested_records(value[:children], id) if value[:children]
33
+ end
34
+ end
35
+
36
+ def self.block_to_nested_records(block)
37
+ fiber = Fiber.new do
38
+ @dsl_evaluator.instance_eval(&block)
39
+ end
40
+
41
+ nested_records = {}
42
+ while (method_result = fiber.resume)
43
+ method, arg_keys, block = method_result
44
+ @record_id += 1
45
+ if block
46
+ nested_records[@record_id] = { subworker_class: method, arg_keys: arg_keys, children: block_to_nested_records(block) }
47
+ else
48
+ nested_records[@record_id] = { subworker_class: method, arg_keys: arg_keys }
49
+ end
50
+
51
+ # For superworkers nested within other superworkers, we'll take the subworkers' nested_records,
52
+ # adjust their ids to fit in with our current @record_id value, and add them into the tree.
53
+ if method != :parallel
54
+ subworker_class = method.to_s.constantize
55
+ if subworker_class.respond_to?(:is_a_superworker?) && subworker_class.is_a_superworker?
56
+ parent_record_id = @record_id
57
+ nested_records[parent_record_id][:children] = rewrite_ids_of_subworker_records(subworker_class.nested_records)
58
+ end
59
+ end
60
+ end
61
+ nested_records
62
+ end
63
+
64
+ def self.rewrite_ids_of_subworker_records(nested_records)
65
+ new_hash = {}
66
+ nested_records.each do |old_record_id, record|
67
+ @record_id += 1
68
+ parent_record_id = @record_id
69
+ new_hash[parent_record_id] = record
70
+ if record[:children]
71
+ new_hash[parent_record_id][:children] = rewrite_ids_of_subworker_records(record[:children])
72
+ end
73
+ end
74
+ new_hash
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,31 @@
1
+ module Sidekiq
2
+ module Superworker
3
+ class Processor
4
+ def complete(item, new_thread=true)
5
+ if new_thread
6
+ # Run this in a new thread so that its execution isn't considered to be part of the
7
+ # completed job's execution.
8
+ Thread.new do
9
+ complete_item(item)
10
+ end
11
+ else
12
+ complete_item(item)
13
+ end
14
+ end
15
+
16
+ protected
17
+
18
+ def complete_item(item)
19
+ raise "Job has nil jid: #{item}" if item['jid'].nil?
20
+ # The job may complete before the Subjob record is created; in case that happens,
21
+ # we need to sleep briefly and requery.
22
+ tries = 3
23
+ while !(subjob = Subjob.find_by_jid(item['jid'])) && tries > 0
24
+ sleep 1
25
+ tries -= 1
26
+ end
27
+ SubjobProcessor.complete(subjob) if subjob
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ module Sidekiq
2
+ module Superworker
3
+ module Server
4
+ class Middleware
5
+ def initialize(options=nil)
6
+ @processor = Sidekiq::Superworker::Processor.new
7
+ end
8
+
9
+ def call(worker, item, queue)
10
+ yield
11
+ @processor.complete(item)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,112 @@
1
+ module Sidekiq
2
+ module Superworker
3
+ class SubjobProcessor
4
+ def self.enqueue(subjob)
5
+ # Only enqueue subjobs that aren't running, complete, etc
6
+ return unless subjob.status == 'initialized'
7
+
8
+ # If this is a parallel subjob, enqueue all of its children
9
+ if subjob.subworker_class == 'parallel'
10
+ subjob.update_attribute(:status, 'running')
11
+ jids = subjob.children.collect do |child|
12
+ enqueue(child)
13
+ end
14
+ jid = jids.first
15
+ else
16
+ klass = "::#{subjob.subworker_class}".constantize
17
+
18
+ # If this is a superworker, mark it as complete, which will queue its children or its next subjob
19
+ if klass.respond_to?(:is_a_superworker?) && klass.is_a_superworker?
20
+ complete(subjob)
21
+ # Otherwise, enqueue it in Sidekiq
22
+ else
23
+ jid = enqueue_in_sidekiq(subjob, klass)
24
+ subjob.update_attributes(
25
+ jid: jid,
26
+ status: 'queued'
27
+ )
28
+ end
29
+ end
30
+ jid
31
+ end
32
+
33
+ def self.enqueue_in_sidekiq(subjob, klass)
34
+ # If sidekiq-unique-jobs is being used for this worker, a number of issues arise if the subjob isn't
35
+ # queued, so we'll bypass the unique functionality of the worker while running the subjob.
36
+ is_unique = klass.respond_to?(:sidekiq_options_hash) && !!klass.sidekiq_options_hash['unique']
37
+ if is_unique
38
+ unique_value = klass.sidekiq_options_hash.delete('unique')
39
+ unique_job_expiration_value = klass.sidekiq_options_hash.delete('unique_job_expiration')
40
+ end
41
+
42
+ arg_values = subjob.arg_values
43
+ jid = klass.perform_async(*arg_values)
44
+ warn "Nil JID returned by #{subjob.subworker_class}.perform_async with arguments #{arg_values}" if jid.nil?
45
+
46
+ if is_unique
47
+ klass.sidekiq_options_hash['unique'] = unique_value
48
+ klass.sidekiq_options_hash['unique_job_expiration'] = unique_job_expiration_value
49
+ end
50
+
51
+ jid
52
+ end
53
+
54
+ def self.complete(subjob)
55
+ subjob.update_attribute(:status, 'complete')
56
+
57
+ # If children are present, enqueue the first one
58
+ children = subjob.children
59
+ if children.present?
60
+ enqueue(children.first)
61
+ return
62
+ # Otherwise, set this as having its descendants complete
63
+ else
64
+ descendants_are_complete(subjob)
65
+ end
66
+ end
67
+
68
+ def self.descendants_are_complete(subjob)
69
+ subjob.update_attribute(:descendants_are_complete, true)
70
+
71
+ parent = subjob.parent
72
+ is_child_of_parallel = parent && parent.subworker_class == 'parallel'
73
+
74
+ # If this is a child of a parallel subjob, check to see if the parent's descendants are all complete
75
+ # and call descendants_are_complete(parent) if so
76
+ if parent
77
+ siblings_descendants_are_complete = parent.children.all? { |child| child.descendants_are_complete }
78
+ if siblings_descendants_are_complete
79
+ descendants_are_complete(parent)
80
+ parent.update_attribute(:status, 'complete') if is_child_of_parallel
81
+ end
82
+ end
83
+
84
+ unless is_child_of_parallel
85
+ # If a next subjob is present, enqueue it
86
+ next_subjob = subjob.next
87
+ if next_subjob
88
+ enqueue(next_subjob)
89
+ return
90
+ end
91
+
92
+ # Otherwise, if a parent exists, the parent's descendants are complete
93
+ if parent
94
+ descendants_are_complete(parent)
95
+ # Otherwise, this is the final subjob of the superjob
96
+ else
97
+ # Set the superjob Sidekiq::Monitor::Job as being complete
98
+ if defined?(Sidekiq::Monitor)
99
+ job = Sidekiq::Monitor::Job.where(queue: :superworker, jid: subjob.superjob_id).first
100
+ if job
101
+ job.update_attributes(
102
+ status: 'complete',
103
+ finished_at: Time.now
104
+ )
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,5 @@
1
+ module Sidekiq
2
+ module Superworker
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,72 @@
1
+ module Sidekiq
2
+ module Superworker
3
+ class Worker
4
+ def self.create(*args, &block)
5
+ class_name = args.shift.to_sym
6
+ dsl = DSLParser.parse(block)
7
+ create_class(class_name, args, dsl)
8
+ end
9
+
10
+ protected
11
+
12
+ def self.create_class(class_name, arg_keys, dsl)
13
+ klass = Class.new do
14
+ @class_name = class_name
15
+ @arg_keys = arg_keys
16
+ @records = dsl[:records]
17
+ @nested_records = dsl[:nested_records]
18
+
19
+ class << self
20
+ attr_reader :nested_records
21
+
22
+ def is_a_superworker?
23
+ true
24
+ end
25
+
26
+ def perform_async(*arg_values)
27
+ @args = Hash[@arg_keys.zip(arg_values)]
28
+ subjobs = create_subjobs
29
+
30
+ # Create a Sidekiq::Monitor::Job for the superjob
31
+ if defined?(Sidekiq::Monitor)
32
+ now = Time.now
33
+ Sidekiq::Monitor::Job.create(
34
+ args: arg_values,
35
+ class_name: @class_name,
36
+ enqueued_at: now,
37
+ jid: @superjob_id,
38
+ queue: :superworker,
39
+ started_at: now,
40
+ status: 'running'
41
+ )
42
+ end
43
+
44
+ # Enqueue the first root-level subjob
45
+ first_subjob = subjobs.select{ |subjob| subjob.parent_id.nil? }.first
46
+ SubjobProcessor.enqueue(first_subjob)
47
+ end
48
+
49
+ protected
50
+
51
+ def create_subjobs
52
+ @superjob_id = SecureRandom.hex(12)
53
+ @records.collect do |id, record|
54
+ record[:status] = 'initialized'
55
+ record[:superjob_id] = @superjob_id
56
+ record[:superworker_class] = @class_name
57
+ record[:arg_values] = record[:arg_keys].collect do |arg_key|
58
+ # Allow for subjob arg_values to be set within the superworker definition; if a symbol is
59
+ # used in the DSL, use @args[arg_key], and otherwise use arg_key as the value
60
+ arg_key.is_a?(Symbol) ? @args[arg_key] : arg_key
61
+ end
62
+ Sidekiq::Superworker::Subjob.create(record)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ Object.const_set(class_name, klass)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,22 @@
1
+ require 'sidekiq'
2
+
3
+ directory = File.dirname(File.absolute_path(__FILE__))
4
+ Dir.glob("#{directory}/superworker/**/*.rb") { |file| require file }
5
+ Dir.glob("#{directory}/../generators/sidekiq/superworker/**/*.rb") { |file| require file }
6
+ Dir.glob("#{directory}/../../app/models/sidekiq/superworker/*.rb") { |file| require file }
7
+
8
+ module Sidekiq
9
+ module Superworker
10
+ def self.table_name_prefix
11
+ 'sidekiq_superworker_'
12
+ end
13
+ end
14
+ end
15
+
16
+ Sidekiq.configure_server do |config|
17
+ config.server_middleware do |chain|
18
+ chain.add Sidekiq::Superworker::Server::Middleware
19
+ end
20
+ end
21
+
22
+ Superworker = Sidekiq::Superworker::Worker unless Object.const_defined?('Superworker')
@@ -0,0 +1 @@
1
+ require 'sidekiq/superworker'
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sidekiq-superworker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tom Benner
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sidekiq
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 2.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 2.1.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rails
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: sqlite3
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec-rails
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: database_cleaner
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Chain together Sidekiq workers in parallel and/or serial configurations
95
+ email:
96
+ - tombenner@gmail.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - app/models/sidekiq/superworker/subjob.rb
102
+ - lib/generators/sidekiq/superworker/install/install_generator.rb
103
+ - lib/generators/sidekiq/superworker/install/templates/create_sidekiq_superworker_subjobs.rb
104
+ - lib/sidekiq/superworker/dsl_evaluator.rb
105
+ - lib/sidekiq/superworker/dsl_parser.rb
106
+ - lib/sidekiq/superworker/processor.rb
107
+ - lib/sidekiq/superworker/server/middleware.rb
108
+ - lib/sidekiq/superworker/subjob_processor.rb
109
+ - lib/sidekiq/superworker/version.rb
110
+ - lib/sidekiq/superworker/worker.rb
111
+ - lib/sidekiq/superworker.rb
112
+ - lib/sidekiq-superworker.rb
113
+ - MIT-LICENSE
114
+ - Rakefile
115
+ - README.md
116
+ homepage: https://github.com/socialpandas/sidekiq-superworker
117
+ licenses: []
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ! '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 1.8.24
137
+ signing_key:
138
+ specification_version: 3
139
+ summary: Chain together Sidekiq workers in parallel and/or serial configurations
140
+ test_files: []