background_worker 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +11 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +64 -0
- data/Rakefile +1 -0
- data/background_worker.gemspec +26 -0
- data/lib/background_worker/base.rb +86 -0
- data/lib/background_worker/config.rb +40 -0
- data/lib/background_worker/persistent_state.rb +53 -0
- data/lib/background_worker/uid.rb +25 -0
- data/lib/background_worker/version.rb +3 -0
- data/lib/background_worker/worker_execution.rb +44 -0
- data/lib/background_worker.rb +42 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d96a3cbffa01ee490b1a2542a80b8fdf9593aacc
|
4
|
+
data.tar.gz: 609ef34234495ba8fa0f17da87ead826a2c134b5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2ae408b3eeb8261323d747d279c518a9a8e07906b86eb9b5c74d4d7b9830e58ef16dd146b480d4407e1a7fe7054d894c5ac7f8f65a29a48d5f063cd00ae61b8c
|
7
|
+
data.tar.gz: dd3a09b5ebe64ec8bffd43d8381b1024c1cc3344f74e24ba0cc423b7b0ad12cd37032e666ad50a4679eaed113f4f471d6104ee4fab1b2a834dd4fc67f1430820
|
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 The Sealink Travel Group
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
Background Worker
|
2
|
+
=================
|
3
|
+
|
4
|
+
Provides a worker abstraction with an additional status channel.
|
5
|
+
|
6
|
+
Start by making a worker class which extends from BackgroundWorker::Base
|
7
|
+
|
8
|
+
class MyWorker < BackgroundWorker::Base
|
9
|
+
def my_task(options={})
|
10
|
+
report_progress('Starting')
|
11
|
+
if options[:message].blank?
|
12
|
+
report_failed("No message provided")
|
13
|
+
return
|
14
|
+
end
|
15
|
+
|
16
|
+
puts options[:message]
|
17
|
+
{original_message: message}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Then, when you want to perform a task in the background, use
|
22
|
+
klass#perform_in_background which exists in Base:
|
23
|
+
|
24
|
+
worker_id = MyWorker.perform_in_background(:my_task, message: "hello!")
|
25
|
+
|
26
|
+
# Backgrounded
|
27
|
+
|
28
|
+
By default this will call your instance method in the foreground -- you have to
|
29
|
+
provide an #enqueue_with configuration like so:
|
30
|
+
|
31
|
+
BackgroundWorker.configure(
|
32
|
+
enqueue_with: -> klass, method_name, options {
|
33
|
+
Resque.enqueue(klass, method_name, options)
|
34
|
+
}
|
35
|
+
)
|
36
|
+
|
37
|
+
This is independent of the status reporting which (currently) always uses Redis.
|
38
|
+
|
39
|
+
# Getting the status
|
40
|
+
|
41
|
+
The worker_id you are returned can be used to get the status, and
|
42
|
+
whether the worker has finished successfully, failed, or in progress:
|
43
|
+
|
44
|
+
state = BackgroundWorker.get_state_of(worker_id)
|
45
|
+
|
46
|
+
The state is represented by a hash with the following keys:
|
47
|
+
|
48
|
+
message: Reported message
|
49
|
+
detailed_message: Detailed version of above when provided
|
50
|
+
status: :successful, :failed, or null if still processing
|
51
|
+
completed: True if report_failed, report_successful called (or worker
|
52
|
+
finished without exception -- which calls report_successful)
|
53
|
+
data: Arbitrary data returned by worker method on success or report_failed
|
54
|
+
|
55
|
+
If an exception is raised, the worker will call #report_failed with the
|
56
|
+
details. You can provide a callback with #after_exception in the config
|
57
|
+
|
58
|
+
# INSTALLATION
|
59
|
+
|
60
|
+
gem install background_worker
|
61
|
+
|
62
|
+
or add to your Gemfile:
|
63
|
+
gem 'background_worker'
|
64
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'background_worker/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'background_worker'
|
8
|
+
spec.version = BackgroundWorker::VERSION
|
9
|
+
spec.authors = ['Michael Noack', 'Adam Davies', 'Alessandro Berardi']
|
10
|
+
spec.email = ['development@travellink.com.au',
|
11
|
+
'adzdavies@gmail.com',
|
12
|
+
'berardialessandro@gmail.com']
|
13
|
+
spec.summary = 'Background worker abstraction with status updates'
|
14
|
+
spec.description = 'See README for full details'
|
15
|
+
spec.homepage = 'http://github.com/sealink/background_worker'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0")
|
19
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'rubocop'
|
26
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module BackgroundWorker
|
2
|
+
class Base
|
3
|
+
attr_accessor :uid, :state
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
Time.zone = Setting.time_zone
|
7
|
+
|
8
|
+
@uid = options['uid']
|
9
|
+
|
10
|
+
# Store state persistently, to enable status checkups & progress reporting
|
11
|
+
@state = BackgroundWorker::PersistentState.new(@uid, options.except('uid'))
|
12
|
+
log("Created #{self.class}")
|
13
|
+
log("Options are: #{options.pretty_inspect}")
|
14
|
+
end
|
15
|
+
|
16
|
+
# Report progress...
|
17
|
+
def report_progress(message)
|
18
|
+
state.message = message
|
19
|
+
state.save
|
20
|
+
end
|
21
|
+
|
22
|
+
# Report a minor progress -- may get called a lot, so don't save it so often
|
23
|
+
def report_minor_progress(message)
|
24
|
+
state.message = message
|
25
|
+
|
26
|
+
# Only report minor events once per second
|
27
|
+
@last_report ||= Time.now - 2
|
28
|
+
time_elapsed = Time.now - @last_report
|
29
|
+
return unless time_elapsed > 1
|
30
|
+
|
31
|
+
@last_report = Time.now
|
32
|
+
state.save
|
33
|
+
end
|
34
|
+
|
35
|
+
def report_successful(message = 'Finished successfully')
|
36
|
+
state.set_completed(message, :successful)
|
37
|
+
end
|
38
|
+
|
39
|
+
def report_failed(message = 'Failed', detailed_message = nil)
|
40
|
+
state.detailed_message = detailed_message
|
41
|
+
state.set_completed(message, :failed)
|
42
|
+
end
|
43
|
+
|
44
|
+
def logger
|
45
|
+
BackgroundWorker.logger
|
46
|
+
end
|
47
|
+
|
48
|
+
def log(message, options = {})
|
49
|
+
severity = options.fetch(:severity, :info)
|
50
|
+
logger.send(severity, "uid=#{uid} #{message}")
|
51
|
+
end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
def get_state_of(worker_id)
|
55
|
+
BackgroundWorker::PersistentState.get_state_of(worker_id)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Public method to do in background...
|
59
|
+
def perform_in_background(method_name, options = {})
|
60
|
+
method_name = method_name.to_sym
|
61
|
+
options[:uid] ||= BackgroundWorker::Uid.new(to_s, method_name).generate
|
62
|
+
|
63
|
+
# Store into shared-cache before kicking job off
|
64
|
+
BackgroundWorker::PersistentState.new(options[:uid], options.except(:uid))
|
65
|
+
|
66
|
+
# Enqueue to the background queue
|
67
|
+
BackgroundWorker.enqueue(self, method_name, options)
|
68
|
+
|
69
|
+
options[:uid]
|
70
|
+
end
|
71
|
+
|
72
|
+
# This method is called by the job runner
|
73
|
+
#
|
74
|
+
# It will just call your preferred method in the worker.
|
75
|
+
def perform(method_name, options = {})
|
76
|
+
BackgroundWorker.verify_active_connections!
|
77
|
+
|
78
|
+
|
79
|
+
worker = new(options)
|
80
|
+
execution = WorkerExecution.new(worker, method_name, options)
|
81
|
+
execution.call
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module BackgroundWorker
|
4
|
+
class Config
|
5
|
+
|
6
|
+
attr_reader :logger
|
7
|
+
|
8
|
+
# Configuration includes following options:
|
9
|
+
# logger: what logger to user throughout
|
10
|
+
# enqueue_with: callback to execute the process
|
11
|
+
# after_exception: callback to handle exceptions (for example, error reporting service)
|
12
|
+
#
|
13
|
+
# eg:
|
14
|
+
# Config.new(
|
15
|
+
# logger: Rails.logger,
|
16
|
+
# enqueue_with: -> klass, method_name, opts { Resque.enqueue(klass, method_name, opts) },
|
17
|
+
# after_exception: -> e { Airbrake.notify(e) }
|
18
|
+
# )
|
19
|
+
def initialize(attrs)
|
20
|
+
@logger = attrs.fetch(:logger, ::Logger.new(STDOUT))
|
21
|
+
@enqueue_with = attrs.fetch(:enqueue_with, method(:foreground_enqueue))
|
22
|
+
@after_exception = attrs.fetch(:after_exception, method(:default_after_exception))
|
23
|
+
end
|
24
|
+
|
25
|
+
# Callback fired when an exception occurs
|
26
|
+
def after_exception(e)
|
27
|
+
@after_exception.call(e)
|
28
|
+
end
|
29
|
+
|
30
|
+
def foreground_enqueue(klass, method_name, opts)
|
31
|
+
klass.perform(method_name, opts)
|
32
|
+
end
|
33
|
+
|
34
|
+
def default_after_exception(e)
|
35
|
+
logger.error '** No after_exception handler installed **'
|
36
|
+
logger.error "Exception: #{e}"
|
37
|
+
logger.error "#{e.backtrace.join("\n")}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Progress reporter is used by background processes, to communicate with
|
2
|
+
# standard rails controllers.
|
3
|
+
#
|
4
|
+
# It works by storing a hash of all progress-report data in a redis value
|
5
|
+
# keyed by the worker_uid.
|
6
|
+
module BackgroundWorker
|
7
|
+
class PersistentState
|
8
|
+
attr_accessor :message, :detailed_message, :status, :completed, :data
|
9
|
+
|
10
|
+
def initialize(worker_uid, data)
|
11
|
+
@message = 'Waiting for task to queue...'
|
12
|
+
@status = :processing
|
13
|
+
@completed = false
|
14
|
+
|
15
|
+
@worker_uid = worker_uid
|
16
|
+
@data = data
|
17
|
+
save
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_completed(message, status)
|
21
|
+
self.status = status
|
22
|
+
self.message = message
|
23
|
+
|
24
|
+
self.completed = true
|
25
|
+
save
|
26
|
+
end
|
27
|
+
|
28
|
+
# Save persistently (well for an hour at least)
|
29
|
+
def save
|
30
|
+
Rails.cache.write(@worker_uid, generate_persistent_hash, expires_in: 1.hour)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get a report out the queue
|
34
|
+
# (was .get_report, then .progress)
|
35
|
+
def self.get_state_of(worker_uid)
|
36
|
+
Rails.cache.read(worker_uid)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# Generate a hash of this objects state
|
42
|
+
# (representing this status progress report)
|
43
|
+
def generate_persistent_hash
|
44
|
+
{
|
45
|
+
message: message,
|
46
|
+
detailed_message: detailed_message,
|
47
|
+
status: status,
|
48
|
+
completed: completed,
|
49
|
+
data: data
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Generates a unique identifier for a particular job identified by class_name/method
|
2
|
+
module BackgroundWorker
|
3
|
+
class Uid
|
4
|
+
attr_reader :class_name, :method
|
5
|
+
|
6
|
+
def initialize(class_name, method)
|
7
|
+
@class_name = class_name
|
8
|
+
@method = method
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate
|
12
|
+
"#{generate_uid_name}:#{generate_uid_hash}"
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def generate_uid_hash
|
18
|
+
::Digest::MD5.hexdigest("#{class_name}:#{method}:#{rand(1 << 64)}:#{Time.now}")
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate_uid_name
|
22
|
+
"#{class_name.underscore}/#{method}".split('/').join(':')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module BackgroundWorker
|
2
|
+
class WorkerExecution
|
3
|
+
attr_reader :worker, :method_name, :options
|
4
|
+
|
5
|
+
def initialize(worker, method_name, options)
|
6
|
+
fail ArgumentError, "'uid' is required to identify worker" unless options['uid'].present?
|
7
|
+
@worker = worker
|
8
|
+
@method_name = method_name
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
worker.send(method_name, options)
|
14
|
+
report_implicitly_successful unless completed?
|
15
|
+
|
16
|
+
rescue StandardError => e
|
17
|
+
log_worker_error(e)
|
18
|
+
BackgroundWorker.after_exception.call(e)
|
19
|
+
|
20
|
+
ensure
|
21
|
+
log_worker_finality
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def completed?
|
27
|
+
worker.state.completed
|
28
|
+
end
|
29
|
+
|
30
|
+
def report_implicitly_successful
|
31
|
+
worker.report_successful
|
32
|
+
end
|
33
|
+
|
34
|
+
def log_worker_error(e)
|
35
|
+
worker.log("Implicit failure: Exception: #{e}", severity: :error)
|
36
|
+
worker.report_failed("An unhandled error occurred: #{e}") unless completed?
|
37
|
+
end
|
38
|
+
|
39
|
+
def log_worker_finality
|
40
|
+
worker.log "Final state: #{worker.state.data}"
|
41
|
+
worker.log "Job was #{worker.state.status}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'background_worker/version'
|
2
|
+
require 'background_worker/config'
|
3
|
+
require 'background_worker/uid'
|
4
|
+
require 'background_worker/base'
|
5
|
+
require 'background_worker/persistent_state'
|
6
|
+
|
7
|
+
module BackgroundWorker
|
8
|
+
# Configure worker
|
9
|
+
#
|
10
|
+
# eg:
|
11
|
+
# BackgroundWorker.configure(
|
12
|
+
# logger: Rails.logger,
|
13
|
+
# enqueue_with: -> klass, method_name, opts { Resque.enqueue(klass, method_name, opts) },
|
14
|
+
# after_exception: -> e { Airbrake.notify(e) }
|
15
|
+
# )
|
16
|
+
def self.configure(options)
|
17
|
+
@config = Config.new(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.enqueue(klass, method_name, options)
|
21
|
+
config.enqueue_with.call(klass, method_name, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.logger
|
25
|
+
config.logger
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.verify_active_connections!
|
29
|
+
Rails.cache.reconnect if defined?(Rails)
|
30
|
+
ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.after_exception(e)
|
34
|
+
config.after_exception(e)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.config
|
38
|
+
raise "Not configured!" unless @config
|
39
|
+
@config
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: background_worker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Noack
|
8
|
+
- Adam Davies
|
9
|
+
- Alessandro Berardi
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2015-04-30 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: bundler
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ">="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: rake
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
type: :development
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: rubocop
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
type: :development
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
description: See README for full details
|
58
|
+
email:
|
59
|
+
- development@travellink.com.au
|
60
|
+
- adzdavies@gmail.com
|
61
|
+
- berardialessandro@gmail.com
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- ".rubocop.yml"
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- background_worker.gemspec
|
72
|
+
- lib/background_worker.rb
|
73
|
+
- lib/background_worker/base.rb
|
74
|
+
- lib/background_worker/config.rb
|
75
|
+
- lib/background_worker/persistent_state.rb
|
76
|
+
- lib/background_worker/uid.rb
|
77
|
+
- lib/background_worker/version.rb
|
78
|
+
- lib/background_worker/worker_execution.rb
|
79
|
+
homepage: http://github.com/sealink/background_worker
|
80
|
+
licenses:
|
81
|
+
- MIT
|
82
|
+
metadata: {}
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 2.4.5
|
100
|
+
signing_key:
|
101
|
+
specification_version: 4
|
102
|
+
summary: Background worker abstraction with status updates
|
103
|
+
test_files: []
|