taskinator 0.5.0 → 0.5.2

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.
@@ -48,6 +48,7 @@ module Taskinator
48
48
  @parent = value
49
49
  # update the uuid to be "scoped" within the parent task
50
50
  @uuid = "#{@parent.uuid}:subprocess"
51
+ @key = nil # NB: invalidate memoized key
51
52
  end
52
53
 
53
54
  def tasks
@@ -123,7 +124,7 @@ module Taskinator
123
124
  instrument('taskinator.process.completed', completed_payload) do
124
125
  complete if respond_to?(:complete)
125
126
  # notify the parent task (if there is one) that this process has completed
126
- # note: parent may be a proxy, so explicity check for nil?
127
+ # note: parent may be a proxy, so explicitly check for nil?
127
128
  unless parent.nil?
128
129
  parent.complete!
129
130
  else
@@ -133,6 +134,9 @@ module Taskinator
133
134
  end
134
135
  end
135
136
 
137
+ # TODO: add retry method - to pick up from a failed task
138
+ # e.g. like retrying a failed job in Resque Web
139
+
136
140
  def tasks_completed?
137
141
  # TODO: optimize this
138
142
  tasks.all?(&:completed?)
@@ -151,7 +155,7 @@ module Taskinator
151
155
  instrument('taskinator.process.failed', failed_payload(error)) do
152
156
  fail(error) if respond_to?(:fail)
153
157
  # notify the parent task (if there is one) that this process has failed
154
- # note: parent may be a proxy, so explicity check for nil?
158
+ # note: parent may be a proxy, so explicitly check for nil?
155
159
  parent.fail!(error) unless parent.nil?
156
160
  end
157
161
  end
@@ -166,6 +170,7 @@ module Taskinator
166
170
  # subclasses must implement the following methods
167
171
  #--------------------------------------------------
168
172
 
173
+ # :nocov:
169
174
  def enqueue
170
175
  raise NotImplementedError
171
176
  end
@@ -177,6 +182,7 @@ module Taskinator
177
182
  def task_completed(task)
178
183
  raise NotImplementedError
179
184
  end
185
+ # :nocov:
180
186
 
181
187
  #--------------------------------------------------
182
188
 
@@ -199,10 +205,9 @@ module Taskinator
199
205
  end
200
206
 
201
207
  def task_completed(task)
202
- # deincrement the count of pending sequential tasks
208
+ # decrement the count of pending sequential tasks
203
209
  pending = deincr_pending_tasks
204
210
 
205
- Taskinator.statsd_client.count("taskinator.#{definition.name.underscore.parameterize}.pending", pending)
206
211
  Taskinator.logger.info("Completed task for process '#{uuid}'. Pending is #{pending}.")
207
212
 
208
213
  next_task = task.next
@@ -237,7 +242,6 @@ module Taskinator
237
242
  if tasks.empty?
238
243
  complete! # weren't any tasks to start with
239
244
  else
240
- Taskinator.statsd_client.count("taskinator.#{definition.name.underscore.parameterize}.pending", tasks.count)
241
245
  Taskinator.logger.info("Enqueuing #{tasks.count} tasks for process '#{uuid}'.")
242
246
  tasks.each(&:enqueue!)
243
247
  end
@@ -249,6 +253,7 @@ module Taskinator
249
253
  complete! # weren't any tasks to start with
250
254
  else
251
255
  if concurrency_method == :fork
256
+ # :nocov:
252
257
  warn("[DEPRECATED]: concurrency_method will be removed in a future version.")
253
258
  tasks.each do |task|
254
259
  fork do
@@ -256,6 +261,7 @@ module Taskinator
256
261
  end
257
262
  end
258
263
  Process.waitall
264
+ # :nocov:
259
265
  else
260
266
  threads = tasks.map do |task|
261
267
  Thread.new do
@@ -268,14 +274,13 @@ module Taskinator
268
274
  end
269
275
 
270
276
  def task_completed(task)
271
- # deincrement the count of pending concurrent tasks
277
+ # decrement the count of pending concurrent tasks
272
278
  pending = deincr_pending_tasks
273
279
 
274
- Taskinator.statsd_client.count("taskinator.#{definition.name.underscore.parameterize}.pending", pending)
275
280
  Taskinator.logger.info("Completed task for process '#{uuid}'. Pending is #{pending}.")
276
281
 
277
282
  # when complete on first, then don't bother with subsequent tasks completing
278
- if (complete_on == CompleteOn::First)
283
+ if complete_on == CompleteOn::First
279
284
  complete! unless completed?
280
285
  else
281
286
  complete! if pending < 1
@@ -283,7 +288,7 @@ module Taskinator
283
288
  end
284
289
 
285
290
  def tasks_completed?
286
- if (complete_on == CompleteOn::First)
291
+ if complete_on == CompleteOn::First
287
292
  tasks.any?(&:completed?)
288
293
  else
289
294
  super # all
@@ -21,6 +21,7 @@ module Taskinator
21
21
  end
22
22
 
23
23
  attr_reader :process
24
+ attr_reader :definition
24
25
  attr_reader :uuid
25
26
  attr_reader :options
26
27
  attr_reader :queue
@@ -35,6 +36,7 @@ module Taskinator
35
36
 
36
37
  @uuid = "#{process.uuid}:task:#{Taskinator.generate_uuid}"
37
38
  @process = process
39
+ @definition = process.definition
38
40
  @options = options
39
41
  @queue = options.delete(:queue)
40
42
  @created_at = Time.now.utc
@@ -45,6 +47,7 @@ module Taskinator
45
47
  def accept(visitor)
46
48
  visitor.visit_attribute(:uuid)
47
49
  visitor.visit_process_reference(:process)
50
+ visitor.visit_type(:definition)
48
51
  visitor.visit_task_reference(:next)
49
52
  visitor.visit_args(:options)
50
53
  visitor.visit_attribute(:queue)
@@ -134,6 +137,7 @@ module Taskinator
134
137
  # subclasses must implement the following methods
135
138
  #--------------------------------------------------
136
139
 
140
+ # :nocov:
137
141
  def enqueue
138
142
  raise NotImplementedError
139
143
  end
@@ -141,6 +145,7 @@ module Taskinator
141
145
  def start
142
146
  raise NotImplementedError
143
147
  end
148
+ # :nocov:
144
149
 
145
150
  #--------------------------------------------------
146
151
  # and optionally, provide methods:
@@ -155,13 +160,11 @@ module Taskinator
155
160
  # a task which invokes the specified method on the definition
156
161
  # the args must be intrinsic types, since they are serialized to YAML
157
162
  class Step < Task
158
- attr_reader :definition
159
163
  attr_reader :method
160
164
  attr_reader :args
161
165
 
162
166
  def initialize(process, method, args, options={})
163
167
  super(process, options)
164
- @definition = process.definition # for convenience
165
168
 
166
169
  raise ArgumentError, 'method' if method.nil?
167
170
  raise NoMethodError, method unless executor.respond_to?(method)
@@ -188,17 +191,16 @@ module Taskinator
188
191
 
189
192
  def accept(visitor)
190
193
  super
191
- visitor.visit_type(:definition)
192
194
  visitor.visit_attribute(:method)
193
195
  visitor.visit_args(:args)
194
196
  end
195
197
 
196
198
  def executor
197
- @executor ||= Taskinator::Executor.new(@definition, self)
199
+ @executor ||= Taskinator::Executor.new(definition, self)
198
200
  end
199
201
 
200
202
  def inspect
201
- %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", method=:#{method}, args=#{args}, current_state=:#{current_state}>)
203
+ %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", definition=:#{definition}, method=:#{method}, args=#{args}, current_state=:#{current_state}>)
202
204
  end
203
205
  end
204
206
 
@@ -207,13 +209,11 @@ module Taskinator
207
209
  # a task which invokes the specified background job
208
210
  # the args must be intrinsic types, since they are serialized to YAML
209
211
  class Job < Task
210
- attr_reader :definition
211
212
  attr_reader :job
212
213
  attr_reader :args
213
214
 
214
215
  def initialize(process, job, args, options={})
215
216
  super(process, options)
216
- @definition = process.definition # for convenience
217
217
 
218
218
  raise ArgumentError, 'job' if job.nil?
219
219
  raise ArgumentError, 'job' unless job.methods.include?(:perform) || job.instance_methods.include?(:perform)
@@ -228,7 +228,6 @@ module Taskinator
228
228
 
229
229
  def start
230
230
  # NNB: if other job types are required, may need to implement how they get invoked here!
231
- # FIXME: possible implement using ActiveJob instead, so it doesn't matter how the worker is implemented
232
231
 
233
232
  if job.respond_to?(:perform)
234
233
  # resque
@@ -250,13 +249,12 @@ module Taskinator
250
249
 
251
250
  def accept(visitor)
252
251
  super
253
- visitor.visit_type(:definition)
254
252
  visitor.visit_type(:job)
255
253
  visitor.visit_args(:args)
256
254
  end
257
255
 
258
256
  def inspect
259
- %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", job=#{job}, args=#{args}, current_state=:#{current_state}>)
257
+ %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", definition=:#{definition}, job=#{job}, args=#{args}, current_state=:#{current_state}>)
260
258
  end
261
259
  end
262
260
 
@@ -302,7 +300,7 @@ module Taskinator
302
300
  end
303
301
 
304
302
  def inspect
305
- %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", sub_process=#{sub_process.inspect}, current_state=:#{current_state}>)
303
+ %(#<#{self.class.name}:0x#{self.__id__.to_s(16)} uuid="#{uuid}", definition=:#{definition}, sub_process=#{sub_process.inspect}, current_state=:#{current_state}>)
306
304
  end
307
305
  end
308
306
  end
@@ -1,3 +1,3 @@
1
1
  module Taskinator
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.2"
3
3
  end
data/lib/taskinator.rb CHANGED
@@ -6,8 +6,6 @@ require 'delegate'
6
6
 
7
7
  require 'taskinator/version'
8
8
 
9
- require 'taskinator/log_stats'
10
-
11
9
  require 'taskinator/complete_on'
12
10
  require 'taskinator/redis_connection'
13
11
  require 'taskinator/logger'
@@ -19,7 +17,6 @@ require 'taskinator/workflow'
19
17
  require 'taskinator/visitor'
20
18
  require 'taskinator/persistence'
21
19
  require 'taskinator/instrumentation'
22
- require 'taskinator/xml_visitor'
23
20
 
24
21
  require 'taskinator/task'
25
22
  require 'taskinator/tasks'
@@ -87,14 +84,6 @@ module Taskinator
87
84
  Taskinator::Logging.logger = log
88
85
  end
89
86
 
90
- def statsd_client
91
- Taskinator::LogStats.client
92
- end
93
-
94
- def statsd_client=(client)
95
- Taskinator::LogStats.client = client
96
- end
97
-
98
87
  # the queue adapter to use
99
88
  # supported adapters include
100
89
  # :active_job, :delayed_job, :redis and :sidekiq
@@ -135,14 +124,12 @@ module Taskinator
135
124
  end
136
125
 
137
126
  class NoOpInstrumenter
138
- # :nocov:
139
127
  def instrument(event, payload={})
140
128
  yield(payload) if block_given?
141
129
  end
142
130
  end
143
131
 
144
132
  class ConsoleInstrumenter
145
- # :nocov:
146
133
  def instrument(event, payload={})
147
134
  puts [event.inspect, payload.to_yaml]
148
135
  yield(payload) if block_given?
@@ -0,0 +1,30 @@
1
+ module TestJob
2
+ def self.perform(*args)
3
+ end
4
+ end
5
+
6
+ class TestJobClass
7
+ def perform(*args)
8
+ end
9
+ end
10
+
11
+ module TestJobModule
12
+ def self.perform(*args)
13
+ end
14
+ end
15
+
16
+ class TestJobClassNoArgs
17
+ def perform
18
+ end
19
+ end
20
+
21
+ module TestJobModuleNoArgs
22
+ def self.perform
23
+ end
24
+ end
25
+
26
+ module TestJobError
27
+ def self.perform
28
+ raise ArgumentError
29
+ end
30
+ end
@@ -0,0 +1,2 @@
1
+ class TestJobTask < Taskinator::Task::Job
2
+ end
@@ -0,0 +1,2 @@
1
+ class TestStepTask < Taskinator::Task::Step
2
+ end
@@ -0,0 +1,2 @@
1
+ class TestSubProcessTask < Taskinator::Task::SubProcess
2
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Taskinator::Definition::Builder do
3
+ describe Taskinator::Builder do
4
4
 
5
5
  let(:definition) do
6
6
  Module.new do
@@ -26,7 +26,7 @@ describe Taskinator::Definition::Builder do
26
26
  Proc.new {|*args| the_block.call }
27
27
  }
28
28
 
29
- subject { Taskinator::Definition::Builder.new(process, definition, *[*args, builder_options]) }
29
+ subject { Taskinator::Builder.new(process, definition, *[*args, builder_options]) }
30
30
 
31
31
  it "assign attributes" do
32
32
  expect(subject.process).to eq(process)
@@ -106,6 +106,37 @@ describe Taskinator::Definition do
106
106
  expect(subject.create_process).to be_a(Taskinator::Process)
107
107
  end
108
108
 
109
+ it "handles error" do
110
+ subject.define_process do
111
+ raise ArgumentError
112
+ end
113
+
114
+ expect {
115
+ subject.create_process
116
+ }.to raise_error(ArgumentError)
117
+ end
118
+
119
+ it "checks defined arguments provided" do
120
+ subject.define_process :arg1, :arg2 do
121
+ end
122
+
123
+ expect {
124
+ subject.create_process
125
+ }.to raise_error(ArgumentError)
126
+
127
+ expect {
128
+ subject.create_process :foo
129
+ }.to raise_error(ArgumentError)
130
+
131
+ expect {
132
+ subject.create_process :foo, :bar
133
+ }.not_to raise_error
134
+
135
+ expect {
136
+ subject.create_process :foo, :bar, :baz
137
+ }.not_to raise_error
138
+ end
139
+
109
140
  it "defaults the scope to :shared" do
110
141
  block = SpecSupport::Block.new
111
142
  allow(block).to receive(:to_proc) {
@@ -2,8 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  describe Taskinator::Instrumentation, :redis => true do
4
4
 
5
- let(:definition) { TestDefinition }
6
-
7
5
  subject do
8
6
  klass = Class.new do
9
7
  include Taskinator::Persistence
@@ -15,10 +13,12 @@ describe Taskinator::Instrumentation, :redis => true do
15
13
 
16
14
  attr_reader :uuid
17
15
  attr_reader :options
16
+ attr_reader :definition
18
17
 
19
18
  def initialize
20
19
  @uuid = Taskinator.generate_uuid
21
20
  @options = { :bar => :baz }
21
+ @definition = TestDefinition
22
22
  end
23
23
  end
24
24
 
@@ -75,6 +75,7 @@ describe Taskinator::Instrumentation, :redis => true do
75
75
  expect(subject.completed_payload(:baz => :qux)).to eq(
76
76
  OpenStruct.new({
77
77
  :type => subject.class.name,
78
+ :definition => subject.definition.name,
78
79
  :process_uuid => subject.uuid,
79
80
  :process_options => {:foo => :bar},
80
81
  :uuid => subject.uuid,
@@ -46,6 +46,15 @@ describe Taskinator::Persistence, :redis => true do
46
46
  expect(subject.fetch('uuid', cache)).to eq(item)
47
47
  end
48
48
 
49
+ it "yields UnknownType" do
50
+ Taskinator.redis do |conn|
51
+ conn.hmset(*[subject.key_for("foo"), [:type, 'UnknownFoo']])
52
+ end
53
+ instance = subject.fetch("foo")
54
+ expect(instance).to be_a(Taskinator::Persistence::UnknownType)
55
+ expect(instance.type).to eq("UnknownFoo")
56
+ end
57
+
49
58
  describe "for processes" do
50
59
  let(:process) { TestProcess.new(definition) }
51
60
 
@@ -53,6 +62,19 @@ describe Taskinator::Persistence, :redis => true do
53
62
  process.save
54
63
  expect(TestProcess.fetch(process.uuid)).to eq(process)
55
64
  }
65
+
66
+ describe "unknown definition" do
67
+ it "yields UnknownType" do
68
+ Taskinator.redis do |conn|
69
+ conn.hmset(*[process.key, [:type, TestProcess.name], [:uuid, process.uuid], [:definition, 'UnknownFoo']])
70
+ end
71
+
72
+ instance = TestProcess.fetch(process.uuid)
73
+ expect(instance.uuid).to eq(process.uuid)
74
+ expect(instance.definition).to be_a(Taskinator::Persistence::UnknownType)
75
+ expect(instance.definition.type).to eq("UnknownFoo")
76
+ end
77
+ end
56
78
  end
57
79
 
58
80
  describe "for tasks" do
@@ -62,9 +84,45 @@ describe Taskinator::Persistence, :redis => true do
62
84
  it {
63
85
  process.tasks << task
64
86
  process.save
65
- expect(TestTask.fetch(task.uuid)).to eq(task)
66
- expect(TestTask.fetch(task.uuid).process).to eq(process)
87
+
88
+ instance = TestTask.fetch(task.uuid)
89
+ expect(instance).to eq(task)
90
+ expect(instance.process).to eq(process)
67
91
  }
92
+
93
+ describe "unknown job" do
94
+ let(:task) { TestJobTask.new(process, TestJob, []) }
95
+
96
+ it "yields UnknownType" do
97
+ Taskinator.redis do |conn|
98
+ conn.hmset(*[task.key, [:type, task.class.name], [:uuid, task.uuid], [:job, 'UnknownBar']])
99
+ end
100
+
101
+ instance = TestJobTask.fetch(task.uuid)
102
+ expect(instance.uuid).to eq(task.uuid)
103
+ expect(instance.job).to be_a(Taskinator::Persistence::UnknownType)
104
+ expect(instance.job.type).to eq("UnknownBar")
105
+ end
106
+ end
107
+
108
+ describe "unknown subprocess" do
109
+ let(:sub_process) { TestProcess.new(definition) }
110
+ let(:task) { TestSubProcessTask.new(process, sub_process) }
111
+
112
+ it "yields UnknownType" do
113
+ Taskinator.redis do |conn|
114
+ conn.multi do |transaction|
115
+ transaction.hmset(*[task.key, [:type, task.class.name], [:uuid, task.uuid], [:sub_process, sub_process.uuid]])
116
+ transaction.hmset(*[sub_process.key, [:type, sub_process.class.name], [:uuid, sub_process.uuid], [:definition, 'UnknownBaz']])
117
+ end
118
+ end
119
+
120
+ instance = TestSubProcessTask.fetch(task.uuid)
121
+ expect(instance.uuid).to eq(task.uuid)
122
+ expect(instance.sub_process.definition).to be_a(Taskinator::Persistence::UnknownType)
123
+ expect(instance.sub_process.definition.type).to eq("UnknownBaz")
124
+ end
125
+ end
68
126
  end
69
127
  end
70
128
  end
@@ -188,6 +246,72 @@ describe Taskinator::Persistence, :redis => true do
188
246
  end
189
247
  end
190
248
 
249
+ describe "unknown type helpers" do
250
+ subject { Taskinator::Persistence::UnknownType }
251
+
252
+ describe "#new" do
253
+ it "instantiates new module instance" do
254
+ instance = subject.new("foo")
255
+ expect(instance).to_not be_nil
256
+ expect(instance).to be_a(::Module)
257
+ end
258
+
259
+ it "yields same instance for same type" do
260
+ instance1 = subject.new("foo")
261
+ instance2 = subject.new("foo")
262
+ expect(instance1).to eq(instance2)
263
+ end
264
+ end
265
+
266
+ describe ".type" do
267
+ it {
268
+ instance = subject.new("foo")
269
+ expect(instance.type).to eq("foo")
270
+ }
271
+ end
272
+
273
+ describe ".to_s" do
274
+ it {
275
+ instance = subject.new("foo")
276
+ expect(instance.to_s).to eq("Unknown type 'foo'.")
277
+ }
278
+ end
279
+
280
+ describe ".allocate" do
281
+ it "emulates Object#allocate" do
282
+ instance = subject.new("foo")
283
+ expect(instance.allocate).to eq(instance)
284
+ end
285
+ end
286
+
287
+ describe ".accept" do
288
+ it {
289
+ instance = subject.new("foo")
290
+ expect(instance).to respond_to(:accept)
291
+ }
292
+ end
293
+
294
+ describe ".perform" do
295
+ it "raises UnknownTypeError" do
296
+ instance = subject.new("foo")
297
+ expect {
298
+ instance.perform(:foo, 1, false)
299
+ }.to raise_error(Taskinator::Persistence::UnknownTypeError)
300
+ end
301
+ end
302
+
303
+ describe "via executor" do
304
+ it "raises UnknownTypeError" do
305
+ instance = subject.new("foo")
306
+ executor = Taskinator::Executor.new(instance)
307
+
308
+ expect {
309
+ executor.foo
310
+ }.to raise_error(Taskinator::Persistence::UnknownTypeError)
311
+ end
312
+ end
313
+ end
314
+
191
315
  describe "instance methods" do
192
316
  subject {
193
317
  klass = Class.new do
@@ -207,11 +331,14 @@ describe Taskinator::Persistence, :redis => true do
207
331
  }
208
332
 
209
333
  describe "#save" do
210
- pending __FILE__
334
+ pending
211
335
  end
212
336
 
213
337
  describe "#to_xml" do
214
- pending __FILE__
338
+ it {
339
+ process = TestFlows::Task.create_process(1)
340
+ expect(process.to_xml).to match(/xml/)
341
+ }
215
342
  end
216
343
 
217
344
  describe "#key" do
@@ -426,6 +553,5 @@ describe Taskinator::Persistence, :redis => true do
426
553
  end
427
554
 
428
555
  end
429
-
430
556
  end
431
557
  end
@@ -35,6 +35,14 @@ describe Taskinator::Process do
35
35
  it { expect(subject.tasks).to be_a(Taskinator::Tasks) }
36
36
  end
37
37
 
38
+ describe "#no_tasks_defined?" do
39
+ it { expect(subject.no_tasks_defined?).to be }
40
+ it {
41
+ subject.tasks << :mock
42
+ expect(subject.no_tasks_defined?).to_not be
43
+ }
44
+ end
45
+
38
46
  describe "#to_s" do
39
47
  it { expect(subject.to_s).to match(/#{subject.uuid}/) }
40
48
  end
@@ -288,7 +296,7 @@ describe Taskinator::Process do
288
296
  subject.task_completed(task1)
289
297
  end
290
298
 
291
- it "deincrements the pending task count" do
299
+ it "decrements the pending task count" do
292
300
  tasks.each {|t| subject.tasks << t }
293
301
  task1 = tasks[0]
294
302
  task2 = tasks[1]
@@ -367,7 +375,7 @@ describe Taskinator::Process do
367
375
  subject.task_failed(tasks.first, error)
368
376
  end
369
377
 
370
- it "doesn't deincement pending task count" do
378
+ it "doesn't decrement pending task count" do
371
379
  tasks.each {|t| subject.tasks << t }
372
380
 
373
381
  expect(subject).to_not receive(:deincr_pending_tasks)
@@ -515,7 +523,7 @@ describe Taskinator::Process do
515
523
  end
516
524
  end
517
525
 
518
- it "deincrements the pending task count" do
526
+ it "decrements the pending task count" do
519
527
  tasks.each {|t| subject.tasks << t }
520
528
  task1 = tasks[0]
521
529
  task2 = tasks[1]