pallets 0.1.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.
@@ -0,0 +1,13 @@
1
+ module Pallets
2
+ module Serializers
3
+ class Base
4
+ def dump(data)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def load(data)
9
+ raise NotImplementedError
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ require 'json'
2
+
3
+ module Pallets
4
+ module Serializers
5
+ class Json
6
+ def dump(data)
7
+ JSON.generate(data)
8
+ end
9
+
10
+ def load(data)
11
+ JSON.parse(data)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ module Pallets
2
+ class Task
3
+ attr_reader :context
4
+
5
+ def initialize(context = {})
6
+ @context = context
7
+ end
8
+
9
+ def run
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Pallets
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,109 @@
1
+ module Pallets
2
+ class Worker
3
+ attr_reader :manager
4
+
5
+ def initialize(manager)
6
+ @manager = manager
7
+ @current_job = nil
8
+ @needs_to_stop = false
9
+ @thread = nil
10
+ end
11
+
12
+ def start
13
+ @thread ||= Thread.new { work }
14
+ end
15
+
16
+ def graceful_shutdown
17
+ @needs_to_stop = true
18
+ end
19
+
20
+ def hard_shutdown
21
+ return unless @thread
22
+ @thread.raise Pallets::Shutdown
23
+ end
24
+
25
+ def needs_to_stop?
26
+ @needs_to_stop
27
+ end
28
+
29
+ def id
30
+ "W#{@thread.object_id.to_s(36)}".upcase if @thread
31
+ end
32
+
33
+ private
34
+
35
+ def work
36
+ loop do
37
+ break if needs_to_stop?
38
+
39
+ @current_job = backend.pick
40
+ # No need to requeue because of the reliability queue
41
+ break if needs_to_stop?
42
+ next if @current_job.nil?
43
+
44
+ process @current_job
45
+
46
+ @current_job = nil
47
+ end
48
+ @manager.remove_worker(self)
49
+ rescue Pallets::Shutdown
50
+ @manager.remove_worker(self)
51
+ rescue => ex
52
+ @manager.replace_worker(self)
53
+ end
54
+
55
+ def process(job)
56
+ Pallets.logger.info "[#{id}] Picked job: #{job}"
57
+ begin
58
+ job_hash = serializer.load(job)
59
+ rescue
60
+ # We ensure only valid jobs are created. If something fishy reaches this
61
+ # point, just discard it
62
+ backend.discard(job)
63
+ return
64
+ end
65
+
66
+ task_class = job_hash["class_name"].constantize
67
+ task = task_class.new(job_hash["context"])
68
+ begin
69
+ task.run
70
+ rescue => ex
71
+ handle_job_error(ex, job, job_hash)
72
+ else
73
+ backend.save(job_hash["workflow_id"], job)
74
+ Pallets.logger.info "[#{id}] Successfully processed #{job}"
75
+ end
76
+ end
77
+
78
+ def handle_job_error(ex, job, job_hash)
79
+ Pallets.logger.error "[#{id}] Error while processing: #{ex}"
80
+ failures = job_hash.fetch('failures', 0) + 1
81
+ new_job = serializer.dump(job_hash.merge(
82
+ 'failures' => failures,
83
+ 'failed_at' => Time.now.to_f,
84
+ 'error_class' => ex.class.name,
85
+ 'error_message' => ex.message
86
+ ))
87
+ if failures < job_hash['max_failures']
88
+ retry_at = Time.now.to_f + backoff_in_seconds(failures)
89
+ backend.retry(new_job, job, retry_at)
90
+ Pallets.logger.info "[#{id}] Scheduled job for retry"
91
+ else
92
+ backend.give_up(new_job, job, Time.now.to_f)
93
+ Pallets.logger.info "[#{id}] Given up on job"
94
+ end
95
+ end
96
+
97
+ def backoff_in_seconds(count)
98
+ count ** 4 + 6
99
+ end
100
+
101
+ def backend
102
+ @backend ||= Pallets.backend
103
+ end
104
+
105
+ def serializer
106
+ @serializer ||= Pallets.serializer
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,58 @@
1
+ module Pallets
2
+ class Workflow
3
+ extend DSL::Workflow
4
+
5
+ attr_reader :context
6
+
7
+ def initialize(context = {})
8
+ @id = nil
9
+ @context = context
10
+ end
11
+
12
+ def run
13
+ backend.run_workflow(id, jobs_with_order)
14
+ id
15
+ end
16
+
17
+ def id
18
+ @id ||= begin
19
+ initials = self.class.name.gsub(/[^A-Z]+([A-Z])/, '\1')[0,3]
20
+ random = SecureRandom.hex(5)
21
+ "P#{initials}#{random}".upcase
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def jobs_with_order
28
+ self.class.graph.sorted_with_order.map do |task_name, order|
29
+ job = serializer.dump(job_hash.merge(self.class.task_config[task_name]))
30
+ [order, job]
31
+ end
32
+ end
33
+
34
+ def job_hash
35
+ {
36
+ 'workflow_id' => id,
37
+ 'context' => context,
38
+ 'created_at' => Time.now.to_f
39
+ }
40
+ end
41
+
42
+ def backend
43
+ Pallets.backend
44
+ end
45
+
46
+ def serializer
47
+ Pallets.serializer
48
+ end
49
+
50
+ def self.task_config
51
+ @task_config ||= {}
52
+ end
53
+
54
+ def self.graph
55
+ @graph ||= Graph.new
56
+ end
57
+ end
58
+ end
data/pallets.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pallets/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pallets"
8
+ spec.version = Pallets::VERSION
9
+ spec.authors = ["Andrei Horak"]
10
+ spec.email = ["linkyndy@gmail.com"]
11
+
12
+ spec.summary = 'Toy workflow engine, written in Ruby'
13
+ spec.description = 'Toy workflow engine, written in Ruby'
14
+ spec.homepage = 'https://github.com/linkyndy/pallets'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "bin"
18
+ spec.executables = ["pallets"]
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activesupport"
22
+ spec.add_dependency "redis"
23
+ spec.add_development_dependency "bundler", "~> 1.11"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.0"
26
+ spec.add_development_dependency "timecop"
27
+ spec.add_development_dependency "fuubar"
28
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pallets
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrei Horak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-09-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: timecop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: fuubar
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Toy workflow engine, written in Ruby
112
+ email:
113
+ - linkyndy@gmail.com
114
+ executables:
115
+ - pallets
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".rspec"
121
+ - ".travis.yml"
122
+ - CONTRIBUTING.md
123
+ - Gemfile
124
+ - LICENSE
125
+ - README.md
126
+ - Rakefile
127
+ - bin/console
128
+ - bin/pallets
129
+ - bin/setup
130
+ - lib/pallets.rb
131
+ - lib/pallets/backends/base.rb
132
+ - lib/pallets/backends/redis.rb
133
+ - lib/pallets/backends/scripts/discard.lua
134
+ - lib/pallets/backends/scripts/give_up.lua
135
+ - lib/pallets/backends/scripts/reschedule_all.lua
136
+ - lib/pallets/backends/scripts/retry.lua
137
+ - lib/pallets/backends/scripts/run_workflow.lua
138
+ - lib/pallets/backends/scripts/save.lua
139
+ - lib/pallets/cli.rb
140
+ - lib/pallets/configuration.rb
141
+ - lib/pallets/dsl/workflow.rb
142
+ - lib/pallets/errors.rb
143
+ - lib/pallets/graph.rb
144
+ - lib/pallets/manager.rb
145
+ - lib/pallets/pool.rb
146
+ - lib/pallets/scheduler.rb
147
+ - lib/pallets/serializers/base.rb
148
+ - lib/pallets/serializers/json.rb
149
+ - lib/pallets/task.rb
150
+ - lib/pallets/version.rb
151
+ - lib/pallets/worker.rb
152
+ - lib/pallets/workflow.rb
153
+ - pallets.gemspec
154
+ homepage: https://github.com/linkyndy/pallets
155
+ licenses: []
156
+ metadata: {}
157
+ post_install_message:
158
+ rdoc_options: []
159
+ require_paths:
160
+ - lib
161
+ required_ruby_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ required_rubygems_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ requirements: []
172
+ rubyforge_project:
173
+ rubygems_version: 2.7.7
174
+ signing_key:
175
+ specification_version: 4
176
+ summary: Toy workflow engine, written in Ruby
177
+ test_files: []