pallets 0.8.0 → 0.9.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
2
  SHA256:
3
- metadata.gz: 46a631b6e48f2f1c4efe5bce09c5d74fa2cb064a78d5e546492c6c8ce1dec843
4
- data.tar.gz: 61f6414e16941de41defb30ac8dc9121703f75251e47422239c311a8b7749787
3
+ metadata.gz: ede234db1dfe8c744cf11e6e2fde3eb2e982e7764738715acbbad35710422274
4
+ data.tar.gz: a55b3a714dcc5db2a294b27ca53724a1236111e08f63078bcef5007bda65532c
5
5
  SHA512:
6
- metadata.gz: 4b6f77bd93576dc7dc2c5cf945d52c425a63178740c12fa958f0c18c4ca870da7b02d303c7b8e871504d84a13461f79edc52da2869069322f5a81943bdbd79df
7
- data.tar.gz: a2a71f4a2343927a94871523b7475f587bb64181e9c6a4654a40111453e2fc2f20e82a02da44adbc32865f68c53a95dd3a9c6f0b641cfd85c7939f1e06d3521f
6
+ metadata.gz: 74e23087ba192aeffeb7e167d751bfb6f85de4018fb3c16e992ac6119d7670d60862f41a8e1624817a25944409602e41ee38f13adad7b14a38b185d51142b644
7
+ data.tar.gz: 96ae4f2aa4edf5d27d366e7a35f16527e9d390a45794d306acc109b1be335682c8b1e13e7d708d7eb3451eb8b6ed96db37ba724da08abb13d7b84f3468869826
@@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.0] - 2020-07-05
10
+ ### Added
11
+ - limit number of jobs in given up set by number (#56)
12
+ - job duration and metadata to all task logs (#57)
13
+
14
+ ### Changed
15
+ - remove all related workflow keys when giving up on a job (#55)
16
+ - support redis-rb ~> 4.2 (#58)
17
+
18
+ ### Removed
19
+ - support for configuring custom loggers (#57)
20
+
9
21
  ## [0.8.0] - 2020-06-09
10
22
  ### Added
11
23
  - sync output in CLI (#49)
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2019 Andrei Horak
3
+ Copyright (c) 2020 Andrei Horak
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.com/linkyndy/pallets.svg?branch=master)](https://travis-ci.com/linkyndy/pallets)
4
4
 
5
- Toy workflow engine, written in Ruby
5
+ Simple and reliable workflow engine, written in Ruby
6
6
 
7
7
  ## It is plain simple!
8
8
 
@@ -11,10 +11,10 @@ Toy workflow engine, written in Ruby
11
11
  require 'pallets'
12
12
 
13
13
  class MyWorkflow < Pallets::Workflow
14
- task Foo
15
- task Bar => Foo
16
- task Baz => Foo
17
- task Qux => [Bar, Baz]
14
+ task 'Foo'
15
+ task 'Bar' => 'Foo'
16
+ task 'Baz' => 'Foo'
17
+ task 'Qux' => ['Bar', 'Baz']
18
18
  end
19
19
 
20
20
  class Foo < Pallets::Task
@@ -120,7 +120,7 @@ end
120
120
 
121
121
  ## Motivation
122
122
 
123
- The main reason for Pallet's existence was the need of a fast, simple and reliable workflow engine, one that is easily extensible with various backends and serializer, one that does not lose your data and one that is intelligent enough to concurrently schedule a workflow's tasks.
123
+ The main reason for Pallets' existence was the need of a fast, simple and reliable workflow engine, one that is easily extensible with various backends and serializer, one that does not lose your data and one that is intelligent enough to concurrently schedule a workflow's tasks.
124
124
 
125
125
  ## Status
126
126
 
@@ -1,4 +1,3 @@
1
- require 'logger'
2
1
  require 'pallets'
3
2
 
4
3
  class AnnounceProcessing
@@ -32,8 +31,6 @@ Pallets.configure do |c|
32
31
  # given up. Retry times are exponential and happen after: 7, 22, 87, 262, ...
33
32
  c.max_failures = 5
34
33
 
35
- # Custom loggers can be used too
36
- c.logger = Logger.new(STDOUT)
37
34
  # Job execution can be wrapped with middleware to provide custom logic.
38
35
  # Anything that responds to `call` would do
39
36
  c.middleware << AnnounceProcessing
@@ -38,6 +38,7 @@ module Pallets
38
38
  cls.new(
39
39
  blocking_timeout: configuration.blocking_timeout,
40
40
  failed_job_lifespan: configuration.failed_job_lifespan,
41
+ failed_job_max_count: configuration.failed_job_max_count,
41
42
  job_timeout: configuration.job_timeout,
42
43
  pool_size: configuration.pool_size,
43
44
  **configuration.backend_args
@@ -57,6 +58,9 @@ module Pallets
57
58
  end
58
59
 
59
60
  def self.logger
60
- @logger ||= configuration.logger
61
+ @logger ||= Pallets::Logger.new(STDOUT,
62
+ level: Pallets::Logger::INFO,
63
+ formatter: Pallets::Logger::Formatters::Pretty.new
64
+ )
61
65
  end
62
66
  end
@@ -20,8 +20,13 @@ module Pallets
20
20
  raise NotImplementedError
21
21
  end
22
22
 
23
+ # Discards malformed job
24
+ def discard(job)
25
+ raise NotImplementedError
26
+ end
27
+
23
28
  # Gives up job after repeteadly failing to process it
24
- def give_up(job, old_job)
29
+ def give_up(wfid, job, old_job)
25
30
  raise NotImplementedError
26
31
  end
27
32
 
@@ -9,13 +9,15 @@ module Pallets
9
9
  RETRY_SET_KEY = 'retry-set'
10
10
  GIVEN_UP_SET_KEY = 'given-up-set'
11
11
  WORKFLOW_QUEUE_KEY = 'workflow-queue:%s'
12
+ JOBMASKS_KEY = 'jobmasks:%s'
12
13
  JOBMASK_KEY = 'jobmask:%s'
13
14
  CONTEXT_KEY = 'context:%s'
14
15
  REMAINING_KEY = 'remaining:%s'
15
16
 
16
- def initialize(blocking_timeout:, failed_job_lifespan:, job_timeout:, pool_size:, **options)
17
+ def initialize(blocking_timeout:, failed_job_lifespan:, failed_job_max_count:, job_timeout:, pool_size:, **options)
17
18
  @blocking_timeout = blocking_timeout
18
19
  @failed_job_lifespan = failed_job_lifespan
20
+ @failed_job_max_count = failed_job_max_count
19
21
  @job_timeout = job_timeout
20
22
  @pool = Pallets::Pool.new(pool_size) { ::Redis.new(options) }
21
23
 
@@ -46,7 +48,7 @@ module Pallets
46
48
  @pool.execute do |client|
47
49
  client.evalsha(
48
50
  @scripts['save'],
49
- [WORKFLOW_QUEUE_KEY % wfid, QUEUE_KEY, RELIABILITY_QUEUE_KEY, RELIABILITY_SET_KEY, CONTEXT_KEY % wfid, REMAINING_KEY % wfid, JOBMASK_KEY % jid],
51
+ [WORKFLOW_QUEUE_KEY % wfid, QUEUE_KEY, RELIABILITY_QUEUE_KEY, RELIABILITY_SET_KEY, CONTEXT_KEY % wfid, REMAINING_KEY % wfid, JOBMASK_KEY % jid, JOBMASKS_KEY % wfid],
50
52
  context_buffer.to_a << job
51
53
  )
52
54
  end
@@ -62,12 +64,22 @@ module Pallets
62
64
  end
63
65
  end
64
66
 
65
- def give_up(job, old_job)
67
+ def discard(job)
66
68
  @pool.execute do |client|
67
69
  client.evalsha(
68
- @scripts['give_up'],
70
+ @scripts['discard'],
69
71
  [GIVEN_UP_SET_KEY, RELIABILITY_QUEUE_KEY, RELIABILITY_SET_KEY],
70
- [Time.now.to_f, job, old_job, Time.now.to_f - @failed_job_lifespan]
72
+ [Time.now.to_f, job, Time.now.to_f - @failed_job_lifespan, @failed_job_max_count]
73
+ )
74
+ end
75
+ end
76
+
77
+ def give_up(wfid, job, old_job)
78
+ @pool.execute do |client|
79
+ client.evalsha(
80
+ @scripts['give_up'],
81
+ [GIVEN_UP_SET_KEY, RELIABILITY_QUEUE_KEY, RELIABILITY_SET_KEY, JOBMASKS_KEY % wfid, WORKFLOW_QUEUE_KEY % wfid, REMAINING_KEY % wfid, CONTEXT_KEY % wfid],
82
+ [Time.now.to_f, job, old_job, Time.now.to_f - @failed_job_lifespan, @failed_job_max_count]
71
83
  )
72
84
  end
73
85
  end
@@ -86,6 +98,7 @@ module Pallets
86
98
  @pool.execute do |client|
87
99
  client.multi do
88
100
  jobmasks.each { |jid, jobmask| client.zadd(JOBMASK_KEY % jid, jobmask) }
101
+ client.sadd(JOBMASKS_KEY % wfid, jobmasks.map { |jid, _| JOBMASK_KEY % jid }) unless jobmasks.empty?
89
102
  client.evalsha(
90
103
  @scripts['run_workflow'],
91
104
  [WORKFLOW_QUEUE_KEY % wfid, QUEUE_KEY, REMAINING_KEY % wfid],
@@ -0,0 +1,11 @@
1
+ -- Remove job from reliability queue
2
+ redis.call("LREM", KEYS[2], 0, ARGV[2])
3
+ redis.call("ZREM", KEYS[3], ARGV[2])
4
+
5
+ -- Add job and its fail time (score) to failed sorted set
6
+ redis.call("ZADD", KEYS[1], ARGV[1], ARGV[2])
7
+
8
+ -- Remove any jobs that have been given up long enough ago (their score is
9
+ -- below given value) and make sure the number of jobs is capped
10
+ redis.call("ZREMRANGEBYSCORE", KEYS[1], "-inf", ARGV[3])
11
+ redis.call("ZREMRANGEBYRANK", KEYS[1], 0, -ARGV[4] - 1)
@@ -5,6 +5,11 @@ redis.call("ZREM", KEYS[3], ARGV[3])
5
5
  -- Add job and its fail time (score) to failed sorted set
6
6
  redis.call("ZADD", KEYS[1], ARGV[1], ARGV[2])
7
7
 
8
+ -- Remove all related workflow keys
9
+ local keys = redis.call("SMEMBERS", KEYS[4])
10
+ redis.call("DEL", KEYS[4], KEYS[5], KEYS[6], KEYS[7], unpack(keys))
11
+
8
12
  -- Remove any jobs that have been given up long enough ago (their score is
9
- -- below given value)
13
+ -- below given value) and make sure the number of jobs is capped
10
14
  redis.call("ZREMRANGEBYSCORE", KEYS[1], "-inf", ARGV[4])
15
+ redis.call("ZREMRANGEBYRANK", KEYS[1], 0, -ARGV[5] - 1)
@@ -22,9 +22,9 @@ if #work > 0 then
22
22
  redis.call("ZREM", KEYS[1], unpack(work))
23
23
  end
24
24
 
25
- -- Decrement ETA and remove it together with the context if all tasks have
26
- -- been processed (ETA is 0)
25
+ -- Decrement ETA and remove it together with the context and jobmasks if all
26
+ -- tasks have been processed (ETA is 0) or if workflow has been given up (ETA is -1)
27
27
  local remaining = redis.call("DECR", KEYS[6])
28
- if remaining == 0 then
29
- redis.call("DEL", KEYS[5], KEYS[6])
28
+ if remaining <= 0 then
29
+ redis.call("DEL", KEYS[5], KEYS[6], KEYS[8])
30
30
  end
@@ -67,6 +67,10 @@ module Pallets
67
67
  Pallets.configuration.failed_job_lifespan = failed_job_lifespan
68
68
  end
69
69
 
70
+ opts.on('-m', '--failed-job-max-count NUM', Integer, 'Maximum number of jobs in the given up set') do |failed_job_max_count|
71
+ Pallets.configuration.failed_job_max_count = failed_job_max_count
72
+ end
73
+
70
74
  opts.on('-p', '--pool-size NUM', Integer, 'Size of backend pool') do |pool_size|
71
75
  Pallets.configuration.pool_size = pool_size
72
76
  end
@@ -16,13 +16,14 @@ module Pallets
16
16
  # this period, jobs will be permanently deleted
17
17
  attr_accessor :failed_job_lifespan
18
18
 
19
+ # Maximum number of failed jobs that can be in the given up set. When this
20
+ # number is reached, the oldest jobs will be permanently deleted
21
+ attr_accessor :failed_job_max_count
22
+
19
23
  # Number of seconds allowed for a job to be processed. If a job exceeds this
20
24
  # period, it is considered failed, and scheduled to be processed again
21
25
  attr_accessor :job_timeout
22
26
 
23
- # Custom logger used throughout Pallets
24
- attr_writer :logger
25
-
26
27
  # Maximum number of failures allowed per job. Can also be configured on a
27
28
  # per task basis
28
29
  attr_accessor :max_failures
@@ -48,19 +49,13 @@ module Pallets
48
49
  @blocking_timeout = 5
49
50
  @concurrency = 2
50
51
  @failed_job_lifespan = 7_776_000 # 3 months
52
+ @failed_job_max_count = 1_000
51
53
  @job_timeout = 1_800 # 30 minutes
52
54
  @max_failures = 3
53
55
  @serializer = :json
54
56
  @middleware = default_middleware
55
57
  end
56
58
 
57
- def logger
58
- @logger || Pallets::Logger.new(STDOUT,
59
- level: Pallets::Logger::INFO,
60
- formatter: Pallets::Logger::Formatters::Pretty.new
61
- )
62
- end
63
-
64
59
  def pool_size
65
60
  @pool_size || @concurrency + 1
66
61
  end
@@ -5,14 +5,22 @@ module Pallets
5
5
  class Logger < ::Logger
6
6
  # Overwrite severity methods to add metadata capabilities
7
7
  %i[debug info warn error fatal unknown].each do |severity|
8
- define_method severity do |message, metadata = {}|
9
- return super(message) if metadata.empty?
8
+ define_method severity do |message|
9
+ metadata = Thread.current[:pallets_log_metadata]
10
+ return super(message) if metadata.nil?
10
11
 
11
12
  formatted_metadata = ' ' + metadata.map { |k, v| "#{k}=#{v}" }.join(' ')
12
13
  super(formatted_metadata) { message }
13
14
  end
14
15
  end
15
16
 
17
+ def with_metadata(hash)
18
+ Thread.current[:pallets_log_metadata] = hash
19
+ yield
20
+ ensure
21
+ Thread.current[:pallets_log_metadata] = nil
22
+ end
23
+
16
24
  module Formatters
17
25
  class Pretty < ::Logger::Formatter
18
26
  def call(severity, time, metadata, message)
@@ -2,14 +2,21 @@ module Pallets
2
2
  module Middleware
3
3
  class JobLogger
4
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
5
+ start_time = current_time
6
+
7
+ Pallets.logger.with_metadata(extract_metadata(worker.id, job)) do
8
+ begin
9
+ Pallets.logger.info 'Started'
10
+ result = yield
11
+ Pallets.logger.info "Done in #{(current_time - start_time).round(3)}s"
12
+ result
13
+ rescue => ex
14
+ Pallets.logger.warn "Failed after #{(current_time - start_time).round(3)}s"
15
+ Pallets.logger.warn "#{ex.class.name}: #{ex.message}"
16
+ Pallets.logger.warn ex.backtrace.join("\n") unless ex.backtrace.nil?
17
+ raise
18
+ end
19
+ end
13
20
  end
14
21
 
15
22
  def self.extract_metadata(wid, job)
@@ -21,6 +28,10 @@ module Pallets
21
28
  tsk: job['task_class'],
22
29
  }
23
30
  end
31
+
32
+ def self.current_time
33
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
34
+ end
24
35
  end
25
36
  end
26
37
  end
@@ -1,3 +1,3 @@
1
1
  module Pallets
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -53,8 +53,8 @@ module Pallets
53
53
  rescue Pallets::Shutdown
54
54
  @manager.remove_worker(self)
55
55
  rescue => ex
56
- Pallets.logger.error "#{ex.class.name}: #{ex.message}", wid: id
57
- Pallets.logger.error ex.backtrace.join("\n"), wid: id unless ex.backtrace.nil?
56
+ Pallets.logger.error "#{ex.class.name}: #{ex.message}"
57
+ Pallets.logger.error ex.backtrace.join("\n") unless ex.backtrace.nil?
58
58
  @manager.replace_worker(self)
59
59
  end
60
60
 
@@ -64,8 +64,8 @@ module Pallets
64
64
  rescue
65
65
  # We ensure only valid jobs are created. If something fishy reaches this
66
66
  # point, just give up on it
67
- backend.give_up(job, job)
68
- Pallets.logger.error "Could not deserialize #{job}. Gave up job", wid: id
67
+ backend.discard(job)
68
+ Pallets.logger.error "Could not deserialize #{job}. Gave up job"
69
69
  return
70
70
  end
71
71
 
@@ -103,7 +103,7 @@ module Pallets
103
103
  retry_at = Time.now.to_f + backoff_in_seconds(failures)
104
104
  backend.retry(new_job, job, retry_at)
105
105
  else
106
- backend.give_up(new_job, job)
106
+ backend.give_up(job_hash['wfid'], new_job, job)
107
107
  end
108
108
  end
109
109
 
@@ -112,7 +112,7 @@ module Pallets
112
112
  'given_up_at' => Time.now.to_f,
113
113
  'reason' => 'returned_false'
114
114
  ))
115
- backend.give_up(new_job, job)
115
+ backend.give_up(job_hash['wfid'], new_job, job)
116
116
  end
117
117
 
118
118
  def handle_job_success(context, job, job_hash)
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ['Andrei Horak']
10
10
  spec.email = ['linkyndy@gmail.com']
11
11
 
12
- spec.summary = 'Toy workflow engine, written in Ruby'
13
- spec.description = 'Toy workflow engine, written in Ruby'
12
+ spec.summary = 'Simple and reliable workflow engine, written in Ruby'
13
+ spec.description = 'Simple and reliable workflow engine, written in Ruby'
14
14
  spec.homepage = 'https://github.com/linkyndy/pallets'
15
15
  spec.license = 'MIT'
16
16
 
@@ -20,6 +20,6 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.required_ruby_version = '>= 2.4'
22
22
 
23
- spec.add_dependency 'redis'
23
+ spec.add_dependency 'redis', '~> 4.2'
24
24
  spec.add_dependency 'msgpack'
25
25
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pallets
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Horak
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-09 00:00:00.000000000 Z
11
+ date: 2020-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '4.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '4.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: msgpack
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,7 +38,7 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- description: Toy workflow engine, written in Ruby
41
+ description: Simple and reliable workflow engine, written in Ruby
42
42
  email:
43
43
  - linkyndy@gmail.com
44
44
  executables:
@@ -67,6 +67,7 @@ files:
67
67
  - lib/pallets.rb
68
68
  - lib/pallets/backends/base.rb
69
69
  - lib/pallets/backends/redis.rb
70
+ - lib/pallets/backends/scripts/discard.lua
70
71
  - lib/pallets/backends/scripts/give_up.lua
71
72
  - lib/pallets/backends/scripts/reschedule_all.lua
72
73
  - lib/pallets/backends/scripts/retry.lua
@@ -98,7 +99,7 @@ homepage: https://github.com/linkyndy/pallets
98
99
  licenses:
99
100
  - MIT
100
101
  metadata: {}
101
- post_install_message:
102
+ post_install_message:
102
103
  rdoc_options: []
103
104
  require_paths:
104
105
  - lib
@@ -114,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
115
  version: '0'
115
116
  requirements: []
116
117
  rubygems_version: 3.1.2
117
- signing_key:
118
+ signing_key:
118
119
  specification_version: 4
119
- summary: Toy workflow engine, written in Ruby
120
+ summary: Simple and reliable workflow engine, written in Ruby
120
121
  test_files: []