burstflow 0.2.0 → 0.2.1

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.
@@ -1,123 +1,122 @@
1
1
  module Burstflow
2
- class Manager
3
2
 
4
- attr_accessor :workflow
3
+ class Manager
5
4
 
6
- def initialize(workflow)
7
- @workflow = workflow
8
- end
9
-
10
- #workflow management
5
+ attr_accessor :workflow
11
6
 
12
- def start_workflow!
13
- workflow.with_lock do
14
- workflow.runnig!
15
-
16
- workflow.initial_jobs.each do |job|
17
- enqueue_job!(job)
18
- end
7
+ def initialize(workflow)
8
+ @workflow = workflow
19
9
  end
20
- end
21
10
 
22
- def resume_workflow! job_id, data
23
- workflow.with_lock do
24
- workflow.resumed!
11
+ # workflow management
12
+
13
+ def start_workflow!
14
+ workflow.with_lock do
15
+ workflow.runnig!
25
16
 
26
- job = workflow.job(job_id)
27
- resume_job!(job, data)
17
+ workflow.initial_jobs.each do |job|
18
+ enqueue_job!(job)
19
+ end
20
+ end
28
21
  end
29
- end
30
22
 
23
+ def resume_workflow!(job_id, data)
24
+ workflow.with_lock do
25
+ workflow.resumed!
31
26
 
32
- #Mark job enqueued and enqueue it
33
- def enqueue_job!(job)
34
- job.run_callbacks :enqueue do
35
- job.enqueue!
36
- job.save! do
37
- Burstflow::Worker.perform_later(workflow.id, job.id)
27
+ job = workflow.job(job_id)
28
+ resume_job!(job, data)
38
29
  end
39
30
  end
40
- end
41
31
 
42
- #Enqueue job for resuming
43
- def resume_job!(job, data)
44
- job.save! do
45
- Burstflow::Worker.perform_later(workflow.id, job.id, data)
32
+ # Mark job enqueued and enqueue it
33
+ def enqueue_job!(job)
34
+ job.run_callbacks :enqueue do
35
+ job.enqueue!
36
+ job.save! do
37
+ Burstflow::Worker.perform_later(workflow.id, job.id)
38
+ end
39
+ end
46
40
  end
47
- end
48
41
 
49
-
50
- #Mark job suspended and forget it until resume
51
- def suspend_job!(job)
52
- job.run_callbacks :suspend do
53
- job.suspend!
42
+ # Enqueue job for resuming
43
+ def resume_job!(job, data)
54
44
  job.save! do
55
- analyze_workflow_state(job)
45
+ Burstflow::Worker.perform_later(workflow.id, job.id, data)
56
46
  end
57
47
  end
58
- end
59
48
 
60
-
61
- #Mark job finished and make further actions
62
- def finish_job!(job)
63
- job.finish!
64
- job.save! do
65
- analyze_workflow_state(job)
49
+ # Mark job suspended and forget it until resume
50
+ def suspend_job!(job)
51
+ job.run_callbacks :suspend do
52
+ job.suspend!
53
+ job.save! do
54
+ analyze_workflow_state(job)
55
+ end
56
+ end
66
57
  end
67
- end
68
-
69
- #Mark job failed and make further actions
70
- def fail_job!(job, exception)
71
- job.run_callbacks :failure do
72
- job.fail! exception
73
- workflow.add_error(job)
74
- workflow.save!
75
58
 
59
+ # Mark job finished and make further actions
60
+ def finish_job!(job)
61
+ job.finish!
76
62
  job.save! do
77
63
  analyze_workflow_state(job)
78
64
  end
79
- end
80
- end
81
-
82
- #Mark job finished or suspended depends on result or output
83
- def job_performed!(job, result)
84
- if result == Burstflow::Job::SUSPEND || job.output == Burstflow::Job::SUSPEND
85
- suspend_job!(job)
86
- else
87
- finish_job!(job)
88
65
  end
89
- rescue => e
90
- raise Burstflow::Workflow::InternalError.new(workflow, e.message)
91
- end
92
66
 
93
- private
67
+ # Mark job failed and make further actions
68
+ def fail_job!(job, exception)
69
+ job.run_callbacks :failure do
70
+ job.fail! exception
71
+ workflow.add_error(job)
72
+ workflow.save!
94
73
 
95
- #analyze job completition, current workflow state and perform futher actions
96
- def analyze_workflow_state job
97
- unless ActiveRecord::Base.connection.open_transactions > 0
98
- raise Burstflow::Workflow::InternalError.new(workflow, "analyze_workflow_state must be called in transaction with lock!")
74
+ job.save! do
75
+ analyze_workflow_state(job)
76
+ end
77
+ end
99
78
  end
100
79
 
101
- if job.succeeded? && job.outgoing.any? && !workflow.has_errors?
102
- return enqueue_outgoing_jobs(job)
103
- else
104
- if workflow.has_scheduled_jobs?
105
- #do nothing
106
- #scheduled jobs will perform finish action
80
+ # Mark job finished or suspended depends on result or output
81
+ def job_performed!(job, result)
82
+ if result == Burstflow::Job::SUSPEND || job.output == Burstflow::Job::SUSPEND
83
+ suspend_job!(job)
107
84
  else
108
- workflow.complete!
85
+ finish_job!(job)
109
86
  end
87
+ rescue StandardError => e
88
+ raise Burstflow::Workflow::InternalError.new(workflow, e.message)
110
89
  end
111
- end
112
90
 
113
- #enqueue outgoing jobs if all requirements are met
114
- def enqueue_outgoing_jobs(job)
115
- job.outgoing.each do |job_id|
116
- out = workflow.job(job_id)
91
+ private
92
+
93
+ # analyze job completition, current workflow state and perform futher actions
94
+ def analyze_workflow_state(job)
95
+ unless ActiveRecord::Base.connection.open_transactions > 0
96
+ raise Burstflow::Workflow::InternalError.new(workflow, 'analyze_workflow_state must be called in transaction with lock!')
97
+ end
98
+
99
+ if job.succeeded? && job.outgoing.any? && !workflow.has_errors?
100
+ return enqueue_outgoing_jobs(job)
101
+ else
102
+ if workflow.has_scheduled_jobs?
103
+ # do nothing
104
+ # scheduled jobs will perform finish action
105
+ else
106
+ workflow.complete!
107
+ end
108
+ end
109
+ end
110
+
111
+ # enqueue outgoing jobs if all requirements are met
112
+ def enqueue_outgoing_jobs(job)
113
+ job.outgoing.each do |job_id|
114
+ out = workflow.job(job_id)
115
+
116
+ enqueue_job!(out) if out.ready_to_start?
117
+ end
118
+ end
117
119
 
118
- enqueue_job!(out) if out.ready_to_start?
119
- end
120
120
  end
121
121
 
122
122
  end
123
- end
@@ -1,6 +1,9 @@
1
-
2
1
  module Burstflow
2
+
3
3
  class Railtie < Rails::Engine #:nodoc:
4
+
4
5
  engine_name 'burstflow'
6
+
5
7
  end
6
- end
8
+
9
+ end
@@ -1,3 +1,5 @@
1
1
  module Burstflow
2
- VERSION = '0.2.0'
2
+
3
+ VERSION = '0.2.1'.freeze
4
+
3
5
  end
@@ -27,33 +27,33 @@ class Burstflow::Worker < ::ActiveJob::Base
27
27
  set_incoming_payloads(job)
28
28
  end
29
29
 
30
- def perform(workflow_id, job_id, resume_data = nil)
30
+ def perform(_workflow_id, _job_id, resume_data = nil)
31
31
  result = if resume_data.nil?
32
- job.start!
33
- job.save!
34
-
35
- job.perform_now
36
- else
37
- job.resume!
38
- job.save!
39
-
40
- job.resume_now(resume_data)
32
+ job.start!
33
+ job.save!
34
+
35
+ job.perform_now
36
+ else
37
+ job.resume!
38
+ job.save!
39
+
40
+ job.resume_now(resume_data)
41
41
  end
42
42
 
43
43
  @manager.job_performed!(job, result)
44
44
  end
45
45
 
46
- private
47
-
48
- def set_incoming_payloads job
49
- job.payloads = job.incoming.map do |job_id|
50
- incoming = workflow.job(job_id)
51
- {
52
- id: incoming.id,
53
- class: incoming.klass.to_s,
54
- value: incoming.output
55
- }
46
+ private
47
+
48
+ def set_incoming_payloads(job)
49
+ job.payloads = job.incoming.map do |job_id|
50
+ incoming = workflow.job(job_id)
51
+ {
52
+ id: incoming.id,
53
+ class: incoming.klass.to_s,
54
+ value: incoming.output
55
+ }
56
+ end
56
57
  end
57
- end
58
58
 
59
59
  end
@@ -3,205 +3,213 @@ require 'active_record'
3
3
  require 'burstflow/manager'
4
4
 
5
5
  module Burstflow
6
- class Workflow < ActiveRecord::Base
7
- require 'burstflow/workflow/exception'
8
- require 'burstflow/workflow/builder'
9
- require 'burstflow/workflow/configuration'
10
- require 'burstflow/workflow/callbacks'
11
-
12
- self.table_name_prefix = 'burstflow_'
13
6
 
14
- INITIAL = 'initial'.freeze
15
- RUNNING = 'running'.freeze
16
- FINISHED = 'finished'.freeze
17
- FAILED = 'failed'.freeze
18
- SUSPENDED = 'suspended'.freeze
7
+ class Workflow < ActiveRecord::Base
19
8
 
20
- STATUSES = [INITIAL, RUNNING, FINISHED, FAILED, SUSPENDED].freeze
9
+ require 'burstflow/workflow/exception'
10
+ require 'burstflow/workflow/builder'
11
+ require 'burstflow/workflow/configuration'
12
+ require 'burstflow/workflow/callbacks'
21
13
 
22
- include Burstflow::Workflow::Configuration
23
- include Burstflow::Workflow::Callbacks
14
+ self.table_name_prefix = 'burstflow_'
24
15
 
25
- attr_accessor :manager, :cache
26
- define_flow_attributes :jobs_config, :failures
16
+ INITIAL = 'initial'.freeze
17
+ RUNNING = 'running'.freeze
18
+ FINISHED = 'finished'.freeze
19
+ FAILED = 'failed'.freeze
20
+ SUSPENDED = 'suspended'.freeze
27
21
 
28
- after_initialize do
29
- @cache = {}
22
+ STATUSES = [INITIAL, RUNNING, FINISHED, FAILED, SUSPENDED].freeze
30
23
 
31
- self.status ||= INITIAL
32
- self.id ||= SecureRandom.uuid
33
- self.jobs_config ||= {}.with_indifferent_access
34
- self.failures ||= []
24
+ include Burstflow::Workflow::Configuration
25
+ include Burstflow::Workflow::Callbacks
35
26
 
36
- @manager = Burstflow::Manager.new(self)
37
- end
27
+ attr_accessor :manager, :cache
28
+ define_flow_attributes :jobs_config, :failures
38
29
 
39
- STATUSES.each do |name|
40
- define_method "#{name}?".to_sym do
41
- self.status == name
42
- end
43
- end
30
+ after_initialize do
31
+ @cache = {}
44
32
 
45
- def attributes
46
- {
47
- id: self.id,
48
- jobs_config: self.jobs_config,
49
- type: self.class.to_s,
50
- status: status,
51
- failures: failures
52
- }
53
- end
33
+ self.status ||= INITIAL
34
+ self.id ||= SecureRandom.uuid
35
+ self.jobs_config ||= {}.with_indifferent_access
36
+ self.failures ||= []
54
37
 
55
- def self.build(*args)
56
- new.tap do |wf|
57
- builder = Burstflow::Workflow::Builder.new(wf, *args, &configuration)
58
- wf.flow = {'jobs_config' => builder.as_json}
38
+ @manager = Burstflow::Manager.new(self)
59
39
  end
60
- end
61
40
 
62
- def reload(*)
63
- self.cache = {}
64
- super
65
- end
66
-
67
- def start!
68
- manager.start_workflow!
69
- self
70
- end
41
+ STATUSES.each do |name|
42
+ define_method "#{name}?".to_sym do
43
+ self.status == name
44
+ end
45
+ end
71
46
 
72
- def resume!(job_id, data)
73
- manager.resume_workflow!(job_id, data)
74
- self
75
- end
47
+ def attributes
48
+ {
49
+ id: self.id,
50
+ jobs_config: self.jobs_config,
51
+ type: self.class.to_s,
52
+ status: status,
53
+ failures: failures
54
+ }
55
+ end
76
56
 
77
- def jobs
78
- Enumerator.new do |y|
79
- jobs_config.keys.each do |id|
80
- y << job(id)
57
+ def self.build(*args)
58
+ new.tap do |wf|
59
+ builder = Burstflow::Workflow::Builder.new(wf, *args, &configuration)
60
+ wf.flow = { 'jobs_config' => builder.as_json }
81
61
  end
82
62
  end
83
- end
84
63
 
85
- def job_hash(id)
86
- jobs_config[id].deep_dup
87
- end
64
+ def reload(*)
65
+ self.cache = {}
66
+ super
67
+ end
88
68
 
89
- def job(id)
90
- Burstflow::Job.from_hash(self, job_hash(id))
91
- end
69
+ def start!
70
+ manager.start_workflow!
71
+ self
72
+ end
92
73
 
93
- def set_job(job)
94
- jobs_config[job.id] = job.as_json
95
- end
74
+ def resume!(job_id, data)
75
+ manager.resume_workflow!(job_id, data)
76
+ self
77
+ end
96
78
 
97
- def initial_jobs
98
- cache[:initial_jobs] ||= jobs.select(&:initial?)
99
- end
79
+ def jobs
80
+ Enumerator.new do |y|
81
+ jobs_config.keys.each do |id|
82
+ y << job(id)
83
+ end
84
+ end
85
+ end
100
86
 
101
- def add_error job_orexception
102
- context = {
103
- created_at: Time.now.to_i
104
- }
105
- if job_orexception.is_a?(::Exception)
106
- context[:message] = job_orexception.message
107
- context[:klass] = job_orexception.class.to_s
108
- context[:backtrace] = job_orexception.backtrace.first(10)
109
- context[:cause] = job_orexception.cause
110
- else
111
- context[:job] = job_orexception.id
87
+ def job_hash(id)
88
+ jobs_config[id].deep_dup
112
89
  end
113
90
 
114
- failures.push(context)
115
- end
91
+ def job(id)
92
+ Burstflow::Job.from_hash(self, job_hash(id))
93
+ end
116
94
 
117
- def has_errors?
118
- failures.any?
119
- end
95
+ def set_job(job)
96
+ jobs_config[job.id] = job.as_json
97
+ end
120
98
 
121
- def has_scheduled_jobs?
122
- cache[:has_scheduled_jobs] ||= jobs.any? do |job|
123
- job.scheduled? || (job.initial? && !job.enqueued?)
99
+ def initial_jobs
100
+ cache[:initial_jobs] ||= jobs.select(&:initial?)
124
101
  end
125
- end
126
102
 
127
- def has_suspended_jobs?
128
- cache[:has_suspended_jobs] ||= jobs.any?(&:suspended?)
129
- end
103
+ def add_error(job_orexception)
104
+ context = {
105
+ created_at: Time.now.to_i
106
+ }
107
+ if job_orexception.is_a?(::Exception)
108
+ context[:message] = job_orexception.message
109
+ context[:klass] = job_orexception.class.to_s
110
+ context[:backtrace] = job_orexception.backtrace.first(10)
111
+ context[:cause] = job_orexception.cause
112
+ else
113
+ context[:job] = job_orexception.id
114
+ end
130
115
 
131
- def complete!
132
- if has_errors?
133
- failed!
134
- elsif has_suspended_jobs?
135
- suspended!
136
- else
137
- finished!
116
+ failures.push(context)
138
117
  end
139
- end
140
118
 
141
- def first_job
142
- all_jobs.min_by{|n| n.started_at || Time.now.to_i }
143
- end
119
+ def has_errors?
120
+ failures.any?
121
+ end
144
122
 
145
- def last_job
146
- all_jobs.max_by{|n| n.finished_at || 0 } if finished?
147
- end
123
+ def has_scheduled_jobs?
124
+ cache[:has_scheduled_jobs] ||= jobs.any? do |job|
125
+ job.scheduled? || (job.initial? && !job.enqueued?)
126
+ end
127
+ end
148
128
 
149
- def started_at
150
- first_job&.started_at
151
- end
129
+ def has_suspended_jobs?
130
+ cache[:has_suspended_jobs] ||= jobs.any?(&:suspended?)
131
+ end
152
132
 
153
- def finished_at
154
- last_job&.finished_at
155
- end
133
+ def complete!
134
+ if has_errors?
135
+ failed!
136
+ elsif has_suspended_jobs?
137
+ suspended!
138
+ else
139
+ finished!
140
+ end
141
+ end
156
142
 
157
- def runnig!
158
- raise InternalError.new(self, "Can't start: workflow already running") if (running? || suspended?)
159
- raise InternalError.new(self, "Can't start: workflow already failed") if failed?
160
- raise InternalError.new(self, "Can't start: workflow already finished") if finished?
161
- self.status = RUNNING
162
- save!
163
- end
143
+ def first_job
144
+ all_jobs.min_by{|n| n.started_at || Time.now.to_i }
145
+ end
164
146
 
165
- def failed!
166
- run_callbacks :failure do
167
- raise InternalError.new(self, "Can't fail: workflow already failed") if failed?
168
- raise InternalError.new(self, "Can't fail: workflow already finished") if finished?
169
- raise InternalError.new(self, "Can't fail: workflow in not runnig") if !(running? || suspended?)
170
- self.status = FAILED
171
- save!
147
+ def last_job
148
+ all_jobs.max_by{|n| n.finished_at || 0 } if finished?
172
149
  end
173
- end
174
150
 
175
- def finished!
176
- run_callbacks :finish do
177
- raise InternalError.new(self, "Can't finish: workflow already finished") if finished?
178
- raise InternalError.new(self, "Can't finish: workflow already failed") if failed?
179
- raise InternalError.new(self, "Can't finish: workflow in not runnig") if !running?
180
- self.status = FINISHED
181
- save!
182
- end
183
- end
151
+ def started_at
152
+ first_job&.started_at
153
+ end
184
154
 
185
- def suspended!
186
- run_callbacks :suspend do
187
- raise InternalError.new(self, "Can't suspend: workflow already finished") if finished?
188
- raise InternalError.new(self, "Can't suspend: workflow already failed") if failed?
189
- raise InternalError.new(self, "Can't suspend: workflow in not runnig") if !running?
190
- self.status = SUSPENDED
191
- save!
155
+ def finished_at
156
+ last_job&.finished_at
192
157
  end
193
- end
194
158
 
195
- def resumed!
196
- run_callbacks :resume do
197
- raise InternalError.new(self, "Can't resume: workflow already running") if running?
198
- raise InternalError.new(self, "Can't resume: workflow already finished") if finished?
199
- raise InternalError.new(self, "Can't resume: workflow already failed") if failed?
200
- raise InternalError.new(self, "Can't resume: workflow in not suspended") if !suspended?
159
+ def runnig!
160
+ raise InternalError.new(self, "Can't start: workflow already running") if running? || suspended?
161
+ raise InternalError.new(self, "Can't start: workflow already failed") if failed?
162
+ raise InternalError.new(self, "Can't start: workflow already finished") if finished?
163
+
201
164
  self.status = RUNNING
202
165
  save!
203
166
  end
167
+
168
+ def failed!
169
+ run_callbacks :failure do
170
+ raise InternalError.new(self, "Can't fail: workflow already failed") if failed?
171
+ raise InternalError.new(self, "Can't fail: workflow already finished") if finished?
172
+ raise InternalError.new(self, "Can't fail: workflow in not runnig") unless running? || suspended?
173
+
174
+ self.status = FAILED
175
+ save!
176
+ end
177
+ end
178
+
179
+ def finished!
180
+ run_callbacks :finish do
181
+ raise InternalError.new(self, "Can't finish: workflow already finished") if finished?
182
+ raise InternalError.new(self, "Can't finish: workflow already failed") if failed?
183
+ raise InternalError.new(self, "Can't finish: workflow in not runnig") unless running?
184
+
185
+ self.status = FINISHED
186
+ save!
187
+ end
188
+ end
189
+
190
+ def suspended!
191
+ run_callbacks :suspend do
192
+ raise InternalError.new(self, "Can't suspend: workflow already finished") if finished?
193
+ raise InternalError.new(self, "Can't suspend: workflow already failed") if failed?
194
+ raise InternalError.new(self, "Can't suspend: workflow in not runnig") unless running?
195
+
196
+ self.status = SUSPENDED
197
+ save!
198
+ end
199
+ end
200
+
201
+ def resumed!
202
+ run_callbacks :resume do
203
+ raise InternalError.new(self, "Can't resume: workflow already running") if running?
204
+ raise InternalError.new(self, "Can't resume: workflow already finished") if finished?
205
+ raise InternalError.new(self, "Can't resume: workflow already failed") if failed?
206
+ raise InternalError.new(self, "Can't resume: workflow in not suspended") unless suspended?
207
+
208
+ self.status = RUNNING
209
+ save!
210
+ end
211
+ end
212
+
204
213
  end
205
214
 
206
215
  end
207
- end