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 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