Skip to content

Commit 4c66c1c

Browse files
authored
perf(calibration): skip enablement re-check when input places disjoint (audit #5) (#75)
1 parent e328dbe commit 4c66c1c

2 files changed

Lines changed: 79 additions & 3 deletions

File tree

lib/coloured_flow/runner/enactment/workitem_calibration.ex

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,16 @@ defmodule ColouredFlow.Runner.Enactment.WorkitemCalibration do
149149
defp withdraw_workitems(%Enactment{} = state, to_consume_markings) do
150150
place_tokens = Map.new(state.markings, fn {place, marking} -> {place, marking.tokens} end)
151151
place_tokens = consume_markings(to_consume_markings, place_tokens)
152+
consumed_places = MapSet.new(to_consume_markings, & &1.place)
152153

153154
{workitems, to_withdraw} =
154155
Map.split_with(state.workitems, fn
155-
# TODO: only workitems that their input places share with the to_consume_markings should be re-check enabled
156-
# only enabled workitems should be re-check enabled
156+
# only enabled workitems should be re-checked for enablement, and only
157+
# those whose input places overlap with the consumed places — others
158+
# cannot have changed enablement since their tokens are untouched.
157159
{_workitem_id, %Workitem{state: :enabled} = workitem} ->
158-
binding_element_enabled?(workitem.binding_element, place_tokens)
160+
disjoint_input_places?(workitem.binding_element, consumed_places) or
161+
binding_element_enabled?(workitem.binding_element, place_tokens)
159162

160163
_other ->
161164
true
@@ -164,6 +167,13 @@ defmodule ColouredFlow.Runner.Enactment.WorkitemCalibration do
164167
{%Enactment{state | workitems: workitems}, Map.values(to_withdraw)}
165168
end
166169

170+
@spec disjoint_input_places?(BindingElement.t(), MapSet.t(Place.name())) :: boolean()
171+
defp disjoint_input_places?(%BindingElement{} = binding_element, consumed_places) do
172+
Enum.all?(binding_element.to_consume, fn %Marking{place: place} ->
173+
not MapSet.member?(consumed_places, place)
174+
end)
175+
end
176+
167177
@spec produce_workitems(
168178
enactment_state(),
169179
workitem_occurrences :: [{Workitem.t(:completed), Occurrence.t()}],

test/coloured_flow/runner/enactment/workitem_calibration_test.exs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,72 @@ defmodule ColouredFlow.Runner.Enactment.WorkitemCalibrationTest do
426426
assert expected_state === calibration.state
427427
assert [aj_workitem_2] === calibration.to_withdraw
428428
end
429+
430+
# ```mermaid
431+
# flowchart LR
432+
# %% colset int() :: integer()
433+
# %% place_1 ~MS[1], place_2 ~MS[1]
434+
# p1((place_1))
435+
# p2((place_2))
436+
# o1((output_1))
437+
# o2((output_2))
438+
# pt1[pass_through_1]
439+
# pt2[pass_through_2]
440+
# p1 --{1,x}--> pt1 --> o1
441+
# p2 --{1,x}--> pt2 --> o2
442+
# ```
443+
test "keeps enabled workitems whose input places are disjoint from the started workitem's input places" do
444+
enactment_id = Ecto.UUID.generate()
445+
446+
pt1_workitem = %Enactment.Workitem{
447+
id: Ecto.UUID.generate(),
448+
state: :enabled,
449+
binding_element: %BindingElement{
450+
transition: "pass_through_1",
451+
binding: [x: 1],
452+
to_consume: [%Marking{place: "place_1", tokens: ~MS[1]}]
453+
}
454+
}
455+
456+
pt2_workitem = %Enactment.Workitem{
457+
id: Ecto.UUID.generate(),
458+
state: :enabled,
459+
binding_element: %BindingElement{
460+
transition: "pass_through_2",
461+
binding: [x: 1],
462+
to_consume: [%Marking{place: "place_2", tokens: ~MS[1]}]
463+
}
464+
}
465+
466+
state = %Enactment{
467+
enactment_id: enactment_id,
468+
version: 0,
469+
markings:
470+
to_map([
471+
%Marking{place: "place_1", tokens: ~MS[1]},
472+
%Marking{place: "place_2", tokens: ~MS[1]}
473+
]),
474+
workitems:
475+
to_map([
476+
%Enactment.Workitem{pt1_workitem | state: :started},
477+
pt2_workitem
478+
])
479+
}
480+
481+
expected_state = %Enactment{
482+
state
483+
| workitems:
484+
to_map([
485+
%Enactment.Workitem{pt1_workitem | state: :started},
486+
pt2_workitem
487+
])
488+
}
489+
490+
calibration = WorkitemCalibration.calibrate(state, :start, workitems: [pt1_workitem])
491+
492+
assert expected_state === calibration.state
493+
assert [] === calibration.to_withdraw
494+
end
429495
end
430496

431497
describe "calibrate after completed" do

0 commit comments

Comments
 (0)