taskinator 0.3.10 → 0.3.11

Sign up to get free protection for your applications and to get access to all the features.
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