taskinator 0.0.15 → 0.0.16

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/Gemfile.lock +2 -1
  4. data/README.md +1 -1
  5. data/lib/taskinator/api.rb +3 -1
  6. data/lib/taskinator/create_process_worker.rb +60 -0
  7. data/lib/taskinator/definition.rb +47 -8
  8. data/lib/taskinator/job_worker.rb +2 -2
  9. data/lib/taskinator/logger.rb +13 -75
  10. data/lib/taskinator/persistence.rb +48 -40
  11. data/lib/taskinator/process.rb +13 -6
  12. data/lib/taskinator/process_worker.rb +3 -1
  13. data/lib/taskinator/queues/delayed_job.rb +12 -5
  14. data/lib/taskinator/queues/resque.rb +16 -5
  15. data/lib/taskinator/queues/sidekiq.rb +14 -5
  16. data/lib/taskinator/queues.rb +12 -0
  17. data/lib/taskinator/task.rb +21 -8
  18. data/lib/taskinator/task_worker.rb +3 -1
  19. data/lib/taskinator/tasks.rb +1 -1
  20. data/lib/taskinator/version.rb +1 -1
  21. data/lib/taskinator.rb +20 -1
  22. data/spec/examples/queue_adapter_examples.rb +10 -0
  23. data/spec/spec_helper.rb +11 -0
  24. data/spec/support/mock_definition.rb +22 -0
  25. data/spec/support/spec_support.rb +3 -1
  26. data/spec/support/test_definition.rb +3 -0
  27. data/spec/support/test_process.rb +2 -0
  28. data/spec/support/test_queue.rb +7 -1
  29. data/spec/support/test_task.rb +2 -0
  30. data/spec/taskinator/api_spec.rb +2 -2
  31. data/spec/taskinator/create_process_worker_spec.rb +44 -0
  32. data/spec/taskinator/definition/builder_spec.rb +5 -5
  33. data/spec/taskinator/definition_spec.rb +103 -6
  34. data/spec/taskinator/executor_spec.rb +1 -1
  35. data/spec/taskinator/job_worker_spec.rb +3 -3
  36. data/spec/taskinator/persistence_spec.rb +12 -19
  37. data/spec/taskinator/process_spec.rb +29 -5
  38. data/spec/taskinator/process_worker_spec.rb +3 -3
  39. data/spec/taskinator/queues/delayed_job_spec.rb +23 -6
  40. data/spec/taskinator/queues/resque_spec.rb +27 -6
  41. data/spec/taskinator/queues/sidekiq_spec.rb +28 -10
  42. data/spec/taskinator/task_spec.rb +80 -7
  43. data/spec/taskinator/task_worker_spec.rb +3 -3
  44. data/spec/taskinator/{intermodal_spec.rb → taskinator_spec.rb} +35 -1
  45. data/spec/taskinator/tasks_spec.rb +1 -2
  46. data/taskinator.gemspec +17 -15
  47. metadata +30 -4
@@ -9,11 +9,12 @@ module Taskinator
9
9
 
10
10
  class SidekiqAdapter
11
11
  def initialize(config={})
12
- @config = {
13
- :process_queue => :default,
14
- :task_queue => :default,
15
- :job_queue => :default
16
- }.merge(config)
12
+ @config = Taskinator::Queues::DefaultConfig.merge(config)
13
+ end
14
+
15
+ def enqueue_create_process(definition, uuid, args)
16
+ queue = definition.queue || @config[:definition_queue]
17
+ ProcessWorker.client_push('class' => CreateProcessWorker, 'args' => [definition.name, uuid, Taskinator::Persistence.serialize(args)], 'queue' => queue)
17
18
  end
18
19
 
19
20
  def enqueue_process(process)
@@ -31,6 +32,14 @@ module Taskinator
31
32
  JobWorker.client_push('class' => JobWorker, 'args' => [job.uuid], 'queue' => queue)
32
33
  end
33
34
 
35
+ class CreateProcessWorker
36
+ include ::Sidekiq::Worker
37
+
38
+ def perform(definition_name, uuid, args)
39
+ Taskinator::CreateProcessWorker.new(definition_name, uuid, args).perform
40
+ end
41
+ end
42
+
34
43
  class ProcessWorker
35
44
  include ::Sidekiq::Worker
36
45
 
@@ -1,6 +1,13 @@
1
1
  module Taskinator
2
2
  module Queues
3
3
 
4
+ DefaultConfig = {
5
+ :definition_queue => :default,
6
+ :process_queue => :default,
7
+ :task_queue => :default,
8
+ :job_queue => :default,
9
+ }.freeze
10
+
4
11
  def self.create_adapter(adapter, config={})
5
12
  begin
6
13
  LoggedAdapter.new(send("create_#{adapter}_adapter", config))
@@ -22,6 +29,11 @@ module Taskinator
22
29
  adapter
23
30
  end
24
31
 
32
+ def enqueue_create_process(definition, uuid, args)
33
+ Taskinator.logger.info("Enqueuing process creation for #{definition}")
34
+ adapter.enqueue_create_process(definition, uuid, args)
35
+ end
36
+
25
37
  def enqueue_process(process)
26
38
  Taskinator.logger.info("Enqueuing process #{process}")
27
39
  adapter.enqueue_process(process)
@@ -24,6 +24,7 @@ module Taskinator
24
24
  attr_reader :process
25
25
  attr_reader :uuid
26
26
  attr_reader :options
27
+ attr_reader :queue
27
28
 
28
29
  # the next task in the sequence
29
30
  attr_accessor :next
@@ -34,6 +35,7 @@ module Taskinator
34
35
  @uuid = SecureRandom.uuid
35
36
  @process = process
36
37
  @options = options
38
+ @queue = options.delete(:queue)
37
39
  end
38
40
 
39
41
  def accept(visitor)
@@ -41,6 +43,7 @@ module Taskinator
41
43
  visitor.visit_process_reference(:process)
42
44
  visitor.visit_task_reference(:next)
43
45
  visitor.visit_args(:options)
46
+ visitor.visit_attribute(:queue)
44
47
  end
45
48
 
46
49
  def <=>(other)
@@ -51,10 +54,6 @@ module Taskinator
51
54
  "#<#{self.class.name}:#{uuid}>"
52
55
  end
53
56
 
54
- def queue
55
- options[:queue]
56
- end
57
-
58
57
  workflow do
59
58
  state :initial do
60
59
  event :enqueue, :transitions_to => :enqueued
@@ -150,7 +149,9 @@ module Taskinator
150
149
 
151
150
  def start
152
151
  # ASSUMPTION: when the method returns, the task is considered to be complete
153
- executor.send(method, *args)
152
+ Taskinator.instrumenter.instrument(:execute_step_task, :uuid => uuid) do
153
+ executor.send(method, *args)
154
+ end
154
155
  @is_complete = true
155
156
  end
156
157
 
@@ -189,8 +190,10 @@ module Taskinator
189
190
  Taskinator.queue.enqueue_job(self)
190
191
  end
191
192
 
192
- def perform(&block)
193
- yield(job, args)
193
+ def perform
194
+ Taskinator.instrumenter.instrument(:execute_job_task, :uuid => uuid) do
195
+ yield(job, args)
196
+ end
194
197
  @is_complete = true
195
198
  end
196
199
 
@@ -220,7 +223,9 @@ module Taskinator
220
223
  end
221
224
 
222
225
  def start
223
- sub_process.start!
226
+ Taskinator.instrumenter.instrument(:execute_subprocess, :uuid => uuid) do
227
+ sub_process.start!
228
+ end
224
229
  end
225
230
 
226
231
  def can_complete_task?
@@ -233,5 +238,13 @@ module Taskinator
233
238
  visitor.visit_process(:sub_process)
234
239
  end
235
240
  end
241
+
242
+ # reloads the task from storage
243
+ # NB: only implemented by LazyLoader so that
244
+ # the task can be lazy loaded, thereafter
245
+ # it has no effect
246
+ def reload
247
+ false
248
+ end
236
249
  end
237
250
  end
@@ -1,5 +1,7 @@
1
1
  module Taskinator
2
2
  class TaskWorker
3
+ attr_reader :uuid
4
+
3
5
  def initialize(uuid)
4
6
  @uuid = uuid
5
7
  end
@@ -10,7 +12,7 @@ module Taskinator
10
12
  begin
11
13
  task.start!
12
14
  task.complete! if task.can_complete?
13
- rescue Exception => e
15
+ rescue => e
14
16
  Taskinator.logger.error(e)
15
17
  task.fail!(e)
16
18
  raise e
@@ -31,7 +31,7 @@ module Taskinator
31
31
  @head.nil?
32
32
  end
33
33
 
34
- def each(&block)
34
+ def each
35
35
  return to_enum(__method__) unless block_given?
36
36
 
37
37
  current = @head
@@ -1,3 +1,3 @@
1
1
  module Taskinator
2
- VERSION = "0.0.15"
2
+ VERSION = "0.0.16"
3
3
  end
data/lib/taskinator.rb CHANGED
@@ -21,6 +21,7 @@ require 'taskinator/process'
21
21
  require 'taskinator/task_worker'
22
22
  require 'taskinator/job_worker'
23
23
  require 'taskinator/process_worker'
24
+ require 'taskinator/create_process_worker'
24
25
 
25
26
  require 'taskinator/executor'
26
27
  require 'taskinator/queues'
@@ -46,7 +47,7 @@ module Taskinator
46
47
  ##
47
48
  # Configuration for Taskinator client, use like:
48
49
  #
49
- # Taskinator.configure_client do |config|
50
+ # Taskinator.configure do |config|
50
51
  # config.redis = { :namespace => 'myapp', :pool_size => 1, :url => 'redis://myhost:8877/0' }
51
52
  # config.queue_config = { :process_queue => 'processes', :task_queue => 'tasks' }
52
53
  # end
@@ -101,5 +102,23 @@ module Taskinator
101
102
  config = queue_config || {}
102
103
  @queue ||= Taskinator::Queues.create_adapter(adapter, config)
103
104
  end
105
+
106
+ # set the instrumenter to use.
107
+ # can be ActiveSupport::Notifications
108
+ def instrumenter
109
+ @instrumenter ||= NoOpInstrumenter.new
110
+ end
111
+ def instrumenter=(value)
112
+ @instrumenter = value
113
+ end
114
+
104
115
  end
116
+
117
+ class NoOpInstrumenter
118
+ # just yield to the given block
119
+ def instrument(event, payload={})
120
+ yield(payload) if block_given?
121
+ end
122
+ end
123
+
105
124
  end
@@ -10,6 +10,16 @@ shared_examples_for "a queue adapter" do |adapter_name, adapter_type|
10
10
  expect(Taskinator.queue.adapter).to be_a(adapter_type)
11
11
  end
12
12
 
13
+ describe "#enqueue_create_process" do
14
+ it { expect(subject).to respond_to(:enqueue_create_process) }
15
+
16
+ it "should enqueue a create process" do
17
+ expect {
18
+ subject.enqueue_create_process(double('definition', :name => 'definition', :queue => nil), 'xx-xx-xx-xx', :foo => :bar)
19
+ }.to_not raise_error
20
+ end
21
+ end
22
+
13
23
  describe "#enqueue_process" do
14
24
  it { expect(subject).to respond_to(:enqueue_process) }
15
25
 
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,7 @@ Bundler.setup
4
4
  require 'simplecov'
5
5
  require 'coveralls'
6
6
  require 'pry'
7
+ require 'active_support/notifications'
7
8
 
8
9
  SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
9
10
  SimpleCov::Formatter::HTMLFormatter,
@@ -26,6 +27,16 @@ ResqueSpec.disable_ext = false
26
27
 
27
28
  require 'taskinator'
28
29
 
30
+ Taskinator.configure do |config|
31
+
32
+ # use active support for instrumentation
33
+ config.instrumenter = ActiveSupport::Notifications
34
+
35
+ # use a "null stream" for logging
36
+ config.logger = Logger.new(File::NULL)
37
+
38
+ end
39
+
29
40
  # require supporting files with custom matchers and macros, etc
30
41
  Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f }
31
42
 
@@ -0,0 +1,22 @@
1
+ module MockDefinition
2
+
3
+ class << self
4
+ def create(queue=nil)
5
+
6
+ definition = Module.new do
7
+ extend Taskinator::Definition
8
+
9
+ define_process :foo_hash do
10
+ # empty on purpose
11
+ end
12
+ end
13
+
14
+ definition.queue = queue
15
+
16
+ # create a constant, so that the mock definition isn't anonymous
17
+ Object.const_set("Mock#{SecureRandom.hex}Definition", definition)
18
+
19
+ end
20
+ end
21
+
22
+ end
@@ -1,7 +1,9 @@
1
1
  module SpecSupport
2
2
  class Block
3
3
  def to_proc
4
- lambda { |*args| call(*args) }
4
+ lambda { |*args|
5
+ call(*args)
6
+ }
5
7
  end
6
8
 
7
9
  # the call method must be provided by in specs
@@ -0,0 +1,3 @@
1
+ module TestDefinition
2
+ extend Taskinator::Definition
3
+ end
@@ -0,0 +1,2 @@
1
+ class TestProcess < Taskinator::Process
2
+ end
@@ -2,11 +2,12 @@ module Taskinator
2
2
  module Queues
3
3
 
4
4
  def self.create_test_queue_adapter(config={})
5
- TestQueueAdapter::new()
5
+ TestQueueAdapter.new
6
6
  end
7
7
 
8
8
  class TestQueueAdapter
9
9
 
10
+ attr_reader :creates
10
11
  attr_reader :processes
11
12
  attr_reader :tasks
12
13
  attr_reader :jobs
@@ -16,11 +17,16 @@ module Taskinator
16
17
  end
17
18
 
18
19
  def clear
20
+ @creates = []
19
21
  @processes = []
20
22
  @tasks = []
21
23
  @jobs = []
22
24
  end
23
25
 
26
+ def enqueue_create_process(definition, uuid, args)
27
+ @creates << [definition, uuid, args]
28
+ end
29
+
24
30
  def enqueue_process(process)
25
31
  @processes << process
26
32
  end
@@ -0,0 +1,2 @@
1
+ class TestTask < Taskinator::Task
2
+ end
@@ -8,7 +8,7 @@ describe Taskinator::Api, :redis => true do
8
8
 
9
9
  describe "#each" do
10
10
  it "does not enumerate when there aren't any processes" do
11
- block = SpecSupport::Block.new()
11
+ block = SpecSupport::Block.new
12
12
  expect(block).to_not receive(:call)
13
13
  subject.each(&block)
14
14
  end
@@ -22,7 +22,7 @@ describe Taskinator::Api, :redis => true do
22
22
  end
23
23
  end
24
24
 
25
- block = SpecSupport::Block.new()
25
+ block = SpecSupport::Block.new
26
26
  expect(block).to receive(:call).exactly(3).times
27
27
 
28
28
  subject.each(&block)
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Taskinator::CreateProcessWorker do
4
+
5
+ let(:definition) { MockDefinition.create }
6
+ let(:uuid) { SecureRandom.uuid }
7
+ let(:args) { {:foo => :bar} }
8
+
9
+ subject { Taskinator::CreateProcessWorker.new(definition.name, uuid, Taskinator::Persistence.serialize(:foo => :bar)) }
10
+
11
+ describe "#initialize" do
12
+ it {
13
+ expect(subject.definition).to eq(definition)
14
+ }
15
+
16
+ it {
17
+ MockDefinition.const_set(definition.name, definition)
18
+ Taskinator::CreateProcessWorker.new("MockDefinition::#{definition.name}", uuid, Taskinator::Persistence.serialize(:foo => :bar))
19
+ expect(subject.definition).to eq(definition)
20
+ }
21
+
22
+ it {
23
+ expect(subject.uuid).to eq(uuid)
24
+ }
25
+
26
+ it {
27
+ expect(subject.args).to eq(args)
28
+ }
29
+ end
30
+
31
+ describe "#perform" do
32
+ it "should create the process" do
33
+ expect(definition).to receive(:_create_process_).with(*args, :uuid => uuid).and_return(double('process', :enqueue! => nil))
34
+ subject.perform
35
+ end
36
+
37
+ it "should enqueue the process" do
38
+ expect_any_instance_of(Taskinator::Process).to receive(:enqueue!)
39
+ subject.perform
40
+ end
41
+ end
42
+
43
+
44
+ end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Taskinator::Definition::Builder do
4
4
 
5
5
  let(:definition) do
6
- Module.new() do
6
+ Module.new do
7
7
  extend Taskinator::Definition
8
8
 
9
9
  def iterator_method(*); end
@@ -17,7 +17,7 @@ describe Taskinator::Definition::Builder do
17
17
 
18
18
  let(:args) { [:arg1, :arg2, {:option => 1, :another => false}] }
19
19
 
20
- let(:block) { SpecSupport::Block.new() }
20
+ let(:block) { SpecSupport::Block.new }
21
21
 
22
22
  let(:define_block) {
23
23
  the_block = block
@@ -64,7 +64,7 @@ describe Taskinator::Definition::Builder do
64
64
 
65
65
  it "fails if block isn't given" do
66
66
  expect {
67
- subject.sequential()
67
+ subject.sequential
68
68
  }.to raise_error(ArgumentError)
69
69
  end
70
70
  end
@@ -83,7 +83,7 @@ describe Taskinator::Definition::Builder do
83
83
 
84
84
  it "fails if block isn't given" do
85
85
  expect {
86
- subject.concurrent()
86
+ subject.concurrent
87
87
  }.to raise_error(ArgumentError)
88
88
  end
89
89
  end
@@ -187,7 +187,7 @@ describe Taskinator::Definition::Builder do
187
187
 
188
188
  describe "#sub_process" do
189
189
  let(:sub_definition) do
190
- Module.new() do
190
+ Module.new do
191
191
  extend Taskinator::Definition
192
192
 
193
193
  define_process :some_arg1, :some_arg2, :some_arg3 do
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Taskinator::Definition do
4
4
 
5
5
  subject do
6
- Module.new() do
6
+ Module.new do
7
7
  extend Taskinator::Definition
8
8
  end
9
9
  end
@@ -23,21 +23,28 @@ describe Taskinator::Definition do
23
23
  end
24
24
 
25
25
  it "should not invoke the given block" do
26
- block = SpecSupport::Block.new()
26
+ block = SpecSupport::Block.new
27
27
  expect(block).to_not receive(:call)
28
28
  subject.define_process(&block)
29
29
  end
30
+
31
+ it "should raise ProcessAlreadyDefinedError error if already defined" do
32
+ subject.define_process
33
+ expect {
34
+ subject.define_process
35
+ }.to raise_error(Taskinator::Definition::ProcessAlreadyDefinedError)
36
+ end
30
37
  end
31
38
 
32
39
  describe "#create_process" do
33
- it "raises UndefinedProcessError" do
40
+ it "raises ProcessUndefinedError" do
34
41
  expect {
35
42
  subject.create_process
36
- }.to raise_error(Taskinator::Definition::UndefinedProcessError)
43
+ }.to raise_error(Taskinator::Definition::ProcessUndefinedError)
37
44
  end
38
45
 
39
46
  it "returns a process" do
40
- block = SpecSupport::Block.new()
47
+ block = SpecSupport::Block.new
41
48
  allow(block).to receive(:to_proc) {
42
49
  Proc.new {|*args| }
43
50
  }
@@ -47,7 +54,7 @@ describe Taskinator::Definition do
47
54
  end
48
55
 
49
56
  it "invokes the given block in the context of a ProcessBuilder" do
50
- block = SpecSupport::Block.new()
57
+ block = SpecSupport::Block.new
51
58
  expect(block).to receive(:call)
52
59
 
53
60
  subject.define_process do
@@ -67,6 +74,96 @@ describe Taskinator::Definition do
67
74
  subject.create_process
68
75
  }.to_not raise_error
69
76
  end
77
+
78
+ context "is instrumented" do
79
+ subject { MockDefinition.create }
80
+
81
+ it "for create process" do
82
+ instrumentation_block = SpecSupport::Block.new
83
+ expect(instrumentation_block).to receive(:call)
84
+
85
+ # temporary subscription
86
+ ActiveSupport::Notifications.subscribed(instrumentation_block, /create_process/) do
87
+ subject.create_process :foo
88
+ end
89
+ end
90
+
91
+ it "for save process" do
92
+ instrumentation_block = SpecSupport::Block.new
93
+ expect(instrumentation_block).to receive(:call)
94
+
95
+ # temporary subscription
96
+ ActiveSupport::Notifications.subscribed(instrumentation_block, /save_process/) do
97
+ subject.create_process :foo
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ describe "#create_process_remotely" do
104
+ it "raises ProcessUndefinedError" do
105
+ expect {
106
+ subject.create_process_remotely
107
+ }.to raise_error(Taskinator::Definition::ProcessUndefinedError)
108
+ end
109
+
110
+ it "returns the process uuid" do
111
+ block = SpecSupport::Block.new
112
+ allow(block).to receive(:to_proc) {
113
+ Proc.new {|*args| }
114
+ }
115
+ subject.define_process(&block)
116
+
117
+ process = subject.create_process_remotely
118
+
119
+ expect(process).to_not be_nil
120
+ expect(process.uuid).to_not be_nil
121
+ end
122
+
123
+ it "enqueues" do
124
+ block = SpecSupport::Block.new
125
+ allow(block).to receive(:to_proc) {
126
+ Proc.new {|*args| }
127
+ }
128
+ subject.define_process(&block)
129
+
130
+ expect(Taskinator.queue).to receive(:enqueue_create_process)
131
+
132
+ subject.create_process_remotely
133
+ end
134
+
135
+ describe "reloading" do
136
+ it "returns false if not persisted yet" do
137
+ block = SpecSupport::Block.new
138
+ allow(block).to receive(:to_proc) {
139
+ Proc.new {|*args| }
140
+ }
141
+ subject.define_process(&block)
142
+ process = subject.create_process_remotely
143
+
144
+ expect(process.reload).to eq(false)
145
+ end
146
+
147
+ it "returns true if persisted" do
148
+ definition = MockDefinition.create
149
+ process = definition.create_process_remotely(:foo)
150
+ definition._create_process_(:foo, :uuid => process.uuid).save
151
+
152
+ expect(process.reload).to eq(true)
153
+ end
154
+ end
155
+
156
+ end
157
+
158
+ describe "#queue" do
159
+ it {
160
+ expect(subject.queue).to be_nil
161
+ }
162
+
163
+ it {
164
+ subject.queue = :foo
165
+ expect(subject.queue).to eq(:foo)
166
+ }
70
167
  end
71
168
 
72
169
  end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Taskinator::Executor do
4
4
 
5
5
  let(:definition) do
6
- Module.new() do
6
+ Module.new do
7
7
  def method; end
8
8
  end
9
9
  end
@@ -63,11 +63,11 @@ describe Taskinator::JobWorker do
63
63
  it "should fail if job raises an error" do
64
64
  job = mock_job
65
65
  allow(Taskinator::Task).to receive(:fetch).with(uuid) { job }
66
- allow(job).to receive(:start!) { raise NotImplementedError }
67
- expect(job).to receive(:fail!).with(NotImplementedError)
66
+ allow(job).to receive(:start!) { raise StandardError }
67
+ expect(job).to receive(:fail!).with(StandardError)
68
68
  expect {
69
69
  subject.perform
70
- }.to raise_error(NotImplementedError)
70
+ }.to raise_error(StandardError)
71
71
  end
72
72
 
73
73
  end