dynflow 1.4.5 → 1.4.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/dynflow/action.rb +22 -12
- data/lib/dynflow/action/suspended.rb +4 -4
- data/lib/dynflow/action/timeouts.rb +2 -2
- data/lib/dynflow/actor.rb +20 -4
- data/lib/dynflow/clock.rb +2 -2
- data/lib/dynflow/director.rb +5 -1
- data/lib/dynflow/director/running_steps_manager.rb +2 -2
- data/lib/dynflow/dispatcher.rb +2 -1
- data/lib/dynflow/dispatcher/client_dispatcher.rb +8 -2
- data/lib/dynflow/dispatcher/executor_dispatcher.rb +4 -2
- data/lib/dynflow/executors/abstract/core.rb +1 -1
- data/lib/dynflow/executors/parallel.rb +2 -2
- data/lib/dynflow/rails.rb +1 -1
- data/lib/dynflow/rails/configuration.rb +5 -1
- data/lib/dynflow/testing/in_thread_executor.rb +2 -2
- data/lib/dynflow/testing/in_thread_world.rb +5 -5
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/world.rb +5 -5
- data/lib/dynflow/world/invalidation.rb +1 -1
- data/test/dispatcher_test.rb +6 -0
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f677a2e5d7f119264258b7b36fe187203db60b8b4df4136beb73954857d59bd
|
4
|
+
data.tar.gz: a7cd75185cc99ce4a1e3e18e07565542ebf24bec8109a3291e78c56345bbc0db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c00baa2c020df6b035aa5a45aaa1b58a08ccc310752f4de0ab54da0207a03439cfde09132a7e8a6f4fe7911058a027e87f5cdb7b2563da035725ba5b482d0ab4
|
7
|
+
data.tar.gz: 4189a3f752642000d4eda05a8d82013d350eb77f7325450dfa01e774672aa33c61f3b32a1c8d5eb75e030d2fae1cb42a1e92da3278278569ba250245b06c3aec
|
data/lib/dynflow/action.rb
CHANGED
@@ -93,7 +93,8 @@ module Dynflow
|
|
93
93
|
fields! execution_plan_id: String,
|
94
94
|
step_id: Integer,
|
95
95
|
event: Object,
|
96
|
-
time: type { variants Time, NilClass }
|
96
|
+
time: type { variants Time, NilClass },
|
97
|
+
optional: Algebrick::Types::Boolean
|
97
98
|
end
|
98
99
|
|
99
100
|
def self.constantize(action_name)
|
@@ -332,9 +333,9 @@ module Dynflow
|
|
332
333
|
|
333
334
|
# Plan an +event+ to be send to the action defined by +action+, what defaults to be self.
|
334
335
|
# if +time+ is not passed, event is sent as soon as possible.
|
335
|
-
def plan_event(event, time = nil, execution_plan_id: self.execution_plan_id, step_id: self.run_step_id)
|
336
|
+
def plan_event(event, time = nil, execution_plan_id: self.execution_plan_id, step_id: self.run_step_id, optional: false)
|
336
337
|
time = @world.clock.current_time + time if time.is_a?(Numeric)
|
337
|
-
delayed_events << DelayedEvent[execution_plan_id, step_id, event, time]
|
338
|
+
delayed_events << DelayedEvent[execution_plan_id, step_id, event, time, optional]
|
338
339
|
end
|
339
340
|
|
340
341
|
def delayed_events
|
@@ -352,15 +353,12 @@ module Dynflow
|
|
352
353
|
@step.state = state
|
353
354
|
end
|
354
355
|
|
356
|
+
# If this save returns an integer, it means it was an update. The number
|
357
|
+
# represents the number of updated records. If it is 0, then the step was in
|
358
|
+
# an unexpected state and couldn't be updated
|
355
359
|
def save_state(conditions = {})
|
356
360
|
phase! Executable
|
357
|
-
|
358
|
-
# represents the number of updated records. If it is 0, then the step
|
359
|
-
# was in an unexpected state and couldn't be updated, in which case we
|
360
|
-
# raise an exception and crash hard to prevent the step from being
|
361
|
-
# executed twice
|
362
|
-
count = @step.save(conditions)
|
363
|
-
raise 'Could not save state' if count.kind_of?(Integer) && !count.positive?
|
361
|
+
@step.save(conditions)
|
364
362
|
end
|
365
363
|
|
366
364
|
def delay(delay_options, *args)
|
@@ -536,11 +534,11 @@ module Dynflow
|
|
536
534
|
end
|
537
535
|
|
538
536
|
# TODO: This is getting out of hand, refactoring needed
|
537
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
539
538
|
def execute_run(event)
|
540
539
|
phase! Run
|
541
540
|
@world.logger.debug format('%13s %s:%2d got event %s',
|
542
541
|
'Step', execution_plan_id, @step.id, event) if event
|
543
|
-
@input = OutputReference.dereference @input, world.persistence
|
544
542
|
|
545
543
|
case
|
546
544
|
when state == :running
|
@@ -551,8 +549,19 @@ module Dynflow
|
|
551
549
|
raise 'event can be processed only when in suspended state'
|
552
550
|
end
|
553
551
|
|
552
|
+
old_state = self.state
|
554
553
|
self.state = :running unless self.state == :skipping
|
555
|
-
save_state(:state => %w(pending error skipping suspended))
|
554
|
+
saved = save_state(:state => %w(pending error skipping suspended))
|
555
|
+
if saved.kind_of?(Integer) && !saved.positive?
|
556
|
+
# The step was already in a state we're trying to transition to, most
|
557
|
+
# likely we were about to execute it for the second time after first
|
558
|
+
# execution was forcefully interrupted.
|
559
|
+
# Set error and return to prevent the step from being executed twice
|
560
|
+
set_error "Could not transition step from #{old_state} to #{self.state}, step already in #{self.state}."
|
561
|
+
return
|
562
|
+
end
|
563
|
+
|
564
|
+
@input = OutputReference.dereference @input, world.persistence
|
556
565
|
with_error_handling do
|
557
566
|
event = Skip if state == :skipping
|
558
567
|
|
@@ -573,6 +582,7 @@ module Dynflow
|
|
573
582
|
raise "wrong state #{state} when event:#{event}"
|
574
583
|
end
|
575
584
|
end
|
585
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
576
586
|
|
577
587
|
def execute_finalize
|
578
588
|
phase! Finalize
|
@@ -9,14 +9,14 @@ module Dynflow
|
|
9
9
|
@step_id = action.run_step_id
|
10
10
|
end
|
11
11
|
|
12
|
-
def plan_event(event, time, sent = Concurrent::Promises.resolvable_future)
|
13
|
-
@world.plan_event(execution_plan_id, step_id, event, time, sent)
|
12
|
+
def plan_event(event, time, sent = Concurrent::Promises.resolvable_future, optional: false)
|
13
|
+
@world.plan_event(execution_plan_id, step_id, event, time, sent, optional: optional)
|
14
14
|
end
|
15
15
|
|
16
|
-
def event(event, sent = Concurrent::Promises.resolvable_future)
|
16
|
+
def event(event, sent = Concurrent::Promises.resolvable_future, optional: false)
|
17
17
|
# TODO: deprecate 2 levels backtrace (to know it's called from clock or internaly)
|
18
18
|
# remove lib/dynflow/clock.rb ClockReference#ping branch condition on removal.
|
19
|
-
plan_event(event, nil, sent)
|
19
|
+
plan_event(event, nil, sent, optional: optional)
|
20
20
|
end
|
21
21
|
|
22
22
|
def <<(event = nil)
|
data/lib/dynflow/actor.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module Dynflow
|
3
3
|
|
4
|
+
FULL_BACKTRACE = %w[1 y yes].include?((ENV['DYNFLOW_FULL_BACKTRACE'] || '').downcase)
|
5
|
+
BACKTRACE_LIMIT = begin
|
6
|
+
limit = ENV['DYNFLOW_BACKTRACE_LIMIT'].to_i
|
7
|
+
limit.zero? ? nil : limit
|
8
|
+
end
|
9
|
+
|
4
10
|
module MethodicActor
|
5
11
|
def on_message(message)
|
6
12
|
method, *args = message
|
@@ -44,7 +50,11 @@ module Dynflow
|
|
44
50
|
include LogWithFullBacktrace
|
45
51
|
|
46
52
|
def on_envelope(envelope)
|
47
|
-
|
53
|
+
if FULL_BACKTRACE
|
54
|
+
Actor::BacktraceCollector.with_backtrace(envelope.origin_backtrace) { super }
|
55
|
+
else
|
56
|
+
super
|
57
|
+
end
|
48
58
|
end
|
49
59
|
end
|
50
60
|
|
@@ -83,9 +93,15 @@ module Dynflow
|
|
83
93
|
|
84
94
|
# takes an array of backtrace lines and replaces each chunk
|
85
95
|
def filter_backtrace(backtrace)
|
86
|
-
backtrace.map { |line| filter_line(line) }
|
87
|
-
|
88
|
-
|
96
|
+
trace = backtrace.map { |line| filter_line(line) }
|
97
|
+
.chunk_while { |l1, l2| l1 == l2}
|
98
|
+
.map(&:first)
|
99
|
+
if BACKTRACE_LIMIT
|
100
|
+
count = trace.count
|
101
|
+
trace = trace.take(BACKTRACE_LIMIT)
|
102
|
+
trace << "[ backtrace omitted #{count - BACKTRACE_LIMIT} lines ]" if trace.count < count
|
103
|
+
end
|
104
|
+
trace
|
89
105
|
end
|
90
106
|
end
|
91
107
|
end
|
data/lib/dynflow/clock.rb
CHANGED
@@ -114,11 +114,11 @@ module Dynflow
|
|
114
114
|
Time.now
|
115
115
|
end
|
116
116
|
|
117
|
-
def ping(who, time, with_what = nil, where =
|
117
|
+
def ping(who, time, with_what = nil, where = :<<, optional: false)
|
118
118
|
Type! time, Time, Numeric
|
119
119
|
time = current_time + time if time.is_a? Numeric
|
120
120
|
if who.is_a?(Action::Suspended)
|
121
|
-
who.plan_event(with_what, time)
|
121
|
+
who.plan_event(with_what, time, optional: optional)
|
122
122
|
else
|
123
123
|
timer = Clock::Timer[who, time, with_what.nil? ? Algebrick::Types::None : Some[Object][with_what], where]
|
124
124
|
self.tell([:add_timer, timer])
|
data/lib/dynflow/director.rb
CHANGED
@@ -15,7 +15,8 @@ module Dynflow
|
|
15
15
|
execution_plan_id: String,
|
16
16
|
step_id: Integer,
|
17
17
|
event: Object,
|
18
|
-
result: Concurrent::Promises::ResolvableFuture
|
18
|
+
result: Concurrent::Promises::ResolvableFuture,
|
19
|
+
optional: Algebrick::Types::Boolean
|
19
20
|
end
|
20
21
|
|
21
22
|
UnprocessableEvent = Class.new(Dynflow::Error)
|
@@ -163,6 +164,9 @@ module Dynflow
|
|
163
164
|
execution_plan_manager = @execution_plan_managers[event.execution_plan_id]
|
164
165
|
if execution_plan_manager
|
165
166
|
execution_plan_manager.event(event)
|
167
|
+
elsif event.optional
|
168
|
+
event.result.reject "no manager for #{event.inspect}"
|
169
|
+
[]
|
166
170
|
else
|
167
171
|
raise Dynflow::Error, "no manager for #{event.inspect}"
|
168
172
|
end
|
@@ -20,8 +20,8 @@ module Dynflow
|
|
20
20
|
def terminate
|
21
21
|
pending_work = @work_items.clear.values.flatten(1)
|
22
22
|
pending_work.each do |w|
|
23
|
-
|
24
|
-
|
23
|
+
finish_event_result(w) do |result|
|
24
|
+
result.reject UnprocessableEvent.new("dropping due to termination")
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
data/lib/dynflow/dispatcher.rb
CHANGED
@@ -132,11 +132,13 @@ module Dynflow
|
|
132
132
|
end
|
133
133
|
|
134
134
|
def dispatch_request(request, client_world_id, request_id)
|
135
|
+
ignore_unknown = false
|
135
136
|
executor_id = match request,
|
136
137
|
(on ~Execution do |execution|
|
137
138
|
AnyExecutor
|
138
139
|
end),
|
139
140
|
(on ~Event do |event|
|
141
|
+
ignore_unknown = event.optional
|
140
142
|
find_executor(event.execution_plan_id)
|
141
143
|
end),
|
142
144
|
(on Ping.(~any, ~any) | Status.(~any, ~any) do |receiver_id, _|
|
@@ -144,7 +146,11 @@ module Dynflow
|
|
144
146
|
end)
|
145
147
|
envelope = Envelope[request_id, client_world_id, executor_id, request]
|
146
148
|
if Dispatcher::UnknownWorld === envelope.receiver_id
|
147
|
-
raise Dynflow::Error, "Could not find an executor for #{envelope}"
|
149
|
+
raise Dynflow::Error, "Could not find an executor for #{envelope}" unless ignore_unknown
|
150
|
+
|
151
|
+
message = "Could not find an executor for optional #{envelope}, discarding."
|
152
|
+
log(Logger::DEBUG, message)
|
153
|
+
return respond(envelope, Failed[message])
|
148
154
|
end
|
149
155
|
connector.send(envelope).value!
|
150
156
|
rescue => e
|
@@ -252,7 +258,7 @@ module Dynflow
|
|
252
258
|
future.fulfill(true)
|
253
259
|
else
|
254
260
|
if @ping_cache.executor?(request.receiver_id)
|
255
|
-
future.reject
|
261
|
+
future.reject false
|
256
262
|
else
|
257
263
|
yield
|
258
264
|
end
|
@@ -52,12 +52,14 @@ module Dynflow
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
if event_request.time.nil? || event_request.time < Time.now
|
55
|
-
@world.executor.event(envelope.request_id, event_request.execution_plan_id, event_request.step_id, event_request.event, future
|
55
|
+
@world.executor.event(envelope.request_id, event_request.execution_plan_id, event_request.step_id, event_request.event, future,
|
56
|
+
optional: event_request.optional)
|
56
57
|
else
|
57
58
|
@world.clock.ping(
|
58
59
|
@world.executor,
|
59
60
|
event_request.time,
|
60
|
-
Director::Event[envelope.request_id, event_request.execution_plan_id, event_request.step_id, event_request.event, Concurrent::Promises.resolvable_future
|
61
|
+
Director::Event[envelope.request_id, event_request.execution_plan_id, event_request.step_id, event_request.event, Concurrent::Promises.resolvable_future,
|
62
|
+
event_request.optional],
|
61
63
|
:delayed_event
|
62
64
|
)
|
63
65
|
# resolves the future right away - currently we do not wait for the clock ping
|
@@ -37,7 +37,7 @@ module Dynflow
|
|
37
37
|
|
38
38
|
def plan_events(delayed_events)
|
39
39
|
delayed_events.each do |event|
|
40
|
-
@world.plan_event(event.execution_plan_id, event.step_id, event.event, event.time)
|
40
|
+
@world.plan_event(event.execution_plan_id, event.step_id, event.event, event.time, optional: event.optional)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -33,8 +33,8 @@ module Dynflow
|
|
33
33
|
raise e
|
34
34
|
end
|
35
35
|
|
36
|
-
def event(request_id, execution_plan_id, step_id, event, future = nil)
|
37
|
-
@core.ask([:handle_event, Director::Event[request_id, execution_plan_id, step_id, event, future]])
|
36
|
+
def event(request_id, execution_plan_id, step_id, event, future = nil, optional: false)
|
37
|
+
@core.ask([:handle_event, Director::Event[request_id, execution_plan_id, step_id, event, future, optional]])
|
38
38
|
future
|
39
39
|
end
|
40
40
|
|
data/lib/dynflow/rails.rb
CHANGED
@@ -38,8 +38,8 @@ module Dynflow
|
|
38
38
|
init_world.tap do |world|
|
39
39
|
@world = world
|
40
40
|
config.run_on_init_hooks(false, world)
|
41
|
+
config.increase_db_pool_size(world)
|
41
42
|
unless config.remote?
|
42
|
-
config.increase_db_pool_size(world)
|
43
43
|
config.run_on_init_hooks(true, world)
|
44
44
|
# leave this just for long-running executors
|
45
45
|
unless config.rake_task_with_executor?
|
@@ -96,7 +96,11 @@ module Dynflow
|
|
96
96
|
end
|
97
97
|
|
98
98
|
def increase_db_pool_size?
|
99
|
-
!::Rails.env.test? && !remote?
|
99
|
+
!::Rails.env.test? && (!remote? || sidekiq_worker?)
|
100
|
+
end
|
101
|
+
|
102
|
+
def sidekiq_worker?
|
103
|
+
defined?(::Sidekiq) && ::Sidekiq.options[:queues].any?
|
100
104
|
end
|
101
105
|
|
102
106
|
def calculate_db_pool_size(world)
|
@@ -34,8 +34,8 @@ module Dynflow
|
|
34
34
|
@director.work_finished(work_item)
|
35
35
|
end
|
36
36
|
|
37
|
-
def event(execution_plan_id, step_id, event, future = Concurrent::Promises.resolvable_future)
|
38
|
-
event = (Director::Event[SecureRandom.uuid, execution_plan_id, step_id, event, future])
|
37
|
+
def event(execution_plan_id, step_id, event, future = Concurrent::Promises.resolvable_future, optional: false)
|
38
|
+
event = (Director::Event[SecureRandom.uuid, execution_plan_id, step_id, event, future, optional])
|
39
39
|
@director.handle_event(event).each do |work_item|
|
40
40
|
@work_items << work_item
|
41
41
|
end
|
@@ -58,15 +58,15 @@ module Dynflow
|
|
58
58
|
future.reject e
|
59
59
|
end
|
60
60
|
|
61
|
-
def event(execution_plan_id, step_id, event, done = Concurrent::Promises.resolvable_future)
|
62
|
-
@executor.event(execution_plan_id, step_id, event, done)
|
61
|
+
def event(execution_plan_id, step_id, event, done = Concurrent::Promises.resolvable_future, optional: false)
|
62
|
+
@executor.event(execution_plan_id, step_id, event, done, optional: optional)
|
63
63
|
end
|
64
64
|
|
65
|
-
def plan_event(execution_plan_id, step_id, event, time, done = Concurrent::Promises.resolvable_future)
|
65
|
+
def plan_event(execution_plan_id, step_id, event, time, done = Concurrent::Promises.resolvable_future, optional: false)
|
66
66
|
if time.nil? || time < Time.now
|
67
|
-
event(execution_plan_id, step_id, event, done)
|
67
|
+
event(execution_plan_id, step_id, event, done, optional: optional)
|
68
68
|
else
|
69
|
-
clock.ping(executor, time, Director::Event[SecureRandom.uuid, execution_plan_id, step_id, event, done], :delayed_event)
|
69
|
+
clock.ping(executor, time, Director::Event[SecureRandom.uuid, execution_plan_id, step_id, event, done, optional], :delayed_event)
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
data/lib/dynflow/version.rb
CHANGED
data/lib/dynflow/world.rb
CHANGED
@@ -219,12 +219,12 @@ module Dynflow
|
|
219
219
|
publish_request(Dispatcher::Execution[execution_plan_id], done, true)
|
220
220
|
end
|
221
221
|
|
222
|
-
def event(execution_plan_id, step_id, event, done = Concurrent::Promises.resolvable_future)
|
223
|
-
publish_request(Dispatcher::Event[execution_plan_id, step_id, event], done, false)
|
222
|
+
def event(execution_plan_id, step_id, event, done = Concurrent::Promises.resolvable_future, optional: false)
|
223
|
+
publish_request(Dispatcher::Event[execution_plan_id, step_id, event, nil, optional], done, false)
|
224
224
|
end
|
225
225
|
|
226
|
-
def plan_event(execution_plan_id, step_id, event, time, accepted = Concurrent::Promises.resolvable_future)
|
227
|
-
publish_request(Dispatcher::Event[execution_plan_id, step_id, event, time], accepted, false)
|
226
|
+
def plan_event(execution_plan_id, step_id, event, time, accepted = Concurrent::Promises.resolvable_future, optional: false)
|
227
|
+
publish_request(Dispatcher::Event[execution_plan_id, step_id, event, time, optional], accepted, false)
|
228
228
|
end
|
229
229
|
|
230
230
|
def ping(world_id, timeout, done = Concurrent::Promises.resolvable_future)
|
@@ -341,7 +341,7 @@ module Dynflow
|
|
341
341
|
@terminating = Concurrent::Promises.future do
|
342
342
|
termination_future.wait(termination_timeout)
|
343
343
|
end.on_resolution do
|
344
|
-
@terminated.
|
344
|
+
@terminated.resolve
|
345
345
|
Thread.new { Kernel.exit } if @exit_on_terminate.true?
|
346
346
|
end
|
347
347
|
end
|
data/test/dispatcher_test.rb
CHANGED
@@ -49,6 +49,12 @@ module Dynflow
|
|
49
49
|
plan = result.finished.value
|
50
50
|
assert_equal('finish', plan.actions.first.output[:event])
|
51
51
|
end
|
52
|
+
|
53
|
+
it 'does not error on dispatching an optional event' do
|
54
|
+
request = client_world.event('123', 1, nil, optional: true)
|
55
|
+
request.wait(20)
|
56
|
+
assert_match /Could not find an executor for optional .*, discarding/, request.reason.message
|
57
|
+
end
|
52
58
|
end
|
53
59
|
end
|
54
60
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.4.
|
4
|
+
version: 1.4.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Necas
|
8
8
|
- Petr Chalupa
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2021-08-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|
@@ -625,7 +625,7 @@ homepage: https://github.com/Dynflow/dynflow
|
|
625
625
|
licenses:
|
626
626
|
- MIT
|
627
627
|
metadata: {}
|
628
|
-
post_install_message:
|
628
|
+
post_install_message:
|
629
629
|
rdoc_options: []
|
630
630
|
require_paths:
|
631
631
|
- lib
|
@@ -640,8 +640,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
640
640
|
- !ruby/object:Gem::Version
|
641
641
|
version: '0'
|
642
642
|
requirements: []
|
643
|
-
rubygems_version: 3.
|
644
|
-
signing_key:
|
643
|
+
rubygems_version: 3.1.2
|
644
|
+
signing_key:
|
645
645
|
specification_version: 4
|
646
646
|
summary: DYNamic workFLOW engine
|
647
647
|
test_files:
|