taskinator 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile +17 -2
- data/Gemfile.lock +57 -18
- data/README.md +20 -16
- data/lib/taskinator/definition.rb +2 -2
- data/lib/taskinator/instrumentation.rb +77 -0
- data/lib/taskinator/persistence.rb +72 -61
- data/lib/taskinator/process.rb +118 -99
- data/lib/taskinator/queues/delayed_job.rb +0 -14
- data/lib/taskinator/queues/resque.rb +0 -18
- data/lib/taskinator/queues/sidekiq.rb +0 -14
- data/lib/taskinator/queues.rb +0 -5
- data/lib/taskinator/task.rb +113 -70
- data/lib/taskinator/version.rb +1 -1
- data/lib/taskinator/visitor.rb +6 -0
- data/lib/taskinator/workflow.rb +36 -0
- data/lib/taskinator.rb +3 -2
- data/spec/examples/process_examples.rb +6 -9
- data/spec/examples/queue_adapter_examples.rb +2 -12
- data/spec/examples/task_examples.rb +5 -8
- data/spec/support/process_methods.rb +25 -0
- data/spec/support/task_methods.rb +13 -0
- data/spec/support/test_flows.rb +1 -3
- data/spec/support/test_instrumenter.rb +39 -0
- data/spec/support/test_queue.rb +0 -12
- data/spec/taskinator/definition_spec.rb +3 -5
- data/spec/taskinator/instrumentation_spec.rb +98 -0
- data/spec/taskinator/persistence_spec.rb +3 -41
- data/spec/taskinator/process_spec.rb +36 -34
- data/spec/taskinator/queues/delayed_job_spec.rb +0 -41
- data/spec/taskinator/queues/resque_spec.rb +0 -51
- data/spec/taskinator/queues/sidekiq_spec.rb +0 -50
- data/spec/taskinator/queues_spec.rb +1 -1
- data/spec/taskinator/task_spec.rb +96 -64
- data/spec/taskinator/test_flows_spec.rb +266 -1
- data/taskinator.gemspec +0 -21
- metadata +12 -173
- data/lib/taskinator/job_worker.rb +0 -17
- data/spec/taskinator/job_worker_spec.rb +0 -62
data/lib/taskinator/process.rb
CHANGED
@@ -4,7 +4,10 @@ require 'thwait'
|
|
4
4
|
module Taskinator
|
5
5
|
class Process
|
6
6
|
include ::Comparable
|
7
|
-
|
7
|
+
|
8
|
+
include Workflow
|
9
|
+
include Persistence
|
10
|
+
include Instrumentation
|
8
11
|
|
9
12
|
class << self
|
10
13
|
def define_sequential_process_for(definition, options={})
|
@@ -24,6 +27,8 @@ module Taskinator
|
|
24
27
|
attr_reader :definition
|
25
28
|
attr_reader :options
|
26
29
|
attr_reader :queue
|
30
|
+
attr_reader :created_at
|
31
|
+
attr_reader :updated_at
|
27
32
|
|
28
33
|
# in the case of sub process tasks, the containing task
|
29
34
|
attr_accessor :parent
|
@@ -36,12 +41,18 @@ module Taskinator
|
|
36
41
|
@definition = definition
|
37
42
|
@options = options
|
38
43
|
@queue = options.delete(:queue)
|
44
|
+
@created_at = Time.now.utc
|
45
|
+
@updated_at = created_at
|
39
46
|
end
|
40
47
|
|
41
48
|
def tasks
|
42
49
|
@tasks ||= Tasks.new
|
43
50
|
end
|
44
51
|
|
52
|
+
def no_tasks_defined?
|
53
|
+
tasks.empty?
|
54
|
+
end
|
55
|
+
|
45
56
|
def accept(visitor)
|
46
57
|
visitor.visit_attribute(:uuid)
|
47
58
|
visitor.visit_task_reference(:parent)
|
@@ -49,6 +60,8 @@ module Taskinator
|
|
49
60
|
visitor.visit_tasks(tasks)
|
50
61
|
visitor.visit_args(:options)
|
51
62
|
visitor.visit_attribute(:queue)
|
63
|
+
visitor.visit_attribute_time(:created_at)
|
64
|
+
visitor.visit_attribute_time(:updated_at)
|
52
65
|
end
|
53
66
|
|
54
67
|
def <=>(other)
|
@@ -59,58 +72,79 @@ module Taskinator
|
|
59
72
|
"#<#{self.class.name}:#{uuid}>"
|
60
73
|
end
|
61
74
|
|
62
|
-
|
63
|
-
|
64
|
-
event :enqueue, :transitions_to => :enqueued
|
65
|
-
event :start, :transitions_to => :processing
|
66
|
-
event :complete, :transitions_to => :completed
|
67
|
-
event :cancel, :transitions_to => :cancelled
|
68
|
-
event :fail, :transitions_to => :failed
|
69
|
-
end
|
75
|
+
def enqueue!
|
76
|
+
return if paused? || cancelled?
|
70
77
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
event :fail, :transitions_to => :failed
|
78
|
+
transition(:enqueued) do
|
79
|
+
instrument('taskinator.process.enqueued', enqueued_payload) do
|
80
|
+
enqueue
|
81
|
+
end
|
76
82
|
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def start!
|
86
|
+
return if paused? || cancelled?
|
77
87
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
88
|
+
transition(:processing) do
|
89
|
+
instrument('taskinator.process.processing', processing_payload) do
|
90
|
+
start
|
91
|
+
end
|
82
92
|
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def pause!
|
96
|
+
return unless enqueued? || processing?
|
83
97
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
98
|
+
transition(:paused) do
|
99
|
+
instrument('taskinator.process.paused', paused_payload) do
|
100
|
+
pause if respond_to?(:pause)
|
101
|
+
end
|
88
102
|
end
|
103
|
+
end
|
89
104
|
|
90
|
-
|
91
|
-
|
92
|
-
state :failed
|
105
|
+
def resume!
|
106
|
+
return unless paused?
|
93
107
|
|
94
|
-
|
95
|
-
|
108
|
+
transition(:processing) do
|
109
|
+
instrument('taskinator.process.resumed', resumed_payload) do
|
110
|
+
resume if respond_to?(:resume)
|
111
|
+
end
|
96
112
|
end
|
113
|
+
end
|
97
114
|
|
115
|
+
def complete!
|
116
|
+
transition(:completed) do
|
117
|
+
instrument('taskinator.process.completed', completed_payload) do
|
118
|
+
complete if respond_to?(:complete)
|
119
|
+
# notify the parent task (if there is one) that this process has completed
|
120
|
+
# note: parent may be a proxy, so explicity check for nil?
|
121
|
+
parent.complete! unless parent.nil?
|
122
|
+
end
|
123
|
+
end
|
98
124
|
end
|
99
125
|
|
100
|
-
def
|
101
|
-
|
126
|
+
def tasks_completed?
|
127
|
+
# TODO: optimize this
|
128
|
+
tasks.all?(&:completed?)
|
102
129
|
end
|
103
130
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
131
|
+
def cancel!
|
132
|
+
transition(:cancelled) do
|
133
|
+
instrument('taskinator.process.cancelled', cancelled_payload) do
|
134
|
+
cancel if respond_to?(:cancel)
|
135
|
+
end
|
108
136
|
end
|
109
137
|
end
|
110
138
|
|
111
|
-
|
112
|
-
|
113
|
-
|
139
|
+
def fail!(error)
|
140
|
+
transition(:failed) do
|
141
|
+
instrument('taskinator.process.failed', failed_payload(error)) do
|
142
|
+
fail(error) if respond_to?(:fail)
|
143
|
+
# notify the parent task (if there is one) that this process has failed
|
144
|
+
# note: parent may be a proxy, so explicity check for nil?
|
145
|
+
parent.fail!(error) unless parent.nil?
|
146
|
+
end
|
147
|
+
end
|
114
148
|
end
|
115
149
|
|
116
150
|
def task_failed(task, error)
|
@@ -118,47 +152,39 @@ module Taskinator
|
|
118
152
|
fail!(error)
|
119
153
|
end
|
120
154
|
|
121
|
-
|
122
|
-
#
|
123
|
-
|
124
|
-
include Persistence
|
155
|
+
#--------------------------------------------------
|
156
|
+
# subclasses must implement the following methods
|
157
|
+
#--------------------------------------------------
|
125
158
|
|
126
|
-
def
|
127
|
-
|
128
|
-
# notify the parent task (if there is one) that this process has completed
|
129
|
-
# note: parent may be a proxy, so explicity check for nil?
|
130
|
-
parent.complete! unless parent.nil?
|
131
|
-
end
|
159
|
+
def enqueue
|
160
|
+
raise NotImplementedError
|
132
161
|
end
|
133
162
|
|
134
|
-
|
135
|
-
|
136
|
-
Taskinator.instrumenter.instrument('taskinator.process.failed', instrumentation_payload) do
|
137
|
-
# notify the parent task (if there is one) that this process has failed
|
138
|
-
# note: parent may be a proxy, so explicity check for nil?
|
139
|
-
parent.fail!(*args) unless parent.nil?
|
140
|
-
end
|
163
|
+
def start
|
164
|
+
raise NotImplementedError
|
141
165
|
end
|
142
166
|
|
167
|
+
def task_completed(task)
|
168
|
+
raise NotImplementedError
|
169
|
+
end
|
170
|
+
|
171
|
+
#--------------------------------------------------
|
172
|
+
|
143
173
|
class Sequential < Process
|
144
174
|
def enqueue
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
tasks.first.enqueue!
|
150
|
-
end
|
175
|
+
if tasks.empty?
|
176
|
+
complete! # weren't any tasks to start with
|
177
|
+
else
|
178
|
+
tasks.first.enqueue!
|
151
179
|
end
|
152
180
|
end
|
153
181
|
|
154
182
|
def start
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
complete! # weren't any tasks to start with
|
161
|
-
end
|
183
|
+
task = tasks.first
|
184
|
+
if task
|
185
|
+
task.start!
|
186
|
+
else
|
187
|
+
complete! # weren't any tasks to start with
|
162
188
|
end
|
163
189
|
end
|
164
190
|
|
@@ -167,20 +193,17 @@ module Taskinator
|
|
167
193
|
if next_task
|
168
194
|
next_task.enqueue!
|
169
195
|
else
|
170
|
-
complete!
|
196
|
+
complete! # aren't any more tasks
|
171
197
|
end
|
172
198
|
end
|
173
199
|
|
174
|
-
def tasks_completed?(*args)
|
175
|
-
# TODO: optimize this
|
176
|
-
tasks.all?(&:completed?)
|
177
|
-
end
|
178
|
-
|
179
200
|
def inspect
|
180
|
-
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", state=:#{current_state
|
201
|
+
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", state=:#{current_state}, tasks=[#{tasks.inspect}]>)
|
181
202
|
end
|
182
203
|
end
|
183
204
|
|
205
|
+
#--------------------------------------------------
|
206
|
+
|
184
207
|
class Concurrent < Process
|
185
208
|
attr_reader :complete_on
|
186
209
|
attr_reader :concurrency_method
|
@@ -192,35 +215,31 @@ module Taskinator
|
|
192
215
|
end
|
193
216
|
|
194
217
|
def enqueue
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
tasks.each(&:enqueue!)
|
200
|
-
end
|
218
|
+
if tasks.empty?
|
219
|
+
complete! # weren't any tasks to start with
|
220
|
+
else
|
221
|
+
tasks.each(&:enqueue!)
|
201
222
|
end
|
202
223
|
end
|
203
224
|
|
204
225
|
def start
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
task.start!
|
213
|
-
end
|
226
|
+
if tasks.empty?
|
227
|
+
complete! # weren't any tasks to start with
|
228
|
+
else
|
229
|
+
if concurrency_method == :fork
|
230
|
+
tasks.each do |task|
|
231
|
+
fork do
|
232
|
+
task.start!
|
214
233
|
end
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
234
|
+
end
|
235
|
+
Process.waitall
|
236
|
+
else
|
237
|
+
threads = tasks.map do |task|
|
238
|
+
Thread.new do
|
239
|
+
task.start!
|
221
240
|
end
|
222
|
-
ThreadsWait.all_waits(*threads)
|
223
241
|
end
|
242
|
+
ThreadsWait.all_waits(*threads)
|
224
243
|
end
|
225
244
|
end
|
226
245
|
end
|
@@ -231,21 +250,21 @@ module Taskinator
|
|
231
250
|
complete!
|
232
251
|
end
|
233
252
|
|
234
|
-
def tasks_completed?
|
253
|
+
def tasks_completed?
|
235
254
|
if (complete_on == CompleteOn::First)
|
236
255
|
tasks.any?(&:completed?)
|
237
256
|
else
|
238
|
-
|
257
|
+
super # all
|
239
258
|
end
|
240
259
|
end
|
241
260
|
|
242
261
|
def accept(visitor)
|
243
262
|
super
|
244
|
-
visitor.
|
263
|
+
visitor.visit_attribute_enum(:complete_on, CompleteOn)
|
245
264
|
end
|
246
265
|
|
247
266
|
def inspect
|
248
|
-
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", state=:#{current_state
|
267
|
+
%(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", state=:#{current_state}, complete_on=:#{complete_on}, tasks=[#{tasks.inspect}]>)
|
249
268
|
end
|
250
269
|
end
|
251
270
|
end
|
@@ -22,12 +22,6 @@ module Taskinator
|
|
22
22
|
::Delayed::Job.enqueue TaskWorker.new(task.uuid), :queue => queue
|
23
23
|
end
|
24
24
|
|
25
|
-
def enqueue_job(job)
|
26
|
-
# delayed jobs don't define the queue so use the configured queue instead
|
27
|
-
queue = job.queue || @config[:job_queue]
|
28
|
-
::Delayed::Job.enqueue JobWorker.new(job.uuid), :queue => queue
|
29
|
-
end
|
30
|
-
|
31
25
|
CreateProcessWorker = Struct.new(:definition_name, :uuid, :args) do
|
32
26
|
def perform
|
33
27
|
Taskinator::CreateProcessWorker.new(definition_name, uuid, args).perform
|
@@ -40,14 +34,6 @@ module Taskinator
|
|
40
34
|
end
|
41
35
|
end
|
42
36
|
|
43
|
-
JobWorker = Struct.new(:job_uuid) do
|
44
|
-
def perform
|
45
|
-
Taskinator::JobWorker.new(job_uuid).perform do |job, args|
|
46
|
-
job.new(*args).perform
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
37
|
end
|
52
38
|
end
|
53
39
|
end
|
@@ -19,9 +19,6 @@ module Taskinator
|
|
19
19
|
@queue = config[:task_queue]
|
20
20
|
end
|
21
21
|
|
22
|
-
JobWorker.class_eval do
|
23
|
-
@queue = config[:job_queue]
|
24
|
-
end
|
25
22
|
end
|
26
23
|
|
27
24
|
def enqueue_create_process(definition, uuid, args)
|
@@ -34,14 +31,6 @@ module Taskinator
|
|
34
31
|
Resque.enqueue_to(queue, TaskWorker, task.uuid)
|
35
32
|
end
|
36
33
|
|
37
|
-
def enqueue_job(job)
|
38
|
-
queue = job.queue ||
|
39
|
-
Resque.queue_from_class(job.job) ||
|
40
|
-
Resque.queue_from_class(JobWorker)
|
41
|
-
|
42
|
-
Resque.enqueue_to(queue, JobWorker, job.uuid)
|
43
|
-
end
|
44
|
-
|
45
34
|
class CreateProcessWorker
|
46
35
|
def self.perform(definition_name, uuid, args)
|
47
36
|
Taskinator::CreateProcessWorker.new(definition_name, uuid, args).perform
|
@@ -54,13 +43,6 @@ module Taskinator
|
|
54
43
|
end
|
55
44
|
end
|
56
45
|
|
57
|
-
class JobWorker
|
58
|
-
def self.perform(job_uuid)
|
59
|
-
Taskinator::JobWorker.new(job_uuid).perform do |job, args|
|
60
|
-
job.perform(*args)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
46
|
end
|
65
47
|
end
|
66
48
|
end
|
@@ -22,11 +22,6 @@ module Taskinator
|
|
22
22
|
TaskWorker.client_push('class' => TaskWorker, 'args' => [task.uuid], 'queue' => queue)
|
23
23
|
end
|
24
24
|
|
25
|
-
def enqueue_job(job)
|
26
|
-
queue = job.queue || job.job.get_sidekiq_options[:queue] || @config[:job_queue]
|
27
|
-
JobWorker.client_push('class' => JobWorker, 'args' => [job.uuid], 'queue' => queue)
|
28
|
-
end
|
29
|
-
|
30
25
|
class CreateProcessWorker
|
31
26
|
include ::Sidekiq::Worker
|
32
27
|
|
@@ -43,15 +38,6 @@ module Taskinator
|
|
43
38
|
end
|
44
39
|
end
|
45
40
|
|
46
|
-
class JobWorker
|
47
|
-
include ::Sidekiq::Worker
|
48
|
-
|
49
|
-
def perform(job_uuid)
|
50
|
-
Taskinator::JobWorker.new(job_uuid).perform do |job, args|
|
51
|
-
job.new.perform(*args)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
41
|
end
|
56
42
|
end
|
57
43
|
end
|