taskinator 0.3.10 → 0.3.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1cac7446426c45fc14b6cc0dee7620874e322fa2
4
- data.tar.gz: 7dd667765ef5e6a139b99ea03604aef198f80130
3
+ metadata.gz: e57a5add32ebc7ffc6af22bbe9768db6c89f896f
4
+ data.tar.gz: 434f7323883114425d9f24351f503557a2cc72c2
5
5
  SHA512:
6
- metadata.gz: de3cc2b3dd447ed10b1662cca01ea0398c35c375466ee3a55b3ed67eefc28cd1a8d5888e56f50258bc2027f7ea4f8462ba0bc52cac2d2f242dbd56b46b7548b4
7
- data.tar.gz: c98d31d5968ecfc7289831b327a1fd00bb9be75c0206304f3d8aed6b38fbf3ec4d3460b85b3cf439604ea2490095d8fee60280c27c43a18b71c6a9b3aa47d03c
6
+ metadata.gz: 917159bb5af8a31a8ea8ef8dc76b2cdb1a0c78336fc163fd38af2b63e327692550e3eecf92275bdc5f3fe24f7e17ab93d053809bb3a23b99ed69b1d9b01d9938
7
+ data.tar.gz: 0ff149a53cb1952b160e9cf3c4d3eb196da080c2dd0f63112a63c0511cec01258f5d47a548326ddd03cbeda5cf8fd056fb85c54d57677e2a0a41aba363d30eec
data/Gemfile CHANGED
@@ -20,3 +20,5 @@ gem 'rspec'
20
20
  gem 'coveralls' , '>= 0.7.0'
21
21
  gem 'pry' , '>= 0.9.0'
22
22
  gem 'pry-byebug' , '>= 1.3.0'
23
+
24
+ gem 'fakeredis' , '~> 0.6.0'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- taskinator (0.3.10)
4
+ taskinator (0.3.11)
5
5
  builder (>= 3.2.2)
6
6
  connection_pool (>= 2.2.0)
7
7
  globalid (~> 0.3)
@@ -9,6 +9,7 @@ PATH
9
9
  redis (>= 3.2.1)
10
10
  redis-namespace (>= 1.5.2)
11
11
  redis-semaphore (>= 0.2.4)
12
+ statsd-ruby (~> 1.2.0)
12
13
 
13
14
  GEM
14
15
  remote: https://rubygems.org/
@@ -20,7 +21,7 @@ GEM
20
21
  thread_safe (~> 0.3, >= 0.3.4)
21
22
  tzinfo (~> 1.1)
22
23
  builder (3.2.2)
23
- byebug (9.0.5)
24
+ byebug (9.0.6)
24
25
  coderay (1.1.1)
25
26
  concurrent-ruby (1.0.2)
26
27
  connection_pool (2.2.0)
@@ -34,12 +35,14 @@ GEM
34
35
  activesupport (>= 3.0, < 5.1)
35
36
  diff-lcs (1.2.5)
36
37
  docile (1.1.5)
38
+ fakeredis (0.6.0)
39
+ redis (~> 3.2)
37
40
  globalid (0.3.7)
38
41
  activesupport (>= 4.1.0)
39
42
  i18n (0.7.0)
40
43
  json (1.8.3)
41
44
  method_source (0.8.2)
42
- minitest (5.9.0)
45
+ minitest (5.9.1)
43
46
  mono_logger (1.1.0)
44
47
  multi_json (1.12.1)
45
48
  pry (0.10.4)
@@ -52,7 +55,7 @@ GEM
52
55
  rack (1.6.4)
53
56
  rack-protection (1.5.3)
54
57
  rack
55
- rake (11.2.2)
58
+ rake (11.3.0)
56
59
  redis (3.3.1)
57
60
  redis-namespace (1.5.2)
58
61
  redis (~> 3.0, >= 3.0.4)
@@ -73,7 +76,7 @@ GEM
73
76
  rspec-core (~> 3.5.0)
74
77
  rspec-expectations (~> 3.5.0)
75
78
  rspec-mocks (~> 3.5.0)
76
- rspec-core (3.5.3)
79
+ rspec-core (3.5.4)
77
80
  rspec-support (~> 3.5.0)
78
81
  rspec-expectations (3.5.0)
79
82
  diff-lcs (>= 1.2.0, < 2.0)
@@ -85,11 +88,11 @@ GEM
85
88
  rspec (~> 3.0, >= 3.0.0)
86
89
  sidekiq (>= 2.4.0)
87
90
  rspec-support (3.5.0)
88
- sidekiq (4.1.4)
91
+ sidekiq (4.2.3)
89
92
  concurrent-ruby (~> 1.0)
90
93
  connection_pool (~> 2.2, >= 2.2.0)
94
+ rack-protection (>= 1.5.0)
91
95
  redis (~> 3.2, >= 3.2.1)
92
- sinatra (>= 1.4.7)
93
96
  simplecov (0.12.0)
94
97
  docile (~> 1.1.0)
95
98
  json (>= 1.8, < 3)
@@ -100,7 +103,8 @@ GEM
100
103
  rack-protection (~> 1.4)
101
104
  tilt (>= 1.3, < 3)
102
105
  slop (3.6.0)
103
- term-ansicolor (1.3.2)
106
+ statsd-ruby (1.2.1)
107
+ term-ansicolor (1.4.0)
104
108
  tins (~> 1.0)
105
109
  thor (0.19.1)
106
110
  thread_safe (0.3.5)
@@ -119,6 +123,7 @@ DEPENDENCIES
119
123
  bundler (>= 1.6.0)
120
124
  coveralls (>= 0.7.0)
121
125
  delayed_job (~> 4.1.0)
126
+ fakeredis (~> 0.6.0)
122
127
  pry (>= 0.9.0)
123
128
  pry-byebug (>= 1.3.0)
124
129
  rake (>= 10.3.0)
@@ -130,4 +135,4 @@ DEPENDENCIES
130
135
  taskinator!
131
136
 
132
137
  BUNDLED WITH
133
- 1.12.5
138
+ 1.13.4
@@ -6,6 +6,8 @@ require 'benchmark'
6
6
 
7
7
  require 'taskinator/version'
8
8
 
9
+ require 'taskinator/log_stats'
10
+
9
11
  require 'taskinator/complete_on'
10
12
  require 'taskinator/redis_connection'
11
13
  require 'taskinator/logger'
@@ -70,16 +72,6 @@ module Taskinator
70
72
  redis_pool.with(&block)
71
73
  end
72
74
 
73
- def redis_mutex(lockid, options={}, &block)
74
- raise ArgumentError, "requires a block" unless block_given?
75
- m = Benchmark.measure do
76
- redis do |r|
77
- Redis::Semaphore.new(lockid, {:redis => r}.merge(options)).lock(&block)
78
- end
79
- end
80
- logger.debug("Time spent in mutex: #{m.real}")
81
- end
82
-
83
75
  def redis_pool
84
76
  @redis ||= Taskinator::RedisConnection.create
85
77
  end
@@ -96,6 +88,14 @@ module Taskinator
96
88
  Taskinator::Logging.logger = log
97
89
  end
98
90
 
91
+ def statsd_client
92
+ Taskinator::LogStats.client
93
+ end
94
+
95
+ def statsd_client=(client)
96
+ Taskinator::LogStats.client = client
97
+ end
98
+
99
99
  # the queue adapter to use
100
100
  # supported adapters include
101
101
  # :delayed_job, :redis and :sidekiq
@@ -0,0 +1,49 @@
1
+ require 'statsd'
2
+
3
+ module Taskinator
4
+ module LogStats
5
+ class << self
6
+
7
+ def initialize_client
8
+ @client = Statsd.new()
9
+ end
10
+
11
+ def client
12
+ defined?(@client) ? @client : initialize_client
13
+ end
14
+
15
+ def client=(statsd_client)
16
+ @client = (statsd_client ? statsd_client : initialize_client)
17
+ end
18
+
19
+ def duration(stat, duration)
20
+ client.timing(stat, duration * 1000)
21
+ end
22
+
23
+ def timing(stat, &block)
24
+ result = nil
25
+ duration = Benchmark.realtime do
26
+ result = yield
27
+ end
28
+ duration(stat, duration)
29
+ result
30
+ end
31
+
32
+ def gauge(stat, count)
33
+ client.gauge(stat, count)
34
+ end
35
+
36
+ def count(stat, count)
37
+ client.count(stat, count)
38
+ end
39
+
40
+ def increment(stat)
41
+ client.increment(stat)
42
+ end
43
+
44
+ def decrement(stat)
45
+ client.decrement(stat)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -191,6 +191,12 @@ module Taskinator
191
191
 
192
192
  end
193
193
 
194
+ def deincr_pending_tasks
195
+ Taskinator.redis do |conn|
196
+ conn.incrby("#{key}.pending", -1)
197
+ end
198
+ end
199
+
194
200
  # retrieves the process options of the root process
195
201
  # this is so that meta data of the process can be maintained
196
202
  # and accessible to instrumentation subscribers
@@ -203,13 +209,13 @@ module Taskinator
203
209
  end
204
210
  end
205
211
 
206
- EXPIRE_IN = 10 * 60 # 10 minutes
212
+ EXPIRE_IN = 30 * 60 # 30 minutes
207
213
 
208
- def cleanup(expire_at=Time.now+EXPIRE_IN)
214
+ def cleanup(expire_in=EXPIRE_IN)
209
215
  Taskinator.redis do |conn|
210
216
 
211
217
  # use the "clean up" visitor
212
- RedisCleanupVisitor.new(conn, self, expire_at).visit
218
+ RedisCleanupVisitor.new(conn, self, expire_in).visit
213
219
 
214
220
  # remove from the list
215
221
  conn.srem(Persistence.processes_list_key(scope), uuid)
@@ -276,6 +282,8 @@ module Taskinator
276
282
  @base_visitor.incr_task_count
277
283
  end
278
284
  end
285
+ @conn.set("#{@key}.count", tasks.count)
286
+ @conn.set("#{@key}.pending", tasks.count)
279
287
  end
280
288
 
281
289
  def visit_attribute(attribute)
@@ -377,7 +385,7 @@ module Taskinator
377
385
  end
378
386
 
379
387
  def visit_tasks(tasks)
380
- builder.tag!('tasks') do |xml|
388
+ builder.tag!('tasks', :count => tasks.count) do |xml|
381
389
  tasks.each do |task|
382
390
  xml.tag!('task', :key => task.key) do |xml2|
383
391
  XmlSerializationVisitor.new(xml2, task, @base_visitor).visit
@@ -495,7 +503,7 @@ module Taskinator
495
503
  # tasks are a linked list, so just get the first one
496
504
  Taskinator.redis do |conn|
497
505
  uuid = conn.lindex("#{@key}:tasks", 0)
498
- tasks << lazy_instance_for(Task, uuid) if uuid
506
+ tasks.attach(lazy_instance_for(Task, uuid), conn.get("#{@key}.count").to_i) if uuid
499
507
  end
500
508
  end
501
509
 
@@ -576,28 +584,28 @@ module Taskinator
576
584
  class RedisCleanupVisitor < Taskinator::Visitor::Base
577
585
 
578
586
  attr_reader :instance
579
- attr_reader :expire_at
587
+ attr_reader :expire_in # seconds
580
588
 
581
- def initialize(conn, instance, expire_at)
589
+ def initialize(conn, instance, expire_in)
582
590
  @conn = conn
583
591
  @instance = instance
584
- @expire_at = expire_at.utc
592
+ @expire_in = expire_in.to_i
585
593
  @key = instance.key
586
594
  end
587
595
 
588
596
  def visit
589
597
  @instance.accept(self)
590
- @conn.expireat(@key, expire_at.to_i)
598
+ @conn.expire(@key, expire_in)
591
599
  end
592
600
 
593
601
  def visit_process(attribute)
594
602
  process = @instance.send(attribute)
595
- RedisCleanupVisitor.new(@conn, process, expire_at).visit if process
603
+ RedisCleanupVisitor.new(@conn, process, expire_in).visit if process
596
604
  end
597
605
 
598
606
  def visit_tasks(tasks)
599
607
  tasks.each do |task|
600
- RedisCleanupVisitor.new(@conn, task, expire_at).visit
608
+ RedisCleanupVisitor.new(@conn, task, expire_in).visit
601
609
  end
602
610
  end
603
611
 
@@ -228,6 +228,8 @@ module Taskinator
228
228
  if tasks.empty?
229
229
  complete! # weren't any tasks to start with
230
230
  else
231
+ Taskinator.statsd_client.count("taskinator.#{definition.name.underscore.parameterize}.pending", tasks.count)
232
+ Taskinator.logger.info("Enqueuing #{tasks.count} tasks for process '#{uuid}'.")
231
233
  tasks.each(&:enqueue!)
232
234
  end
233
235
  end
@@ -256,18 +258,20 @@ module Taskinator
256
258
  end
257
259
 
258
260
  def task_completed(task)
261
+ # skip if failed
262
+ return if failed?
263
+
264
+ # deincrement the count of pending concurrent tasks
265
+ pending = deincr_pending_tasks
266
+
267
+ Taskinator.statsd_client.count("taskinator.#{definition.name.underscore.parameterize}.pending", pending)
268
+ Taskinator.logger.info("Completed task for process '#{uuid}'. Pending is #{pending}.")
269
+
259
270
  # when complete on first, then don't bother with subsequent tasks completing
260
- return if completed? || failed?
261
-
262
- if tasks_completed?
263
- # prevent re-entrance so that two tasks completing
264
- # simultaneously can't complete the process twice,
265
- # which enqueues/starts the same subsequent task
266
- Taskinator.redis_mutex(uuid) do
267
- # double check, since the status may have
268
- # changed while waiting in the mutex
269
- complete! if tasks_completed?
270
- end
271
+ if (complete_on == CompleteOn::First)
272
+ complete! unless completed?
273
+ else
274
+ complete! if pending < 1
271
275
  end
272
276
  end
273
277
 
@@ -60,8 +60,6 @@ module Taskinator
60
60
  opts.delete(:network_timeout)
61
61
  end
62
62
 
63
- opts[:driver] = opts[:driver] || 'ruby'
64
-
65
63
  opts
66
64
  end
67
65
 
@@ -8,6 +8,7 @@ module Taskinator
8
8
 
9
9
  def perform
10
10
  task = Taskinator::Task.fetch(@uuid)
11
+ raise "ERROR: Task '#{@uuid}' not found." unless task
11
12
  return if task.paused? || task.cancelled?
12
13
  task.start!
13
14
  end
@@ -7,19 +7,31 @@ module Taskinator
7
7
  attr_reader :head
8
8
  alias_method :first, :head
9
9
 
10
+ attr_reader :count
11
+ alias_method :length, :count
12
+
10
13
  def initialize(first=nil)
11
- @head = first
14
+ @count = 0
15
+ add(first) if first
16
+ end
17
+
18
+ def attach(task, count)
19
+ @head = task
20
+ @count = count
21
+ task
12
22
  end
13
23
 
14
24
  def add(task)
15
25
  if @head.nil?
16
26
  @head = task
27
+ @count = 1
17
28
  else
18
29
  current = @head
19
30
  while current.next
20
31
  current = current.next
21
32
  end
22
33
  current.next = task
34
+ @count += 1
23
35
  end
24
36
  task
25
37
  end
@@ -1,3 +1,3 @@
1
1
  module Taskinator
2
- VERSION = "0.3.10"
2
+ VERSION = "0.3.11"
3
3
  end
@@ -6,15 +6,17 @@ require 'coveralls'
6
6
  require 'pry'
7
7
  require 'active_support/notifications'
8
8
 
9
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
9
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
10
10
  SimpleCov::Formatter::HTMLFormatter,
11
11
  Coveralls::SimpleCov::Formatter
12
- ]
12
+ ])
13
13
 
14
14
  SimpleCov.start do
15
15
  add_filter 'spec'
16
16
  end
17
17
 
18
+ require 'fakeredis/rspec'
19
+
18
20
  require 'delayed_job'
19
21
 
20
22
  require 'sidekiq'
@@ -341,6 +341,18 @@ describe Taskinator::Persistence, :redis => true do
341
341
 
342
342
  end
343
343
 
344
+ describe "#deincr_pending_tasks" do
345
+ it {
346
+ Taskinator.redis do |conn|
347
+ conn.set("#{subject.key}.pending", 99)
348
+ end
349
+
350
+ pending = subject.deincr_pending_tasks
351
+
352
+ expect(pending).to eq(98)
353
+ }
354
+ end
355
+
344
356
  describe "#process_options" do
345
357
  it {
346
358
  Taskinator.redis do |conn|
@@ -368,7 +380,7 @@ describe Taskinator::Persistence, :redis => true do
368
380
  Taskinator.redis do |conn|
369
381
  expect(conn.hget(process.key, :uuid)).to eq(process.uuid)
370
382
 
371
- process.cleanup(Time.now)
383
+ process.cleanup(0) # immediately
372
384
 
373
385
  expect(conn.hget(process.key, :uuid)).to be_nil
374
386
 
@@ -389,7 +401,7 @@ describe Taskinator::Persistence, :redis => true do
389
401
  Taskinator.redis do |conn|
390
402
  expect(conn.hget(process.key, :uuid)).to eq(process.uuid)
391
403
 
392
- process.cleanup(Time.now + 1)
404
+ process.cleanup(2)
393
405
 
394
406
  # still available...
395
407
  expect(conn.hget(process.key, :uuid)).to_not be_nil
@@ -397,7 +409,7 @@ describe Taskinator::Persistence, :redis => true do
397
409
  expect(conn.hget(task.key, :uuid)).to_not be_nil
398
410
  end
399
411
 
400
- sleep 2
412
+ sleep 3
401
413
 
402
414
  # gone!
403
415
  expect(conn.hget(process.key, :uuid)).to be_nil
@@ -419,16 +419,17 @@ describe Taskinator::Process do
419
419
 
420
420
  describe "#task_completed" do
421
421
  it "completes when tasks complete (CompleteOn::First)" do
422
- allow_any_instance_of(Taskinator::Task).to receive(:completed?) { true }
423
-
424
422
  process = Taskinator::Process.define_concurrent_process_for(definition, Taskinator::CompleteOn::First)
425
-
426
423
  tasks.each {|t| process.tasks << t }
427
424
 
428
- expect(process).to receive(:complete!).and_call_original
425
+ allow(process).to receive(:deincr_pending_tasks) { tasks.count - 1 }
426
+
427
+ expect(process).to receive(:complete!).once.and_call_original
429
428
 
430
429
  process.task_completed(tasks.first)
431
430
 
431
+ expect(process.completed?).to be(true)
432
+
432
433
  # remaining tasks should do nothing...
433
434
  tasks.each do |task|
434
435
  process.task_completed(task)
@@ -436,16 +437,17 @@ describe Taskinator::Process do
436
437
  end
437
438
 
438
439
  it "completes when tasks complete (CompleteOn::Last)" do
439
- allow_any_instance_of(Taskinator::Task).to receive(:completed?) { true }
440
-
441
440
  process = Taskinator::Process.define_concurrent_process_for(definition, Taskinator::CompleteOn::Last)
442
-
443
441
  tasks.each {|t| process.tasks << t }
444
442
 
445
- expect(process).to receive(:complete!).and_call_original
443
+ pending_count = tasks.count
444
+ allow(process).to receive(:deincr_pending_tasks) { pending_count -= 1 }
445
+
446
+ expect(process).to receive(:complete!).once.and_call_original
446
447
 
447
448
  tasks.each do |task|
448
449
  process.task_completed(task)
450
+ expect(process.completed?).to be(false) unless pending_count < 1
449
451
  end
450
452
  end
451
453
  end
@@ -28,4 +28,5 @@ Gem::Specification.new do |spec|
28
28
  spec.add_dependency 'json' , '>= 1.8.2'
29
29
  spec.add_dependency 'builder' , '>= 3.2.2'
30
30
  spec.add_dependency 'globalid' , '~> 0.3'
31
+ spec.add_dependency 'statsd-ruby' , '~> 1.2.0'
31
32
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: taskinator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.10
4
+ version: 0.3.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Stefano
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0.3'
111
+ - !ruby/object:Gem::Dependency
112
+ name: statsd-ruby
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 1.2.0
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 1.2.0
111
125
  description: Simple process orchestration
112
126
  email:
113
127
  - virtualstaticvoid@gmail.com
@@ -139,6 +153,7 @@ files:
139
153
  - lib/taskinator/definition/builder.rb
140
154
  - lib/taskinator/executor.rb
141
155
  - lib/taskinator/instrumentation.rb
156
+ - lib/taskinator/log_stats.rb
142
157
  - lib/taskinator/logger.rb
143
158
  - lib/taskinator/persistence.rb
144
159
  - lib/taskinator/process.rb