pallets 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|