pallets 0.4.0 → 0.5.0
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 +5 -5
- data/.travis.yml +3 -4
- data/CHANGELOG.md +12 -1
- data/Gemfile +1 -0
- data/examples/appsignal.rb +32 -0
- data/examples/config/appsignal.yml +12 -0
- data/examples/config_savvy.rb +11 -0
- data/lib/pallets.rb +10 -8
- data/lib/pallets/configuration.rb +16 -0
- data/lib/pallets/middleware/appsignal_instrumenter.rb +46 -0
- data/lib/pallets/middleware/job_logger.rb +26 -0
- data/lib/pallets/middleware/stack.rb +13 -0
- data/lib/pallets/serializers/json.rb +2 -3
- data/lib/pallets/version.rb +1 -1
- data/lib/pallets/worker.rb +7 -18
- data/pallets.gemspec +1 -1
- metadata +9 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e275565357790942bf8c3d92e452921a5650122cf12679a399bafe67818ea75c
|
|
4
|
+
data.tar.gz: 3f8c9c584b71385fd679c93cdf208b924e45efea43562062ded98b7a83e05d86
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e197b5b10ba2e569b8e7df245a607046831af3412b5417dc3084e986af27088240c61de963a9e60e6a1431921014c92a09b9f4dd0aec9003c6dd3ffe0ce84f4
|
|
7
|
+
data.tar.gz: cdfa840985dfcea75efea45f7dc5bc42e0aed4933cd8e02c996e46f4d8a58300c1feb47fe5fd5a6f46930b50e5b782525ed9c41434cb4cf21afcbe4ad8158c4e
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.0] - 2019-05-12
|
|
10
|
+
### Added
|
|
11
|
+
- wrap job execution with middleware (#38)
|
|
12
|
+
- use `Middleware::JobLogger` for job logging (#39)
|
|
13
|
+
- allow Appsignal instrumentation using `Middleware::AppsignalInstrumenter` (#40)
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
- support for Ruby 2.3 (#41)
|
|
17
|
+
|
|
9
18
|
## [0.4.0] - 2019-04-07
|
|
10
19
|
### Added
|
|
11
20
|
- give up workflow before it finishes by returning `false` in any of its tasks (#25)
|
|
@@ -47,6 +56,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
47
56
|
## 0.1.0 - 2018-09-29
|
|
48
57
|
- Pallets' inception <3
|
|
49
58
|
|
|
50
|
-
[Unreleased]: https://github.com/linkyndy/pallets/compare/compare/v0.
|
|
59
|
+
[Unreleased]: https://github.com/linkyndy/pallets/compare/compare/v0.5.0...HEAD
|
|
60
|
+
[0.5.0]: https://github.com/linkyndy/pallets/compare/v0.4.0...v0.5.0
|
|
61
|
+
[0.4.0]: https://github.com/linkyndy/pallets/compare/v0.3.0...v0.5.0
|
|
51
62
|
[0.3.0]: https://github.com/linkyndy/pallets/compare/v0.2.0...v0.3.0
|
|
52
63
|
[0.2.0]: https://github.com/linkyndy/pallets/compare/v0.1.0...v0.2.0
|
data/Gemfile
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'pallets'
|
|
2
|
+
require 'pallets/middleware/appsignal_instrumenter'
|
|
3
|
+
|
|
4
|
+
Appsignal.config = Appsignal::Config.new(
|
|
5
|
+
File.expand_path(File.dirname(__FILE__)),
|
|
6
|
+
"development"
|
|
7
|
+
)
|
|
8
|
+
Appsignal.start
|
|
9
|
+
Appsignal.start_logger
|
|
10
|
+
|
|
11
|
+
Pallets.configure do |c|
|
|
12
|
+
c.middleware << Pallets::Middleware::AppsignalInstrumenter
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Appsignaling < Pallets::Workflow
|
|
16
|
+
task 'Signaling'
|
|
17
|
+
task 'ReturningSignal' => 'Signaling'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Signaling < Pallets::Task
|
|
21
|
+
def run
|
|
22
|
+
puts context['signal']
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class ReturningSignal < Pallets::Task
|
|
27
|
+
def run
|
|
28
|
+
puts 'Ho!'
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Appsignaling.new(signal: 'Hey').run
|
data/examples/config_savvy.rb
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
require 'pallets'
|
|
2
2
|
|
|
3
|
+
class AnnounceProcessing
|
|
4
|
+
def self.call(worker, job, context)
|
|
5
|
+
puts "Starting to process job..."
|
|
6
|
+
yield
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
3
10
|
Pallets.configure do |c|
|
|
4
11
|
# Harness 4 Pallets workers per process
|
|
5
12
|
c.concurrency = 4
|
|
@@ -23,6 +30,10 @@ Pallets.configure do |c|
|
|
|
23
30
|
# Jobs will be retried up to 5 times upon failure. After that, they will be
|
|
24
31
|
# given up. Retry times are exponential and happen after: 7, 22, 87, 262, ...
|
|
25
32
|
c.max_failures = 5
|
|
33
|
+
|
|
34
|
+
# Job execution can be wrapped with middleware to provide custom logic.
|
|
35
|
+
# Anything that responds to `call` would do
|
|
36
|
+
c.middleware << AnnounceProcessing
|
|
26
37
|
end
|
|
27
38
|
|
|
28
39
|
class ConfigSavvy < Pallets::Workflow
|
data/lib/pallets.rb
CHANGED
|
@@ -9,6 +9,8 @@ require 'pallets/errors'
|
|
|
9
9
|
require 'pallets/graph'
|
|
10
10
|
require 'pallets/logger'
|
|
11
11
|
require 'pallets/manager'
|
|
12
|
+
require 'pallets/middleware/job_logger'
|
|
13
|
+
require 'pallets/middleware/stack'
|
|
12
14
|
require 'pallets/pool'
|
|
13
15
|
require 'pallets/scheduler'
|
|
14
16
|
require 'pallets/serializers/base'
|
|
@@ -50,15 +52,15 @@ module Pallets
|
|
|
50
52
|
end
|
|
51
53
|
end
|
|
52
54
|
|
|
55
|
+
def self.middleware
|
|
56
|
+
@middleware ||= configuration.middleware
|
|
57
|
+
end
|
|
58
|
+
|
|
53
59
|
def self.logger
|
|
54
|
-
@logger ||=
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
logger.level = Pallets::Logger::INFO
|
|
59
|
-
logger.formatter = Pallets::Logger::Formatters::Pretty.new
|
|
60
|
-
logger
|
|
61
|
-
end
|
|
60
|
+
@logger ||= Pallets::Logger.new(STDOUT,
|
|
61
|
+
level: Pallets::Logger::INFO,
|
|
62
|
+
formatter: Pallets::Logger::Formatters::Pretty.new
|
|
63
|
+
)
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
def self.logger=(logger)
|
|
@@ -30,6 +30,15 @@ module Pallets
|
|
|
30
30
|
# Serializer used for jobs
|
|
31
31
|
attr_accessor :serializer
|
|
32
32
|
|
|
33
|
+
# Middleware used to wrap job execution with custom logic. Acts like a stack
|
|
34
|
+
# and accepts callable objects (lambdas, procs, objects that respond to call)
|
|
35
|
+
# that take three arguments: the worker handling the job, the job hash and
|
|
36
|
+
# the context
|
|
37
|
+
#
|
|
38
|
+
# A minimal example of a middleware is:
|
|
39
|
+
# ->(worker, job, context, &b) { puts 'Hello World!'; b.call }
|
|
40
|
+
attr_reader :middleware
|
|
41
|
+
|
|
33
42
|
def initialize
|
|
34
43
|
@backend = :redis
|
|
35
44
|
@backend_args = {}
|
|
@@ -39,10 +48,17 @@ module Pallets
|
|
|
39
48
|
@job_timeout = 1_800 # 30 minutes
|
|
40
49
|
@max_failures = 3
|
|
41
50
|
@serializer = :json
|
|
51
|
+
@middleware = default_middleware
|
|
42
52
|
end
|
|
43
53
|
|
|
44
54
|
def pool_size
|
|
45
55
|
@pool_size || @concurrency + 1
|
|
46
56
|
end
|
|
57
|
+
|
|
58
|
+
def default_middleware
|
|
59
|
+
Middleware::Stack[
|
|
60
|
+
Middleware::JobLogger
|
|
61
|
+
]
|
|
62
|
+
end
|
|
47
63
|
end
|
|
48
64
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'appsignal'
|
|
2
|
+
|
|
3
|
+
module Pallets
|
|
4
|
+
module Middleware
|
|
5
|
+
class AppsignalInstrumenter
|
|
6
|
+
extend Appsignal::Hooks::Helpers
|
|
7
|
+
|
|
8
|
+
def self.call(worker, job, context)
|
|
9
|
+
job_status = nil
|
|
10
|
+
transaction = Appsignal::Transaction.create(
|
|
11
|
+
SecureRandom.uuid,
|
|
12
|
+
Appsignal::Transaction::BACKGROUND_JOB,
|
|
13
|
+
Appsignal::Transaction::GenericRequest.new(queue_start: job['created_at'])
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
Appsignal.instrument('perform_job.pallets') do
|
|
17
|
+
begin
|
|
18
|
+
yield
|
|
19
|
+
rescue Exception => ex
|
|
20
|
+
job_status = :failed
|
|
21
|
+
transaction.set_error(ex)
|
|
22
|
+
raise
|
|
23
|
+
ensure
|
|
24
|
+
transaction.set_action_if_nil("#{job['task_class']}#run (#{job['workflow_class']})")
|
|
25
|
+
transaction.params = filtered_context(context)
|
|
26
|
+
formatted_metadata(job).each { |kv| transaction.set_metadata(*kv) }
|
|
27
|
+
transaction.set_http_or_background_queue_start
|
|
28
|
+
Appsignal::Transaction.complete_current!
|
|
29
|
+
Appsignal.increment_counter('pallets_job_count', 1, status: job_status || :successful)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.filtered_context(context)
|
|
35
|
+
Appsignal::Utils::HashSanitizer.sanitize(
|
|
36
|
+
context,
|
|
37
|
+
Appsignal.config[:filter_parameters]
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.formatted_metadata(job)
|
|
42
|
+
job.map { |k, v| [k, truncate(string_or_inspect(v))] }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Pallets
|
|
2
|
+
module Middleware
|
|
3
|
+
class JobLogger
|
|
4
|
+
def self.call(worker, job, context)
|
|
5
|
+
Pallets.logger.info 'Started', extract_metadata(worker.id, job)
|
|
6
|
+
result = yield
|
|
7
|
+
Pallets.logger.info 'Done', extract_metadata(worker.id, job)
|
|
8
|
+
result
|
|
9
|
+
rescue => ex
|
|
10
|
+
Pallets.logger.warn "#{ex.class.name}: #{ex.message}", extract_metadata(worker.id, job)
|
|
11
|
+
Pallets.logger.warn ex.backtrace.join("\n"), extract_metadata(worker.id, job) unless ex.backtrace.nil?
|
|
12
|
+
raise
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.extract_metadata(wid, job)
|
|
16
|
+
{
|
|
17
|
+
wid: wid,
|
|
18
|
+
wfid: job['wfid'],
|
|
19
|
+
jid: job['jid'],
|
|
20
|
+
wf: job['workflow_class'],
|
|
21
|
+
tsk: job['task_class'],
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Pallets
|
|
2
|
+
module Middleware
|
|
3
|
+
# Array-like class that acts like a stack and additionally provides the
|
|
4
|
+
# means to wrap an operation with callable objects
|
|
5
|
+
class Stack < Array
|
|
6
|
+
def invoke(*args, &block)
|
|
7
|
+
reverse.inject(block) do |memo, middleware|
|
|
8
|
+
lambda { middleware.call(*args, &memo) }
|
|
9
|
+
end.call
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -4,12 +4,11 @@ module Pallets
|
|
|
4
4
|
module Serializers
|
|
5
5
|
class Json < Base
|
|
6
6
|
def dump(data)
|
|
7
|
-
|
|
8
|
-
JSON.generate(data, quirks_mode: true)
|
|
7
|
+
JSON.generate(data)
|
|
9
8
|
end
|
|
10
9
|
|
|
11
10
|
def load(data)
|
|
12
|
-
JSON.parse(data
|
|
11
|
+
JSON.parse(data)
|
|
13
12
|
end
|
|
14
13
|
end
|
|
15
14
|
end
|
data/lib/pallets/version.rb
CHANGED
data/lib/pallets/worker.rb
CHANGED
|
@@ -69,8 +69,6 @@ module Pallets
|
|
|
69
69
|
return
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
Pallets.logger.info "Started", extract_metadata(job_hash)
|
|
73
|
-
|
|
74
72
|
context = Context[
|
|
75
73
|
serializer.load_context(backend.get_context(job_hash['wfid']))
|
|
76
74
|
]
|
|
@@ -78,7 +76,9 @@ module Pallets
|
|
|
78
76
|
task_class = Pallets::Util.constantize(job_hash["task_class"])
|
|
79
77
|
task = task_class.new(context)
|
|
80
78
|
begin
|
|
81
|
-
task_result =
|
|
79
|
+
task_result = middleware.invoke(self, job_hash, context) do
|
|
80
|
+
task.run
|
|
81
|
+
end
|
|
82
82
|
rescue => ex
|
|
83
83
|
handle_job_error(ex, job, job_hash)
|
|
84
84
|
else
|
|
@@ -91,8 +91,6 @@ module Pallets
|
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
def handle_job_error(ex, job, job_hash)
|
|
94
|
-
Pallets.logger.warn "#{ex.class.name}: #{ex.message}", extract_metadata(job_hash)
|
|
95
|
-
Pallets.logger.warn ex.backtrace.join("\n"), extract_metadata(job_hash) unless ex.backtrace.nil?
|
|
96
94
|
failures = job_hash.fetch('failures', 0) + 1
|
|
97
95
|
new_job = serializer.dump(job_hash.merge(
|
|
98
96
|
'failures' => failures,
|
|
@@ -106,7 +104,6 @@ module Pallets
|
|
|
106
104
|
backend.retry(new_job, job, retry_at)
|
|
107
105
|
else
|
|
108
106
|
backend.give_up(new_job, job)
|
|
109
|
-
Pallets.logger.info "Gave up after #{failures} failed attempts", extract_metadata(job_hash)
|
|
110
107
|
end
|
|
111
108
|
end
|
|
112
109
|
|
|
@@ -116,22 +113,10 @@ module Pallets
|
|
|
116
113
|
'reason' => 'returned_false'
|
|
117
114
|
))
|
|
118
115
|
backend.give_up(new_job, job)
|
|
119
|
-
Pallets.logger.info "Gave up after returning false", extract_metadata(job_hash)
|
|
120
116
|
end
|
|
121
117
|
|
|
122
118
|
def handle_job_success(context, job, job_hash)
|
|
123
119
|
backend.save(job_hash['wfid'], job, serializer.dump_context(context.buffer))
|
|
124
|
-
Pallets.logger.info "Done", extract_metadata(job_hash)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def extract_metadata(job_hash)
|
|
128
|
-
{
|
|
129
|
-
wid: id,
|
|
130
|
-
wfid: job_hash['wfid'],
|
|
131
|
-
jid: job_hash['jid'],
|
|
132
|
-
wf: job_hash['workflow_class'],
|
|
133
|
-
tsk: job_hash['task_class']
|
|
134
|
-
}
|
|
135
120
|
end
|
|
136
121
|
|
|
137
122
|
def backoff_in_seconds(count)
|
|
@@ -145,5 +130,9 @@ module Pallets
|
|
|
145
130
|
def serializer
|
|
146
131
|
@serializer ||= Pallets.serializer
|
|
147
132
|
end
|
|
133
|
+
|
|
134
|
+
def middleware
|
|
135
|
+
@middleware ||= Pallets.middleware
|
|
136
|
+
end
|
|
148
137
|
end
|
|
149
138
|
end
|
data/pallets.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pallets
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Horak
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2019-
|
|
11
|
+
date: 2019-05-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: redis
|
|
@@ -56,6 +56,8 @@ files:
|
|
|
56
56
|
- README.md
|
|
57
57
|
- Rakefile
|
|
58
58
|
- bin/pallets
|
|
59
|
+
- examples/appsignal.rb
|
|
60
|
+
- examples/config/appsignal.yml
|
|
59
61
|
- examples/config_savvy.rb
|
|
60
62
|
- examples/do_groceries.rb
|
|
61
63
|
- examples/hello_world.rb
|
|
@@ -75,6 +77,9 @@ files:
|
|
|
75
77
|
- lib/pallets/graph.rb
|
|
76
78
|
- lib/pallets/logger.rb
|
|
77
79
|
- lib/pallets/manager.rb
|
|
80
|
+
- lib/pallets/middleware/appsignal_instrumenter.rb
|
|
81
|
+
- lib/pallets/middleware/job_logger.rb
|
|
82
|
+
- lib/pallets/middleware/stack.rb
|
|
78
83
|
- lib/pallets/pool.rb
|
|
79
84
|
- lib/pallets/scheduler.rb
|
|
80
85
|
- lib/pallets/serializers/base.rb
|
|
@@ -98,15 +103,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
98
103
|
requirements:
|
|
99
104
|
- - ">="
|
|
100
105
|
- !ruby/object:Gem::Version
|
|
101
|
-
version: '2.
|
|
106
|
+
version: '2.4'
|
|
102
107
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
108
|
requirements:
|
|
104
109
|
- - ">="
|
|
105
110
|
- !ruby/object:Gem::Version
|
|
106
111
|
version: '0'
|
|
107
112
|
requirements: []
|
|
108
|
-
|
|
109
|
-
rubygems_version: 2.5.2.3
|
|
113
|
+
rubygems_version: 3.0.3
|
|
110
114
|
signing_key:
|
|
111
115
|
specification_version: 4
|
|
112
116
|
summary: Toy workflow engine, written in Ruby
|