taskinator 0.3.16 → 0.4.3

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
  SHA256:
3
- metadata.gz: 231c6c9f1bdcf8bb1a7fb38c0ebd0c893f61f49051d94ace5714233a207ad651
4
- data.tar.gz: 9c66bf91f8420f3fff69487e095a5c36986409f7249224f03ba0b53fdd427190
3
+ metadata.gz: f1a746ba56e5b6a2f57736990eef9e83eafe8701fcbebaaddea258a010f3ee14
4
+ data.tar.gz: 5e17bc8e6a61b93d6df0691dc463fb178aca72957ab79eccaea7b9cd6c1af642
5
5
  SHA512:
6
- metadata.gz: 0e322ccc3214ed171bd75b14b4ebaa7e51c1e1ba33b3f7f292c707059f166566a8b8c08235442d26922b0cd8a8c2affa320af03d069d33b0fce4d9ffe7485e95
7
- data.tar.gz: d16a3220d1be357c77e7a47d3e2c426a99549c3d350979f534a176ec3919444551956d4d41e6b7389e245fe39c1286d7ef60e6f07b1155f3aeae7e9cdce1398b
6
+ metadata.gz: 8f8cd10cc4c8f5d45d5cc781b379927bae7c0ed5c280e2470450a818a6a3132af75bc11a4c16eef6312056f984e912f3fa758bd45e493988cee37fd01ad92d87
7
+ data.tar.gz: f8b330d3f6e6766f6ddbc33becd3bb6d16826a021dd0ba16c3b2ec8308cdfe31caad40621fcbc542d6662c8aababaa946e369d251c82b24a213f955a942192a3
data/.travis.yml CHANGED
@@ -1,15 +1,16 @@
1
+ os: linux
2
+ dist: xenial
1
3
  language: ruby
2
- sudo: false # See http://docs.travis-ci.com/user/migrating-from-legacy
3
4
  cache: bundler
4
5
 
5
6
  services:
6
- - redis-server
7
+ - redis
7
8
 
8
9
  rvm:
9
- - 2.4.10
10
10
  - 2.5.8
11
11
  - 2.6.6
12
12
  - 2.7.2
13
+ - 3.0.0
13
14
 
14
15
  script: 'bundle exec rake spec'
15
16
 
data/CHANGELOG.md CHANGED
@@ -1,3 +1,66 @@
1
+ v0.4.3 - 14 Jan 2022
2
+ ---
3
+ Add `#find_process` and `#find_task` methods to `Taskinator::Api`.
4
+ Bug fix to API when enumerating processes.
5
+ Updated dependencies.
6
+
7
+ v0.4.2 - 16 Mar 2021
8
+ ---
9
+ Bug fix for process/task keys not expired upon completion.
10
+
11
+ v0.4.1 - 15 Mar 2021
12
+ ---
13
+ Optimisation to exclude sub-processes which don't have any tasks.
14
+ Preparations for upgrade to Ruby 3 and ActiveSupport 6
15
+
16
+ v0.4.0 - 4 Mar 2021
17
+ ---
18
+ Bug fix `job` tasks which have no arguments to the `perform` method.
19
+ Added support for having `perform` method as a class method.
20
+
21
+ v0.3.16 - 17 Feb 2021
22
+ ---
23
+ Bug fix to deincrement pending counts for sequential tasks.
24
+ Bug fix to allow concurrent tasks to be retried (via Resque) and to complete processes.
25
+
26
+ v0.3.15 - 22 Nov 2018
27
+ ---
28
+ Updated dependencies.
29
+
30
+ v0.3.14 - 13 Jul 2018
31
+ ---
32
+ Updated dependencies.
33
+ Removed gemnasium.
34
+
35
+ v0.3.13 - 23 Sep 2017
36
+ ---
37
+ Updated dependencies.
38
+
39
+ v0.3.12 - 23 Sep 2017
40
+ ---
41
+ Spec fixes.
42
+ Updated dependencies.
43
+
44
+ v0.3.11 - 1 Nov 2016
45
+ ---
46
+ Removed `redis-semaphore` gem and use INCRBY to track pending concurrent tasks instead.
47
+ Added instrumentation using statsd.
48
+ Bug fixes to key expiry logic.
49
+ Refactored process and task state transistions.
50
+
51
+ v0.3.10 - 1 Nov 2016
52
+ ---
53
+ Added support for serializing to XML.
54
+ Improvements to process and task states.
55
+
56
+ v0.3.9 - 12 Sep 2016
57
+ ---
58
+ Added benchmark for redis-mutex.
59
+
60
+ v0.3.7 - 18 Aug 2016
61
+ ---
62
+ Bug fix to `option?` method.
63
+
1
64
  v0.3.6 - 11 Nov 2015
2
65
  ---
3
66
  Added visitor for performing clean up of completed processes/tasks.
data/Gemfile.lock CHANGED
@@ -1,21 +1,20 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- taskinator (0.3.16)
4
+ taskinator (0.4.3)
5
5
  builder (>= 3.2.2)
6
6
  connection_pool (>= 2.2.0)
7
7
  globalid (~> 0.3)
8
8
  json (>= 1.8.2)
9
9
  redis (>= 3.2.1)
10
10
  redis-namespace (>= 1.5.2)
11
- redis-semaphore (>= 0.2.4)
12
11
  statsd-ruby (~> 1.4.0)
13
12
  thwait (~> 0.2)
14
13
 
15
14
  GEM
16
15
  remote: https://rubygems.org/
17
16
  specs:
18
- activesupport (5.2.4.5)
17
+ activesupport (5.2.6)
19
18
  concurrent-ruby (~> 1.0, >= 1.0.2)
20
19
  i18n (>= 0.7, < 2)
21
20
  minitest (~> 5.1)
@@ -23,8 +22,8 @@ GEM
23
22
  builder (3.2.4)
24
23
  byebug (11.1.3)
25
24
  coderay (1.1.3)
26
- concurrent-ruby (1.1.8)
27
- connection_pool (2.2.3)
25
+ concurrent-ruby (1.1.9)
26
+ connection_pool (2.2.5)
28
27
  coveralls (0.8.23)
29
28
  json (>= 1.8, < 3)
30
29
  simplecov (~> 0.16.1)
@@ -33,38 +32,36 @@ GEM
33
32
  tins (~> 1.6)
34
33
  delayed_job (4.1.9)
35
34
  activesupport (>= 3.0, < 6.2)
36
- diff-lcs (1.4.4)
37
- docile (1.3.5)
35
+ diff-lcs (1.5.0)
36
+ docile (1.4.0)
38
37
  e2mmap (0.1.0)
39
38
  fakeredis (0.7.0)
40
39
  redis (>= 3.2, < 5.0)
41
- globalid (0.4.2)
42
- activesupport (>= 4.2.0)
43
- i18n (1.8.9)
40
+ globalid (0.6.0)
41
+ activesupport (>= 5.0)
42
+ i18n (1.8.11)
44
43
  concurrent-ruby (~> 1.0)
45
- json (2.5.1)
44
+ json (2.6.1)
46
45
  method_source (1.0.0)
47
- minitest (5.14.3)
48
- mono_logger (1.1.0)
46
+ minitest (5.15.0)
47
+ mono_logger (1.1.1)
49
48
  multi_json (1.15.0)
50
49
  mustermann (1.1.1)
51
50
  ruby2_keywords (~> 0.0.1)
52
- pry (0.13.1)
51
+ pry (0.14.1)
53
52
  coderay (~> 1.1)
54
53
  method_source (~> 1.0)
55
- pry-byebug (3.9.0)
54
+ pry-byebug (3.8.0)
56
55
  byebug (~> 11.0)
57
- pry (~> 0.13.0)
56
+ pry (~> 0.10)
58
57
  rack (2.2.3)
59
58
  rack-protection (2.1.0)
60
59
  rack
61
- rake (13.0.3)
62
- redis (4.2.5)
60
+ rake (13.0.6)
61
+ redis (4.5.1)
63
62
  redis-namespace (1.8.1)
64
63
  redis (>= 3.0.4)
65
- redis-semaphore (0.3.1)
66
- redis
67
- resque (2.0.0)
64
+ resque (2.2.0)
68
65
  mono_logger (~> 1.0)
69
66
  multi_json (~> 1.0)
70
67
  redis-namespace (~> 1.6)
@@ -90,9 +87,9 @@ GEM
90
87
  rspec-sidekiq (3.1.0)
91
88
  rspec-core (~> 3.0, >= 3.0.0)
92
89
  sidekiq (>= 2.4.0)
93
- rspec-support (3.10.2)
94
- ruby2_keywords (0.0.4)
95
- sidekiq (6.1.3)
90
+ rspec-support (3.10.3)
91
+ ruby2_keywords (0.0.5)
92
+ sidekiq (6.3.1)
96
93
  connection_pool (>= 2.2.2)
97
94
  rack (~> 2.0)
98
95
  redis (>= 4.2.0)
@@ -110,12 +107,12 @@ GEM
110
107
  sync (0.5.0)
111
108
  term-ansicolor (1.7.1)
112
109
  tins (~> 1.0)
113
- thor (1.1.0)
110
+ thor (1.2.1)
114
111
  thread_safe (0.3.6)
115
112
  thwait (0.2.0)
116
113
  e2mmap
117
114
  tilt (2.0.10)
118
- tins (1.28.0)
115
+ tins (1.31.0)
119
116
  sync
120
117
  tzinfo (1.2.9)
121
118
  thread_safe (~> 0.1)
@@ -142,4 +139,4 @@ DEPENDENCIES
142
139
  taskinator!
143
140
 
144
141
  BUNDLED WITH
145
- 2.2.11
142
+ 2.2.14
data/README.md CHANGED
@@ -5,21 +5,25 @@
5
5
  [![Code Climate](https://codeclimate.com/github/virtualstaticvoid/taskinator.png)](https://codeclimate.com/github/virtualstaticvoid/taskinator)
6
6
  [![Coverage Status](https://coveralls.io/repos/virtualstaticvoid/taskinator/badge.png)](https://coveralls.io/r/virtualstaticvoid/taskinator)
7
7
 
8
- A simple orchestration library for running complex processes or workflows in Ruby. Processes are defined using a simple DSL, where the sequences and
9
- tasks are defined. Processes can then be queued for execution. Sequences can be synchronous or asynchronous, and the overall process can be monitored
10
- for completion or failure.
8
+ A simple orchestration library for running complex processes or workflows in Ruby.
9
+ Processes are defined using a simple DSL, where the sequences and tasks are defined.
10
+ Processes can then be queued for execution. Sequences can be synchronous or asynchronous,
11
+ and the overall process can be monitored for completion or failure.
11
12
 
12
- Processes and tasks are executed by background workers and you can use any one of the following gems:
13
+ Processes and tasks are executed by background workers and you can use any one of the
14
+ following gems:
13
15
 
14
16
  * [resque](https://github.com/resque/resque)
15
17
  * [sidekiq](https://github.com/mperham/sidekiq)
16
18
  * [delayed_job](https://github.com/collectiveidea/delayed_job)
17
19
 
18
- The configuration and state of each process and their respective tasks is stored using Redis key/values.
20
+ The configuration and state of each process and their respective tasks is stored using
21
+ Redis key/values.
19
22
 
20
23
  ## Requirements
21
24
 
22
- The latest MRI (2.1, 2.0) version. Other versions/VMs are untested but might work fine. MRI 1.9 is not supported.
25
+ The latest MRI (2.1, 2.0) version. Other versions/VMs are untested but might work fine.
26
+ MRI 1.9 is not supported.
23
27
 
24
28
  Redis 2.4 or greater is required.
25
29
 
@@ -68,7 +72,8 @@ module MyProcess
68
72
  end
69
73
  ```
70
74
 
71
- The `define_process` method optionally takes the list of expected arguments which are used to validate the arguments supplied when creating a new process.
75
+ The `define_process` method optionally takes the list of expected arguments which are used
76
+ to validate the arguments supplied when creating a new process.
72
77
  These should be specified with symbols.
73
78
 
74
79
  ```ruby
@@ -87,7 +92,8 @@ process = MyProcess.create_process Date.today, :option_1 => true
87
92
 
88
93
  _NOTE:_ The current implementation performs a naive check on the count of arguments.
89
94
 
90
- Next, specify the tasks with their corresponding implementation methods, that make up the process, using the `task` method and providing the `method` to execute for the task.
95
+ Next, specify the tasks with their corresponding implementation methods, that make up the
96
+ process, using the `task` method and providing the `method` to execute for the task.
91
97
 
92
98
  ```ruby
93
99
  module MyProcess
@@ -108,7 +114,8 @@ module MyProcess
108
114
  end
109
115
  ```
110
116
 
111
- More complex processes may define sequential or concurrent steps, using the `sequential` and `concurrent` methods respectively.
117
+ More complex processes may define sequential or concurrent steps, using the `sequential`
118
+ and `concurrent` methods respectively.
112
119
 
113
120
  ```ruby
114
121
  module MyProcess
@@ -141,10 +148,15 @@ module MyProcess
141
148
  end
142
149
  ```
143
150
 
144
- It is likely that you already have worker classes for one of the queueing libraries, such as resque or delayed_job, and wish to reuse them for executing them in the sequence defined by the process definition.
151
+ It is likely that you already have worker classes for one of the queueing libraries,
152
+ such as resque or delayed_job, and wish to reuse them for executing them in the sequence
153
+ defined by the process definition.
145
154
 
146
- Define a `job` step, providing the class of the worker, and then taskinator will execute that worker as part of the process definition.
147
- The `job` step will be queued and executed on same queue as configured by `delayed_job`, or that of the worker for `resque` and `sidekiq`.
155
+ Define a `job` step, providing the class of the worker, and then taskinator will execute
156
+ that worker as part of the process definition.
157
+
158
+ The `job` step will be queued and executed on same queue as configured by `delayed_job`, or
159
+ that of the worker for `resque` and `sidekiq`.
148
160
 
149
161
  ```ruby
150
162
  # E.g. A resque worker
@@ -168,8 +180,11 @@ module MyProcess
168
180
  end
169
181
  ```
170
182
 
171
- You can also define data driven tasks using the `for_each` method, which takes an iterator method name as an argument.
172
- The iterator method yields the parameters necessary for the task or job. Notice that the task method takes a parameter in this case, which will be the return values provided by the iterator.
183
+ You can also define data driven tasks using the `for_each` method, which takes an iterator method
184
+ name as an argument.
185
+
186
+ The iterator method yields the parameters necessary for the task or job. Notice that the task
187
+ method takes a parameter in this case, which will be the return values provided by the iterator.
173
188
 
174
189
  ```ruby
175
190
  module MyProcess
@@ -192,8 +207,9 @@ module MyProcess
192
207
  end
193
208
  ```
194
209
 
195
- It is possible to branch the process logic based on the options hash passed in when creating a process.
196
- The `options?` method takes the options key as an argument and calls the supplied block if the option is present and it's value is truthy.
210
+ It is possible to branch the process logic based on the options hash passed in when creating
211
+ a process. The `options?` method takes the options key as an argument and calls the supplied
212
+ block if the option is present and it's value is _truthy_.
197
213
 
198
214
  ```ruby
199
215
  module MyProcess
@@ -227,8 +243,11 @@ process2 = MyProcess.create_process
227
243
  process2.tasks.count #=> 1
228
244
  ```
229
245
 
230
- In addition, it is possible to transform the arguments used by a task or job, by including a `transform` step in the definition.
231
- Similarly for the `for_each` method, `transform` takes a method name as an argument. The transformer method must yield the new arguments as required.
246
+ In addition, it is possible to transform the arguments used by a task or job, by including
247
+ a `transform` step in the definition.
248
+
249
+ Similarly for the `for_each` method, `transform` takes a method name as an argument.
250
+ The transformer method must yield the new arguments as required.
232
251
 
233
252
  ```ruby
234
253
  module MyProcess
@@ -273,7 +292,8 @@ module MyProcess
273
292
  end
274
293
  ```
275
294
 
276
- Any combination or nesting of `task`, `sequential`, `concurrent` and `for_each` steps are possible. E.g.
295
+ Any combination or nesting of `task`, `sequential`, `concurrent` and `for_each` steps are
296
+ possible. E.g.
277
297
 
278
298
  ```ruby
279
299
  module MyProcess
@@ -306,13 +326,17 @@ module MyProcess
306
326
  end
307
327
  ```
308
328
 
309
- In this example, the `work_step_begin` is executed, followed by the `work_step_all_at_once` steps which are executed concurrently, then
310
- the sub process `MySubProcess` is created and executed, followed by the `work_step_one_by_one` tasks which are executed sequentially and
329
+ In this example, the `work_step_begin` is executed, followed by the `work_step_all_at_once`
330
+ steps which are executed concurrently, then the sub process `MySubProcess` is created and
331
+ executed, followed by the `work_step_one_by_one` tasks which are executed sequentially and
311
332
  finally the `work_step_end` is executed.
312
333
 
313
- It is also possible to embed conditional logic within the process definition stages in order to produce steps based on the required logic.
314
- All builder methods are available within the scope of the `define_process` block. These methods include `args` and `options`
315
- which are passed into the `create_process` method of the definition.
334
+ It is also possible to embed conditional logic within the process definition stages in
335
+ order to produce steps based on the required logic.
336
+
337
+ All builder methods are available within the scope of the `define_process` block. These
338
+ methods include `args` and `options` which are passed into the `create_process` method
339
+ of the definition.
316
340
 
317
341
  E.g.
318
342
 
@@ -332,7 +356,8 @@ module MyProcess
332
356
  end
333
357
 
334
358
  # when creating this proces, you supply to option when calling `create_process`
335
- # in this example, 'args' will be an array [1,2,3] and options will be a Hash {:send_notification => true}
359
+ # in this example, 'args' will be an array [1,2,3]
360
+ # and options will be a Hash {:send_notification => true}
336
361
  MyProcess.create_process(1, 2, 3, :send_notification => true)
337
362
 
338
363
  ```
@@ -364,8 +389,12 @@ To best understand how arguments are handled, you need to break it down into 3 p
364
389
  * Creation and
365
390
  * Execution
366
391
 
367
- Firstly, a process definition is declarative in that the `define_process` and a mix of `sequential`, `concurrent`, `for_each`, `task` and `job` directives provide the way to specify the sequencing of the steps for the process.
368
- Taskinator will interprete this definition and execute each step in the desired sequence or concurrency.
392
+ Firstly, a process definition is declarative in that the `define_process` and a mix of
393
+ `sequential`, `concurrent`, `for_each`, `task` and `job` directives provide the way to
394
+ specify the sequencing of the steps for the process.
395
+
396
+ Taskinator will interprete this definition and execute each step in the desired sequence
397
+ or concurrency.
369
398
 
370
399
  Consider the following process definition:
371
400
 
@@ -411,11 +440,17 @@ end
411
440
 
412
441
  There are three tasks; namely `:work_step_1`, `:work_step_2` and `:work_step_3`.
413
442
 
414
- The third task, `:work_step_3`, is built up using the `for_each` iterator, which means that the number of `:work_step_3` tasks will depend on how many times the `additional_step` iterator method yields to the definition.
443
+ The third task, `:work_step_3`, is built up using the `for_each` iterator, which means that
444
+ the number of `:work_step_3` tasks will depend on how many times the `additional_step`
445
+ iterator method yields to the definition.
446
+
447
+ This brings us to the creation part. When `create_process` is called on the given module,
448
+ you provide arguments to it, which will get passed onto the respective `task` and
449
+ `for_each` iterator methods.
415
450
 
416
- This brings us to the creation part. When `create_process` is called on the given module, you provide arguments to it, which will get passed onto the respective `task` and `for_each` iterator methods.
451
+ So, considering the `MySimpleProcess` module shown above, `work_step_1`, `work_step_2`
452
+ and `work_step_3` methods each expect arguments.
417
453
 
418
- So, considering the `MySimpleProcess` module shown above, `work_step_1`, `work_step_2` and `work_step_3` methods each expect arguments.
419
454
  These will ultimately come from the arguments passed into the `create_process` method.
420
455
 
421
456
  E.g.
@@ -438,7 +473,8 @@ process = MySimpleProcess.create_process(options)
438
473
 
439
474
  ```
440
475
 
441
- To best understand how the process is created, consider the following "procedural" code for how it could work.
476
+ To best understand how the process is created, consider the following "procedural" code
477
+ for how it could work.
442
478
 
443
479
  ```ruby
444
480
  # A process, which maps the target and a list of steps
@@ -535,11 +571,17 @@ process.execute
535
571
 
536
572
  ```
537
573
 
538
- In reality, each task is executed by a worker process, possibly on another host, so the execution process isn't as simple, but this example should help you to understand conceptually how the process is executed, and how the arguments are propagated through.
574
+ In reality, each task is executed by a worker process, possibly on another host, so the
575
+ execution process isn't as simple, but this example should help you to understand
576
+ conceptually how the process is executed, and how the arguments are propagated through.
539
577
 
540
578
  ### Monitoring
541
579
 
542
- To monitor the state of the processes, use the `Taskinator::Api::Processes` class. This is still a work in progress.
580
+ NOTE: This aspect of the library is still a work in progress.
581
+
582
+ #### Processes
583
+
584
+ To monitor the state of the processes, use the `Taskinator::Api::Processes` class.
543
585
 
544
586
  ```ruby
545
587
  processes = Taskinator::Api::Processes.new
@@ -549,11 +591,57 @@ processes.each do |process|
549
591
  end
550
592
  ```
551
593
 
594
+ #### Debugging
595
+
596
+ To aid debugging specific processes and tasks, where the process or task identifier is
597
+ known, it is possible to retrieve the specific task or process using `Taskinator::Api`.
598
+
599
+ To retrieve a specific process, given the process identifier:
600
+
601
+ ```ruby
602
+ process_id = "SUPPLY-PROCESS-IDENTIFIER"
603
+ process = Taskinator::Api.find_process(process_id)
604
+
605
+ puts process.inspect
606
+ puts process.current_state
607
+ puts process.tasks
608
+ # etc...
609
+ ```
610
+
611
+ The type of process may be one of the following:
612
+
613
+ * `Taskinator::Process::Sequential`
614
+ * `Taskinator::Process::Concurrent`
615
+
616
+ Then, to retrieve a specific task, given the task identifier:
617
+
618
+ ```ruby
619
+ task_id = "SUPPLY-TASK-IDENTIFIER"
620
+ task = Taskinator::Api.find_task(task_id)
621
+
622
+ puts task.inspect
623
+ puts task.class
624
+ puts task.args # for Step and Job types
625
+ puts task.sub_process.tasks # for SubProcess type
626
+ # etc...
627
+ ```
628
+
629
+ Depending on the type of task, different attributes will be available for inspection.
630
+
631
+ The types include:
632
+
633
+ * `Taskinator::Task::Step`
634
+ * `Taskinator::Task::Job`
635
+ * `Taskinator::Task::SubProcess`
636
+
552
637
  ## Configuration
553
638
 
554
639
  ### Redis
555
640
 
556
- By default Taskinator assumes Redis is located at `localhost:6397`. This is fine for development, but for many production environments you will need to point to an external Redis server. You may also what to use a namespace for the Redis keys.
641
+ By default Taskinator assumes Redis is located at `localhost:6397`. This is fine for development,
642
+ but for many production environments you will need to point to an external Redis server.
643
+ You may also what to use a namespace for the Redis keys.
644
+
557
645
  _NOTE:_ The configuration hash _must_ have symbolized keys.
558
646
 
559
647
  ```ruby
@@ -568,15 +656,19 @@ end
568
656
  Or, alternatively, via an `ENV` variable
569
657
 
570
658
  Set the `REDIS_PROVIDER` environment variable to the Redis server url.
571
- E.g. On Heroku, with RedisGreen: set REDIS_PROVIDER=REDISGREEN_URL and Taskinator will use the value of the `REDISGREEN_URL` environment variable when connecting to Redis.
659
+ E.g. On Heroku, with RedisGreen: set REDIS_PROVIDER=REDISGREEN_URL and Taskinator will use the
660
+ value of the `REDISGREEN_URL` environment variable when connecting to Redis.
572
661
 
573
662
  You may also use the generic `REDIS_URL` which may be set to your own private Redis server.
574
663
 
575
- The Redis configuration leverages the same setup as `sidekiq`. For advanced options, checkout the [Sidekiq Advanced Options](https://github.com/mperham/sidekiq/wiki/Advanced-Options#complete-control) wiki for more information.
664
+ The Redis configuration leverages the same setup as `sidekiq`. For advanced options, checkout the
665
+ [Sidekiq Advanced Options](https://github.com/mperham/sidekiq/wiki/Advanced-Options#complete-control)
666
+ wiki page for more information.
576
667
 
577
668
  ### Queues
578
669
 
579
- By default the queue names for process and task workers is `default`, however, you can specify the queue names as follows:
670
+ By default the queue names for process and task workers is `default`, however, you can specify
671
+ the queue names as follows:
580
672
 
581
673
  ```ruby
582
674
  Taskinator.configure do |config|
@@ -589,7 +681,8 @@ end
589
681
 
590
682
  ### Instrumentation
591
683
 
592
- It is possible to instrument processes, tasks and jobs by providing an instrumeter such as `ActiveSupport::Notifications`.
684
+ It is possible to instrument processes, tasks and jobs by providing an instrumeter such
685
+ as `ActiveSupport::Notifications`.
593
686
 
594
687
  ```ruby
595
688
  Taskinator.configure do |config|
@@ -607,40 +700,41 @@ end
607
700
 
608
701
  The following instrumentation events are issued:
609
702
 
610
- | Event | When |
611
- |------------------------------------|-----------------------------------------------------------|
612
- | `taskinator.process.created` | After a root process gets created |
613
- | `taskinator.process.saved` | After a root process has been persisted to Redis |
614
- | `taskinator.process.enqueued` | After a process or subprocess is enqueued for processing |
615
- | `taskinator.process.processing` | When a process or subprocess is processing |
616
- | `taskinator.process.paused` | When a process or subprocess is paused |
617
- | `taskinator.process.resumed` | When a process or subprocess is resumed |
618
- | `taskinator.process.completed` | After a process or subprocess has completed processing |
619
- | `taskinator.process.cancelled` | After a process or subprocess has been cancelled |
620
- | `taskinator.process.failed` | After a process or subprocess has failed |
621
- | `taskinator.task.enqueued` | After a task has been enqueued |
622
- | `taskinator.task.processing` | When a task is processing |
623
- | `taskinator.task.completed` | After a task has completed |
624
- | `taskinator.task.cancelled` | After a task has been cancelled |
625
- | `taskinator.task.failed` | After a task has failed |
703
+ | Event | When |
704
+ |---------------------------------|----------------------------------------------------------|
705
+ | `taskinator.process.created` | After a root process gets created |
706
+ | `taskinator.process.saved` | After a root process has been persisted to Redis |
707
+ | `taskinator.process.enqueued` | After a process or subprocess is enqueued for processing |
708
+ | `taskinator.process.processing` | When a process or subprocess is processing |
709
+ | `taskinator.process.paused` | When a process or subprocess is paused |
710
+ | `taskinator.process.resumed` | When a process or subprocess is resumed |
711
+ | `taskinator.process.completed` | After a process or subprocess has completed processing |
712
+ | `taskinator.process.cancelled` | After a process or subprocess has been cancelled |
713
+ | `taskinator.process.failed` | After a process or subprocess has failed |
714
+ | `taskinator.task.enqueued` | After a task has been enqueued |
715
+ | `taskinator.task.processing` | When a task is processing |
716
+ | `taskinator.task.completed` | After a task has completed |
717
+ | `taskinator.task.cancelled` | After a task has been cancelled |
718
+ | `taskinator.task.failed` | After a task has failed |
626
719
 
627
720
  For all events, the data included contains the following information:
628
721
 
629
- | Key | Value |
630
- |--------------------------|-------------------------------------------------------|
631
- | `:type` | The type name of the component reporting the event |
632
- | `:process_uuid` | The UUID of the root process |
633
- | `:process_options` | Options hash of the root process |
634
- | `:uuid` | The UUID of the respective task, job or sub process |
635
- | `:options` | Options hash of the component |
636
- | `:state` | State of the component |
637
- | `:percentage_completed` | The percentage of completed tasks |
638
- | `:percentage_failed` | The percentage of failed tasks |
639
- | `:percentage_cancelled` | The percentage of cancelled tasks |
722
+ | Key | Value |
723
+ |---------------------------------|----------------------------------------------------------|
724
+ | `:type` | The type name of the component reporting the event |
725
+ | `:process_uuid` | The UUID of the root process |
726
+ | `:process_options` | Options hash of the root process |
727
+ | `:uuid` | The UUID of the respective task, job or sub process |
728
+ | `:options` | Options hash of the component |
729
+ | `:state` | State of the component |
730
+ | `:percentage_completed` | The percentage of completed tasks |
731
+ | `:percentage_failed` | The percentage of failed tasks |
732
+ | `:percentage_cancelled` | The percentage of cancelled tasks |
640
733
 
641
734
  ## Notes
642
735
 
643
- The persistence logic is decoupled from the implementation, so it is possible to implement another backing store if required.
736
+ The persistence logic is decoupled from the implementation, so it is possible to implement
737
+ another backing store if required.
644
738
 
645
739
  ## Contributing
646
740
 
@@ -651,12 +745,19 @@ The persistence logic is decoupled from the implementation, so it is possible to
651
745
  5. Create new Pull Request
652
746
 
653
747
  ## License
748
+
654
749
  MIT Copyright (c) 2014 Chris Stefano
655
750
 
656
751
  Portions of code are from the Sidekiq project, Copyright (c) Contributed Systems LLC.
657
752
 
658
753
  ## Inspiration
659
754
 
660
- Inspired by the [sidekiq](https://github.com/mperham/sidekiq) and [workflow](https://github.com/geekq/workflow) gems.
755
+ Inspired by the [sidekiq](https://github.com/mperham/sidekiq) and
756
+ [workflow](https://github.com/geekq/workflow) gems.
757
+
758
+ For other workflow solutions, checkout [Stonepath](https://github.com/bokmann/stonepath),
759
+ the now deprecated [ruote](https://github.com/jmettraux/ruote) gem and
760
+ [workflow](https://github.com/geekq/workflow).
661
761
 
662
- For other workflow solutions, checkout [Stonepath](https://github.com/bokmann/stonepath), the now deprecated [ruote](https://github.com/jmettraux/ruote) gem and [workflow](https://github.com/geekq/workflow). Alternatively, for a robust enterprise ready solution checkout the [AWS Flow Framework for Ruby](http://docs.aws.amazon.com/amazonswf/latest/awsrbflowguide/welcome.html).
762
+ Alternatively, for a robust enterprise ready solution checkout the
763
+ [AWS Flow Framework for Ruby](http://docs.aws.amazon.com/amazonswf/latest/awsrbflowguide/welcome.html).
@@ -13,12 +13,13 @@ module Taskinator
13
13
  def each(&block)
14
14
  return to_enum(__method__) unless block_given?
15
15
 
16
+ identifiers = Taskinator.redis do |conn|
17
+ conn.smembers(@processes_list_key)
18
+ end
19
+
16
20
  instance_cache = {}
17
- Taskinator.redis do |conn|
18
- uuids = conn.smembers(@processes_list_key)
19
- uuids.each do |uuid|
20
- yield Process.fetch(uuid, instance_cache)
21
- end
21
+ identifiers.each do |identifier|
22
+ yield Process.fetch(identifier, instance_cache)
22
23
  end
23
24
  end
24
25
 
@@ -28,5 +29,13 @@ module Taskinator
28
29
  end
29
30
  end
30
31
  end
32
+
33
+ def self.find_process(identifier)
34
+ Process.fetch(identifier)
35
+ end
36
+
37
+ def self.find_task(identifier)
38
+ Task.fetch(identifier)
39
+ end
31
40
  end
32
41
  end
@@ -24,7 +24,10 @@ module Taskinator
24
24
  raise ArgumentError, 'block' unless block_given?
25
25
 
26
26
  sub_process = Process.define_sequential_process_for(@definition, options)
27
- Builder.new(define_sub_process_task(@process, sub_process, options), @definition, *@args).instance_eval(&block)
27
+ task = define_sub_process_task(@process, sub_process, options)
28
+ Builder.new(sub_process, @definition, *@args).instance_eval(&block)
29
+ @process.tasks << task if sub_process.tasks.any?
30
+ nil
28
31
  end
29
32
 
30
33
  # defines a sub process of tasks which are executed concurrently
@@ -32,7 +35,10 @@ module Taskinator
32
35
  raise ArgumentError, 'block' unless block_given?
33
36
 
34
37
  sub_process = Process.define_concurrent_process_for(@definition, complete_on, options)
35
- Builder.new(define_sub_process_task(@process, sub_process, options), @definition, *@args).instance_eval(&block)
38
+ task = define_sub_process_task(@process, sub_process, options)
39
+ Builder.new(sub_process, @definition, *@args).instance_eval(&block)
40
+ @process.tasks << task if sub_process.tasks.any?
41
+ nil
36
42
  end
37
43
 
38
44
  # dynamically defines tasks, using the given @iterator method
@@ -51,6 +57,7 @@ module Taskinator
51
57
  @executor.send(method, *method_args) do |*args|
52
58
  Builder.new(@process, @definition, *args).instance_eval(&block)
53
59
  end
60
+ nil
54
61
  end
55
62
 
56
63
  alias_method :transform, :for_each
@@ -61,6 +68,7 @@ module Taskinator
61
68
  raise NoMethodError, method unless @executor.respond_to?(method)
62
69
 
63
70
  define_step_task(@process, method, @args, options)
71
+ nil
64
72
  end
65
73
 
66
74
  # defines a task which executes the given @job
@@ -70,6 +78,7 @@ module Taskinator
70
78
  raise ArgumentError, 'job' unless job.methods.include?(:perform) || job.instance_methods.include?(:perform)
71
79
 
72
80
  define_job_task(@process, job, @args, options)
81
+ nil
73
82
  end
74
83
 
75
84
  # defines a sub process task, for the given @definition
@@ -82,7 +91,10 @@ module Taskinator
82
91
  # TODO: decide whether the sub process to dynamically receive arguments
83
92
 
84
93
  sub_process = definition.create_sub_process(*@args, combine_options(options))
85
- Builder.new(define_sub_process_task(@process, sub_process, options), definition, *@args)
94
+ task = define_sub_process_task(@process, sub_process, options)
95
+ Builder.new(sub_process, definition, *@args)
96
+ @process.tasks << task if sub_process.tasks.any?
97
+ nil
86
98
  end
87
99
 
88
100
  private
@@ -100,10 +112,7 @@ module Taskinator
100
112
  end
101
113
 
102
114
  def define_sub_process_task(process, sub_process, options={})
103
- define_task(process) {
104
- Task.define_sub_process_task(process, sub_process, combine_options(options))
105
- }
106
- sub_process
115
+ Task.define_sub_process_task(process, sub_process, combine_options(options))
107
116
  end
108
117
 
109
118
  def define_task(process)
@@ -604,6 +604,9 @@ module Taskinator
604
604
  end
605
605
 
606
606
  def visit_tasks(tasks)
607
+ @conn.expire "#{@key}:tasks", expire_in
608
+ @conn.expire "#{@key}.count", expire_in
609
+ @conn.expire "#{@key}.pending", expire_in
607
610
  tasks.each do |task|
608
611
  RedisCleanupVisitor.new(@conn, task, expire_in).visit
609
612
  end
@@ -230,12 +230,12 @@ module Taskinator
230
230
  # NNB: if other job types are required, may need to implement how they get invoked here!
231
231
  # FIXME: possible implement using ActiveJob instead, so it doesn't matter how the worker is implemented
232
232
 
233
- if job.instance_of?(Module)
233
+ if job.respond_to?(:perform)
234
234
  # resque
235
- job.perform(args)
235
+ job.perform(*args)
236
236
  else
237
237
  # delayedjob and sidekiq
238
- job.new.perform(args)
238
+ job.new.perform(*args)
239
239
  end
240
240
 
241
241
  # ASSUMPTION: when the job returns, the task is considered to be complete
@@ -1,3 +1,3 @@
1
1
  module Taskinator
2
- VERSION = "0.3.16"
2
+ VERSION = "0.4.3"
3
3
  end
data/lib/taskinator.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require 'json'
2
2
  require 'yaml'
3
3
  require 'securerandom'
4
- require 'redis-semaphore'
5
4
  require 'benchmark'
6
5
  require 'delegate'
7
6
 
@@ -132,4 +132,42 @@ module TestFlows
132
132
  end
133
133
  end
134
134
 
135
+ module NestedTask
136
+ extend Taskinator::Definition
137
+ include Support
138
+
139
+ define_process :task_count do
140
+ task :task_1
141
+
142
+ concurrent do
143
+ task :task_2
144
+ task :task_3
145
+
146
+ sequential do
147
+ task :task_4
148
+ task :task_5
149
+
150
+ concurrent do
151
+ task :task_6
152
+ task :task_7
153
+
154
+ sequential do
155
+ task :task_8
156
+ task :task_9
157
+
158
+ end
159
+
160
+ task :task_10
161
+ end
162
+
163
+ task :task_11
164
+ end
165
+
166
+ task :task_12
167
+ end
168
+
169
+ task :task_13
170
+ end
171
+ end
172
+
135
173
  end
@@ -44,4 +44,20 @@ describe Taskinator::Api, :redis => true do
44
44
  end
45
45
  end
46
46
 
47
+ describe "#find_process" do
48
+ it {
49
+ # fetch method is covered by persistence spec
50
+ expect(Taskinator::Process).to receive(:fetch) {}
51
+ subject.find_process 'foo:bar:process'
52
+ }
53
+ end
54
+
55
+ describe "#find_task" do
56
+ it {
57
+ # fetch method is covered by persistence spec
58
+ expect(Taskinator::Task).to receive(:fetch) {}
59
+ subject.find_task 'foo:bar:process:baz:task'
60
+ }
61
+ end
62
+
47
63
  end
@@ -75,6 +75,22 @@ describe Taskinator::Definition::Builder do
75
75
  expect(Taskinator::Process).to receive(:define_sequential_process_for).with(definition, options).and_call_original
76
76
  subject.sequential(options, &define_block)
77
77
  end
78
+
79
+ it "adds sub-process task" do
80
+ block = Proc.new {|p|
81
+ p.task :task_method
82
+ }
83
+ expect(process.tasks).to be_empty
84
+ subject.sequential(options, &block)
85
+ expect(process.tasks).to_not be_empty
86
+ end
87
+
88
+ it "ignores sub-processes without tasks" do
89
+ allow(block).to receive(:call)
90
+ expect(process.tasks).to be_empty
91
+ subject.sequential(options, &define_block)
92
+ expect(process.tasks).to be_empty
93
+ end
78
94
  end
79
95
 
80
96
  describe "#concurrent" do
@@ -100,6 +116,22 @@ describe Taskinator::Definition::Builder do
100
116
  expect(Taskinator::Process).to receive(:define_concurrent_process_for).with(definition, Taskinator::CompleteOn::First, options).and_call_original
101
117
  subject.concurrent(Taskinator::CompleteOn::First, options, &define_block)
102
118
  end
119
+
120
+ it "adds sub-process task" do
121
+ block = Proc.new {|p|
122
+ p.task :task_method
123
+ }
124
+ expect(process.tasks).to be_empty
125
+ subject.sequential(options, &block)
126
+ expect(process.tasks).to_not be_empty
127
+ end
128
+
129
+ it "ignores sub-processes without tasks" do
130
+ allow(block).to receive(:call)
131
+ expect(process.tasks).to be_empty
132
+ subject.sequential(options, &define_block)
133
+ expect(process.tasks).to be_empty
134
+ end
103
135
  end
104
136
 
105
137
  describe "#for_each" do
@@ -235,6 +267,22 @@ describe Taskinator::Definition::Builder do
235
267
  expect(sub_definition).to receive(:create_sub_process).with(*args, builder_options.merge(options)).and_call_original
236
268
  subject.sub_process(sub_definition, options)
237
269
  end
270
+
271
+ it "adds sub-process task" do
272
+ block = Proc.new {|p|
273
+ p.task :task_method
274
+ }
275
+ expect(process.tasks).to be_empty
276
+ subject.sequential(options, &block)
277
+ expect(process.tasks).to_not be_empty
278
+ end
279
+
280
+ it "ignores sub-processes without tasks" do
281
+ allow(block).to receive(:call)
282
+ expect(process.tasks).to be_empty
283
+ subject.sequential(options, &define_block)
284
+ expect(process.tasks).to be_empty
285
+ end
238
286
  end
239
287
 
240
288
  end
@@ -370,24 +370,27 @@ describe Taskinator::Persistence, :redis => true do
370
370
  TestFlows::Job,
371
371
  TestFlows::SubProcess,
372
372
  TestFlows::Sequential,
373
- TestFlows::Concurrent
373
+ TestFlows::Concurrent,
374
+ TestFlows::EmptySequentialProcessTest,
375
+ TestFlows::EmptyConcurrentProcessTest,
376
+ TestFlows::NestedTask,
374
377
  ].each do |definition|
375
378
 
376
379
  describe "#{definition.name} expire immediately" do
377
380
  it {
378
- process = definition.create_process(1)
379
-
380
381
  Taskinator.redis do |conn|
381
- expect(conn.hget(process.key, :uuid)).to eq(process.uuid)
382
+ # sanity check
383
+ expect(conn.keys).to be_empty
382
384
 
383
- process.cleanup(0) # immediately
385
+ process = definition.create_process(1)
384
386
 
385
- expect(conn.hget(process.key, :uuid)).to be_nil
387
+ # sanity check
388
+ expect(conn.hget(process.key, :uuid)).to eq(process.uuid)
386
389
 
387
- recursively_enumerate_tasks(process.tasks) do |task|
388
- expect(conn.hget(task.key, :uuid)).to be_nil
389
- end
390
+ process.cleanup(0) # immediately
390
391
 
392
+ # ensure nothing left behind
393
+ expect(conn.keys).to be_empty
391
394
  end
392
395
  }
393
396
  end
@@ -396,9 +399,14 @@ describe Taskinator::Persistence, :redis => true do
396
399
 
397
400
  describe "expires in future" do
398
401
  it {
399
- process = TestFlows::Task.create_process(1)
400
-
401
402
  Taskinator.redis do |conn|
403
+
404
+ # sanity check
405
+ expect(conn.keys).to be_empty
406
+
407
+ process = TestFlows::Task.create_process(1)
408
+
409
+ # sanity check
402
410
  expect(conn.hget(process.key, :uuid)).to eq(process.uuid)
403
411
 
404
412
  process.cleanup(2)
@@ -411,12 +419,8 @@ describe Taskinator::Persistence, :redis => true do
411
419
 
412
420
  sleep 3
413
421
 
414
- # gone!
415
- expect(conn.hget(process.key, :uuid)).to be_nil
416
- recursively_enumerate_tasks(process.tasks) do |task|
417
- expect(conn.hget(task.key, :uuid)).to be_nil
418
- end
419
-
422
+ # ensure nothing left behind
423
+ expect(conn.keys).to be_empty
420
424
  end
421
425
  }
422
426
  end
@@ -227,7 +227,7 @@ describe Taskinator::Task do
227
227
 
228
228
  method = subject.method
229
229
 
230
- executor.class_eval do
230
+ executor.singleton_class.class_eval do
231
231
  define_method method do |*args|
232
232
  # this method executes in the scope of the executor
233
233
  # store the context in an instance variable
@@ -334,13 +334,23 @@ describe Taskinator::Task do
334
334
  end
335
335
  end
336
336
 
337
- subject { Taskinator::Task.define_job_task(process, TestJob, {:a => 1, :b => 2}) }
337
+ class TestJobClassNoArgs
338
+ def perform
339
+ end
340
+ end
341
+
342
+ module TestJobModuleNoArgs
343
+ def self.perform
344
+ end
345
+ end
346
+
347
+ subject { Taskinator::Task.define_job_task(process, TestJob, [1, {:a => 1, :b => 2}]) }
338
348
 
339
349
  it_should_behave_like "a task", Taskinator::Task::Job
340
350
 
341
351
  describe ".define_job_task" do
342
352
  it "sets the queue to use" do
343
- task = Taskinator::Task.define_job_task(process, TestJob, {:a => 1, :b => 2}, :queue => :foo)
353
+ task = Taskinator::Task.define_job_task(process, TestJob, [1, {:a => 1, :b => 2}], :queue => :foo)
344
354
  expect(task.queue).to eq(:foo)
345
355
  end
346
356
  end
@@ -367,23 +377,37 @@ describe Taskinator::Task do
367
377
 
368
378
  describe "#start" do
369
379
  it {
370
- task = Taskinator::Task.define_job_task(process, TestJobClass, {:a => 1, :b => 2})
380
+ task = Taskinator::Task.define_job_task(process, TestJobClass, [1, {:a => 1, :b => 2}])
381
+ expect(process).to receive(:task_completed).with(task)
382
+ expect_any_instance_of(TestJobClass).to receive(:perform).with(1, {:a => 1, :b => 2})
383
+ task.start!
384
+ }
385
+
386
+ it {
387
+ task = Taskinator::Task.define_job_task(process, TestJobModule, [2, {:a => 1, :b => 2}])
388
+ expect(process).to receive(:task_completed).with(task)
389
+ expect(TestJobModule).to receive(:perform).with(2, {:a => 1, :b => 2})
390
+ task.start!
391
+ }
392
+
393
+ it {
394
+ task = Taskinator::Task.define_job_task(process, TestJobClassNoArgs, nil)
371
395
  expect(process).to receive(:task_completed).with(task)
372
- expect_any_instance_of(TestJobClass).to receive(:perform).with({:a => 1, :b => 2})
396
+ expect_any_instance_of(TestJobClassNoArgs).to receive(:perform).and_call_original
373
397
  task.start!
374
398
  }
375
399
 
376
400
  it {
377
- task = Taskinator::Task.define_job_task(process, TestJobModule, {:a => 1, :b => 2})
401
+ task = Taskinator::Task.define_job_task(process, TestJobModuleNoArgs, nil)
378
402
  expect(process).to receive(:task_completed).with(task)
379
- expect(TestJobModule).to receive(:perform).with({:a => 1, :b => 2})
403
+ expect(TestJobModuleNoArgs).to receive(:perform).and_call_original
380
404
  task.start!
381
405
  }
382
406
 
383
407
  it "is instrumented" do
384
408
  allow(process).to receive(:task_completed).with(subject)
385
409
 
386
- allow(TestJob).to receive(:perform).with({:a => 1, :b => 2})
410
+ allow(TestJob).to receive(:perform).with(1, {:a => 1, :b => 2})
387
411
 
388
412
  instrumentation_block = SpecSupport::Block.new
389
413
 
data/taskinator.gemspec CHANGED
@@ -23,7 +23,6 @@ Gem::Specification.new do |spec|
23
23
  # core
24
24
  spec.add_dependency 'redis' , '>= 3.2.1'
25
25
  spec.add_dependency 'redis-namespace' , '>= 1.5.2'
26
- spec.add_dependency 'redis-semaphore' , '>= 0.2.4'
27
26
  spec.add_dependency 'connection_pool' , '>= 2.2.0'
28
27
  spec.add_dependency 'json' , '>= 1.8.2'
29
28
  spec.add_dependency 'builder' , '>= 3.2.2'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: taskinator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.16
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Stefano
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-17 00:00:00.000000000 Z
11
+ date: 2022-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.5.2
41
- - !ruby/object:Gem::Dependency
42
- name: redis-semaphore
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: 0.2.4
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: 0.2.4
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: connection_pool
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -246,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
246
232
  - !ruby/object:Gem::Version
247
233
  version: '0'
248
234
  requirements: []
249
- rubygems_version: 3.1.4
235
+ rubygems_version: 3.2.3
250
236
  signing_key:
251
237
  specification_version: 4
252
238
  summary: A simple orchestration library for running complex processes or workflows