taskinator 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/Gemfile +17 -2
  4. data/Gemfile.lock +57 -18
  5. data/README.md +20 -16
  6. data/lib/taskinator/definition.rb +2 -2
  7. data/lib/taskinator/instrumentation.rb +77 -0
  8. data/lib/taskinator/persistence.rb +72 -61
  9. data/lib/taskinator/process.rb +118 -99
  10. data/lib/taskinator/queues/delayed_job.rb +0 -14
  11. data/lib/taskinator/queues/resque.rb +0 -18
  12. data/lib/taskinator/queues/sidekiq.rb +0 -14
  13. data/lib/taskinator/queues.rb +0 -5
  14. data/lib/taskinator/task.rb +113 -70
  15. data/lib/taskinator/version.rb +1 -1
  16. data/lib/taskinator/visitor.rb +6 -0
  17. data/lib/taskinator/workflow.rb +36 -0
  18. data/lib/taskinator.rb +3 -2
  19. data/spec/examples/process_examples.rb +6 -9
  20. data/spec/examples/queue_adapter_examples.rb +2 -12
  21. data/spec/examples/task_examples.rb +5 -8
  22. data/spec/support/process_methods.rb +25 -0
  23. data/spec/support/task_methods.rb +13 -0
  24. data/spec/support/test_flows.rb +1 -3
  25. data/spec/support/test_instrumenter.rb +39 -0
  26. data/spec/support/test_queue.rb +0 -12
  27. data/spec/taskinator/definition_spec.rb +3 -5
  28. data/spec/taskinator/instrumentation_spec.rb +98 -0
  29. data/spec/taskinator/persistence_spec.rb +3 -41
  30. data/spec/taskinator/process_spec.rb +36 -34
  31. data/spec/taskinator/queues/delayed_job_spec.rb +0 -41
  32. data/spec/taskinator/queues/resque_spec.rb +0 -51
  33. data/spec/taskinator/queues/sidekiq_spec.rb +0 -50
  34. data/spec/taskinator/queues_spec.rb +1 -1
  35. data/spec/taskinator/task_spec.rb +96 -64
  36. data/spec/taskinator/test_flows_spec.rb +266 -1
  37. data/taskinator.gemspec +0 -21
  38. metadata +12 -173
  39. data/lib/taskinator/job_worker.rb +0 -17
  40. data/spec/taskinator/job_worker_spec.rb +0 -62
@@ -4,7 +4,10 @@ require 'thwait'
4
4
  module Taskinator
5
5
  class Process
6
6
  include ::Comparable
7
- include ::Workflow
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
- workflow do
63
- state :initial do
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
- state :enqueued do
72
- event :start, :transitions_to => :processing
73
- event :complete, :transitions_to => :completed
74
- event :cancel, :transitions_to => :cancelled
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
- state :processing do
79
- event :pause, :transitions_to => :paused
80
- event :complete, :transitions_to => :completed
81
- event :fail, :transitions_to => :failed
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
- state :paused do
85
- event :resume, :transitions_to => :processing
86
- event :cancel, :transitions_to => :cancelled
87
- event :fail, :transitions_to => :failed
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
- state :cancelled
91
- state :completed
92
- state :failed
105
+ def resume!
106
+ return unless paused?
93
107
 
94
- on_transition do |from, to, event, *args|
95
- Taskinator.logger.debug("PROCESS: #{self.class.name}:#{uuid} :: #{from} => #{to}")
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 no_tasks_defined?
101
- tasks.empty?
126
+ def tasks_completed?
127
+ # TODO: optimize this
128
+ tasks.all?(&:completed?)
102
129
  end
103
130
 
104
- # callback for when the process was cancelled
105
- def on_cancelled_entry(*args)
106
- Taskinator.instrumenter.instrument('taskinator.process.cancelled', instrumentation_payload) do
107
- # intentionally left empty
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
- # subclasses must implement this method
112
- def tasks_completed?(*args)
113
- raise NotImplementedError
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
- # include after defining the workflow
122
- # since the load and persist state methods
123
- # need to override the ones defined by workflow
124
- include Persistence
155
+ #--------------------------------------------------
156
+ # subclasses must implement the following methods
157
+ #--------------------------------------------------
125
158
 
126
- def complete
127
- Taskinator.instrumenter.instrument('taskinator.process.completed', instrumentation_payload) do
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
- # callback for when the process has failed
135
- def on_failed_entry(*args)
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
- Taskinator.instrumenter.instrument('taskinator.process.enqueued', instrumentation_payload) do
146
- if tasks.empty?
147
- complete! # weren't any tasks to start with
148
- else
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
- Taskinator.instrumenter.instrument('taskinator.process.started', instrumentation_payload) do
156
- task = tasks.first
157
- if task
158
- task.start!
159
- else
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.name}, tasks=[#{tasks.inspect}]>)
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
- Taskinator.instrumenter.instrument('taskinator.process.enqueued', instrumentation_payload) do
196
- if tasks.empty?
197
- complete! # weren't any tasks to start with
198
- else
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
- Taskinator.instrumenter.instrument('taskinator.process.started', instrumentation_payload) do
206
- if tasks.empty?
207
- complete! # weren't any tasks to start with
208
- else
209
- if concurrency_method == :fork
210
- tasks.each do |task|
211
- fork do
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
- Process.waitall
216
- else
217
- threads = tasks.map do |task|
218
- Thread.new do
219
- task.start!
220
- end
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?(*args)
253
+ def tasks_completed?
235
254
  if (complete_on == CompleteOn::First)
236
255
  tasks.any?(&:completed?)
237
256
  else
238
- tasks.all?(&:completed?)
257
+ super # all
239
258
  end
240
259
  end
241
260
 
242
261
  def accept(visitor)
243
262
  super
244
- visitor.visit_attribute(:complete_on)
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.name}, complete_on=:#{complete_on}, tasks=[#{tasks.inspect}]>)
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
@@ -39,11 +39,6 @@ module Taskinator
39
39
  adapter.enqueue_task(task)
40
40
  end
41
41
 
42
- def enqueue_job(job)
43
- Taskinator.logger.info("Enqueuing job #{job}")
44
- adapter.enqueue_job(job)
45
- end
46
-
47
42
  end
48
43
 
49
44
  end