burstflow 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +29 -21
- data/burstflow.gemspec +4 -4
- data/db/schema.rb +11 -13
- data/lib/burstflow/job.rb +23 -26
- data/lib/burstflow/job/callbacks.rb +4 -9
- data/lib/burstflow/job/exception.rb +3 -1
- data/lib/burstflow/job/initialization.rb +3 -6
- data/lib/burstflow/job/state.rb +17 -13
- data/lib/burstflow/manager.rb +85 -86
- data/lib/burstflow/railtie.rb +5 -2
- data/lib/burstflow/version.rb +3 -1
- data/lib/burstflow/worker.rb +21 -21
- data/lib/burstflow/workflow.rb +164 -156
- data/lib/burstflow/workflow/builder.rb +67 -64
- data/lib/burstflow/workflow/callbacks.rb +10 -16
- data/lib/burstflow/workflow/configuration.rb +37 -36
- data/lib/burstflow/workflow/exception.rb +3 -1
- data/lib/generators/burstflow/install/install_generator.rb +11 -5
- data/spec/builder_spec.rb +26 -27
- data/spec/generators/install_generator_spec.rb +11 -7
- data/spec/job_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/workflow_spec.rb +128 -101
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d68ef2fa6c3af3c066b65ae6213d3ac96216ea1eb66b849bb979759ed25c67b
|
4
|
+
data.tar.gz: b7868f4aeca3863996bba8f256fa72b79f244375bc6c82c301386ca88b7f66f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59e06e384d30bec993c880bce050f41dbc017cf1b0814855dca2d538ea270a79d9a9cb6a8f4601b47848f810cc768508e4a0c7aa5eade69717c194dc52b07526
|
7
|
+
data.tar.gz: dfba34ed82a10b598dfac4539be6f0b8d6880061eb61ac85780c9fcd84f5f498e72ce9228c3bb088d83bba32bcda75d4238b03e070ea64eb3b9d334a88718636
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -2,25 +2,33 @@
|
|
2
2
|
|
3
3
|
<!-- ## [![](http://i.imgur.com/ya8Wnyl.png)](https://chaps.io) proudly made by [Chaps](https://chaps.io) -->
|
4
4
|
|
5
|
-
|
5
|
+
[![Build Status](https://travis-ci.com/RnD-Soft/burstflow.svg?branch=master)](https://travis-ci.com/RnD-Soft/burstflow) [![Gem Version](https://badge.fury.io/rb/burstflow.svg)](https://badge.fury.io/rb/burstflow)
|
6
6
|
|
7
|
-
|
7
|
+
Burstflow is a parallel workflow runner using [ActiveRecord] and [ActiveJob](https://guides.rubyonrails.org/v4.2/active_job_basics.html) for scheduling and executing jobs.
|
8
8
|
|
9
|
-
|
9
|
+
This gem is higly inspired by [Gush](https://github.com/chaps-io/gush). But not tied to Reddis or Sidekiq like Gush.
|
10
|
+
|
11
|
+
ActiveRecord used as persisted store during workflow execution.
|
12
|
+
|
13
|
+
Additional features:
|
14
|
+
|
15
|
+
* **susped** and **resume** job(and whole workflow). For example if your job makes asynchronous request and will receive a response some time later. In this case, the job can send request and suspend until some external event resumes it eventually.
|
16
|
+
* before/after callbacks on jobs level
|
17
|
+
* before/after callbacks on workflow level
|
18
|
+
* **Dynamic workflows**. Any job can produce another jobs while executing. This jobs has its parent as incomming jobs, and all parents outgoing jobs as own outgoings.
|
10
19
|
|
11
|
-
Another difference from Gush is **Dynamic workflows**. Any job can produce another jobs while executing. This jobs has its parent as incomming jobs, and all parents outgoing jobs as own outgoings.
|
12
20
|
|
13
21
|
## Installation
|
14
22
|
|
15
23
|
### 1. Add `burst` to Gemfile
|
16
24
|
|
17
25
|
```ruby
|
18
|
-
gem '
|
26
|
+
gem 'burstflow', '~> 0.2.0'
|
19
27
|
```
|
20
28
|
|
21
29
|
### 2. Run migrations
|
22
30
|
|
23
|
-
|
31
|
+
```rake burstflow:install:migrations```
|
24
32
|
|
25
33
|
## Example
|
26
34
|
|
@@ -29,7 +37,7 @@ Here is a complete example of a workflow you can create:
|
|
29
37
|
|
30
38
|
```ruby
|
31
39
|
# app/workflows/sample_workflow.rb
|
32
|
-
class SampleWorkflow <
|
40
|
+
class SampleWorkflow < Burstflow::Workflow
|
33
41
|
configure do |url_to_fetch_from|
|
34
42
|
run FetchJob1, params: { url: url_to_fetch_from }
|
35
43
|
run FetchJob2, params: { some_flag: true, url: 'http://url.com' }
|
@@ -56,7 +64,7 @@ and this is how the graph will look like:
|
|
56
64
|
Let's start with the simplest workflow possible, consisting of a single job:
|
57
65
|
|
58
66
|
```ruby
|
59
|
-
class SimpleWorkflow <
|
67
|
+
class SimpleWorkflow < Burstflow::Workflow
|
60
68
|
configure do
|
61
69
|
run DownloadJob
|
62
70
|
end
|
@@ -74,12 +82,12 @@ class SimpleWorkflow < Burst::Workflow
|
|
74
82
|
end
|
75
83
|
```
|
76
84
|
|
77
|
-
We just told
|
85
|
+
We just told Burstflow to execute `SaveJob` right after `DownloadJob` finishes **successfully**.
|
78
86
|
|
79
87
|
But what if your job must have multiple dependencies? That's easy, just provide an array to the `after` attribute:
|
80
88
|
|
81
89
|
```ruby
|
82
|
-
class SimpleWorkflow <
|
90
|
+
class SimpleWorkflow < Burstflow::Workflow
|
83
91
|
configure do
|
84
92
|
run FirstDownloadJob
|
85
93
|
run SecondDownloadJob
|
@@ -98,7 +106,7 @@ With this simple syntax you can build any complex workflows you can imagine!
|
|
98
106
|
`run` method also accepts `before:` attribute to define the opposite association. So we can write the same workflow as above, but like this:
|
99
107
|
|
100
108
|
```ruby
|
101
|
-
class SimpleWorkflow <
|
109
|
+
class SimpleWorkflow < Burstflow::Workflow
|
102
110
|
configure do
|
103
111
|
run FirstDownloadJob, before: SaveJob
|
104
112
|
run SecondDownloadJob, before: SaveJob
|
@@ -118,7 +126,7 @@ Workflows can accept any primitive arguments in their constructor, which then wi
|
|
118
126
|
Let's assume we are writing a book publishing workflow which needs to know where the PDF of the book is and under what ISBN it will be released:
|
119
127
|
|
120
128
|
```ruby
|
121
|
-
class PublishBookWorkflow <
|
129
|
+
class PublishBookWorkflow < Burstflow::Workflow
|
122
130
|
configure do |url, isbn|
|
123
131
|
run FetchBook, params: { url: url }
|
124
132
|
run PublishBook, params: { book_isbn: isbn }, after: FetchBook
|
@@ -136,7 +144,7 @@ and that's basically it for defining workflows, see below on how to define jobs:
|
|
136
144
|
|
137
145
|
## Defining jobs
|
138
146
|
|
139
|
-
The simplest job is a class inheriting from `
|
147
|
+
The simplest job is a class inheriting from `Burstflow::Job` and responding to `perform` and `resume` method. Much like any other ActiveJob class.
|
140
148
|
|
141
149
|
```ruby
|
142
150
|
class FetchBook < Burst::Job
|
@@ -189,14 +197,14 @@ flow = PublishBookWorkflow.build("http://url.com/book.pdf", "978-0470081204")
|
|
189
197
|
flow.start!
|
190
198
|
```
|
191
199
|
|
192
|
-
Now
|
200
|
+
Now Burstflow will start processing jobs in the background using ActiveJob and your chosen backend.
|
193
201
|
|
194
202
|
### 3. Monitor its progress:
|
195
203
|
|
196
204
|
```ruby
|
197
205
|
flow.reload
|
198
206
|
flow.status
|
199
|
-
#=> :running|:finished|:failed
|
207
|
+
#=> :running|:finished|:failed|:suspended
|
200
208
|
```
|
201
209
|
|
202
210
|
`reload` is needed to see the latest status, since workflows are updated asynchronously.
|
@@ -205,7 +213,7 @@ flow.status
|
|
205
213
|
|
206
214
|
### Pipelining
|
207
215
|
|
208
|
-
|
216
|
+
Burstflow offers a useful tool to pass results of a job to its dependencies, so they can act differently.
|
209
217
|
|
210
218
|
**Example:**
|
211
219
|
|
@@ -228,9 +236,9 @@ end
|
|
228
236
|
Now, since `DownloadVideo` finished and its dependant job `EncodeVideo` started, we can access that payload inside it:
|
229
237
|
|
230
238
|
```ruby
|
231
|
-
class EncodeVideo <
|
239
|
+
class EncodeVideo < Burstflow::Job
|
232
240
|
def perform
|
233
|
-
video_path = payloads.first[:
|
241
|
+
video_path = payloads.first[:value]
|
234
242
|
end
|
235
243
|
end
|
236
244
|
```
|
@@ -243,7 +251,7 @@ end
|
|
243
251
|
{
|
244
252
|
id: "DownloadVideo-41bfb730-b49f-42ac-a808-156327989294" # unique id of the ancestor job
|
245
253
|
class: "DownloadVideo",
|
246
|
-
|
254
|
+
value: "https://s3.amazonaws.com/somebucket/downloaded-file.mp4" #the payload returned by DownloadVideo job using `output()` method
|
247
255
|
}
|
248
256
|
]
|
249
257
|
```
|
@@ -259,7 +267,7 @@ As an example, let's write a workflow which accepts an array of users and has to
|
|
259
267
|
|
260
268
|
```ruby
|
261
269
|
|
262
|
-
class ParentJob <
|
270
|
+
class ParentJob < Burstflow::Job
|
263
271
|
def perform
|
264
272
|
configure do
|
265
273
|
params[:user_ids].map do |user_id|
|
@@ -286,7 +294,7 @@ https://github.com/chaps-io/gush#contributors
|
|
286
294
|
|
287
295
|
## Contributing
|
288
296
|
|
289
|
-
1. Fork it ( https://github.com/RnD-Soft/
|
297
|
+
1. Fork it ( https://github.com/RnD-Soft/burstflow/fork )
|
290
298
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
291
299
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
292
300
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/burstflow.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
$:.push File.expand_path(
|
1
|
+
$:.push File.expand_path('lib', __dir__)
|
2
2
|
|
3
3
|
# Maintain your gem's version:
|
4
|
-
require
|
4
|
+
require 'burstflow/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'burstflow'
|
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
|
|
19
19
|
|
20
20
|
spec.add_dependency 'activejob'
|
21
21
|
spec.add_dependency 'activerecord'
|
22
|
-
|
22
|
+
|
23
23
|
spec.add_development_dependency 'bundler'
|
24
|
+
spec.add_development_dependency 'generator_spec'
|
24
25
|
spec.add_development_dependency 'rake'
|
25
26
|
spec.add_development_dependency 'rspec'
|
26
|
-
spec.add_development_dependency 'generator_spec'
|
27
27
|
end
|
data/db/schema.rb
CHANGED
@@ -10,20 +10,18 @@
|
|
10
10
|
#
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
12
12
|
|
13
|
-
ActiveRecord::Schema.define(version:
|
14
|
-
|
13
|
+
ActiveRecord::Schema.define(version: 20_180_101_000_001) do
|
15
14
|
# These are extensions that must be enabled in order to support this database
|
16
|
-
enable_extension
|
17
|
-
enable_extension
|
15
|
+
enable_extension 'pgcrypto'
|
16
|
+
enable_extension 'plpgsql'
|
18
17
|
|
19
|
-
create_table
|
20
|
-
t.string
|
21
|
-
t.string
|
22
|
-
t.jsonb
|
23
|
-
t.datetime
|
24
|
-
t.datetime
|
25
|
-
t.index [
|
26
|
-
t.index [
|
18
|
+
create_table 'burstflow_workflows', id: :uuid, default: -> { 'gen_random_uuid()' }, force: :cascade do |t|
|
19
|
+
t.string 'type'
|
20
|
+
t.string 'status'
|
21
|
+
t.jsonb 'flow', default: {}, null: false
|
22
|
+
t.datetime 'created_at', null: false
|
23
|
+
t.datetime 'updated_at', null: false
|
24
|
+
t.index ['status'], name: 'index_burstflow_workflows_on_status'
|
25
|
+
t.index ['type'], name: 'index_burstflow_workflows_on_type'
|
27
26
|
end
|
28
|
-
|
29
27
|
end
|
data/lib/burstflow/job.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'active_support/rescuable'
|
2
|
+
require 'active_job/callbacks'
|
3
3
|
|
4
4
|
class Burstflow::Job
|
5
|
-
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
5
|
+
|
6
|
+
require 'burstflow/job/exception'
|
7
|
+
require 'burstflow/job/model'
|
8
|
+
require 'burstflow/job/initialization'
|
9
|
+
require 'burstflow/job/state'
|
10
|
+
require 'burstflow/job/callbacks'
|
10
11
|
|
11
12
|
include Burstflow::Job::Model
|
12
13
|
include Burstflow::Job::Initialization
|
@@ -30,28 +31,28 @@ class Burstflow::Job
|
|
30
31
|
end
|
31
32
|
|
32
33
|
# execute this code by ActiveJob. You may return Burstflow::Job::SUSPEND to suspend job, or call suspend method
|
33
|
-
def perform
|
34
|
-
end
|
34
|
+
def perform; end
|
35
35
|
|
36
36
|
def perform_now
|
37
37
|
run_callbacks :perform do
|
38
38
|
perform
|
39
39
|
end
|
40
|
-
rescue => exception
|
40
|
+
rescue StandardError => exception
|
41
41
|
rescue_with_handler(exception) || raise
|
42
42
|
end
|
43
43
|
|
44
44
|
# execute this code when resumes after suspending
|
45
45
|
def resume(data)
|
46
|
-
raise InternalError.new(self, "Can't perform resume: not resumed")
|
46
|
+
raise InternalError.new(self, "Can't perform resume: not resumed") unless resumed?
|
47
|
+
|
47
48
|
set_output(data)
|
48
49
|
end
|
49
50
|
|
50
|
-
def resume_now
|
51
|
+
def resume_now(data)
|
51
52
|
run_callbacks :resume do
|
52
53
|
resume(data)
|
53
54
|
end
|
54
|
-
rescue => exception
|
55
|
+
rescue StandardError => exception
|
55
56
|
rescue_with_handler(exception) || raise
|
56
57
|
end
|
57
58
|
|
@@ -62,13 +63,13 @@ class Burstflow::Job
|
|
62
63
|
|
63
64
|
# mark execution as suspended
|
64
65
|
def suspend
|
65
|
-
raise InternalError.new(self, "Can't suspend: not running")
|
66
|
+
raise InternalError.new(self, "Can't suspend: not running") unless running?
|
67
|
+
|
66
68
|
set_output(SUSPEND)
|
67
69
|
end
|
68
70
|
|
69
|
-
def configure
|
71
|
+
def configure(*args, &block)
|
70
72
|
workflow.with_lock do
|
71
|
-
|
72
73
|
builder = Burstflow::Workflow::Builder.new(workflow, *args, &block)
|
73
74
|
workflow.flow['jobs_config'] = builder.as_json
|
74
75
|
workflow.save!
|
@@ -78,12 +79,12 @@ class Burstflow::Job
|
|
78
79
|
|
79
80
|
def attributes
|
80
81
|
{
|
81
|
-
workflow_id:
|
82
|
-
id:
|
83
|
-
klass:
|
82
|
+
workflow_id: workflow_id,
|
83
|
+
id: id,
|
84
|
+
klass: klass,
|
84
85
|
params: params,
|
85
|
-
incoming:
|
86
|
-
outgoing:
|
86
|
+
incoming: incoming,
|
87
|
+
outgoing: outgoing,
|
87
88
|
output: output,
|
88
89
|
started_at: started_at,
|
89
90
|
enqueued_at: enqueued_at,
|
@@ -91,12 +92,8 @@ class Burstflow::Job
|
|
91
92
|
failed_at: failed_at,
|
92
93
|
suspended_at: suspended_at,
|
93
94
|
resumed_at: resumed_at,
|
94
|
-
failure:
|
95
|
+
failure: failure
|
95
96
|
}
|
96
97
|
end
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
99
|
end
|
@@ -1,17 +1,15 @@
|
|
1
1
|
module Burstflow::Job::Callbacks
|
2
|
+
|
2
3
|
extend ActiveSupport::Concern
|
3
4
|
include ActiveJob::Callbacks
|
4
5
|
|
5
6
|
included do
|
6
|
-
|
7
7
|
define_callbacks :suspend
|
8
8
|
define_callbacks :resume
|
9
9
|
define_callbacks :failure
|
10
|
-
|
11
10
|
end
|
12
11
|
|
13
12
|
class_methods do
|
14
|
-
|
15
13
|
def before_suspend(*filters, &blk)
|
16
14
|
set_callback(:suspend, :before, *filters, &blk)
|
17
15
|
end
|
@@ -24,7 +22,6 @@ module Burstflow::Job::Callbacks
|
|
24
22
|
set_callback(:suspend, :around, *filters, &blk)
|
25
23
|
end
|
26
24
|
|
27
|
-
|
28
25
|
def before_resume(*filters, &blk)
|
29
26
|
set_callback(:resume, :before, *filters, &blk)
|
30
27
|
end
|
@@ -37,19 +34,17 @@ module Burstflow::Job::Callbacks
|
|
37
34
|
set_callback(:resume, :around, *filters, &blk)
|
38
35
|
end
|
39
36
|
|
40
|
-
|
41
37
|
def before_failure(*filters, &blk)
|
42
38
|
set_callback(:failure, :before, *filters, &blk)
|
43
39
|
end
|
44
|
-
|
40
|
+
|
45
41
|
def after_failure(*filters, &blk)
|
46
42
|
set_callback(:failure, :after, *filters, &blk)
|
47
43
|
end
|
48
|
-
|
44
|
+
|
49
45
|
def around_failure(*filters, &blk)
|
50
46
|
set_callback(:failure, :around, *filters, &blk)
|
51
47
|
end
|
52
|
-
|
53
48
|
end
|
54
49
|
|
55
|
-
end
|
50
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Burstflow::Job::Initialization
|
2
|
-
extend ActiveSupport::Concern
|
3
2
|
|
4
|
-
|
3
|
+
extend ActiveSupport::Concern
|
5
4
|
|
5
|
+
included do
|
6
6
|
def initialize(workflow, job_config = {})
|
7
7
|
@workflow = workflow
|
8
8
|
assign_default_values(job_config)
|
@@ -21,15 +21,12 @@ module Burstflow::Job::Initialization
|
|
21
21
|
def reload
|
22
22
|
assign_default_values(@workflow.job_hash(self.id))
|
23
23
|
end
|
24
|
-
|
25
24
|
end
|
26
25
|
|
27
26
|
class_methods do
|
28
|
-
|
29
27
|
def from_hash(workflow, job_config)
|
30
28
|
job_config[:klass].constantize.new(workflow, job_config)
|
31
29
|
end
|
32
|
-
|
33
30
|
end
|
34
31
|
|
35
|
-
end
|
32
|
+
end
|
data/lib/burstflow/job/state.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
module Burstflow::Job::State
|
2
|
-
extend ActiveSupport::Concern
|
3
2
|
|
4
|
-
|
3
|
+
extend ActiveSupport::Concern
|
5
4
|
|
5
|
+
included do
|
6
6
|
# mark job as enqueued when it is scheduled to queue
|
7
7
|
def enqueue!
|
8
8
|
raise Burstflow::Job::InternalError.new(self, "Can't enqueue: already enqueued") if enqueued?
|
9
|
+
|
9
10
|
self.enqueued_at = current_timestamp
|
10
11
|
self.started_at = nil
|
11
12
|
self.finished_at = nil
|
@@ -17,22 +18,25 @@ module Burstflow::Job::State
|
|
17
18
|
# mark job as started when it is start performing
|
18
19
|
def start!
|
19
20
|
raise Burstflow::Job::InternalError.new(self, "Can't start: already started") if started?
|
20
|
-
raise Burstflow::Job::InternalError.new(self, "Can't start: not enqueued")
|
21
|
+
raise Burstflow::Job::InternalError.new(self, "Can't start: not enqueued") unless enqueued?
|
22
|
+
|
21
23
|
self.started_at = current_timestamp
|
22
24
|
end
|
23
25
|
|
24
26
|
# mark job as finished when it is finish performing
|
25
27
|
def finish!
|
26
28
|
raise Burstflow::Job::InternalError.new(self, "Can't finish: already finished") if finished?
|
27
|
-
raise Burstflow::Job::InternalError.new(self, "Can't finish: not started")
|
29
|
+
raise Burstflow::Job::InternalError.new(self, "Can't finish: not started") unless started?
|
30
|
+
|
28
31
|
self.finished_at = current_timestamp
|
29
32
|
end
|
30
33
|
|
31
34
|
# mark job as failed when it is failed
|
32
|
-
def fail!
|
33
|
-
#raise Burstflow::Job::InternalError.new(self, "Can't fail: already failed") if failed?
|
34
|
-
#raise Burstflow::Job::InternalError.new(self, Can't fail: already finished") if finished?
|
35
|
-
raise Burstflow::Job::InternalError.new(self, "Can't fail: not started")
|
35
|
+
def fail!(msg_or_exception)
|
36
|
+
# raise Burstflow::Job::InternalError.new(self, "Can't fail: already failed") if failed?
|
37
|
+
# raise Burstflow::Job::InternalError.new(self, Can't fail: already finished") if finished?
|
38
|
+
raise Burstflow::Job::InternalError.new(self, "Can't fail: not started") unless started?
|
39
|
+
|
36
40
|
self.finished_at = self.failed_at = current_timestamp
|
37
41
|
|
38
42
|
context = {}
|
@@ -51,14 +55,16 @@ module Burstflow::Job::State
|
|
51
55
|
# mark job as suspended
|
52
56
|
def suspend!
|
53
57
|
raise Burstflow::Job::InternalError.new(self, "Can't suspend: already suspended") if suspended?
|
54
|
-
raise Burstflow::Job::InternalError.new(self, "Can't suspend: not runnig")
|
58
|
+
raise Burstflow::Job::InternalError.new(self, "Can't suspend: not runnig") unless running?
|
59
|
+
|
55
60
|
self.suspended_at = current_timestamp
|
56
61
|
end
|
57
62
|
|
58
63
|
# mark job as resumed
|
59
64
|
def resume!
|
60
65
|
raise Burstflow::Job::InternalError.new(self, "Can't resume: already resumed") if resumed?
|
61
|
-
raise Burstflow::Job::InternalError.new(self, "Can't resume: not suspended")
|
66
|
+
raise Burstflow::Job::InternalError.new(self, "Can't resume: not suspended") unless suspended?
|
67
|
+
|
62
68
|
self.resumed_at = current_timestamp
|
63
69
|
end
|
64
70
|
|
@@ -115,11 +121,9 @@ module Burstflow::Job::State
|
|
115
121
|
def current_timestamp
|
116
122
|
Time.now.to_i
|
117
123
|
end
|
118
|
-
|
119
124
|
end
|
120
125
|
|
121
126
|
class_methods do
|
122
|
-
|
123
127
|
end
|
124
128
|
|
125
|
-
end
|
129
|
+
end
|