From 7a1be45e577127d554a86951016da957b5a98424 Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Wed, 23 Sep 2020 17:23:39 -0400 Subject: [PATCH 01/31] Lowercase 'Kiq' --- lib/simplekiq/batching_job.rb | 98 +++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 lib/simplekiq/batching_job.rb diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb new file mode 100644 index 0000000..fd5ec9c --- /dev/null +++ b/lib/simplekiq/batching_job.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +# This module enables you to break up your work into batches and run those +# batches as background jobs while keeping all your code in the same file. +# Including this module *implements perform* you should not override it. +# It expects you to implement two methods: #perform_batching and #perform_batch. +# Optionally you may also implement #on_success or #on_death +# +# #perform_batching should contain your code for breaking up your work into smaller jobs. +# It handles all the Sidekiq::Batch boilerplate for you. Where you would normally call ExampleBatchJob.perform_async +# you should use #queue_batch. +# +# #perform_batch should contain the code that would be in your batch job. Under the hood #queue_batch +# queues a job which will run #perform_batch. +# +# See Sidekiq::Batch documentation for the signatures and purpose of #on_success and #on_death +# +# class ExampleJob +# include SimpleKiq::BatchingJob +# +# def perform_batching(some_id) +# Record.find(some_id).other_records.in_batches do |other_records| +# queue_batch(other_records.ids) +# end +# end +# +# def perform_batch(other_record_ids) +# OtherRecord.where(id: other_record_ids).do_work +# end +# +# def on_success(_status, options) +# same_id_as_before = options["args"].first +# Record.find(same_id_as_before).success! +# end +# end +# +# ExampleJob.perform_async(some_id) +# +# Come home to the impossible flavor of batch creation + +module Simplekiq + module BatchingJob + BATCH_CLASS_NAME = "SimplekiqBatch" + + class << self + def included(klass) + batch_job_class = Class.new(BaseBatch) + klass.const_set(BATCH_CLASS_NAME, batch_job_class) + end + end + + def perform(*args) + perform_batching(*args) + handle_batches(args) + end + + protected + + attr_accessor :batches + + def handle_batches(args) + if batches.present? + flush_batches(args) + elsif respond_to?(:on_success) + on_success(nil, { "args" => args }) + end + end + + def flush_batches(args) + batching = Sidekiq::Batch.new + batching.description = "SimpleKiq Batch Jobs for #{self.class.name}, args: #{args}" + + batching.on(:death, self.class, "args" => args) if respond_to?(:on_death) + batching.on(:success, self.class, "args" => args) if respond_to?(:on_success) + + batch_job_class = self.class.const_get(BATCH_CLASS_NAME) + + batching.jobs do + batches.each do |job_args| + batch_job_class.perform_async(*job_args) + end + end + end + + def queue_batch(*args) + self.batches ||= [] + self.batches << args + end + end + + class BaseBatch + include Sidekiq::Worker + + def perform(*args) + self.class.parent.new.perform_batch(*args) + end + end +end From edc726abb43c83bca46b73c5d1c7d428b0254b63 Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Wed, 23 Sep 2020 18:48:49 -0400 Subject: [PATCH 02/31] Copy fix --- lib/simplekiq/batching_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb index fd5ec9c..3d57021 100644 --- a/lib/simplekiq/batching_job.rb +++ b/lib/simplekiq/batching_job.rb @@ -68,7 +68,7 @@ def handle_batches(args) def flush_batches(args) batching = Sidekiq::Batch.new - batching.description = "SimpleKiq Batch Jobs for #{self.class.name}, args: #{args}" + batching.description = "Simplekiq Batch Jobs for #{self.class.name}, args: #{args}" batching.on(:death, self.class, "args" => args) if respond_to?(:on_death) batching.on(:success, self.class, "args" => args) if respond_to?(:on_success) From 9c462b7858917d1b80b6a7c3ee45111fdcb1368d Mon Sep 17 00:00:00 2001 From: John Wilkinson Date: Mon, 28 Sep 2020 16:02:17 -0700 Subject: [PATCH 03/31] Update to match the new Simplekiq namespace --- lib/simplekiq/batching_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb index 3d57021..2d38343 100644 --- a/lib/simplekiq/batching_job.rb +++ b/lib/simplekiq/batching_job.rb @@ -16,7 +16,7 @@ # See Sidekiq::Batch documentation for the signatures and purpose of #on_success and #on_death # # class ExampleJob -# include SimpleKiq::BatchingJob +# include Simplekiq::BatchingJob # # def perform_batching(some_id) # Record.find(some_id).other_records.in_batches do |other_records| From 55ef1c8a308f2634d60544a2f79bba577a270ac5 Mon Sep 17 00:00:00 2001 From: Austen Madden Date: Fri, 2 Oct 2020 15:27:46 -0400 Subject: [PATCH 04/31] Allow batch object customization Previously, Simplekiq did not provide a way to interact with the underlying batch object. --- lib/simplekiq/batching_job.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb index 2d38343..ae087bd 100644 --- a/lib/simplekiq/batching_job.rb +++ b/lib/simplekiq/batching_job.rb @@ -67,15 +67,13 @@ def handle_batches(args) end def flush_batches(args) - batching = Sidekiq::Batch.new - batching.description = "Simplekiq Batch Jobs for #{self.class.name}, args: #{args}" - - batching.on(:death, self.class, "args" => args) if respond_to?(:on_death) - batching.on(:success, self.class, "args" => args) if respond_to?(:on_success) - batch_job_class = self.class.const_get(BATCH_CLASS_NAME) + sidekiq_batch.description ||= "Simplekiq Batch Jobs for #{self.class.name}, args: #{args}" + + sidekiq_batch.on(:death, self.class, "args" => args) if respond_to?(:on_death) + sidekiq_batch.on(:success, self.class, "args" => args) if respond_to?(:on_success) - batching.jobs do + sidekiq_batch.jobs do batches.each do |job_args| batch_job_class.perform_async(*job_args) end @@ -86,6 +84,10 @@ def queue_batch(*args) self.batches ||= [] self.batches << args end + + def sidekiq_batch + @sidekiq_batch ||= Sidekiq::Batch.new + end end class BaseBatch From 55c631f9a2c318fc9fb32c634c92154532c16495 Mon Sep 17 00:00:00 2001 From: Austen Madden Date: Mon, 5 Oct 2020 10:24:02 -0400 Subject: [PATCH 05/31] Add documentation --- lib/simplekiq/batching_job.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb index ae087bd..8b58f74 100644 --- a/lib/simplekiq/batching_job.rb +++ b/lib/simplekiq/batching_job.rb @@ -8,7 +8,8 @@ # # #perform_batching should contain your code for breaking up your work into smaller jobs. # It handles all the Sidekiq::Batch boilerplate for you. Where you would normally call ExampleBatchJob.perform_async -# you should use #queue_batch. +# you should use #queue_batch. If you'd like to custommize the sidekiq batch +# object, you can access it in perform_batching through the `sidekiq_batch` method. # # #perform_batch should contain the code that would be in your batch job. Under the hood #queue_batch # queues a job which will run #perform_batch. @@ -19,6 +20,8 @@ # include Simplekiq::BatchingJob # # def perform_batching(some_id) +# sidekiq_batch.description = "My custom batch description" # optional +# # Record.find(some_id).other_records.in_batches do |other_records| # queue_batch(other_records.ids) # end From 24dfc91cbfb5ea49e52c3042564cd40d3263da26 Mon Sep 17 00:00:00 2001 From: Austen Madden Date: Mon, 5 Oct 2020 16:37:34 -0400 Subject: [PATCH 06/31] Eliminate direct use of sidekiq batch object There was concern about exposing the sidekiq batch directly and making a habit of it, so we move it to a private object. Still accessible if you need it through `send` in say a REPL situation, but otherwise not usable. --- lib/simplekiq/batching_job.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb index 8b58f74..0e08d1f 100644 --- a/lib/simplekiq/batching_job.rb +++ b/lib/simplekiq/batching_job.rb @@ -88,6 +88,12 @@ def queue_batch(*args) self.batches << args end + def batch_description=(description) + sidekiq_batch.description = description + end + + private + def sidekiq_batch @sidekiq_batch ||= Sidekiq::Batch.new end From 2b997bb260669486afcadc96f0e8cd3c25824f4d Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Tue, 20 Oct 2020 16:28:51 -0700 Subject: [PATCH 07/31] Fix deprecation warning, system spec --- lib/simplekiq/batching_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb index 0e08d1f..8e8c331 100644 --- a/lib/simplekiq/batching_job.rb +++ b/lib/simplekiq/batching_job.rb @@ -103,7 +103,7 @@ class BaseBatch include Sidekiq::Worker def perform(*args) - self.class.parent.new.perform_batch(*args) + self.class.module_parent.new.perform_batch(*args) end end end From c65655c3e6e3afb8c247bd045ccab42610609e72 Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Thu, 22 Oct 2020 10:20:50 -0700 Subject: [PATCH 08/31] Make child batch if in existing batch --- lib/simplekiq/batching_job.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb index 8e8c331..96bbda3 100644 --- a/lib/simplekiq/batching_job.rb +++ b/lib/simplekiq/batching_job.rb @@ -53,8 +53,15 @@ def included(klass) end def perform(*args) - perform_batching(*args) - handle_batches(args) + if batch + batch.jobs do + perform_batching(*args) + handle_batches(args) + end + else + perform_batching(*args) + handle_batches(args) + end end protected From 38a65ad86af791f3167ed3589c9d929cd6f2d6e0 Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Wed, 11 Nov 2020 08:39:52 -0800 Subject: [PATCH 09/31] Clean up orchestration --- lib/simplekiq/batching_job.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb index 96bbda3..9191664 100644 --- a/lib/simplekiq/batching_job.rb +++ b/lib/simplekiq/batching_job.rb @@ -43,6 +43,7 @@ module Simplekiq module BatchingJob + include Sidekiq::Worker BATCH_CLASS_NAME = "SimplekiqBatch" class << self @@ -53,13 +54,12 @@ def included(klass) end def perform(*args) - if batch + perform_batching(*args) + if batch # If we're part of an existing sidekiq batch make this a child batch batch.jobs do - perform_batching(*args) handle_batches(args) end else - perform_batching(*args) handle_batches(args) end end From 17de5fb8d78db95b32736ff1d798ddf19204932c Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Fri, 18 Dec 2020 17:17:24 -0600 Subject: [PATCH 10/31] Get system specs passing --- lib/simplekiq/orchestration.rb | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 lib/simplekiq/orchestration.rb diff --git a/lib/simplekiq/orchestration.rb b/lib/simplekiq/orchestration.rb new file mode 100644 index 0000000..33e8882 --- /dev/null +++ b/lib/simplekiq/orchestration.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "simplekiq/orchestration_step_job" + +module Simplekiq + class Orchestration + attr_accessor :serial_workflow, :parallel_workflow + def initialize(&block) + @serial_workflow = [] + end + + def run(*step) + workflow = parallel_workflow || serial_workflow + workflow << step + end + + def in_parallel + @parallel_workflow = [] + yield + serial_workflow << @parallel_workflow if @parallel_workflow.any? + @parallel_workflow = nil + serial_workflow + end + + def kickoff + serialized_workflow = serial_workflow.map do |step| + case step[0] + when Array + step.map do |(job, *args)| + { "klass" => job.name, "args" => args } + end + when Class + job, *args = step + { "klass" => job.name, "args" => args } + end + end + + orchestration_batch = Sidekiq::Batch.new + orchestration_batch.jobs do + OrchestrationStepJob.perform_async(serialized_workflow, 0) + end + end + end +end From 34834b96df03370cfa20b7508a990ff6c210acb6 Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Fri, 18 Dec 2020 17:56:53 -0600 Subject: [PATCH 11/31] Add batch test for orchestration job --- lib/simplekiq/orchestration.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/simplekiq/orchestration.rb b/lib/simplekiq/orchestration.rb index 33e8882..391bf69 100644 --- a/lib/simplekiq/orchestration.rb +++ b/lib/simplekiq/orchestration.rb @@ -22,7 +22,7 @@ def in_parallel serial_workflow end - def kickoff + def serialized_workflow serialized_workflow = serial_workflow.map do |step| case step[0] when Array @@ -35,10 +35,6 @@ def kickoff end end - orchestration_batch = Sidekiq::Batch.new - orchestration_batch.jobs do - OrchestrationStepJob.perform_async(serialized_workflow, 0) - end end end end From ce60445098c74edbbaf0c6d8ad7996e7a1386d32 Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Mon, 18 Jan 2021 23:11:09 -0600 Subject: [PATCH 12/31] Fix spec, cleanup --- lib/simplekiq/orchestration.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/simplekiq/orchestration.rb b/lib/simplekiq/orchestration.rb index 391bf69..6fae092 100644 --- a/lib/simplekiq/orchestration.rb +++ b/lib/simplekiq/orchestration.rb @@ -5,7 +5,7 @@ module Simplekiq class Orchestration attr_accessor :serial_workflow, :parallel_workflow - def initialize(&block) + def initialize @serial_workflow = [] end @@ -34,7 +34,6 @@ def serialized_workflow { "klass" => job.name, "args" => args } end end - end end end From bb4d1106e9b1907f933d7bfc91066e4471d0ddfe Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Tue, 19 Jan 2021 01:19:07 -0600 Subject: [PATCH 13/31] Expand comment on child batches --- lib/simplekiq/batching_job.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb index 9191664..d7b187a 100644 --- a/lib/simplekiq/batching_job.rb +++ b/lib/simplekiq/batching_job.rb @@ -55,7 +55,11 @@ def included(klass) def perform(*args) perform_batching(*args) - if batch # If we're part of an existing sidekiq batch make this a child batch + + # If we're part of an existing sidekiq batch make this a child batch + # This is necessary for it work with orchestration; we could add an option + # to toggle the behavior on and off. + if batch batch.jobs do handle_batches(args) end From f4b285c1884612da4d0a1e72c36cfd9cf215598a Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Tue, 19 Jan 2021 13:05:38 -0600 Subject: [PATCH 14/31] Add ensure, move list match orchestration inside parent --- lib/simplekiq/orchestration.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/simplekiq/orchestration.rb b/lib/simplekiq/orchestration.rb index 6fae092..b8c6fad 100644 --- a/lib/simplekiq/orchestration.rb +++ b/lib/simplekiq/orchestration.rb @@ -18,6 +18,7 @@ def in_parallel @parallel_workflow = [] yield serial_workflow << @parallel_workflow if @parallel_workflow.any? + ensure @parallel_workflow = nil serial_workflow end From 23bb4904194889004f20947ca96e7820714e0b01 Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Tue, 19 Jan 2021 19:10:16 -0600 Subject: [PATCH 15/31] Refactor to remove intermediate job --- lib/simplekiq/orchestration.rb | 6 ++-- lib/simplekiq/orchestration_executor.rb | 43 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 lib/simplekiq/orchestration_executor.rb diff --git a/lib/simplekiq/orchestration.rb b/lib/simplekiq/orchestration.rb index b8c6fad..b5f26ee 100644 --- a/lib/simplekiq/orchestration.rb +++ b/lib/simplekiq/orchestration.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "simplekiq/orchestration_step_job" - module Simplekiq class Orchestration attr_accessor :serial_workflow, :parallel_workflow @@ -23,6 +21,10 @@ def in_parallel serial_workflow end + def execute(parent_batch) + OrchestrationExecutor.execute(workflow: serialized_workflow, parent_batch: parent_batch) + end + def serialized_workflow serialized_workflow = serial_workflow.map do |step| case step[0] diff --git a/lib/simplekiq/orchestration_executor.rb b/lib/simplekiq/orchestration_executor.rb new file mode 100644 index 0000000..40ac1fd --- /dev/null +++ b/lib/simplekiq/orchestration_executor.rb @@ -0,0 +1,43 @@ +module Simplekiq + class OrchestrationExecutor + def self.execute(workflow: workflow, parent_batch: parent_batch) + run_step(parent_batch, workflow, 0) + end + + def run_step(parent_batch, workflow, step) + nest_under(parent_batch) do + *jobs = workflow.at(step) + sidekiq_batch = Sidekiq::Batch.new + sidekiq_batch.on( + :success, + self.class, + "workflow" => workflow, "step" => step + 1 + ) + + sidekiq_batch.jobs do + jobs.each do |job| + Object.const_get(job["klass"]).perform_async(*job["args"]) + end + end + end + end + + def nest_under(parent_batch) + if parent_batch + parent_batch.jobs do + yield + end + else + yield + end + end + + def on_success(status, options) + return if options["step"] == options["workflow"].length + + parent_batch = Sidekiq::Batch.new(status.parent_bid) + run_step(parent_batch, options["workflow"], options["step"]) + end + + end +end From 96000c6d9d1e5b1a662de2718d6da85d574ea525 Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Wed, 20 Jan 2021 01:26:16 -0600 Subject: [PATCH 16/31] Rubocop fixes --- lib/simplekiq/orchestration_executor.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/simplekiq/orchestration_executor.rb b/lib/simplekiq/orchestration_executor.rb index 40ac1fd..72c9bed 100644 --- a/lib/simplekiq/orchestration_executor.rb +++ b/lib/simplekiq/orchestration_executor.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + module Simplekiq class OrchestrationExecutor - def self.execute(workflow: workflow, parent_batch: parent_batch) - run_step(parent_batch, workflow, 0) + def self.execute(workflow:, parent_batch:) + new.run_step(parent_batch, workflow, 0) end def run_step(parent_batch, workflow, step) @@ -38,6 +40,5 @@ def on_success(status, options) parent_batch = Sidekiq::Batch.new(status.parent_bid) run_step(parent_batch, options["workflow"], options["step"]) end - end end From 5651adea6f28dc2c893f2bc59612fd0eac232534 Mon Sep 17 00:00:00 2001 From: Jack Noble Date: Wed, 20 Jan 2021 02:39:26 -0600 Subject: [PATCH 17/31] Avoid 'workflow' to prevent confusion with the workflows functionality --- lib/simplekiq/orchestration_executor.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/simplekiq/orchestration_executor.rb b/lib/simplekiq/orchestration_executor.rb index 72c9bed..a4a9a18 100644 --- a/lib/simplekiq/orchestration_executor.rb +++ b/lib/simplekiq/orchestration_executor.rb @@ -13,7 +13,7 @@ def run_step(parent_batch, workflow, step) sidekiq_batch.on( :success, self.class, - "workflow" => workflow, "step" => step + 1 + "orchestration_workflow" => workflow, "step" => step + 1 ) sidekiq_batch.jobs do @@ -35,10 +35,10 @@ def nest_under(parent_batch) end def on_success(status, options) - return if options["step"] == options["workflow"].length + return if options["step"] == options["orchestration_workflow"].length parent_batch = Sidekiq::Batch.new(status.parent_bid) - run_step(parent_batch, options["workflow"], options["step"]) + run_step(parent_batch, options["orchestration_workflow"], options["step"]) end end end From cdc05f0fc0cbc99152b66129364fd30f517ef0f4 Mon Sep 17 00:00:00 2001 From: "Brian J. Dillard" Date: Thu, 1 Apr 2021 17:35:22 -0700 Subject: [PATCH 18/31] Add on_complete support to simplekiq --- lib/simplekiq/batching_job.rb | 45 ++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb index d7b187a..dfc2f9d 100644 --- a/lib/simplekiq/batching_job.rb +++ b/lib/simplekiq/batching_job.rb @@ -3,18 +3,26 @@ # This module enables you to break up your work into batches and run those # batches as background jobs while keeping all your code in the same file. # Including this module *implements perform* you should not override it. -# It expects you to implement two methods: #perform_batching and #perform_batch. -# Optionally you may also implement #on_success or #on_death +# It expects you to implement two methods: #perform_batching and +# #perform_batch. # -# #perform_batching should contain your code for breaking up your work into smaller jobs. -# It handles all the Sidekiq::Batch boilerplate for you. Where you would normally call ExampleBatchJob.perform_async -# you should use #queue_batch. If you'd like to custommize the sidekiq batch -# object, you can access it in perform_batching through the `sidekiq_batch` method. +# Optionally you may also implement any combination of Sidekiq::Batch +# callbacks. +# - #on_complete +# - #on_success +# - #on_death # -# #perform_batch should contain the code that would be in your batch job. Under the hood #queue_batch -# queues a job which will run #perform_batch. +# #perform_batching should contain your code for breaking up your work into +# smaller jobs. It handles all the Sidekiq::Batch boilerplate for you. Where +# you would normally call ExampleBatchJob.perform_async you should use +# #queue_batch. If you'd like to custommize the sidekiq batch object, you can +# access it in perform_batching through the `sidekiq_batch` method. # -# See Sidekiq::Batch documentation for the signatures and purpose of #on_success and #on_death +# #perform_batch should contain the code that would be in your batch job. Under +# the hood, #queue_batch queues a job which will run #perform_batch. +# +# [Sidekiq::Batch documentation](https://github.com/mperham/sidekiq/wiki/Batches) +# explains batches, their lifecycle, callbacks, etc. # # class ExampleJob # include Simplekiq::BatchingJob @@ -31,6 +39,16 @@ # OtherRecord.where(id: other_record_ids).do_work # end # +# def on_death(_status, options) +# same_id_as_before = options["args"].first +# Record.find(same_id_as_before).death! +# end + +# def on_complete(_status, options) +# same_id_as_before = options["args"].first +# Record.find(same_id_as_before).complete! +# end + # def on_success(_status, options) # same_id_as_before = options["args"].first # Record.find(same_id_as_before).success! @@ -44,6 +62,7 @@ module Simplekiq module BatchingJob include Sidekiq::Worker + BATCH_CLASS_NAME = "SimplekiqBatch" class << self @@ -75,8 +94,11 @@ def perform(*args) def handle_batches(args) if batches.present? flush_batches(args) - elsif respond_to?(:on_success) - on_success(nil, { "args" => args }) + else + # Empty batches with no jobs will never invoke callbacks, so handle + # that case by immediately manually invoking :complete & :success. + on_complete(nil, { "args" => args }) if respond_to?(:on_complete) + on_success(nil, { "args" => args }) if respond_to?(:on_success) end end @@ -85,6 +107,7 @@ def flush_batches(args) sidekiq_batch.description ||= "Simplekiq Batch Jobs for #{self.class.name}, args: #{args}" sidekiq_batch.on(:death, self.class, "args" => args) if respond_to?(:on_death) + sidekiq_batch.on(:complete, self.class, "args" => args) if respond_to?(:on_complete) sidekiq_batch.on(:success, self.class, "args" => args) if respond_to?(:on_success) sidekiq_batch.jobs do From 7128f4b2f1b39aa3a08e1775f740d35ff3e434bf Mon Sep 17 00:00:00 2001 From: Tiffany Troha Date: Mon, 12 Jul 2021 14:50:17 -0600 Subject: [PATCH 19/31] batching sidekiq options --- lib/simplekiq/batching_job.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb index dfc2f9d..1b489ca 100644 --- a/lib/simplekiq/batching_job.rb +++ b/lib/simplekiq/batching_job.rb @@ -69,6 +69,17 @@ class << self def included(klass) batch_job_class = Class.new(BaseBatch) klass.const_set(BATCH_CLASS_NAME, batch_job_class) + + klass.extend ClassMethods + end + end + + module ClassMethods + def batch_sidekiq_options(options) + batch_class = const_get(BATCH_CLASS_NAME) + batch_class.instance_eval do + sidekiq_options(options) + end end end From 28bd13e15c81b4e76af36d1d00de50ec60a6ae99 Mon Sep 17 00:00:00 2001 From: Austen Madden Date: Fri, 3 Sep 2021 16:52:27 -0400 Subject: [PATCH 20/31] Refactor PersonalizedContents job into Simplekiq The Simplekiq Batching Job API now supports everything this job needed. We can have the ProcessAllJob now take advantage of this so the batch can be defined in a single class. --- lib/simplekiq/batching_job.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb index 1b489ca..6489804 100644 --- a/lib/simplekiq/batching_job.rb +++ b/lib/simplekiq/batching_job.rb @@ -108,8 +108,8 @@ def handle_batches(args) else # Empty batches with no jobs will never invoke callbacks, so handle # that case by immediately manually invoking :complete & :success. - on_complete(nil, { "args" => args }) if respond_to?(:on_complete) - on_success(nil, { "args" => args }) if respond_to?(:on_success) + on_complete(Sidekiq::Batch::Status.new, { "args" => args }) if respond_to?(:on_complete) + on_success(Sidekiq::Batch::Status.new, { "args" => args }) if respond_to?(:on_success) end end From cadbfa911319ff7327a5c8bd3be0efedb0be23ff Mon Sep 17 00:00:00 2001 From: John Wilkinson Date: Tue, 11 Jan 2022 14:09:13 -0800 Subject: [PATCH 21/31] Staged version of simplekiq extraction Still needs some polish around the edges, but this is at least functional --- gems/simplekiq/.gitignore | 11 ++ gems/simplekiq/.rspec | 3 + gems/simplekiq/.travis.yml | 6 + gems/simplekiq/Gemfile | 4 + gems/simplekiq/Gemfile.lock | 61 +++++++ gems/simplekiq/LICENSE.txt | 13 ++ gems/simplekiq/README.md | 40 ++++ gems/simplekiq/Rakefile | 6 + gems/simplekiq/bin/console | 14 ++ gems/simplekiq/bin/setup | 8 + gems/simplekiq/lib/simplekiq.rb | 12 ++ .../simplekiq/lib}/simplekiq/batching_job.rb | 21 ++- .../simplekiq/lib}/simplekiq/orchestration.rb | 0 .../lib}/simplekiq/orchestration_executor.rb | 0 .../lib/simplekiq/orchestration_job.rb | 28 +++ gems/simplekiq/lib/simplekiq/version.rb | 3 + gems/simplekiq/simplekiq.gemspec | 32 ++++ gems/simplekiq/spec/batching_job_spec.rb | 172 ++++++++++++++++++ gems/simplekiq/spec/orchestration_job_spec.rb | 86 +++++++++ gems/simplekiq/spec/simplekiq_spec.rb | 7 + gems/simplekiq/spec/spec_helper.rb | 80 ++++++++ 21 files changed, 601 insertions(+), 6 deletions(-) create mode 100644 gems/simplekiq/.gitignore create mode 100644 gems/simplekiq/.rspec create mode 100644 gems/simplekiq/.travis.yml create mode 100644 gems/simplekiq/Gemfile create mode 100644 gems/simplekiq/Gemfile.lock create mode 100644 gems/simplekiq/LICENSE.txt create mode 100644 gems/simplekiq/README.md create mode 100644 gems/simplekiq/Rakefile create mode 100755 gems/simplekiq/bin/console create mode 100755 gems/simplekiq/bin/setup create mode 100644 gems/simplekiq/lib/simplekiq.rb rename {lib => gems/simplekiq/lib}/simplekiq/batching_job.rb (88%) rename {lib => gems/simplekiq/lib}/simplekiq/orchestration.rb (100%) rename {lib => gems/simplekiq/lib}/simplekiq/orchestration_executor.rb (100%) create mode 100644 gems/simplekiq/lib/simplekiq/orchestration_job.rb create mode 100644 gems/simplekiq/lib/simplekiq/version.rb create mode 100644 gems/simplekiq/simplekiq.gemspec create mode 100644 gems/simplekiq/spec/batching_job_spec.rb create mode 100644 gems/simplekiq/spec/orchestration_job_spec.rb create mode 100644 gems/simplekiq/spec/simplekiq_spec.rb create mode 100644 gems/simplekiq/spec/spec_helper.rb diff --git a/gems/simplekiq/.gitignore b/gems/simplekiq/.gitignore new file mode 100644 index 0000000..b04a8c8 --- /dev/null +++ b/gems/simplekiq/.gitignore @@ -0,0 +1,11 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ + +# rspec failure tracking +.rspec_status diff --git a/gems/simplekiq/.rspec b/gems/simplekiq/.rspec new file mode 100644 index 0000000..34c5164 --- /dev/null +++ b/gems/simplekiq/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/gems/simplekiq/.travis.yml b/gems/simplekiq/.travis.yml new file mode 100644 index 0000000..d4d04d5 --- /dev/null +++ b/gems/simplekiq/.travis.yml @@ -0,0 +1,6 @@ +--- +language: ruby +cache: bundler +rvm: + - 2.6.6 +before_install: gem install bundler -v 2.1.4 diff --git a/gems/simplekiq/Gemfile b/gems/simplekiq/Gemfile new file mode 100644 index 0000000..00b638b --- /dev/null +++ b/gems/simplekiq/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +# Specify your gem's dependencies in simplekiq.gemspec +gemspec diff --git a/gems/simplekiq/Gemfile.lock b/gems/simplekiq/Gemfile.lock new file mode 100644 index 0000000..e06da17 --- /dev/null +++ b/gems/simplekiq/Gemfile.lock @@ -0,0 +1,61 @@ +PATH + remote: . + specs: + simplekiq (1.0.0) + sidekiq (~> 5.2.9) + sidekiq-ent (>= 1.8.1) + +GEM + remote: https://rubygems.org/ + specs: + coderay (1.1.3) + concurrent-ruby (1.1.9) + connection_pool (2.2.5) + diff-lcs (1.5.0) + einhorn (0.7.4) + method_source (1.0.0) + pry (0.13.1) + coderay (~> 1.1) + method_source (~> 1.0) + rack (2.2.3) + rack-protection (2.1.0) + rack + rake (12.3.3) + redis (4.1.4) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-support (3.10.3) + sidekiq (5.2.9) + connection_pool (~> 2.2, >= 2.2.2) + rack (~> 2.0) + rack-protection (>= 1.5.0) + redis (>= 3.3.5, < 4.2) + sidekiq-ent (1.8.1) + einhorn (= 0.7.4) + sidekiq (>= 5.2.3) + sidekiq-pro (>= 4.0.4) + sidekiq-pro (5.0.0) + concurrent-ruby (>= 1.0.5) + sidekiq (>= 5.2.7) + +PLATFORMS + ruby + +DEPENDENCIES + pry + rake (~> 12.0) + rspec (~> 3.2) + simplekiq! + +BUNDLED WITH + 2.1.4 diff --git a/gems/simplekiq/LICENSE.txt b/gems/simplekiq/LICENSE.txt new file mode 100644 index 0000000..a146f63 --- /dev/null +++ b/gems/simplekiq/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright 2022 Doximity, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/gems/simplekiq/README.md b/gems/simplekiq/README.md new file mode 100644 index 0000000..e6d9bc5 --- /dev/null +++ b/gems/simplekiq/README.md @@ -0,0 +1,40 @@ +# Simplekiq + +Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/simplekiq`. To experiment with that code, run `bin/console` for an interactive prompt. + +TODO: Delete this and the text above, and describe your gem + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'simplekiq' +``` + +And then execute: + + $ bundle install + +Or install it yourself as: + + $ gem install simplekiq + +## Usage + +TODO: Write usage instructions here + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/simplekiq. + + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/gems/simplekiq/Rakefile b/gems/simplekiq/Rakefile new file mode 100644 index 0000000..b7e9ed5 --- /dev/null +++ b/gems/simplekiq/Rakefile @@ -0,0 +1,6 @@ +require "bundler/gem_tasks" +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) + +task :default => :spec diff --git a/gems/simplekiq/bin/console b/gems/simplekiq/bin/console new file mode 100755 index 0000000..d45b29a --- /dev/null +++ b/gems/simplekiq/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "simplekiq" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/gems/simplekiq/bin/setup b/gems/simplekiq/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/gems/simplekiq/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/gems/simplekiq/lib/simplekiq.rb b/gems/simplekiq/lib/simplekiq.rb new file mode 100644 index 0000000..f3ce15c --- /dev/null +++ b/gems/simplekiq/lib/simplekiq.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "sidekiq" +require "sidekiq-ent" + +require "simplekiq/orchestration_executor" +require "simplekiq/orchestration" +require "simplekiq/orchestration_job" +require "simplekiq/batching_job" + +module Simplekiq +end diff --git a/lib/simplekiq/batching_job.rb b/gems/simplekiq/lib/simplekiq/batching_job.rb similarity index 88% rename from lib/simplekiq/batching_job.rb rename to gems/simplekiq/lib/simplekiq/batching_job.rb index 6489804..16011d5 100644 --- a/lib/simplekiq/batching_job.rb +++ b/gems/simplekiq/lib/simplekiq/batching_job.rb @@ -84,6 +84,8 @@ def batch_sidekiq_options(options) end def perform(*args) + self.batches = [] + perform_batching(*args) # If we're part of an existing sidekiq batch make this a child batch @@ -98,18 +100,18 @@ def perform(*args) end end - protected + protected # TODO: should this be private? attr_accessor :batches def handle_batches(args) - if batches.present? + if !batches.empty? flush_batches(args) else # Empty batches with no jobs will never invoke callbacks, so handle # that case by immediately manually invoking :complete & :success. - on_complete(Sidekiq::Batch::Status.new, { "args" => args }) if respond_to?(:on_complete) - on_success(Sidekiq::Batch::Status.new, { "args" => args }) if respond_to?(:on_success) + on_complete(nil, { "args" => args }) if respond_to?(:on_complete) + on_success(nil, { "args" => args }) if respond_to?(:on_success) end end @@ -129,7 +131,6 @@ def flush_batches(args) end def queue_batch(*args) - self.batches ||= [] self.batches << args end @@ -148,7 +149,15 @@ class BaseBatch include Sidekiq::Worker def perform(*args) - self.class.module_parent.new.perform_batch(*args) + module_parent_of_class.new.perform_batch(*args) + end + + private + + def module_parent_of_class + # Borrowed from https://apidock.com/rails/Module/module_parent_name + parent_name = self.class.name =~ /::[^:]+\Z/ ? $`.freeze : nil + parent_name ? Object.const_get(parent_name) : Object end end end diff --git a/lib/simplekiq/orchestration.rb b/gems/simplekiq/lib/simplekiq/orchestration.rb similarity index 100% rename from lib/simplekiq/orchestration.rb rename to gems/simplekiq/lib/simplekiq/orchestration.rb diff --git a/lib/simplekiq/orchestration_executor.rb b/gems/simplekiq/lib/simplekiq/orchestration_executor.rb similarity index 100% rename from lib/simplekiq/orchestration_executor.rb rename to gems/simplekiq/lib/simplekiq/orchestration_executor.rb diff --git a/gems/simplekiq/lib/simplekiq/orchestration_job.rb b/gems/simplekiq/lib/simplekiq/orchestration_job.rb new file mode 100644 index 0000000..b59b55b --- /dev/null +++ b/gems/simplekiq/lib/simplekiq/orchestration_job.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "forwardable" + +module Simplekiq + module OrchestrationJob + include Sidekiq::Worker + + extend Forwardable + def_delegators :orchestration, :run, :in_parallel + + def perform(*args) + perform_orchestration(*args) + orchestration.execute(batch) + end + + def workflow_plan(*args) + perform_orchestration(*args) + orchestration.serialized_workflow + end + + private + + def orchestration + @orchestration ||= Orchestration.new + end + end +end diff --git a/gems/simplekiq/lib/simplekiq/version.rb b/gems/simplekiq/lib/simplekiq/version.rb new file mode 100644 index 0000000..0faa8f7 --- /dev/null +++ b/gems/simplekiq/lib/simplekiq/version.rb @@ -0,0 +1,3 @@ +module Simplekiq + VERSION = "1.0.0" +end diff --git a/gems/simplekiq/simplekiq.gemspec b/gems/simplekiq/simplekiq.gemspec new file mode 100644 index 0000000..b599a34 --- /dev/null +++ b/gems/simplekiq/simplekiq.gemspec @@ -0,0 +1,32 @@ +require_relative 'lib/simplekiq/version' + +Gem::Specification.new do |spec| + spec.name = "simplekiq" + spec.version = Simplekiq::VERSION + spec.authors = ["John Wilkinson", "Jack Noble"] + spec.email = ["jcwilkinson@doximity.com", "jnoble@doximity.com"] + spec.summary = %q{Sidekiq-based workflow orchestration library} + spec.description = %q{Provides tools for representing long chains of parallel and serial jobs in a flat, simple way.} + spec.homepage = "https://github.com/doximity/simplekiq" + spec.license = "MIT" + spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") + + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = spec.homepage + # TODO: spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do + `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + end + spec.executables = [] + spec.require_paths = ["lib"] + + spec.add_development_dependency "rspec", "~> 3.2" + spec.add_development_dependency "rake", "~> 12.0" + spec.add_development_dependency "pry" + + spec.add_dependency "sidekiq", "~> 5.2.9" + spec.add_dependency "sidekiq-ent", ">= 1.8.1" +end diff --git a/gems/simplekiq/spec/batching_job_spec.rb b/gems/simplekiq/spec/batching_job_spec.rb new file mode 100644 index 0000000..0781c55 --- /dev/null +++ b/gems/simplekiq/spec/batching_job_spec.rb @@ -0,0 +1,172 @@ +# frozen_string_literal: true + +require "sidekiq/testing" + +RSpec.describe Simplekiq::BatchingJob do + before do + Sidekiq::Testing.inline! + end + + describe "batching" do + let(:test_job) do + Class.new do + include Simplekiq::BatchingJob + + def perform_batching(arg) + queue_batch(arg) + end + + def perform_batch(arg) + Output.call(arg) + end + end + end + + it "runs batches" do + stub_const("TestJob", test_job) + stub_const("Output", output = double("Output", call: nil)) + + test_job.new.perform("test") + + expect(output).to have_received(:call).with("test") + end + + it "queues a job with a readable name" do + stub_const("TestJob", test_job) + allow(TestJob::SimplekiqBatch).to receive(:perform_async) + + test_job.new.perform("test") + + expect(TestJob::SimplekiqBatch).to have_received(:perform_async) + end + end + + describe "on_success" do + let(:test_job) do + Class.new do + include Simplekiq::BatchingJob + + def perform_batching(things) + things.each { |t| queue_batch(t) } + end + + def perform_batch(arg); end + + def on_success(_, options) + Output.call(options["args"].first) + end + end + end + + it "runs the on_success callback even if no batches are run" do + stub_const("TestJob", test_job) + stub_const("Output", output = double("Output", call: nil)) + + test_job.new.perform([]) + + expect(output).to have_received(:call).with([]) + end + + it "runs the on_success callback when batches complete successfully", stub_batches: false, sidekiq: :fake do + stub_const("TestJob", test_job) + stub_const("Output", output = double("Output", call: nil)) + stub_batches + + test_job.new.perform(["test"]) + run_all_jobs_and_batches + + expect(output).to have_received(:call).with(["test"]) + end + end + + describe "on_complete" do + let(:test_job) do + Class.new do + include Simplekiq::BatchingJob + + def perform_batching(things) + things.each { |t| queue_batch(t) } + end + + def perform_batch(arg); end + + def on_complete(_, options) + Output.call(options["args"].first) + end + end + end + + it "runs the on_complete callback even if no batches are run" do + stub_const("TestJob", test_job) + stub_const("Output", output = double("Output", call: nil)) + + test_job.new.perform([]) + + expect(output).to have_received(:call).with([]) + end + + it "runs the on_complete callback when each job has been run once", stub_batches: false, sidekiq: :fake do + stub_const("TestJob", test_job) + stub_const("Output", output = double("Output", call: nil)) + stub_batches + + test_job.new.perform(["test"]) + run_all_jobs_and_batches + + expect(output).to have_received(:call).with(["test"]) + end + end + + describe "on_death" do + let(:test_job) do + Class.new do + include Simplekiq::BatchingJob + + def perform_batching(things) + things.each { |t| queue_batch(t) } + end + + def perform_batch(arg); end + + def on_death(_, options) + Output.call(options["args"].first) + end + end + end + + it "runs the on_death callback when a batch fails", stub_batches: false, sidekiq: :fake do + stub_const("TestJob", test_job) + stub_const("Output", output = double("Output", call: nil)) + stub_batches + + test_job.new.perform(["test"]) + fail_one_batch + + expect(output).to have_received(:call).with(["test"]) + end + end + + describe "batch_sidekiq_options" do + let(:test_job) do + Class.new do + include Simplekiq::BatchingJob + + batch_sidekiq_options queue: "test_queue" + + def perform_batching(arg) + queue_batch(arg) + end + + def perform_batch(arg) + Output.call(arg) + end + end + end + + it "sets the sidekiq options for the base class" do + stub_const("Test", test_job) + + expect(Test::SimplekiqBatch.sidekiq_options).to eq("queue" => "test_queue", "retry" => true) + end + end +end diff --git a/gems/simplekiq/spec/orchestration_job_spec.rb b/gems/simplekiq/spec/orchestration_job_spec.rb new file mode 100644 index 0000000..3981edf --- /dev/null +++ b/gems/simplekiq/spec/orchestration_job_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +module OrcTest + JobA = Class.new + JobB = Class.new + JobC = Class.new +end + +RSpec.describe Simplekiq::OrchestrationJob do + it "adds a new job to the sequence with #run" do + allow(Simplekiq::OrchestrationExecutor).to receive(:execute) + klass = Class.new do + include Simplekiq::OrchestrationJob + def perform_orchestration + run OrcTest::JobA, 1 + run OrcTest::JobB + end + end + + klass.new.perform + + expect(Simplekiq::OrchestrationExecutor) + .to have_received(:execute).with({ + workflow: [ + { "klass" => "OrcTest::JobA", "args" => [1] }, + { "klass" => "OrcTest::JobB", "args" => [] } + ], + parent_batch: nil + }) + end + + it "adds a new jobs in parallel with #in_parallel" do + allow(Simplekiq::OrchestrationExecutor).to receive(:execute) + klass = Class.new do + include Simplekiq::OrchestrationJob + + def perform_orchestration + run OrcTest::JobA + in_parallel do + run OrcTest::JobB + run OrcTest::JobC + end + end + end + + klass.new.perform + + expect(Simplekiq::OrchestrationExecutor) + .to have_received(:execute).with({ + workflow: [ + { "klass" => "OrcTest::JobA", "args" => [] }, + [ + { "klass" => "OrcTest::JobB", "args" => [] }, + { "klass" => "OrcTest::JobC", "args" => [] } + + ] + ], + parent_batch: nil + }) + end + + it "enables composition of orchestrations by re-opening the parent batch" do + allow(Simplekiq::OrchestrationExecutor).to receive(:execute) + batch_double = instance_double(Sidekiq::Batch) + allow(batch_double).to receive(:jobs).and_yield + + job = Class.new do + include Simplekiq::OrchestrationJob + def perform_orchestration + run OrcTest::JobA + end + end.new + + allow(job).to receive(:batch).and_return(batch_double) + + job.perform + + expect(Simplekiq::OrchestrationExecutor) + .to have_received(:execute).with( + { + workflow: [{ "klass" => "OrcTest::JobA", "args" => [] }], + parent_batch: batch_double + } + ) + end +end diff --git a/gems/simplekiq/spec/simplekiq_spec.rb b/gems/simplekiq/spec/simplekiq_spec.rb new file mode 100644 index 0000000..d929762 --- /dev/null +++ b/gems/simplekiq/spec/simplekiq_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +RSpec.describe Simplekiq do + it "has a version number" do + expect(Simplekiq::VERSION).not_to be nil + end +end diff --git a/gems/simplekiq/spec/spec_helper.rb b/gems/simplekiq/spec/spec_helper.rb new file mode 100644 index 0000000..ddb4239 --- /dev/null +++ b/gems/simplekiq/spec/spec_helper.rb @@ -0,0 +1,80 @@ +require "bundler/setup" +require "simplekiq" +require "pry" + +module SidekiqBatchTestHelpers + # These helper methods only work in the following test mode: + # sidekiq: :fake, stub_batches: false + + class NoBatchesError < StandardError + def message + "No batches queued. Ensure you the test has `stub_batches: false` and that you are actually queueing a batch" + end + end + + # https://github.com/mperham/sidekiq/issues/2700 + def stub_batches + @batches = [] + allow_any_instance_of(Sidekiq::Batch).to receive(:jobs) do |batch, &block| + block.call + @batches << batch + end + end + + def fail_one_batch + raise NoBatchesError if @batches.empty? + raise "Tried to fail one batch but there were multiple batches" if @batches.length > 1 + + @batches.first.callbacks["death"].each do |callback| + callback.each do |klass, args| + klass.new.send(:on_death, "death", args) + end + end + end + + def succeed_all_batches + current_batches = @batches + @batches = [] + + send_batch_callbacks_for(current_batches, "success") + send_batch_callbacks_for(current_batches, "complete") + end + + def send_batch_callbacks_for(current_batches, status) + callback_symbol = "on_#{status}".to_sym + current_batches.each do |batch| + next unless batch.callbacks[status] + + batch.callbacks[status].each do |callback| + callback.each do |klass, args| # callback is a hash + klass.new.send(callback_symbol, status, args) + end + end + end + end + + def run_all_jobs_and_batches + loops_left = 100 + while Sidekiq::Worker.jobs.any? || @batches.any? + loops_left -= 1 + raise "no more loops!" if loops_left.negative? + + Sidekiq::Worker.drain_all # This will raise if any job fails + succeed_all_batches # Because nothing raised, we can assume success for all batches + end + end +end + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end + + config.include SidekiqBatchTestHelpers, sidekiq: :fake +end From 9054fd7c938cc43d09f100bfeeed61eb227c4456 Mon Sep 17 00:00:00 2001 From: John Wilkinson Date: Fri, 4 Feb 2022 16:03:25 -0800 Subject: [PATCH 22/31] Delete the default README --- README.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 276acba..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# simplekiq -Sidekiq-based workflow orchestration library From 6164c32577dc85b8c9b8a6a11ab5c8add11a2668 Mon Sep 17 00:00:00 2001 From: John Wilkinson Date: Fri, 4 Feb 2022 16:03:45 -0800 Subject: [PATCH 23/31] move files from staged location to gem root --- gems/simplekiq/Gemfile => Gemfile | 0 gems/simplekiq/Gemfile.lock => Gemfile.lock | 0 gems/simplekiq/LICENSE.txt => LICENSE.txt | 0 gems/simplekiq/README.md => README.md | 0 gems/simplekiq/Rakefile => Rakefile | 0 {gems/simplekiq/bin => bin}/console | 0 {gems/simplekiq/bin => bin}/setup | 0 {gems/simplekiq/lib => lib}/simplekiq.rb | 0 {gems/simplekiq/lib => lib}/simplekiq/batching_job.rb | 0 {gems/simplekiq/lib => lib}/simplekiq/orchestration.rb | 0 {gems/simplekiq/lib => lib}/simplekiq/orchestration_executor.rb | 0 {gems/simplekiq/lib => lib}/simplekiq/orchestration_job.rb | 0 {gems/simplekiq/lib => lib}/simplekiq/version.rb | 0 gems/simplekiq/simplekiq.gemspec => simplekiq.gemspec | 0 {gems/simplekiq/spec => spec}/batching_job_spec.rb | 0 {gems/simplekiq/spec => spec}/orchestration_job_spec.rb | 0 {gems/simplekiq/spec => spec}/simplekiq_spec.rb | 0 {gems/simplekiq/spec => spec}/spec_helper.rb | 0 18 files changed, 0 insertions(+), 0 deletions(-) rename gems/simplekiq/Gemfile => Gemfile (100%) rename gems/simplekiq/Gemfile.lock => Gemfile.lock (100%) rename gems/simplekiq/LICENSE.txt => LICENSE.txt (100%) rename gems/simplekiq/README.md => README.md (100%) rename gems/simplekiq/Rakefile => Rakefile (100%) rename {gems/simplekiq/bin => bin}/console (100%) rename {gems/simplekiq/bin => bin}/setup (100%) rename {gems/simplekiq/lib => lib}/simplekiq.rb (100%) rename {gems/simplekiq/lib => lib}/simplekiq/batching_job.rb (100%) rename {gems/simplekiq/lib => lib}/simplekiq/orchestration.rb (100%) rename {gems/simplekiq/lib => lib}/simplekiq/orchestration_executor.rb (100%) rename {gems/simplekiq/lib => lib}/simplekiq/orchestration_job.rb (100%) rename {gems/simplekiq/lib => lib}/simplekiq/version.rb (100%) rename gems/simplekiq/simplekiq.gemspec => simplekiq.gemspec (100%) rename {gems/simplekiq/spec => spec}/batching_job_spec.rb (100%) rename {gems/simplekiq/spec => spec}/orchestration_job_spec.rb (100%) rename {gems/simplekiq/spec => spec}/simplekiq_spec.rb (100%) rename {gems/simplekiq/spec => spec}/spec_helper.rb (100%) diff --git a/gems/simplekiq/Gemfile b/Gemfile similarity index 100% rename from gems/simplekiq/Gemfile rename to Gemfile diff --git a/gems/simplekiq/Gemfile.lock b/Gemfile.lock similarity index 100% rename from gems/simplekiq/Gemfile.lock rename to Gemfile.lock diff --git a/gems/simplekiq/LICENSE.txt b/LICENSE.txt similarity index 100% rename from gems/simplekiq/LICENSE.txt rename to LICENSE.txt diff --git a/gems/simplekiq/README.md b/README.md similarity index 100% rename from gems/simplekiq/README.md rename to README.md diff --git a/gems/simplekiq/Rakefile b/Rakefile similarity index 100% rename from gems/simplekiq/Rakefile rename to Rakefile diff --git a/gems/simplekiq/bin/console b/bin/console similarity index 100% rename from gems/simplekiq/bin/console rename to bin/console diff --git a/gems/simplekiq/bin/setup b/bin/setup similarity index 100% rename from gems/simplekiq/bin/setup rename to bin/setup diff --git a/gems/simplekiq/lib/simplekiq.rb b/lib/simplekiq.rb similarity index 100% rename from gems/simplekiq/lib/simplekiq.rb rename to lib/simplekiq.rb diff --git a/gems/simplekiq/lib/simplekiq/batching_job.rb b/lib/simplekiq/batching_job.rb similarity index 100% rename from gems/simplekiq/lib/simplekiq/batching_job.rb rename to lib/simplekiq/batching_job.rb diff --git a/gems/simplekiq/lib/simplekiq/orchestration.rb b/lib/simplekiq/orchestration.rb similarity index 100% rename from gems/simplekiq/lib/simplekiq/orchestration.rb rename to lib/simplekiq/orchestration.rb diff --git a/gems/simplekiq/lib/simplekiq/orchestration_executor.rb b/lib/simplekiq/orchestration_executor.rb similarity index 100% rename from gems/simplekiq/lib/simplekiq/orchestration_executor.rb rename to lib/simplekiq/orchestration_executor.rb diff --git a/gems/simplekiq/lib/simplekiq/orchestration_job.rb b/lib/simplekiq/orchestration_job.rb similarity index 100% rename from gems/simplekiq/lib/simplekiq/orchestration_job.rb rename to lib/simplekiq/orchestration_job.rb diff --git a/gems/simplekiq/lib/simplekiq/version.rb b/lib/simplekiq/version.rb similarity index 100% rename from gems/simplekiq/lib/simplekiq/version.rb rename to lib/simplekiq/version.rb diff --git a/gems/simplekiq/simplekiq.gemspec b/simplekiq.gemspec similarity index 100% rename from gems/simplekiq/simplekiq.gemspec rename to simplekiq.gemspec diff --git a/gems/simplekiq/spec/batching_job_spec.rb b/spec/batching_job_spec.rb similarity index 100% rename from gems/simplekiq/spec/batching_job_spec.rb rename to spec/batching_job_spec.rb diff --git a/gems/simplekiq/spec/orchestration_job_spec.rb b/spec/orchestration_job_spec.rb similarity index 100% rename from gems/simplekiq/spec/orchestration_job_spec.rb rename to spec/orchestration_job_spec.rb diff --git a/gems/simplekiq/spec/simplekiq_spec.rb b/spec/simplekiq_spec.rb similarity index 100% rename from gems/simplekiq/spec/simplekiq_spec.rb rename to spec/simplekiq_spec.rb diff --git a/gems/simplekiq/spec/spec_helper.rb b/spec/spec_helper.rb similarity index 100% rename from gems/simplekiq/spec/spec_helper.rb rename to spec/spec_helper.rb From f0d1252a97b104c5c7f0a8cfa90f3d9a1cad0585 Mon Sep 17 00:00:00 2001 From: John Wilkinson Date: Fri, 4 Feb 2022 16:10:46 -0800 Subject: [PATCH 24/31] Additional tweaks prepping for release --- .gitignore | 11 +++++ .rspec | 3 ++ .travis.yml | 6 +++ README.md | 96 ++++++++++++++++++++++++++++++++++++---- lib/simplekiq/version.rb | 2 +- simplekiq.gemspec | 2 +- 6 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 .gitignore create mode 100644 .rspec create mode 100644 .travis.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b04a8c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ + +# rspec failure tracking +.rspec_status diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..34c5164 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d4d04d5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +--- +language: ruby +cache: bundler +rvm: + - 2.6.6 +before_install: gem install bundler -v 2.1.4 diff --git a/README.md b/README.md index e6d9bc5..79b8fa8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Simplekiq -Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/simplekiq`. To experiment with that code, run `bin/console` for an interactive prompt. - -TODO: Delete this and the text above, and describe your gem +Any time that you find yourself needing to string together a long chain of jobs, particularly when there are multiple stages of Sidekiq-pro batches and callbacks involved, come home instead to the simple flavor of orchestrated job flow with Simplekiq. ## Installation @@ -22,19 +20,101 @@ Or install it yourself as: ## Usage -TODO: Write usage instructions here +There are currently two primary components of the system which were designed to work in harmony: + +* [Simplekiq::OrchestrationJob](./lib/simplekiq/orchestration_job.rb) - A mixin for a Sidekiq jobs to be able to orchestrate a flow of jobs in one place. It makes long complicated flows between jobs easier to understand, iterate on, and test. It eliminates the need to hop between dozens of files to determine when, where, and why a particular job gets called. +* [Simplekiq::BatchingJob](./lib/simplekiq/batching_job.rb) - A mixin designed to make breaking a large job into a batched process dead simple and contained within a single class while still being trivially composable in orchestrations. + +## Tool Drilldown + +### Simplekiq::OrchestrationJob + +Mixing in the [Simplekiq::Orchestration](./lib/simplekiq/orchestration_job.rb) module lets you define a human-readable workflow of jobs in a single file with almost* no special requirements or restrictions on how the child jobs are designed. In most cases, Sidekiq jobs not designed for use in orchestrations should be compatible for use in orchestrations. A job implementing `OrchestrationJob` might look like: + +```ruby +class SomeOrchestrationJob < BaseJob + include Sidekiq::Worker + include Simplekiq::OrchestrationJob + + def perform_orchestration(some_id) + @some_model = SomeModel.find(some_id) # 1. + + run SomeInitialSetupJob, some_model.id # 2. + + in_parallel do + some_related_models.each do |related_model| + run SomeParallelizableJob, related_model.id # 3. + end + end + + run SomeFinalizationJob, some_model.id # 4. + end + + private + + attr_reader :some_model + + def some_related_models + @some_related_models ||= some_model.some_relation + end +end +``` + +Let's use the above example to describe some specifics of how the flow works. + +1. `SomeOrchestrationJob` pulls up some instance of parent model `SomeModel`. +2. It does some initial work in `SomeInitialSetupJob`, which blocks the rest of the workflow until it completes successfully. +3. Then it will run a `SomeParallelizableJob` for each of some number of associated models `some_related_models`. These jobs will all run parallel to each other independently. +4. Finally, after all of the parallel jobs from #3 complete successfully, `SomeFinalizationJob` will run and then after it finishes the orchestration will be complete. + +**Note** - it's fine to add utility methods and `attr_accessor`s to keep the code tidy and maintainable. + +When `SomeOrchestrationJob` itself gets called though, the first thing it does it turn these directives into a big serialized structure indicating which job will be called under what conditions (eg, serial or in parallel) and with what arguments, and then keeps passing that between the simplekiq-internal jobs that actually conduct the flow. + +This means when you want to deploy a change to this flow all previous in-flight workflows will continue undisturbed because the workflow is frozen in sidekiq job arguments and will remain frozen until the workflow completes. This is generally a boon, but note that if you remove a job from a workflow you'll need to remember to either keep the job itself (eg, the `SomeFinalizationJob` class file from our above example) in the codebase or replace it with a stub so that any in-flight workflows won't crash due to not being able to pull up the prior-specified workflow. + +"almost* no special requirements or restrictions on how the child jobs are designed" - The one thing you'll want to keep in mind when feeding arbitrary jobs into orchestrations is that if the job creates any new sidekiq batches then those new sidekiq batches should be added as child sidekiq batches of the parent sidekiq batch of the job. The parent sidekiq batch of the job is the sidekiq batch that drives the orchestration from step to step, so if you don't do this it will move onto the next step in the orchestration once your job finishes even if the new sidekiq batches it started didn't finish. This sounds more complicated than it is, you can see an example of code that does this in [`BatchingJob#perform`](./lib/simplekiq/batching_job.rb): + +```ruby +if batch # is there a parent batch? + batch.jobs do # open the parent batch back up + create_a_new_batch_and_add_jobs_to_it_to_run # make our new batch as a child batch of the parent batch + end # close the parent batch again +else # there's no parent batches, this job was run directly outside of an orchestration + create_a_new_batch_and_add_jobs_to_it_to_run # make our new batch without a parent batch +end +``` + +### Simplekiq::BatchingJob + +See the [Simplekiq::BatchingJob](./lib/simplekiq/batching_job.rb) module itself for a description and example usage in the header comments. Nutshell is that you should use this if you're planning on making a batched asynchronous process as it shaves off a lot of ceremony and unexpressive structure. eg - Instead of having `BeerBottlerJob` which queues some number of `BeerBottlerBatchJob`s to handle the broken down sub-tasks you can just have `BeerBottlerJob` with a method for batching, executing individual batches, and a callback that gets run after all batches have completed successfully. + +## History + +Simplekiq was initially released for private use within Doximity applications in Oct 2020 where it continued to be iterated on towards stability and general use until Jan 2022 when it was deemed settled enough for public release. + +The primary driving factor that inspired this work was a series of over a dozen differently defined and structured jobs part of a single workflow of which the logical flow was extraordinarily difficult to cognitively trace. This led to exteme difficulty in debugging and following problematic instances of the workflow in production as well as needlessly high cost to refactoring and iterative adjustments. + +The crux of the problem was that each job was highly coupled to its position in the overall flow as well as the absence of any central mechanism to indicate what the overall flow was. After building Simplekiq and implementing it into the flow, significant changes to the flow became quick adjustments requiring only a couple lines of code to change and folks unfamiliar with the system could quickly get up to speed by reading through the orchestration job. + +## Versioning + +This project follows semantic versioning. At time of writing it is sitting at 0.0.1 until its integration with the application it was extracted from is confirmed to be stable. Once confirmed it will be started off at 1.0.0 as it has otherwise been used in a production system already for some time. ## Development -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. +After checking out the repo, run `bin/setup` to install dependencies. Note that this depends on `sidekiq-pro` which requires a [commercial license](https://sidekiq.org/products/pro.html) to install and use. + +Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). -## Contributing +TODO: Update this section with more specific/appropriate instructions once this is a public repository. -Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/simplekiq. +## Contributing +Bug reports and pull requests are welcome on GitHub at https://github.com/doximity/simplekiq. ## License -The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). +The gem is available as open source under the terms of the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). diff --git a/lib/simplekiq/version.rb b/lib/simplekiq/version.rb index 0faa8f7..fe865d2 100644 --- a/lib/simplekiq/version.rb +++ b/lib/simplekiq/version.rb @@ -1,3 +1,3 @@ module Simplekiq - VERSION = "1.0.0" + VERSION = "0.0.1" end diff --git a/simplekiq.gemspec b/simplekiq.gemspec index b599a34..4e7c865 100644 --- a/simplekiq.gemspec +++ b/simplekiq.gemspec @@ -28,5 +28,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "pry" spec.add_dependency "sidekiq", "~> 5.2.9" - spec.add_dependency "sidekiq-ent", ">= 1.8.1" + spec.add_dependency "sidekiq-pro", "~> 5.0.0" end From 1a0445998678e058e78805544f223af33a7b510a Mon Sep 17 00:00:00 2001 From: John Wilkinson Date: Wed, 2 Mar 2022 16:58:02 -0800 Subject: [PATCH 25/31] Clarify contributors/authors/etc --- CONTRIBUTORS.md | 19 +++++++++++++++++++ simplekiq.gemspec | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 CONTRIBUTORS.md diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..719483b --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,19 @@ +## List of All Known Code Contributors to Simplekiq + +### Jack Noble +* Collaborated on initial concept +* Wrote the majority of the code as of initial release + +### John Wilkinson +* Collaborated on initial concept +* Conducted the gem extraction and release + +### Brian Dillard +* Added additional comment documentation +* Added support for `on_complete` batch callback support in `Simplekiq::BatchingJob` + +### Austin Madden +* Fixed bug with batch statuses in callbacks for empty batches + +### Tiffany Troha +* Added support for specifying `sidekiq_options` for the child job in `Simplekiq::BatchingJob` \ No newline at end of file diff --git a/simplekiq.gemspec b/simplekiq.gemspec index 4e7c865..e4f7094 100644 --- a/simplekiq.gemspec +++ b/simplekiq.gemspec @@ -3,8 +3,8 @@ require_relative 'lib/simplekiq/version' Gem::Specification.new do |spec| spec.name = "simplekiq" spec.version = Simplekiq::VERSION - spec.authors = ["John Wilkinson", "Jack Noble"] - spec.email = ["jcwilkinson@doximity.com", "jnoble@doximity.com"] + spec.authors = ["Jack Noble", "John Wilkinson"] + spec.email = ["jcwilkinson@doximity.com"] spec.summary = %q{Sidekiq-based workflow orchestration library} spec.description = %q{Provides tools for representing long chains of parallel and serial jobs in a flat, simple way.} spec.homepage = "https://github.com/doximity/simplekiq" From b5c55da86751ead17c2817b11734ec9a9f3f0dff Mon Sep 17 00:00:00 2001 From: John Wilkinson Date: Wed, 2 Mar 2022 17:06:09 -0800 Subject: [PATCH 26/31] Add CONTRIBUTING.md for license agreement --- CONTRIBUTING.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..aa92be4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing + +We welcome contributions to this repository. Feel free to submit issues for bugs you encounter and pull requests for code and documentation contributions. + +In order to prevent licensing issues, we require all contributors to sign an individual contributor license agreement, which is reproduced below: + +## Individual Contributor License Agreement + +In order to clarify the intellectual property license granted with Contributions from any person or entity, Doximity Inc. ("Doximity") must have a Contributor License Agreement ("CLA") on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of Doximity; it does not change your rights to use your own Contributions for any other purpose. + +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to Doximity. Except for the license granted herein to Doximity and recipients of software distributed by Doximity, You reserve all right, title, and interest in and to Your Contributions. + +### Definitions + +"You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with Doximity. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +1. "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to Doximity for inclusion in, or documentation of, any of the products owned or managed by Doximity (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to Doximity or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Doximity for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to Doximity and to recipients of software distributed by Doximity a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to Doximity and to recipients of software distributed by Doximity a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to Doximity, or that your employer has executed a separate Corporate CLA with Doximity. + +5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions. + +6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, You may submit it to Doximity separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [[]named here]". + +8. You agree to notify Doximity of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. \ No newline at end of file From 23a3af21609c51a1c9bc99b5f432ffe4cc0e5966 Mon Sep 17 00:00:00 2001 From: John Wilkinson Date: Wed, 2 Mar 2022 17:17:09 -0800 Subject: [PATCH 27/31] update the fine print of the README --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 79b8fa8..b11e764 100644 --- a/README.md +++ b/README.md @@ -113,8 +113,13 @@ TODO: Update this section with more specific/appropriate instructions once this ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/doximity/simplekiq. +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request +6. Sign the CLA if you haven't yet. See CONTRIBUTING.md ## License -The gem is available as open source under the terms of the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). +The gem is licensed under an Apache 2 license. Contributors are required to sign an contributor license agreement. See LICENSE.txt and CONTRIBUTING.md for more information. From acb0fdd53c3311f995a43fac04f41be4d0339b96 Mon Sep 17 00:00:00 2001 From: John Wilkinson Date: Wed, 23 Mar 2022 16:47:54 -0700 Subject: [PATCH 28/31] Add first attempt at auto-building ci config Stolen from https://github.com/doximity/shoulda-matchers-uuid/blob/5da06b2068633516e3776966e39a544f864d795f/.circleci/config.yml with minor adjustments (mainly skipping anything related to mysql) --- .circleci/config.yml | 125 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..66fb730 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,125 @@ +version: 2.1 + +orbs: + gem: doximity/gem-publisher@0 + +executors: + ruby-latest: + resource_class: small + docker: + - image: circleci/ruby:2.6 + environment: + BUNDLE_VERSION: "~> 2.1" + +# yaml anchor filters +master_only: &master_only + filters: + branches: + only: master + tags: + ignore: /.*/ +pr_only: &pr_only + filters: + branches: + ignore: master + tags: + ignore: /.*/ +version_tags_only: &version_tags_only + filters: + branches: + ignore: /.*/ + tags: + only: /^v.*/ + +jobs: + build: + executor: ruby-latest + steps: + - checkout + - run: + name: Install Bundler specific version + command: | + gem install bundler --version "${BUNDLE_VERSION}" --force + - restore_cache: + keys: + - v1-bundle-{{ checksum "Gemfile.lock" }}- + - run: + name: Install Ruby Dependencies + command: bundle check --path=vendor/bundle || bundle install --local --frozen --path=vendor/bundle --jobs=4 --retry=3 + - save_cache: + key: v1-bundle-{{ checksum "Gemfile.lock" }}- + paths: + - vendor/bundle + - run: + name: Run Tests + command: bundle exec rake ci:specs + - store_test_results: + name: Store test results + path: tmp/test-results + - run: + name: Build documentation + command: bundle exec rake ci:doc + - store_artifacts: + name: Saves documentation + path: doc + - persist_to_workspace: + root: . + paths: + - vendor/bundle + +workflows: + version: 2 + + trunk: + jobs: + - build: + <<: *master_only + - gem/build: + <<: *master_only + executor: ruby-latest + name: gem-build + requires: + - build + + pull-requests: + jobs: + - build: + <<: *pr_only + - gem/build: + <<: *pr_only + executor: ruby-latest + name: gem-build + requires: + - build + - pre-release-approval: + <<: *pr_only + type: approval + requires: + - gem-build + - gem/publish: + <<: *pr_only + name: gem-publish + to_rubygems: true + pre_release: true + requires: + - pre-release-approval + context: artifact_publishing + + final-release: + jobs: + - build: + <<: *version_tags_only + - gem/build: + <<: *version_tags_only + executor: ruby-latest + name: gem-build + requires: + - build + - gem/publish: + <<: *version_tags_only + name: gem-publish + to_rubygems: true + pre_release: false + requires: + - gem-build + context: artifact_publishing From 65580d017c9c4088051d2b63c90492c145cf633c Mon Sep 17 00:00:00 2001 From: John Wilkinson Date: Fri, 25 Mar 2022 11:18:51 -0700 Subject: [PATCH 29/31] remove Gemfile.lock Avoids confusion about sidekiq-pro being under rubygems when it's in fact not available there --- Gemfile.lock | 61 ---------------------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 Gemfile.lock diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index e06da17..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,61 +0,0 @@ -PATH - remote: . - specs: - simplekiq (1.0.0) - sidekiq (~> 5.2.9) - sidekiq-ent (>= 1.8.1) - -GEM - remote: https://rubygems.org/ - specs: - coderay (1.1.3) - concurrent-ruby (1.1.9) - connection_pool (2.2.5) - diff-lcs (1.5.0) - einhorn (0.7.4) - method_source (1.0.0) - pry (0.13.1) - coderay (~> 1.1) - method_source (~> 1.0) - rack (2.2.3) - rack-protection (2.1.0) - rack - rake (12.3.3) - redis (4.1.4) - rspec (3.10.0) - rspec-core (~> 3.10.0) - rspec-expectations (~> 3.10.0) - rspec-mocks (~> 3.10.0) - rspec-core (3.10.1) - rspec-support (~> 3.10.0) - rspec-expectations (3.10.1) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-mocks (3.10.2) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-support (3.10.3) - sidekiq (5.2.9) - connection_pool (~> 2.2, >= 2.2.2) - rack (~> 2.0) - rack-protection (>= 1.5.0) - redis (>= 3.3.5, < 4.2) - sidekiq-ent (1.8.1) - einhorn (= 0.7.4) - sidekiq (>= 5.2.3) - sidekiq-pro (>= 4.0.4) - sidekiq-pro (5.0.0) - concurrent-ruby (>= 1.0.5) - sidekiq (>= 5.2.7) - -PLATFORMS - ruby - -DEPENDENCIES - pry - rake (~> 12.0) - rspec (~> 3.2) - simplekiq! - -BUNDLED WITH - 2.1.4 From 7672c427cea436e59ad915199cbc04d1834078ad Mon Sep 17 00:00:00 2001 From: John Wilkinson Date: Fri, 25 Mar 2022 11:46:05 -0700 Subject: [PATCH 30/31] Remove travis.yml --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d4d04d5..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -language: ruby -cache: bundler -rvm: - - 2.6.6 -before_install: gem install bundler -v 2.1.4 From d67bc223bbb7ff62563cf60f2f82043877a7033e Mon Sep 17 00:00:00 2001 From: John Wilkinson Date: Fri, 25 Mar 2022 14:46:37 -0700 Subject: [PATCH 31/31] Oops these aren't supposed to be here Artifacts from the git history migration --- gems/simplekiq/.gitignore | 11 ----------- gems/simplekiq/.rspec | 3 --- gems/simplekiq/.travis.yml | 6 ------ 3 files changed, 20 deletions(-) delete mode 100644 gems/simplekiq/.gitignore delete mode 100644 gems/simplekiq/.rspec delete mode 100644 gems/simplekiq/.travis.yml diff --git a/gems/simplekiq/.gitignore b/gems/simplekiq/.gitignore deleted file mode 100644 index b04a8c8..0000000 --- a/gems/simplekiq/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -/.bundle/ -/.yardoc -/_yardoc/ -/coverage/ -/doc/ -/pkg/ -/spec/reports/ -/tmp/ - -# rspec failure tracking -.rspec_status diff --git a/gems/simplekiq/.rspec b/gems/simplekiq/.rspec deleted file mode 100644 index 34c5164..0000000 --- a/gems/simplekiq/.rspec +++ /dev/null @@ -1,3 +0,0 @@ ---format documentation ---color ---require spec_helper diff --git a/gems/simplekiq/.travis.yml b/gems/simplekiq/.travis.yml deleted file mode 100644 index d4d04d5..0000000 --- a/gems/simplekiq/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -language: ruby -cache: bundler -rvm: - - 2.6.6 -before_install: gem install bundler -v 2.1.4