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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6bd6ebdb788c39b7d3cb434b60f94d927dacd47c
4
- data.tar.gz: b463178c06885308a2192bbcf3d549c52391acbd
2
+ SHA256:
3
+ metadata.gz: e275565357790942bf8c3d92e452921a5650122cf12679a399bafe67818ea75c
4
+ data.tar.gz: 3f8c9c584b71385fd679c93cdf208b924e45efea43562062ded98b7a83e05d86
5
5
  SHA512:
6
- metadata.gz: 7e976084d28bba01327e0a8c41b8a948e27f6b3041d6a3a31665825d66b57225867ec410e9bf4f24afcf13241f41b855e1150601f37de37425fc72ee9582b5a4
7
- data.tar.gz: c05e17653dac7716b9f52958b0bfb295517d614f5f97a00ffb91ad7100e83fbbf20f526f08f8315a1344829d01bd992c4920a7b0881aec4fea9b68eb0d259ff3
6
+ metadata.gz: 0e197b5b10ba2e569b8e7df245a607046831af3412b5417dc3084e986af27088240c61de963a9e60e6a1431921014c92a09b9f4dd0aec9003c6dd3ffe0ce84f4
7
+ data.tar.gz: cdfa840985dfcea75efea45f7dc5bc42e0aed4933cd8e02c996e46f4d8a58300c1feb47fe5fd5a6f46930b50e5b782525ed9c41434cb4cf21afcbe4ad8158c4e
@@ -4,10 +4,9 @@ services:
4
4
  - redis-server
5
5
  cache: bundler
6
6
  rvm:
7
- - 2.3.8
8
- - 2.4.5
9
- - 2.5.3
10
- - 2.6.0
7
+ - 2.4.6
8
+ - 2.5.5
9
+ - 2.6.3
11
10
  before_install:
12
11
  # Bundler 2.0 needs a newer RubyGems
13
12
  - gem update --system
@@ -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.3.0...HEAD
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
@@ -3,6 +3,7 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  gem 'rake'
6
+ gem 'appsignal'
6
7
 
7
8
  group :test do
8
9
  gem 'rspec'
@@ -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
@@ -0,0 +1,12 @@
1
+ default: &defaults
2
+ push_api_key: "<%= ENV['APPSIGNAL_PUSH_API_KEY'] %>"
3
+ name: "Pallets"
4
+ debug: true
5
+
6
+ development:
7
+ <<: *defaults
8
+ active: true
9
+
10
+ production:
11
+ <<: *defaults
12
+ active: true
@@ -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
@@ -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 ||= begin
55
- logger = Pallets::Logger.new(STDOUT)
56
- # TODO: Ruby 2.4 supports Logger initialization with the arguments below, so
57
- # we can drop this after we drop support for Ruby 2.3
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
- # TODO: Remove option after dropping support for Ruby 2.3
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, quirks_mode: true)
11
+ JSON.parse(data)
13
12
  end
14
13
  end
15
14
  end
@@ -1,3 +1,3 @@
1
1
  module Pallets
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -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 = task.run
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
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.executables = ['pallets']
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.required_ruby_version = '>= 2.3'
21
+ spec.required_ruby_version = '>= 2.4'
22
22
 
23
23
  spec.add_dependency 'redis'
24
24
  spec.add_dependency 'msgpack'
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.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-04-07 00:00:00.000000000 Z
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.3'
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
- rubyforge_project:
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