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