burstflow 0.2.0 → 0.2.1

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