background_job 0.0.1.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/specs.yml +34 -0
- data/.gitignore +11 -0
- data/.rspec +1 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +58 -0
- data/LICENSE.txt +21 -0
- data/README.md +247 -0
- data/Rakefile +2 -0
- data/background_job.gemspec +39 -0
- data/bin/console +14 -0
- data/bin/setup +6 -0
- data/docker-compose.yml +6 -0
- data/lib/background-job.rb +3 -0
- data/lib/background_job/configuration/base.rb +102 -0
- data/lib/background_job/configuration/faktory.rb +6 -0
- data/lib/background_job/configuration/middleware_chain.rb +109 -0
- data/lib/background_job/configuration/sidekiq.rb +23 -0
- data/lib/background_job/configuration.rb +63 -0
- data/lib/background_job/errors.rb +24 -0
- data/lib/background_job/jobs/faktory.rb +87 -0
- data/lib/background_job/jobs/job.rb +126 -0
- data/lib/background_job/jobs/sidekiq.rb +75 -0
- data/lib/background_job/jobs.rb +8 -0
- data/lib/background_job/lock.rb +141 -0
- data/lib/background_job/lock_digest.rb +36 -0
- data/lib/background_job/middleware/unique_job/faktory.rb +41 -0
- data/lib/background_job/middleware/unique_job/sidekiq.rb +48 -0
- data/lib/background_job/middleware/unique_job.rb +67 -0
- data/lib/background_job/mixin/faktory.rb +56 -0
- data/lib/background_job/mixin/shared_interface.rb +49 -0
- data/lib/background_job/mixin/sidekiq.rb +61 -0
- data/lib/background_job/mixin.rb +6 -0
- data/lib/background_job/redis_pool.rb +28 -0
- data/lib/background_job/testing.rb +76 -0
- data/lib/background_job/unique_job.rb +84 -0
- data/lib/background_job/version.rb +5 -0
- data/lib/background_job.rb +87 -0
- metadata +131 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Server midleware for Faktory
|
4
|
+
#
|
5
|
+
# @see https://github.com/contribsys/faktory_worker_ruby/wiki/Middleware
|
6
|
+
module BackgroundJob
|
7
|
+
module Middleware
|
8
|
+
class UniqueJob
|
9
|
+
module Faktory
|
10
|
+
def self.bootstrap
|
11
|
+
if defined?(::Faktory)
|
12
|
+
::Faktory.configure_worker do |config|
|
13
|
+
config.worker_middleware do |chain|
|
14
|
+
chain.add Worker
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Worker middleware runs around the execution of a job
|
21
|
+
class Worker
|
22
|
+
def call(_jobinst, payload)
|
23
|
+
if payload.is_a?(Hash) && (unique_lock = unique_job_lock(payload))
|
24
|
+
unique_lock.unlock
|
25
|
+
end
|
26
|
+
yield
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def unique_job_lock(payload)
|
32
|
+
return unless payload['uniq'].is_a?(Hash)
|
33
|
+
|
34
|
+
unique_job = ::BackgroundJob::UniqueJob.coerce(payload['uniq'])
|
35
|
+
unique_job&.lock
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
|
4
|
+
# Provides the Sidekiq middleware that make the unique job control work
|
5
|
+
#
|
6
|
+
# @see https://github.com/contribsys/faktory_worker_ruby/wiki/Middleware
|
7
|
+
module BackgroundJob
|
8
|
+
module Middleware
|
9
|
+
class UniqueJob
|
10
|
+
module Sidekiq
|
11
|
+
def self.bootstrap
|
12
|
+
if defined?(::Sidekiq)
|
13
|
+
::Sidekiq.configure_worker do |config|
|
14
|
+
config.worker_middleware do |chain|
|
15
|
+
chain.add Worker
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Worker middleware runs around the execution of a job
|
22
|
+
class Worker
|
23
|
+
# @param jobinst [Object] the worker/job instance
|
24
|
+
# @param payload [Hash] the full job payload
|
25
|
+
# * @see https://github.com/mperham/sidekiq/wiki/Job-Format
|
26
|
+
# @param queue [String] the name of the queue the job was pulled from
|
27
|
+
# @yield the next middleware in the chain or worker `perform` method
|
28
|
+
# @return [Void]
|
29
|
+
def call(_jobinst, payload, _queue)
|
30
|
+
if payload.is_a?(Hash) && (unique_lock = unique_job_lock(payload))
|
31
|
+
unique_lock.unlock
|
32
|
+
end
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def unique_job_lock(job)
|
39
|
+
return unless job['uniq'].is_a?(Hash)
|
40
|
+
|
41
|
+
unique_job = ::BackgroundJob::UniqueJob.coerce(job['uniq'])
|
42
|
+
unique_job&.lock
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'background_job/lock'
|
4
|
+
require 'background_job/lock_digest'
|
5
|
+
|
6
|
+
module BackgroundJob
|
7
|
+
module Middleware
|
8
|
+
# This middleware uses an external redis queue to control duplications. The locking key
|
9
|
+
# is composed of job class and its arguments. Before enqueue new jobs it will check if have a "lock" active.
|
10
|
+
# The TTL of lock is 1 week as default. TTL is important to ensure locks won't last forever.
|
11
|
+
class UniqueJob
|
12
|
+
def self.bootstrap(service:)
|
13
|
+
services = Dir[File.expand_path('../unique_job/*.rb', __FILE__)].map { |f| File.basename(f, '.rb').to_sym }
|
14
|
+
unless services.include?(service)
|
15
|
+
msg = "UniqueJob is not supported for the `%<service>p' service. Supported options are: %<services>s."
|
16
|
+
raise BackgroundJob::Error, format(msg, service: service.to_sym, services: services.map { |s| "`:#{s}'" }.join(', '))
|
17
|
+
end
|
18
|
+
if (require("background_job/middleware/unique_job/#{service}"))
|
19
|
+
class_name = service.to_s.split('_').collect!{ |w| w.capitalize }.join
|
20
|
+
BackgroundJob::Middleware::UniqueJob.const_get(class_name).bootstrap
|
21
|
+
end
|
22
|
+
|
23
|
+
service_config = BackgroundJob.config_for(service)
|
24
|
+
service_config.unique_job_active = true
|
25
|
+
service_config.middleware.add(UniqueJob)
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(job, service)
|
29
|
+
if BackgroundJob.config_for(service).unique_job_active? &&
|
30
|
+
(uniq_lock = unique_job_lock(job: job, service: service))
|
31
|
+
return false if uniq_lock.locked? # Don't push job to server
|
32
|
+
|
33
|
+
# Add unique job information to the job payload
|
34
|
+
job.unique_job.lock = uniq_lock
|
35
|
+
job.payload['uniq'] = job.unique_job.to_hash
|
36
|
+
|
37
|
+
uniq_lock.lock
|
38
|
+
end
|
39
|
+
|
40
|
+
yield
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def unique_job_lock(job:, service:)
|
46
|
+
return unless job.unique_job?
|
47
|
+
|
48
|
+
digest = LockDigest.new(
|
49
|
+
*[service || job.options[:service], job.options[:queue]].compact,
|
50
|
+
across: job.unique_job.across,
|
51
|
+
)
|
52
|
+
Lock.new(
|
53
|
+
digest: digest.to_s,
|
54
|
+
lock_id: unique_job_lock_id(job),
|
55
|
+
ttl: job.unique_job.ttl,
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def unique_job_lock_id(job)
|
60
|
+
identifier_data = [job.job_class, job.payload.fetch('args'.freeze, [])]
|
61
|
+
Digest::SHA256.hexdigest(
|
62
|
+
MultiJson.dump(identifier_data, mode: :compat),
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frizen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './shared_interface'
|
4
|
+
|
5
|
+
module BackgroundJob
|
6
|
+
module Mixin
|
7
|
+
module Faktory
|
8
|
+
DEFAULT = {
|
9
|
+
queue: "default",
|
10
|
+
retry: 25
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def self.background_job_options(job_class_name, strict_check: false)
|
14
|
+
BackgroundJob.config.faktory.validate_strict_job!(job_class_name) if strict_check
|
15
|
+
options = {}
|
16
|
+
BackgroundJob.config.faktory.jobs[job_class_name]&.each do |key, value|
|
17
|
+
options[key] = value
|
18
|
+
end
|
19
|
+
if defined?(::Faktory)
|
20
|
+
::Faktory.default_job_options.each do |key, value|
|
21
|
+
options[key.to_sym] ||= value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
DEFAULT.each do |key, value|
|
25
|
+
options[key] ||= value
|
26
|
+
end
|
27
|
+
options
|
28
|
+
end
|
29
|
+
|
30
|
+
class Builder < Module
|
31
|
+
def initialize(**options)
|
32
|
+
@runtime_mod = Module.new do
|
33
|
+
define_method(:background_job_user_options) { options }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def extended(base)
|
38
|
+
base.include(::Faktory::Job) if defined?(::Faktory)
|
39
|
+
base.extend BackgroundJob::Mixin::SharedInterface
|
40
|
+
base.extend ClassMethods
|
41
|
+
base.extend @runtime_mod
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
def background_job_service
|
46
|
+
:faktory
|
47
|
+
end
|
48
|
+
|
49
|
+
def background_job_default_options
|
50
|
+
BackgroundJob::Mixin::Faktory.background_job_options(name)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frizen_string_literal: true
|
2
|
+
|
3
|
+
module BackgroundJob
|
4
|
+
module Mixin
|
5
|
+
module SharedInterface
|
6
|
+
def perform_async(*args)
|
7
|
+
build_job.with_args(*args).push
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform_in(interval, *args)
|
11
|
+
build_job.with_args(*args).at(interval).push
|
12
|
+
end
|
13
|
+
alias_method :perform_at, :perform_in
|
14
|
+
|
15
|
+
# This method should be overridden in the including class
|
16
|
+
# @return [Symbol]
|
17
|
+
# @see BackgroundJob::Mixin::Sidekiq::ClassMethods
|
18
|
+
# @see BackgroundJob::Mixin::Faktory::ClassMethods
|
19
|
+
#
|
20
|
+
# @abstract
|
21
|
+
def background_job_service
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
# This method should be overridden in the including class
|
26
|
+
# @return [Hash]
|
27
|
+
# @see BackgroundJob::Mixin::Sidekiq::ClassMethods
|
28
|
+
# @see BackgroundJob::Mixin::Faktory::ClassMethods
|
29
|
+
#
|
30
|
+
# @abstract
|
31
|
+
def background_job_default_options
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
# This method will be defined as a singleton method when the including class is extended
|
36
|
+
# @return [Hash] The hash of options to be passed to the background job
|
37
|
+
# @see BackgroundJob.mixin to see how this method is defined
|
38
|
+
def background_job_user_options
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def build_job
|
45
|
+
BackgroundJob.send(background_job_service, name, **background_job_default_options, **background_job_user_options)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frizen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './shared_interface'
|
4
|
+
|
5
|
+
module BackgroundJob
|
6
|
+
module Mixin
|
7
|
+
module Sidekiq
|
8
|
+
DEFAULT = {
|
9
|
+
queue: "default",
|
10
|
+
retry: true
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def self.background_job_options(job_class_name, strict_check: false)
|
14
|
+
BackgroundJob.config.sidekiq.validate_strict_job!(job_class_name) if strict_check
|
15
|
+
options = {}
|
16
|
+
BackgroundJob.config.sidekiq.jobs[job_class_name]&.each do |key, value|
|
17
|
+
options[key] = value
|
18
|
+
end
|
19
|
+
if defined?(::Sidekiq) && ::Sidekiq.respond_to?(:default_job_options)
|
20
|
+
::Sidekiq.default_job_options.each do |key, value|
|
21
|
+
options[key.to_sym] ||= value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
if defined?(::Sidekiq) && ::Sidekiq.respond_to?(:default_worker_options)
|
25
|
+
::Sidekiq.default_worker_options.each do |key, value|
|
26
|
+
options[key.to_sym] ||= value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
DEFAULT.each do |key, value|
|
30
|
+
options[key] ||= value
|
31
|
+
end
|
32
|
+
options
|
33
|
+
end
|
34
|
+
|
35
|
+
class Builder < Module
|
36
|
+
def initialize(**options)
|
37
|
+
@runtime_mod = Module.new do
|
38
|
+
define_method(:background_job_user_options) { options }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def extended(base)
|
43
|
+
base.include(::Sidekiq::Worker) if defined?(::Sidekiq)
|
44
|
+
base.extend BackgroundJob::Mixin::SharedInterface
|
45
|
+
base.extend ClassMethods
|
46
|
+
base.extend @runtime_mod
|
47
|
+
end
|
48
|
+
|
49
|
+
module ClassMethods
|
50
|
+
def background_job_service
|
51
|
+
:sidekiq
|
52
|
+
end
|
53
|
+
|
54
|
+
def background_job_default_options
|
55
|
+
BackgroundJob::Mixin::Sidekiq.background_job_options(name)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module BackgroundJob
|
6
|
+
class RedisPool
|
7
|
+
extend Forwardable
|
8
|
+
def_delegator :@connection, :with
|
9
|
+
|
10
|
+
module ConnectionPoolLike
|
11
|
+
def with
|
12
|
+
yield self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(connection)
|
17
|
+
if connection.respond_to?(:with)
|
18
|
+
@connection = connection
|
19
|
+
elsif connection.is_a?(::Redis)
|
20
|
+
@connection = connection
|
21
|
+
@connection.extend(ConnectionPoolLike)
|
22
|
+
else
|
23
|
+
@connection = connection ? ::Redis.new(connection) : ::Redis.new
|
24
|
+
@connection.extend(ConnectionPoolLike)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BackgroundJob
|
4
|
+
class Testing
|
5
|
+
class << self
|
6
|
+
def enable!
|
7
|
+
Thread.current[:background_job_testing] = true
|
8
|
+
end
|
9
|
+
|
10
|
+
def disable!
|
11
|
+
Thread.current[:background_job_testing] = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def enabled?
|
15
|
+
Thread.current[:background_job_testing] == true
|
16
|
+
end
|
17
|
+
|
18
|
+
def disabled?
|
19
|
+
!enabled?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
BackgroundJob::Testing.disable!
|
26
|
+
|
27
|
+
module BackgroundJob::Jobs
|
28
|
+
class << self
|
29
|
+
def jobs
|
30
|
+
@jobs ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
def push(job)
|
34
|
+
jobs.push(job)
|
35
|
+
end
|
36
|
+
|
37
|
+
def clear
|
38
|
+
jobs.clear
|
39
|
+
end
|
40
|
+
|
41
|
+
def size
|
42
|
+
jobs.size
|
43
|
+
end
|
44
|
+
|
45
|
+
def jobs_for(service: nil, class_name: nil)
|
46
|
+
filtered = jobs
|
47
|
+
if service
|
48
|
+
filtered = filtered.select do |job|
|
49
|
+
job.class.name.split("::").last.downcase == service.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
if class_name
|
53
|
+
filtered = filtered.select do |job|
|
54
|
+
job.job_class == class_name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
filtered
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module BackgroundJob::JobsInterceptorAdapter
|
63
|
+
def push
|
64
|
+
return super unless BackgroundJob::Testing.enabled?
|
65
|
+
|
66
|
+
normalize_before_push!
|
67
|
+
BackgroundJob::Jobs.push(self)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
BackgroundJob::SERVICES.each do |service_name, class_name|
|
72
|
+
require_relative "./jobs/#{service_name}"
|
73
|
+
|
74
|
+
klass = Object.const_get("BackgroundJob::Jobs::#{class_name}")
|
75
|
+
klass.prepend(BackgroundJob::JobsInterceptorAdapter)
|
76
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './lock'
|
4
|
+
|
5
|
+
module BackgroundJob
|
6
|
+
class UniqueJob
|
7
|
+
VALID_OPTIONS = {
|
8
|
+
across: %i[queue systemwide],
|
9
|
+
timeout: 604800, # 1 week
|
10
|
+
unlock_policy: %i[success start],
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
attr_reader :across, :timeout, :unlock_policy, :lock
|
14
|
+
|
15
|
+
# @options [Hash] Unique definitions
|
16
|
+
# @option [Symbol] :across Valid options are :queue and :systemwide. If jobs should not to be duplicated on
|
17
|
+
# current queue or the entire system
|
18
|
+
# @option [Integer] :timeout Amount of times in seconds. Timeout decides how long to wait for acquiring the lock.
|
19
|
+
# A default timeout is defined to 1 week so unique locks won't last forever.
|
20
|
+
# @option [Symbol] :unlock_policy Control when the unique lock is removed. The default value is `success`.
|
21
|
+
# The job will not unlock until it executes successfully, it will remain locked even if it raises an error and
|
22
|
+
# goes into the retry queue. The alternative value is `start` the job will unlock right before it starts executing
|
23
|
+
def initialize(across: :queue, timeout: nil, unlock_policy: :success, lock: nil)
|
24
|
+
unless VALID_OPTIONS[:across].include?(across.to_sym)
|
25
|
+
raise Error, format('Invalid `across: %<given>p` option. Only %<expected>p are allowed.',
|
26
|
+
given: across,
|
27
|
+
expected: VALID_OPTIONS[:across])
|
28
|
+
end
|
29
|
+
unless VALID_OPTIONS[:unlock_policy].include?(unlock_policy.to_sym)
|
30
|
+
raise Error, format('Invalid `unlock_policy: %<given>p` option. Only %<expected>p are allowed.',
|
31
|
+
given: unlock_policy,
|
32
|
+
expected: VALID_OPTIONS[:unlock_policy])
|
33
|
+
end
|
34
|
+
timeout = VALID_OPTIONS[:timeout] if timeout.to_i <= 0
|
35
|
+
|
36
|
+
@across = across.to_sym
|
37
|
+
@timeout = timeout.to_i
|
38
|
+
@unlock_policy = unlock_policy.to_sym
|
39
|
+
@lock = lock if lock.is_a?(BackgroundJob::Lock)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.coerce(value)
|
43
|
+
return unless value.is_a?(Hash)
|
44
|
+
|
45
|
+
new(
|
46
|
+
across: (value['across'] || value[:across] || :queue).to_sym,
|
47
|
+
timeout: (value['timeout'] || value[:timeout] || nil),
|
48
|
+
unlock_policy: (value['unlock_policy'] || value[:unlock_policy] || :success).to_sym,
|
49
|
+
).tap do |instance|
|
50
|
+
instance.lock = value['lock'] || value[:lock]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def ttl
|
55
|
+
Time.now.to_f + timeout
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_hash
|
59
|
+
{
|
60
|
+
'across' => (across.to_s if across),
|
61
|
+
'timeout' => timeout,
|
62
|
+
'unlock_policy' => (unlock_policy.to_s if unlock_policy),
|
63
|
+
}.tap do |hash|
|
64
|
+
hash['lock'] = lock.to_hash if lock
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def eql?(other)
|
69
|
+
return false unless other.is_a?(self.class)
|
70
|
+
|
71
|
+
[across, timeout, unlock_policy] == [other.across, other.timeout, other.unlock_policy]
|
72
|
+
end
|
73
|
+
alias == eql?
|
74
|
+
|
75
|
+
def lock=(value)
|
76
|
+
@lock = case value
|
77
|
+
when BackgroundJob::Lock then value
|
78
|
+
when Hash then BackgroundJob::Lock.coerce(value)
|
79
|
+
else
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'time'
|
5
|
+
require 'securerandom'
|
6
|
+
require 'multi_json'
|
7
|
+
|
8
|
+
require_relative 'background_job/version'
|
9
|
+
require_relative 'background_job/errors'
|
10
|
+
require_relative "background_job/redis_pool"
|
11
|
+
require_relative 'background_job/mixin'
|
12
|
+
require_relative 'background_job/jobs'
|
13
|
+
require_relative 'background_job/configuration'
|
14
|
+
# require_relative 'background_job/adapters/adapter'
|
15
|
+
# require_relative 'background_job/adapters/sidekiq'
|
16
|
+
# require_relative 'background_job/adapters/faktory'
|
17
|
+
|
18
|
+
# This is a central point of our background job enqueuing system.
|
19
|
+
# Example:
|
20
|
+
#
|
21
|
+
# Standard job.
|
22
|
+
# BackgroundJob.sidekiq('UserWorker', queue: 'default')
|
23
|
+
# .with_args(1)
|
24
|
+
# .push
|
25
|
+
# Schedule the time when a job will be executed.
|
26
|
+
# BackgroundJob.sidekiq('UserWorker')
|
27
|
+
# .with_args(1)
|
28
|
+
# .at(timestamp)
|
29
|
+
# .push
|
30
|
+
# BackgroundJob.sidekiq('UserWorker')
|
31
|
+
# .with_args(1)
|
32
|
+
# .in(10.minutes)
|
33
|
+
# .push
|
34
|
+
#
|
35
|
+
# Unique jobs.
|
36
|
+
# BackgroundJob.sidekiq('UserWorker', uniq: { across: :queue, timeout: 1.minute, unlock_policy: :start })
|
37
|
+
# .with_args(1)
|
38
|
+
# .push
|
39
|
+
module BackgroundJob
|
40
|
+
SERVICES = {
|
41
|
+
sidekiq: 'Sidekiq',
|
42
|
+
faktory: 'Faktory',
|
43
|
+
}.freeze
|
44
|
+
|
45
|
+
SERVICES.each do |id, name|
|
46
|
+
define_singleton_method(id) do |job_name, **options|
|
47
|
+
Jobs.const_get(name).new(job_name, **options)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.mixin(service, **options)
|
52
|
+
service = service.to_sym
|
53
|
+
unless SERVICES.key?(service)
|
54
|
+
raise Error, "Service `#{service}' is not supported. Supported services are: #{SERVICES.keys.join(', ')}"
|
55
|
+
end
|
56
|
+
require_relative "background_job/mixin/#{service}"
|
57
|
+
require_relative "background_job/jobs/#{service}"
|
58
|
+
|
59
|
+
module_name = service.to_s.split(/_/i).collect!{ |w| w.capitalize }.join
|
60
|
+
mod = Mixin.const_get(module_name)
|
61
|
+
mod::Builder.new(**options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.jid
|
65
|
+
SecureRandom.hex(12)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.config
|
69
|
+
@config ||= Configuration.new
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.configure
|
73
|
+
return unless block_given?
|
74
|
+
|
75
|
+
yield(config)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.config_for(service)
|
79
|
+
service = service.to_sym
|
80
|
+
unless SERVICES.key?(service)
|
81
|
+
raise Error, "Service `#{service}' is not supported. Supported services are: #{SERVICES.keys.join(', ')}"
|
82
|
+
end
|
83
|
+
service_config = config.send(service)
|
84
|
+
yield(service_config) if block_given?
|
85
|
+
service_config
|
86
|
+
end
|
87
|
+
end
|