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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 531c1207ec6665f5773b2e71887e79d0259c4edd068d976f655766b5646ca9b0
4
- data.tar.gz: ae88bf9434ebc2b7672751e24b95a60d1c4a38c1ff9396e68342c0529b68022c
3
+ metadata.gz: 4f677a2e5d7f119264258b7b36fe187203db60b8b4df4136beb73954857d59bd
4
+ data.tar.gz: a7cd75185cc99ce4a1e3e18e07565542ebf24bec8109a3291e78c56345bbc0db
5
5
  SHA512:
6
- metadata.gz: 3f2f5f4ccdc36b9bc0439fc0a864412126877f4da6f01eaa00b6bcadf6af05d0467d1eb1e6fde002ee35d962ea04f3c46df0e369b32e17c56fca9e26450ab1be
7
- data.tar.gz: ac3c24d8f31492c3d053877a03162e54c0c2f2b7ebe3673cb4cb6134f2cc0ea83be6f54ea4251d4f801e2dacc4f90c6cfe16b2ffd818e0f46a573da2fe054189
6
+ metadata.gz: c00baa2c020df6b035aa5a45aaa1b58a08ccc310752f4de0ab54da0207a03439cfde09132a7e8a6f4fe7911058a027e87f5cdb7b2563da035725ba5b482d0ab4
7
+ data.tar.gz: 4189a3f752642000d4eda05a8d82013d350eb77f7325450dfa01e774672aa33c61f3b32a1c8d5eb75e030d2fae1cb42a1e92da3278278569ba250245b06c3aec
@@ -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
- # If this save returns an integer, it means it was an update. The number
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)
@@ -7,8 +7,8 @@ module Dynflow
7
7
  fail("Timeout exceeded.")
8
8
  end
9
9
 
10
- def schedule_timeout(seconds)
11
- plan_event(Timeout, seconds)
10
+ def schedule_timeout(seconds, optional: false)
11
+ plan_event(Timeout, seconds, optional: optional)
12
12
  end
13
13
  end
14
14
  end
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
- Actor::BacktraceCollector.with_backtrace(envelope.origin_backtrace) { super }
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
- .chunk_while { |l1, l2| l1 == l2}
88
- .map(&:first)
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])
@@ -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
- if EventWorkItem === w
24
- w.event.result.reject UnprocessableEvent.new("dropping due to termination")
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
@@ -6,7 +6,8 @@ module Dynflow
6
6
  fields! execution_plan_id: String,
7
7
  step_id: Integer,
8
8
  event: Object,
9
- time: type { variants Time, NilClass }
9
+ time: type { variants Time, NilClass },
10
+ optional: Algebrick::Types::Boolean
10
11
  end
11
12
 
12
13
  Execution = type do
@@ -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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Dynflow
3
- VERSION = '1.4.5'
3
+ VERSION = '1.4.9'
4
4
  end
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.complete
344
+ @terminated.resolve
345
345
  Thread.new { Kernel.exit } if @exit_on_terminate.true?
346
346
  end
347
347
  end
@@ -43,7 +43,7 @@ module Dynflow
43
43
  else
44
44
  :stopped
45
45
  end
46
- plan.update_state(state)
46
+ plan.update_state(state) if plan.state != state
47
47
 
48
48
  coordinator.release(planning_lock)
49
49
  execute(plan.id) if plan.state == :planned
@@ -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.5
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: 2020-06-17 00:00:00.000000000 Z
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.0.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: