From 821f8c932fbef0f642487040f68bd6bfc0efc968 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 27 Feb 2023 15:03:30 +0100 Subject: [PATCH 01/64] Implement resolving auto pads mode --- lib/membrane/core/bin/pad_controller.ex | 1 + lib/membrane/core/child/pad_model.ex | 1 + lib/membrane/core/element.ex | 15 ++ .../core/element/auto_mode_controller.ex | 128 ++++++++++++++++++ .../core/element/lifecycle_controller.ex | 14 +- lib/membrane/core/element/pad_controller.ex | 99 +++++++++++++- lib/membrane/core/element/state.ex | 9 +- lib/membrane/element/pad_data.ex | 4 +- .../core/element/pad_controller_test.exs | 3 +- test/membrane/core/element_test.exs | 9 +- test/membrane/element_test.exs | 10 +- .../elements_compatibility_test.exs | 1 - test/membrane/integration/linking_test.exs | 1 - 13 files changed, 276 insertions(+), 19 deletions(-) create mode 100644 lib/membrane/core/element/auto_mode_controller.ex diff --git a/lib/membrane/core/bin/pad_controller.ex b/lib/membrane/core/bin/pad_controller.ex index 1ec8a9bf5..8d810687b 100644 --- a/lib/membrane/core/bin/pad_controller.ex +++ b/lib/membrane/core/bin/pad_controller.ex @@ -254,6 +254,7 @@ defmodule Membrane.Core.Bin.PadController do child_endpoint, other_endpoint, params + # |> IO.inspect(label: "link_props for child") ]) case handle_link_response do diff --git a/lib/membrane/core/child/pad_model.ex b/lib/membrane/core/child/pad_model.ex index cfda7e50c..79daa7075 100644 --- a/lib/membrane/core/child/pad_model.ex +++ b/lib/membrane/core/child/pad_model.ex @@ -29,6 +29,7 @@ defmodule Membrane.Core.Child.PadModel do end_of_stream?: boolean(), direction: Pad.direction(), flow_control: Pad.flow_control(), + opposite_endpoint_flow_control: :push | :pull | :undefined, name: Pad.name(), ref: Pad.ref(), demand_unit: Membrane.Buffer.Metric.unit() | nil, diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index e648041f5..b4bb97bcb 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -26,6 +26,7 @@ defmodule Membrane.Core.Element do BufferController, DemandController, EventController, + FlowControlUtils, LifecycleController, PadController, State, @@ -215,6 +216,20 @@ defmodule Membrane.Core.Element do {:noreply, state} end + defp do_handle_info( + Message.new(:opposite_endpoint_flow_control, [my_pad_ref, flow_control]), + state + ) do + state = + FlowControlUtils.handle_opposite_endpoint_flow_control( + my_pad_ref, + flow_control, + state + ) + + {:noreply, state} + end + defp do_handle_info(Message.new(:terminate), state) do state = LifecycleController.handle_terminate_request(state) {:noreply, state} diff --git a/lib/membrane/core/element/auto_mode_controller.ex b/lib/membrane/core/element/auto_mode_controller.ex new file mode 100644 index 000000000..59e73500e --- /dev/null +++ b/lib/membrane/core/element/auto_mode_controller.ex @@ -0,0 +1,128 @@ +defmodule Membrane.Core.Element.FlowControlUtils do + @moduledoc false + + alias Membrane.Core.Element.State + + require Membrane.Core.Child.PadModel, as: PadModel + require Membrane.Core.Message, as: Message + require Membrane.Pad, as: Pad + + # algo wiadomosci z wlasnym trybem + + # USTALANIE: + # - trigerrowane na handle_playing lub na przyjscie trybu pada, gdy jestesmy po playing + # - z undefined, gdy jest jakis przodek w pull, to jestem w pull + # - z undefined, gdy wszyscy przodkowie sa w push, to jestem w push + # - z undefined, gdy nie ma nic, to czekam dalej + # - z nie undefined, sprawdzam czy sie zgadza, jak sie cos nie zgadza to sie wywalam (jak bylem w pull, to czilera utopia, jak bylem w push, to nie da ise przejsc juz do pull i raisuje) + + # WYSYLANIE: + # - tylko po output padach + # - gdy jest ustalone, to w handle_link. Gdy na handle_link jest niestualone, to tylko jak zostanie ustalone + + # ODBIERANIE: + # - po input padach + # - w handle_link lub w specjalnej wiadomosci + # - gdy jestesmy przed handle_playing, to tylko zapisz + # - na handle_playing podejmij decyzje + # - gdy dstajesz mode po handle_playing, to jesli nie podjales decyzji to ja podejmij, a jesli ja juz kiedys podjales, to zwaliduj czy sie nie wywalic + + # zatem operacje jakie chce miec w nowym module: + # dodaj pad effective flow control + # handle playing + # hanlde pad added (output) + + # do handle_link dorzuć wynik output_pad_flow_control() + # na wiadomosc :output_pad_flow_control, ..., dostaną na auto pada wywolaj handle_opposite_endpoint_flow_control() + # na dodanie input auto pada wywolaj handle_input_auto_pad_added() + # na handle_playing wywolaj resolve...() + + @spec pad_effective_flow_control(Pad.ref(), State.t()) :: :push | :pull | :undefined + def pad_effective_flow_control(pad_ref, state) do + pad_name = Pad.name_by_ref(pad_ref) + + state.pads_info + |> get_in([pad_name, :flow_control]) + |> case do + :manual -> :pull + :push -> :push + :auto -> state.auto_pads_flow_control + end + end + + @spec handle_input_pad_added(Pad.ref(), State.t()) :: State.t() + def handle_input_pad_added(pad_ref, state) do + with %{pads_data: %{^pad_ref => %{flow_control: :auto} = pad_data}} <- state do + handle_opposite_endpoint_flow_control( + pad_ref, + pad_data.opposite_endpoint_flow_control, + state + ) + end + end + + @spec handle_opposite_endpoint_flow_control( + Pad.ref(), + :push | :pull | :undefined, + State.t() + ) :: + State.t() + def handle_opposite_endpoint_flow_control( + my_pad_ref, + opposite_endpoint_flow_control, + state + ) do + pad_data = PadModel.get_data!(state, my_pad_ref) + + if pad_data.direction != :input do + raise "this function should be called only for input pads" + end + + pad_data = %{pad_data | opposite_endpoint_flow_control: opposite_endpoint_flow_control} + state = PadModel.set_data!(state, my_pad_ref, pad_data) + + cond do + pad_data.flow_control != :auto or opposite_endpoint_flow_control == :undefined or + state.playback == :stopped -> + state + + state.auto_pads_flow_control == :undefined -> + resolve_auto_pads_flow_control(state) + + state.auto_pads_flow_control == :push and opposite_endpoint_flow_control == :pull -> + raise "dupa dupa 123" + + state.auto_pads_flow_control == :pull or opposite_endpoint_flow_control == :push -> + state + end + end + + @spec resolve_auto_pads_flow_control(State.t()) :: State.t() + def resolve_auto_pads_flow_control(state) do + input_auto_pads = + Map.values(state.pads_data) + |> Enum.filter(&(&1.direction == :input && &1.flow_control == :auto)) + + auto_pads_flow_control = + input_auto_pads + |> Enum.group_by(& &1.opposite_endpoint_flow_control) + |> case do + %{pull: _pads} -> :pull + %{push: _pads} -> :push + %{} -> :undefined + end + + if auto_pads_flow_control != :undefined do + Enum.each(state.pads_data, fn {_pad_ref, pad_data} -> + with %{direction: :output, flow_control: :auto} <- pad_data do + Message.send(pad_data.pid, :opposite_endpoint_flow_control, [ + pad_data.other_ref, + auto_pads_flow_control + ]) + end + end) + end + + %{state | auto_pads_flow_control: auto_pads_flow_control} + end +end diff --git a/lib/membrane/core/element/lifecycle_controller.ex b/lib/membrane/core/element/lifecycle_controller.ex index c96a82977..b218bd8dc 100644 --- a/lib/membrane/core/element/lifecycle_controller.ex +++ b/lib/membrane/core/element/lifecycle_controller.ex @@ -8,7 +8,14 @@ defmodule Membrane.Core.Element.LifecycleController do alias Membrane.{Clock, Element, Sync} alias Membrane.Core.{CallbackHandler, Child, Element, Message} - alias Membrane.Core.Element.{ActionHandler, CallbackContext, PlaybackQueue, State} + + alias Membrane.Core.Element.{ + ActionHandler, + CallbackContext, + FlowControlUtils, + PlaybackQueue, + State + } require Membrane.Core.Child.PadModel require Membrane.Core.Message @@ -69,7 +76,10 @@ defmodule Membrane.Core.Element.LifecycleController do Child.PadController.assert_all_static_pads_linked!(state) Membrane.Logger.debug("Got play request") - state = %State{state | playback: :playing} + + state = + %State{state | playback: :playing} + |> FlowControlUtils.resolve_auto_pads_flow_control() state = CallbackHandler.exec_and_handle_callback( diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index d4066a4c6..b0ef6d154 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -13,6 +13,7 @@ defmodule Membrane.Core.Element.PadController do CallbackContext, DemandController, EventController, + FlowControlUtils, InputQueue, State, StreamFormatController, @@ -86,6 +87,8 @@ defmodule Membrane.Core.Element.PadController do %{initiator: :parent} = props, state ) do + flow_control = FlowControlUtils.pad_effective_flow_control(endpoint.pad_ref, state) + handle_link_response = Message.call(other_endpoint.pid, :handle_link, [ Pad.opposite_direction(info.direction), @@ -97,8 +100,10 @@ defmodule Membrane.Core.Element.PadController do link_metadata: %{ observability_metadata: Observability.setup_link(endpoint.pad_ref) }, - stream_format_validation_params: [] + stream_format_validation_params: [], + opposite_endpoint_flow_control: flow_control } + # |> IO.inspect(label: "link_props to sibling") ]) case handle_link_response do @@ -115,6 +120,7 @@ defmodule Membrane.Core.Element.PadController do other_endpoint, info, props.stream_format_validation_params, + :undefined, other_info, link_metadata, state @@ -146,12 +152,97 @@ defmodule Membrane.Core.Element.PadController do %{initiator: :sibling} = link_props, state ) do + # dupa: check if auto_push?: true and sibling is in pull mode + # jak jestes filtrem: + + # Jak przychodzi input pad + # if auto_push? do + # jezeli przychodzi link w pushu, to czilera utopia + # jezeli przychodzi link w pullu, to wyslij wiadomosc zeby zamienil w auto_push. sourcy powinny raisowac na takie cos + + # if not auto_push? do + # jezeli przychodzi link w pullu, to nic nie rob + # jezeli przychodzi w pushu, a są juz inne w pullu, to nic nie rob i ustaw toilet + # jezeli przychodzi w pushu, i jest jedynym linkiem aktualnie, to zamien sie w auto_push i wyslij wiadomosc na output pady + + # Jak przychodzi output pad + # if auto_push, to wysylasz wiadomosc ze jestes pushem + # jak nie, to nic nie robisz + + # raisowanie powinno odbywac sie w handle_playing, lub podczas linkowania po handle_playing + # istnieją filtry w autodemandach, ktore mają flage true, i takie ktore nie + # co zrobic zeby dostac flage w true: miec wszystkie pady inputy w pushu + + # powinno byc tak: + # wczesniej mi sie mieszalo przez te maszynke xd + # teraz robimy wersje konserwatywną: tzn zamieniamy tylko fitry autodemandowe + # raisujemy na probe dolaczenia pull sourcea do auto pusha: potem moze doimpementujemy w 2 strone + # na poczatku stawiamy rzeczy i one se dzialaja w autodemandach + # source pushowy / pullowy wysyla na dzien dobry padem info jaki ma typ pada + # filter ktory ma za sobą jakis source pullowy, wysyla info ze jest pullowy + # filter ktory ma za sobą wszystkie source pushowe, wysyla info ze jest pushowy + + # na dzien dobry filter w auto dziala na autodemandach + # jak zapnie mu sie source, to mu wysyla info jakiego jest typu + # jak wszystkie source są pullowe, to nic sie nie zmienia + # raisowac mamy, jak source pullowy dopnie sie do auto pusha + + # ogolnie auto element moze byc w 3 stanach: + # nie wie jeszcze jaki source jest przed nim -> auto demand A + # wie ze ma przed sobą pull source -> auto demand B + # wue ze ma przed sobą same pushowe source -> auto push C + # narazie sie wywalajmy przy probie przejscia z auto-push w auto-demand (jakiejkolwiek) + # mozna przejsc ze stany A do B lub z A do C + # pad sourcea jest zawsze w stanie B lub C + # jak filter jest w B lub C, to jest output pady same sie takie staja + # jezeli masz jakiegos przodka w B, to sam jestes w B + # jezeli nie masz nikogo w B a masz kogos w C, to sam jestes w C + # jak wchodzisz do B albo C, albo juz jestes ale dodaje ci sie nowy output pad, to wysylasz nim info ze jestes B lub C + # jak przechodzisz z A do C to masz w dupie demandy + # jak przechodzisz z A do B, to ignorujesz potem info o tym ze ktos dolaczyl a jest w C + + # mozna zrobic pole auto_mode: :push / :pull / :undefined + + # czyli tak: + # jezeli wiemy jaki jest mode pada, to wysylamy to w handle_link, w przeciwnym razie jak sie dowiemy + # manual/push output pad jest zawsze w pull/push mode + # jak mamy auto pada, to dziedziczy po typie elementu, jezeli nie jest on :undefined + # jak jest, to jak element wyjdzie z :undefined, to wtedy wysyla poprawną wiadomosc na output pady + + # algo wiadomosci z wlasnym trybem + + # USTALANIE: + # - trigerrowane na handle_playing lub na przyjscie trybu pada, gdy jestesmy po playing + # - z undefined, gdy jest jakis przodek w pull, to jestem w pull + # - z undefined, gdy wszyscy przodkowie sa w push, to jestem w push + # - z undefined, gdy nie ma nic, to czekam dalej + # - z nie undefined, sprawdzam czy sie zgadza, jak sie cos nie zgadza to sie wywalam (jak bylem w pull, to czilera utopia, jak bylem w push, to nie da ise przejsc juz do pull i raisuje) + + # WYSYLANIE: + # - tylko po output padach + # - gdy jest ustalone, to w handle_link. Gdy na handle_link jest niestualone, to tylko jak zostanie ustalone + + # ODBIERANIE: + # - po input padach + # - w handle_link lub w specjalnej wiadomosci + # - gdy jestesmy przed handle_playing, to tylko zapisz + # - na handle_playing podejmij decyzje + # - gdy dstajesz mode po handle_playing, to jesli nie podjales decyzji to ja podejmij, a jesli ja juz kiedys podjales, to zwaliduj czy sie nie wywalic + + # zatem operacje jakie chce miec w nowym module: + # dodaj pad effective flow control + # handle playing + # hanlde pad added (output) + %{ other_info: other_info, link_metadata: link_metadata, - stream_format_validation_params: stream_format_validation_params + stream_format_validation_params: stream_format_validation_params, + opposite_endpoint_flow_control: opposite_endpoint_flow_control } = link_props + # |> IO.inspect(label: "link_props from sibling") + {output_info, input_info, input_endpoint} = if info.direction == :output, do: {info, other_info, other_endpoint}, @@ -188,11 +279,13 @@ defmodule Membrane.Core.Element.PadController do other_endpoint, info, stream_format_validation_params, + opposite_endpoint_flow_control, other_info, link_metadata, state ) + state = FlowControlUtils.handle_input_pad_added(endpoint.pad_ref, state) state = maybe_handle_pad_added(endpoint.pad_ref, state) {{:ok, {endpoint, info, link_metadata}}, state} end @@ -254,6 +347,7 @@ defmodule Membrane.Core.Element.PadController do other_endpoint, info, stream_format_validation_params, + opposite_endpoint_flow_control, other_info, metadata, state @@ -268,6 +362,7 @@ defmodule Membrane.Core.Element.PadController do Child.PadController.parse_pad_options!(info.name, endpoint.pad_props.options, state), ref: endpoint.pad_ref, stream_format_validation_params: stream_format_validation_params, + opposite_endpoint_flow_control: opposite_endpoint_flow_control, stream_format: nil, start_of_stream?: false, end_of_stream?: false, diff --git a/lib/membrane/core/element/state.ex b/lib/membrane/core/element/state.ex index 351efc193..4f2eda3b2 100644 --- a/lib/membrane/core/element/state.ex +++ b/lib/membrane/core/element/state.ex @@ -36,7 +36,8 @@ defmodule Membrane.Core.Element.State do resource_guard: Membrane.ResourceGuard.t(), subprocess_supervisor: pid, terminating?: boolean(), - setup_incomplete?: boolean() + setup_incomplete?: boolean(), + auto_pads_flow_control: :undefined | :push | :pull } defstruct [ @@ -57,7 +58,8 @@ defmodule Membrane.Core.Element.State do :resource_guard, :subprocess_supervisor, :terminating?, - :setup_incomplete? + :setup_incomplete?, + :auto_pads_flow_control ] @doc """ @@ -94,7 +96,8 @@ defmodule Membrane.Core.Element.State do resource_guard: options.resource_guard, subprocess_supervisor: options.subprocess_supervisor, terminating?: false, - setup_incomplete?: false + setup_incomplete?: false, + auto_pads_flow_control: :undefined } |> PadSpecHandler.init_pads() end diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index c3fc6b72d..85e7831ea 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -31,6 +31,7 @@ defmodule Membrane.Element.PadData do end_of_stream?: boolean(), direction: Pad.direction(), flow_control: Pad.flow_control(), + opposite_endpoint_flow_control: :push | :pull | :undefined, name: Pad.name(), ref: Pad.ref(), options: %{optional(atom) => any}, @@ -73,6 +74,7 @@ defmodule Membrane.Element.PadData do associated_pads: [], sticky_events: [], stream_format_validation_params: [], - other_demand_unit: nil + other_demand_unit: nil, + opposite_endpoint_flow_control: :undefined ] end diff --git a/test/membrane/core/element/pad_controller_test.exs b/test/membrane/core/element/pad_controller_test.exs index 4df843206..2106e2b47 100644 --- a/test/membrane/core/element/pad_controller_test.exs +++ b/test/membrane/core/element/pad_controller_test.exs @@ -42,7 +42,8 @@ defmodule Membrane.Core.Element.PadControllerTest do initiator: :sibling, other_info: %{direction: :input, flow_control: :manual, demand_unit: :buffers}, link_metadata: %{toilet: make_ref(), observability_metadata: %{}}, - stream_format_validation_params: [] + stream_format_validation_params: [], + opposite_endpoint_flow_control: :pull }, state ) diff --git a/test/membrane/core/element_test.exs b/test/membrane/core/element_test.exs index a4ef0ad7f..df9306771 100644 --- a/test/membrane/core/element_test.exs +++ b/test/membrane/core/element_test.exs @@ -82,7 +82,8 @@ defmodule Membrane.Core.ElementTest do initiator: :sibling, other_info: %{direction: :input, flow_control: :manual, demand_unit: :buffers}, link_metadata: %{toilet: nil, observability_metadata: %{}}, - stream_format_validation_params: [] + stream_format_validation_params: [], + opposite_endpoint_flow_control: :pull } ]), nil, @@ -112,7 +113,8 @@ defmodule Membrane.Core.ElementTest do initiator: :sibling, other_info: %{direction: :output, flow_control: :manual}, link_metadata: %{toilet: nil, observability_metadata: %{}}, - stream_format_validation_params: [] + stream_format_validation_params: [], + opposite_endpoint_flow_control: :pull } ]), nil, @@ -227,7 +229,8 @@ defmodule Membrane.Core.ElementTest do flow_control: :manual }, link_metadata: %{observability_metadata: %{}}, - stream_format_validation_params: [] + stream_format_validation_params: [], + opposite_endpoint_flow_control: :pull } ]), nil, diff --git a/test/membrane/element_test.exs b/test/membrane/element_test.exs index 2930e05fa..cfa5fc620 100644 --- a/test/membrane/element_test.exs +++ b/test/membrane/element_test.exs @@ -75,16 +75,16 @@ defmodule Membrane.ElementTest do [pipeline: pipeline] end - test "play", %{pipeline: pipeline} do + test "play", %{pipeline: _pipeline} do TestFilter.assert_callback_called(:handle_playing) end describe "Start of stream" do - test "causes handle_start_of_stream/3 to be called", %{pipeline: pipeline} do + test "causes handle_start_of_stream/3 to be called", %{pipeline: _pipeline} do TestFilter.assert_callback_called(:handle_start_of_stream) end - test "does not trigger calling callback handle_event/3", %{pipeline: pipeline} do + test "does not trigger calling callback handle_event/3", %{pipeline: _pipeline} do TestFilter.refute_callback_called(:handle_event) end @@ -94,11 +94,11 @@ defmodule Membrane.ElementTest do end describe "End of stream" do - test "causes handle_end_of_stream/3 to be called", %{pipeline: pipeline} do + test "causes handle_end_of_stream/3 to be called", %{pipeline: _pipeline} do TestFilter.assert_callback_called(:handle_end_of_stream) end - test "does not trigger calling callback handle_event/3", %{pipeline: pipeline} do + test "does not trigger calling callback handle_event/3", %{pipeline: _pipeline} do TestFilter.refute_callback_called(:handle_event) end diff --git a/test/membrane/integration/elements_compatibility_test.exs b/test/membrane/integration/elements_compatibility_test.exs index 4105210cf..320a1114c 100644 --- a/test/membrane/integration/elements_compatibility_test.exs +++ b/test/membrane/integration/elements_compatibility_test.exs @@ -2,7 +2,6 @@ defmodule Membrane.Integration.ElementsCompatibilityTest do use ExUnit.Case, async: true import Membrane.ChildrenSpec - import Membrane.Testing.Assertions alias Membrane.Testing.Pipeline defmodule StreamFormat do diff --git a/test/membrane/integration/linking_test.exs b/test/membrane/integration/linking_test.exs index 9aee46770..b5a0c23fb 100644 --- a/test/membrane/integration/linking_test.exs +++ b/test/membrane/integration/linking_test.exs @@ -373,7 +373,6 @@ defmodule Membrane.Integration.LinkingTest do Membrane.Pipeline.terminate(pipeline) end - @tag :dupa test "Bin should crash if it doesn't link internally within timeout" do defmodule NoInternalLinkBin do use Membrane.Bin From 328837b145b1e94ee7689fb9660c4fb72de78803 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 27 Feb 2023 16:24:22 +0100 Subject: [PATCH 02/64] Refactor effective flow control related code --- lib/membrane/core/child/pad_model.ex | 2 +- lib/membrane/core/element.ex | 6 +- .../core/element/auto_mode_controller.ex | 78 ++++----------- .../core/element/lifecycle_controller.ex | 2 +- lib/membrane/core/element/pad_controller.ex | 94 +------------------ lib/membrane/core/element/state.ex | 6 +- lib/membrane/element/pad_data.ex | 4 +- lib/membrane/pad.ex | 2 + .../core/element/pad_controller_test.exs | 2 +- test/membrane/core/element_test.exs | 6 +- 10 files changed, 41 insertions(+), 161 deletions(-) diff --git a/lib/membrane/core/child/pad_model.ex b/lib/membrane/core/child/pad_model.ex index 79daa7075..b38591a8b 100644 --- a/lib/membrane/core/child/pad_model.ex +++ b/lib/membrane/core/child/pad_model.ex @@ -29,7 +29,7 @@ defmodule Membrane.Core.Child.PadModel do end_of_stream?: boolean(), direction: Pad.direction(), flow_control: Pad.flow_control(), - opposite_endpoint_flow_control: :push | :pull | :undefined, + other_effective_flow_control: Pad.effective_flow_control(), name: Pad.name(), ref: Pad.ref(), demand_unit: Membrane.Buffer.Metric.unit() | nil, diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index b4bb97bcb..bce809d63 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -217,13 +217,13 @@ defmodule Membrane.Core.Element do end defp do_handle_info( - Message.new(:opposite_endpoint_flow_control, [my_pad_ref, flow_control]), + Message.new(:other_effective_flow_control, [my_pad_ref, effective_flow_control]), state ) do state = - FlowControlUtils.handle_opposite_endpoint_flow_control( + FlowControlUtils.handle_other_effective_flow_control( my_pad_ref, - flow_control, + effective_flow_control, state ) diff --git a/lib/membrane/core/element/auto_mode_controller.ex b/lib/membrane/core/element/auto_mode_controller.ex index 59e73500e..5d89828b0 100644 --- a/lib/membrane/core/element/auto_mode_controller.ex +++ b/lib/membrane/core/element/auto_mode_controller.ex @@ -7,37 +7,7 @@ defmodule Membrane.Core.Element.FlowControlUtils do require Membrane.Core.Message, as: Message require Membrane.Pad, as: Pad - # algo wiadomosci z wlasnym trybem - - # USTALANIE: - # - trigerrowane na handle_playing lub na przyjscie trybu pada, gdy jestesmy po playing - # - z undefined, gdy jest jakis przodek w pull, to jestem w pull - # - z undefined, gdy wszyscy przodkowie sa w push, to jestem w push - # - z undefined, gdy nie ma nic, to czekam dalej - # - z nie undefined, sprawdzam czy sie zgadza, jak sie cos nie zgadza to sie wywalam (jak bylem w pull, to czilera utopia, jak bylem w push, to nie da ise przejsc juz do pull i raisuje) - - # WYSYLANIE: - # - tylko po output padach - # - gdy jest ustalone, to w handle_link. Gdy na handle_link jest niestualone, to tylko jak zostanie ustalone - - # ODBIERANIE: - # - po input padach - # - w handle_link lub w specjalnej wiadomosci - # - gdy jestesmy przed handle_playing, to tylko zapisz - # - na handle_playing podejmij decyzje - # - gdy dstajesz mode po handle_playing, to jesli nie podjales decyzji to ja podejmij, a jesli ja juz kiedys podjales, to zwaliduj czy sie nie wywalic - - # zatem operacje jakie chce miec w nowym module: - # dodaj pad effective flow control - # handle playing - # hanlde pad added (output) - - # do handle_link dorzuć wynik output_pad_flow_control() - # na wiadomosc :output_pad_flow_control, ..., dostaną na auto pada wywolaj handle_opposite_endpoint_flow_control() - # na dodanie input auto pada wywolaj handle_input_auto_pad_added() - # na handle_playing wywolaj resolve...() - - @spec pad_effective_flow_control(Pad.ref(), State.t()) :: :push | :pull | :undefined + @spec pad_effective_flow_control(Pad.ref(), State.t()) :: Pad.effective_flow_control() def pad_effective_flow_control(pad_ref, state) do pad_name = Pad.name_by_ref(pad_ref) @@ -46,83 +16,75 @@ defmodule Membrane.Core.Element.FlowControlUtils do |> case do :manual -> :pull :push -> :push - :auto -> state.auto_pads_flow_control + :auto -> state.effective_flow_control end end @spec handle_input_pad_added(Pad.ref(), State.t()) :: State.t() def handle_input_pad_added(pad_ref, state) do with %{pads_data: %{^pad_ref => %{flow_control: :auto} = pad_data}} <- state do - handle_opposite_endpoint_flow_control( + handle_other_effective_flow_control( pad_ref, - pad_data.opposite_endpoint_flow_control, + pad_data.other_effective_flow_control, state ) end end - @spec handle_opposite_endpoint_flow_control( - Pad.ref(), - :push | :pull | :undefined, + @spec handle_other_effective_flow_control(Pad.ref(), Pad.effective_flow_control(), State.t()) :: State.t() - ) :: - State.t() - def handle_opposite_endpoint_flow_control( - my_pad_ref, - opposite_endpoint_flow_control, - state - ) do + def handle_other_effective_flow_control(my_pad_ref, other_effective_flow_control, state) do pad_data = PadModel.get_data!(state, my_pad_ref) if pad_data.direction != :input do raise "this function should be called only for input pads" end - pad_data = %{pad_data | opposite_endpoint_flow_control: opposite_endpoint_flow_control} + pad_data = %{pad_data | other_effective_flow_control: other_effective_flow_control} state = PadModel.set_data!(state, my_pad_ref, pad_data) cond do - pad_data.flow_control != :auto or opposite_endpoint_flow_control == :undefined or + pad_data.flow_control != :auto or other_effective_flow_control == :undefined or state.playback == :stopped -> state - state.auto_pads_flow_control == :undefined -> - resolve_auto_pads_flow_control(state) + state.effective_flow_control == :undefined -> + resolve_effective_flow_control(state) - state.auto_pads_flow_control == :push and opposite_endpoint_flow_control == :pull -> + state.effective_flow_control == :push and other_effective_flow_control == :pull -> raise "dupa dupa 123" - state.auto_pads_flow_control == :pull or opposite_endpoint_flow_control == :push -> + state.effective_flow_control == :pull or other_effective_flow_control == :push -> state end end - @spec resolve_auto_pads_flow_control(State.t()) :: State.t() - def resolve_auto_pads_flow_control(state) do + @spec resolve_effective_flow_control(State.t()) :: State.t() + def resolve_effective_flow_control(state) do input_auto_pads = Map.values(state.pads_data) |> Enum.filter(&(&1.direction == :input && &1.flow_control == :auto)) - auto_pads_flow_control = + effective_flow_control = input_auto_pads - |> Enum.group_by(& &1.opposite_endpoint_flow_control) + |> Enum.group_by(& &1.other_effective_flow_control) |> case do %{pull: _pads} -> :pull %{push: _pads} -> :push %{} -> :undefined end - if auto_pads_flow_control != :undefined do + if effective_flow_control != :undefined do Enum.each(state.pads_data, fn {_pad_ref, pad_data} -> with %{direction: :output, flow_control: :auto} <- pad_data do - Message.send(pad_data.pid, :opposite_endpoint_flow_control, [ + Message.send(pad_data.pid, :other_effective_flow_control, [ pad_data.other_ref, - auto_pads_flow_control + effective_flow_control ]) end end) end - %{state | auto_pads_flow_control: auto_pads_flow_control} + %{state | effective_flow_control: effective_flow_control} end end diff --git a/lib/membrane/core/element/lifecycle_controller.ex b/lib/membrane/core/element/lifecycle_controller.ex index b218bd8dc..e7469b34d 100644 --- a/lib/membrane/core/element/lifecycle_controller.ex +++ b/lib/membrane/core/element/lifecycle_controller.ex @@ -79,7 +79,7 @@ defmodule Membrane.Core.Element.LifecycleController do state = %State{state | playback: :playing} - |> FlowControlUtils.resolve_auto_pads_flow_control() + |> FlowControlUtils.resolve_effective_flow_control() state = CallbackHandler.exec_and_handle_callback( diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index b0ef6d154..35b9d69a2 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -101,7 +101,7 @@ defmodule Membrane.Core.Element.PadController do observability_metadata: Observability.setup_link(endpoint.pad_ref) }, stream_format_validation_params: [], - opposite_endpoint_flow_control: flow_control + other_effective_flow_control: flow_control } # |> IO.inspect(label: "link_props to sibling") ]) @@ -152,97 +152,13 @@ defmodule Membrane.Core.Element.PadController do %{initiator: :sibling} = link_props, state ) do - # dupa: check if auto_push?: true and sibling is in pull mode - # jak jestes filtrem: - - # Jak przychodzi input pad - # if auto_push? do - # jezeli przychodzi link w pushu, to czilera utopia - # jezeli przychodzi link w pullu, to wyslij wiadomosc zeby zamienil w auto_push. sourcy powinny raisowac na takie cos - - # if not auto_push? do - # jezeli przychodzi link w pullu, to nic nie rob - # jezeli przychodzi w pushu, a są juz inne w pullu, to nic nie rob i ustaw toilet - # jezeli przychodzi w pushu, i jest jedynym linkiem aktualnie, to zamien sie w auto_push i wyslij wiadomosc na output pady - - # Jak przychodzi output pad - # if auto_push, to wysylasz wiadomosc ze jestes pushem - # jak nie, to nic nie robisz - - # raisowanie powinno odbywac sie w handle_playing, lub podczas linkowania po handle_playing - # istnieją filtry w autodemandach, ktore mają flage true, i takie ktore nie - # co zrobic zeby dostac flage w true: miec wszystkie pady inputy w pushu - - # powinno byc tak: - # wczesniej mi sie mieszalo przez te maszynke xd - # teraz robimy wersje konserwatywną: tzn zamieniamy tylko fitry autodemandowe - # raisujemy na probe dolaczenia pull sourcea do auto pusha: potem moze doimpementujemy w 2 strone - # na poczatku stawiamy rzeczy i one se dzialaja w autodemandach - # source pushowy / pullowy wysyla na dzien dobry padem info jaki ma typ pada - # filter ktory ma za sobą jakis source pullowy, wysyla info ze jest pullowy - # filter ktory ma za sobą wszystkie source pushowe, wysyla info ze jest pushowy - - # na dzien dobry filter w auto dziala na autodemandach - # jak zapnie mu sie source, to mu wysyla info jakiego jest typu - # jak wszystkie source są pullowe, to nic sie nie zmienia - # raisowac mamy, jak source pullowy dopnie sie do auto pusha - - # ogolnie auto element moze byc w 3 stanach: - # nie wie jeszcze jaki source jest przed nim -> auto demand A - # wie ze ma przed sobą pull source -> auto demand B - # wue ze ma przed sobą same pushowe source -> auto push C - # narazie sie wywalajmy przy probie przejscia z auto-push w auto-demand (jakiejkolwiek) - # mozna przejsc ze stany A do B lub z A do C - # pad sourcea jest zawsze w stanie B lub C - # jak filter jest w B lub C, to jest output pady same sie takie staja - # jezeli masz jakiegos przodka w B, to sam jestes w B - # jezeli nie masz nikogo w B a masz kogos w C, to sam jestes w C - # jak wchodzisz do B albo C, albo juz jestes ale dodaje ci sie nowy output pad, to wysylasz nim info ze jestes B lub C - # jak przechodzisz z A do C to masz w dupie demandy - # jak przechodzisz z A do B, to ignorujesz potem info o tym ze ktos dolaczyl a jest w C - - # mozna zrobic pole auto_mode: :push / :pull / :undefined - - # czyli tak: - # jezeli wiemy jaki jest mode pada, to wysylamy to w handle_link, w przeciwnym razie jak sie dowiemy - # manual/push output pad jest zawsze w pull/push mode - # jak mamy auto pada, to dziedziczy po typie elementu, jezeli nie jest on :undefined - # jak jest, to jak element wyjdzie z :undefined, to wtedy wysyla poprawną wiadomosc na output pady - - # algo wiadomosci z wlasnym trybem - - # USTALANIE: - # - trigerrowane na handle_playing lub na przyjscie trybu pada, gdy jestesmy po playing - # - z undefined, gdy jest jakis przodek w pull, to jestem w pull - # - z undefined, gdy wszyscy przodkowie sa w push, to jestem w push - # - z undefined, gdy nie ma nic, to czekam dalej - # - z nie undefined, sprawdzam czy sie zgadza, jak sie cos nie zgadza to sie wywalam (jak bylem w pull, to czilera utopia, jak bylem w push, to nie da ise przejsc juz do pull i raisuje) - - # WYSYLANIE: - # - tylko po output padach - # - gdy jest ustalone, to w handle_link. Gdy na handle_link jest niestualone, to tylko jak zostanie ustalone - - # ODBIERANIE: - # - po input padach - # - w handle_link lub w specjalnej wiadomosci - # - gdy jestesmy przed handle_playing, to tylko zapisz - # - na handle_playing podejmij decyzje - # - gdy dstajesz mode po handle_playing, to jesli nie podjales decyzji to ja podejmij, a jesli ja juz kiedys podjales, to zwaliduj czy sie nie wywalic - - # zatem operacje jakie chce miec w nowym module: - # dodaj pad effective flow control - # handle playing - # hanlde pad added (output) - %{ other_info: other_info, link_metadata: link_metadata, stream_format_validation_params: stream_format_validation_params, - opposite_endpoint_flow_control: opposite_endpoint_flow_control + other_effective_flow_control: other_effective_flow_control } = link_props - # |> IO.inspect(label: "link_props from sibling") - {output_info, input_info, input_endpoint} = if info.direction == :output, do: {info, other_info, other_endpoint}, @@ -279,7 +195,7 @@ defmodule Membrane.Core.Element.PadController do other_endpoint, info, stream_format_validation_params, - opposite_endpoint_flow_control, + other_effective_flow_control, other_info, link_metadata, state @@ -347,7 +263,7 @@ defmodule Membrane.Core.Element.PadController do other_endpoint, info, stream_format_validation_params, - opposite_endpoint_flow_control, + other_effective_flow_control, other_info, metadata, state @@ -362,7 +278,7 @@ defmodule Membrane.Core.Element.PadController do Child.PadController.parse_pad_options!(info.name, endpoint.pad_props.options, state), ref: endpoint.pad_ref, stream_format_validation_params: stream_format_validation_params, - opposite_endpoint_flow_control: opposite_endpoint_flow_control, + other_effective_flow_control: other_effective_flow_control, stream_format: nil, start_of_stream?: false, end_of_stream?: false, diff --git a/lib/membrane/core/element/state.ex b/lib/membrane/core/element/state.ex index 4f2eda3b2..cfd794a5f 100644 --- a/lib/membrane/core/element/state.ex +++ b/lib/membrane/core/element/state.ex @@ -37,7 +37,7 @@ defmodule Membrane.Core.Element.State do subprocess_supervisor: pid, terminating?: boolean(), setup_incomplete?: boolean(), - auto_pads_flow_control: :undefined | :push | :pull + effective_flow_control: Pad.effective_flow_control() } defstruct [ @@ -59,7 +59,7 @@ defmodule Membrane.Core.Element.State do :subprocess_supervisor, :terminating?, :setup_incomplete?, - :auto_pads_flow_control + :effective_flow_control ] @doc """ @@ -97,7 +97,7 @@ defmodule Membrane.Core.Element.State do subprocess_supervisor: options.subprocess_supervisor, terminating?: false, setup_incomplete?: false, - auto_pads_flow_control: :undefined + effective_flow_control: :undefined } |> PadSpecHandler.init_pads() end diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 85e7831ea..2e239900d 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -31,7 +31,7 @@ defmodule Membrane.Element.PadData do end_of_stream?: boolean(), direction: Pad.direction(), flow_control: Pad.flow_control(), - opposite_endpoint_flow_control: :push | :pull | :undefined, + other_effective_flow_control: Pad.other_effective_flow_control(), name: Pad.name(), ref: Pad.ref(), options: %{optional(atom) => any}, @@ -75,6 +75,6 @@ defmodule Membrane.Element.PadData do sticky_events: [], stream_format_validation_params: [], other_demand_unit: nil, - opposite_endpoint_flow_control: :undefined + other_effective_flow_control: :undefined ] end diff --git a/lib/membrane/pad.ex b/lib/membrane/pad.ex index 3e054239f..10b2eb0b1 100644 --- a/lib/membrane/pad.ex +++ b/lib/membrane/pad.ex @@ -66,6 +66,8 @@ defmodule Membrane.Pad do """ @type flow_control :: :auto | :manual | :push + @type effective_flow_control :: :push | :pull | :undefined + @typedoc """ Values used when defining pad availability: diff --git a/test/membrane/core/element/pad_controller_test.exs b/test/membrane/core/element/pad_controller_test.exs index 2106e2b47..55bc01821 100644 --- a/test/membrane/core/element/pad_controller_test.exs +++ b/test/membrane/core/element/pad_controller_test.exs @@ -43,7 +43,7 @@ defmodule Membrane.Core.Element.PadControllerTest do other_info: %{direction: :input, flow_control: :manual, demand_unit: :buffers}, link_metadata: %{toilet: make_ref(), observability_metadata: %{}}, stream_format_validation_params: [], - opposite_endpoint_flow_control: :pull + other_effective_flow_control: :pull }, state ) diff --git a/test/membrane/core/element_test.exs b/test/membrane/core/element_test.exs index df9306771..59e43f006 100644 --- a/test/membrane/core/element_test.exs +++ b/test/membrane/core/element_test.exs @@ -83,7 +83,7 @@ defmodule Membrane.Core.ElementTest do other_info: %{direction: :input, flow_control: :manual, demand_unit: :buffers}, link_metadata: %{toilet: nil, observability_metadata: %{}}, stream_format_validation_params: [], - opposite_endpoint_flow_control: :pull + other_effective_flow_control: :pull } ]), nil, @@ -114,7 +114,7 @@ defmodule Membrane.Core.ElementTest do other_info: %{direction: :output, flow_control: :manual}, link_metadata: %{toilet: nil, observability_metadata: %{}}, stream_format_validation_params: [], - opposite_endpoint_flow_control: :pull + other_effective_flow_control: :pull } ]), nil, @@ -230,7 +230,7 @@ defmodule Membrane.Core.ElementTest do }, link_metadata: %{observability_metadata: %{}}, stream_format_validation_params: [], - opposite_endpoint_flow_control: :pull + other_effective_flow_control: :pull } ]), nil, From 1d5f38b18ee0b7294f431ee5eb54b5742390e9b6 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 27 Feb 2023 16:26:55 +0100 Subject: [PATCH 03/64] Rename file auto_demand_controller to effective_flow_control_controller --- lib/membrane/core/element.ex | 4 ++-- ..._controller.ex => effective_flow_control_controller.ex} | 2 +- lib/membrane/core/element/lifecycle_controller.ex | 4 ++-- lib/membrane/core/element/pad_controller.ex | 7 ++++--- lib/membrane/element/pad_data.ex | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) rename lib/membrane/core/element/{auto_mode_controller.ex => effective_flow_control_controller.ex} (97%) diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index bce809d63..ecfedf8f5 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -25,8 +25,8 @@ defmodule Membrane.Core.Element do alias Membrane.Core.Element.{ BufferController, DemandController, + EffectiveFlowControlController, EventController, - FlowControlUtils, LifecycleController, PadController, State, @@ -221,7 +221,7 @@ defmodule Membrane.Core.Element do state ) do state = - FlowControlUtils.handle_other_effective_flow_control( + EffectiveFlowControlController.handle_other_effective_flow_control( my_pad_ref, effective_flow_control, state diff --git a/lib/membrane/core/element/auto_mode_controller.ex b/lib/membrane/core/element/effective_flow_control_controller.ex similarity index 97% rename from lib/membrane/core/element/auto_mode_controller.ex rename to lib/membrane/core/element/effective_flow_control_controller.ex index 5d89828b0..38ed20e46 100644 --- a/lib/membrane/core/element/auto_mode_controller.ex +++ b/lib/membrane/core/element/effective_flow_control_controller.ex @@ -1,4 +1,4 @@ -defmodule Membrane.Core.Element.FlowControlUtils do +defmodule Membrane.Core.Element.EffectiveFlowControlController do @moduledoc false alias Membrane.Core.Element.State diff --git a/lib/membrane/core/element/lifecycle_controller.ex b/lib/membrane/core/element/lifecycle_controller.ex index e7469b34d..b9cd379bc 100644 --- a/lib/membrane/core/element/lifecycle_controller.ex +++ b/lib/membrane/core/element/lifecycle_controller.ex @@ -12,7 +12,7 @@ defmodule Membrane.Core.Element.LifecycleController do alias Membrane.Core.Element.{ ActionHandler, CallbackContext, - FlowControlUtils, + EffectiveFlowControlController, PlaybackQueue, State } @@ -79,7 +79,7 @@ defmodule Membrane.Core.Element.LifecycleController do state = %State{state | playback: :playing} - |> FlowControlUtils.resolve_effective_flow_control() + |> EffectiveFlowControlController.resolve_effective_flow_control() state = CallbackHandler.exec_and_handle_callback( diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 35b9d69a2..1d9ad0e03 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -13,7 +13,7 @@ defmodule Membrane.Core.Element.PadController do CallbackContext, DemandController, EventController, - FlowControlUtils, + EffectiveFlowControlController, InputQueue, State, StreamFormatController, @@ -87,7 +87,8 @@ defmodule Membrane.Core.Element.PadController do %{initiator: :parent} = props, state ) do - flow_control = FlowControlUtils.pad_effective_flow_control(endpoint.pad_ref, state) + flow_control = + EffectiveFlowControlController.pad_effective_flow_control(endpoint.pad_ref, state) handle_link_response = Message.call(other_endpoint.pid, :handle_link, [ @@ -201,7 +202,7 @@ defmodule Membrane.Core.Element.PadController do state ) - state = FlowControlUtils.handle_input_pad_added(endpoint.pad_ref, state) + state = EffectiveFlowControlController.handle_input_pad_added(endpoint.pad_ref, state) state = maybe_handle_pad_added(endpoint.pad_ref, state) {{:ok, {endpoint, info, link_metadata}}, state} end diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 2e239900d..f0c310cda 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -31,7 +31,7 @@ defmodule Membrane.Element.PadData do end_of_stream?: boolean(), direction: Pad.direction(), flow_control: Pad.flow_control(), - other_effective_flow_control: Pad.other_effective_flow_control(), + other_effective_flow_control: Pad.effective_flow_control(), name: Pad.name(), ref: Pad.ref(), options: %{optional(atom) => any}, From 490d651d0b2a27f9d5a4604b6de8e7328a82dad5 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 28 Feb 2023 14:21:35 +0100 Subject: [PATCH 04/64] Add simple test for effective flow control resoultion --- lib/membrane/core/element/toilet.ex | 2 +- ...effective_flow_control_resolution_test.exs | 81 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 test/membrane/integration/effective_flow_control_resolution_test.exs diff --git a/lib/membrane/core/element/toilet.ex b/lib/membrane/core/element/toilet.ex index 29e1863bd..b32a86767 100644 --- a/lib/membrane/core/element/toilet.ex +++ b/lib/membrane/core/element/toilet.ex @@ -151,7 +151,7 @@ defmodule Membrane.Core.Element.Toilet do \`"#"`/ |`"`| .-| |-. - jgs / ' ' \ + / ' ' \ '---------' """) diff --git a/test/membrane/integration/effective_flow_control_resolution_test.exs b/test/membrane/integration/effective_flow_control_resolution_test.exs new file mode 100644 index 000000000..2a139c8fd --- /dev/null +++ b/test/membrane/integration/effective_flow_control_resolution_test.exs @@ -0,0 +1,81 @@ +defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do + use ExUnit.Case + + import Membrane.ChildrenSpec + import Membrane.Testing.Assertions + + alias Membrane.Testing + + defmodule DynamicFilter do + use Membrane.Filter + + def_input_pad :input, availability: :on_request, accepted_format: _any + def_output_pad :output, availability: :on_request, accepted_format: _any + + @impl true + def handle_playing(_ctx, state) do + {[notify_parent: :playing], state} + end + end + + defmodule DynamicSource do + use Membrane.Source + + def_output_pad :push_output, accepted_format: _any, flow_control: :push + def_output_pad :pull_output, accepted_format: _any, flow_control: :manual + + @impl true + def handle_demand(_, _, _, _ctx, state), do: {[], state} + end + + @tag :dupa + test "effective_flow_control is resolved in simple scenario " do + spec_beggining = [ + child({:filter_a, 0}, DynamicFilter), + child({:filter_b, 0}, DynamicFilter) + ] + + spec = + Enum.reduce(1..10, spec_beggining, fn idx, [predecessor_a, predecessor_b] -> + [ + predecessor_a + |> child({:filter_a, idx}, DynamicFilter), + predecessor_b + |> child({:filter_b, idx}, DynamicFilter) + ] + end) + + pipeline = Testing.Pipeline.start_link_supervised!(spec: spec) + + Process.sleep(1000) + + for idx <- 0..10, filter_type <- [:filter_a, :filter_b] do + child = {filter_type, idx} + + assert_pipeline_notified(pipeline, child, :playing) + + child_pid = Testing.Pipeline.get_child_pid!(pipeline, child) + child_state = :sys.get_state(child_pid) + + assert child_state.effective_flow_control == :undefined + end + + Testing.Pipeline.execute_actions(pipeline, + spec: [ + child(:source, DynamicSource) |> via_out(:push_output) |> get_child({:filter_a, 0}), + get_child(:source) |> via_out(:pull_output) |> get_child({:filter_b, 0}) + ] + ) + + Process.sleep(1000) + + for idx <- 0..10, filter_type <- [:filter_a, :filter_b] do + child = {filter_type, idx} + child_pid = Testing.Pipeline.get_child_pid!(pipeline, child) + + child_state = :sys.get_state(child_pid) + expected = if filter_type == :filter_a, do: :push, else: :pull + assert child_state.effective_flow_control == expected + end + end +end From c29168735691b2122d1566cb1f319a7ccc01608e Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 28 Feb 2023 18:37:49 +0100 Subject: [PATCH 05/64] Refactor & upgrade toilet --- lib/membrane/core/bin/pad_controller.ex | 1 - .../core/element/demand_controller.ex | 3 + .../effective_flow_control_controller.ex | 15 +- lib/membrane/core/element/pad_controller.ex | 11 +- lib/membrane/core/element/toilet.ex | 215 +++++++++++++----- test/membrane/core/element/toilet_test.exs | 23 +- ...effective_flow_control_resolution_test.exs | 2 +- 7 files changed, 183 insertions(+), 87 deletions(-) diff --git a/lib/membrane/core/bin/pad_controller.ex b/lib/membrane/core/bin/pad_controller.ex index 8d810687b..1ec8a9bf5 100644 --- a/lib/membrane/core/bin/pad_controller.ex +++ b/lib/membrane/core/bin/pad_controller.ex @@ -254,7 +254,6 @@ defmodule Membrane.Core.Bin.PadController do child_endpoint, other_endpoint, params - # |> IO.inspect(label: "link_props for child") ]) case handle_link_response do diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 7adb36c04..201d1b79e 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -81,6 +81,9 @@ defmodule Membrane.Core.Element.DemandController do Also, the `demand_decrease` argument can be passed, decreasing the size of the demand on the input pad before proceeding to the rest of the function logic. """ + # dupa: funkcja do wysylania demandow + # powinna zostac odpalona o ogarnieciu effective flow control + # albo demandy powinny byc obslugiwane po ogarnieciu effective flow control? @spec send_auto_demand_if_needed(Pad.ref(), integer, State.t()) :: State.t() def send_auto_demand_if_needed(pad_ref, demand_decrease \\ 0, state) do data = PadModel.get_data!(state, pad_ref) diff --git a/lib/membrane/core/element/effective_flow_control_controller.ex b/lib/membrane/core/element/effective_flow_control_controller.ex index 38ed20e46..902c5f0af 100644 --- a/lib/membrane/core/element/effective_flow_control_controller.ex +++ b/lib/membrane/core/element/effective_flow_control_controller.ex @@ -36,16 +36,17 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do def handle_other_effective_flow_control(my_pad_ref, other_effective_flow_control, state) do pad_data = PadModel.get_data!(state, my_pad_ref) - if pad_data.direction != :input do - raise "this function should be called only for input pads" - end - pad_data = %{pad_data | other_effective_flow_control: other_effective_flow_control} state = PadModel.set_data!(state, my_pad_ref, pad_data) cond do - pad_data.flow_control != :auto or other_effective_flow_control == :undefined or - state.playback == :stopped -> + state.playback == :stopped -> + state + + pad_data.direction == :output or pad_data.flow_control != :auto -> + state + + other_effective_flow_control == :undefined -> state state.effective_flow_control == :undefined -> @@ -76,7 +77,7 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do if effective_flow_control != :undefined do Enum.each(state.pads_data, fn {_pad_ref, pad_data} -> - with %{direction: :output, flow_control: :auto} <- pad_data do + if pad_data.flow_control == :auto do Message.send(pad_data.pid, :other_effective_flow_control, [ pad_data.other_ref, effective_flow_control diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 1d9ad0e03..040f880dc 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -38,7 +38,8 @@ defmodule Membrane.Core.Element.PadController do other_info: PadModel.pad_info() | nil, link_metadata: %{toilet: Toilet.t() | nil}, stream_format_validation_params: - StreamFormatController.stream_format_validation_params() + StreamFormatController.stream_format_validation_params(), + other_effective_flow_control: Pad.effective_flow_control() } @type link_call_reply_props :: @@ -87,7 +88,7 @@ defmodule Membrane.Core.Element.PadController do %{initiator: :parent} = props, state ) do - flow_control = + effective_flow_control = EffectiveFlowControlController.pad_effective_flow_control(endpoint.pad_ref, state) handle_link_response = @@ -102,9 +103,8 @@ defmodule Membrane.Core.Element.PadController do observability_metadata: Observability.setup_link(endpoint.pad_ref) }, stream_format_validation_params: [], - other_effective_flow_control: flow_control + other_effective_flow_control: effective_flow_control } - # |> IO.inspect(label: "link_props to sibling") ]) case handle_link_response do @@ -340,12 +340,13 @@ defmodule Membrane.Core.Element.PadController do demand_pid: pid, demand_pad: other_ref, log_tag: inspect(ref), + # dupa: ogarnij te zmienną ponizej, to jest nietrywialne. ogolnie trzeba patrzec na effective_flow_control, mozna doda toilet?: enable_toilet?, target_size: props.target_queue_size, min_demand_factor: props.min_demand_factor }) - %{input_queue: input_queue, demand: 0, toilet: if(enable_toilet?, do: metadata.toilet)} + %{input_queue: input_queue, demand: 0, toilet: metadata.toilet} end defp init_pad_mode_data( diff --git a/lib/membrane/core/element/toilet.ex b/lib/membrane/core/element/toilet.ex index b32a86767..00ae222b2 100644 --- a/lib/membrane/core/element/toilet.ex +++ b/lib/membrane/core/element/toilet.ex @@ -5,47 +5,55 @@ defmodule Membrane.Core.Element.Toilet do # time and exceeds its capacity, it overflows by logging an error and killing # the responsible process (passed on the toilet creation). + alias Membrane.Pad + require Membrane.Logger - defmodule DistributedCounter do + defmodule Worker do @moduledoc false - # A module providing a common interface to access and modify a counter used in the toilet implementation. - # The counter uses :atomics module under the hood. - # The module allows to create and modify the value of a counter in the same manner both when the counter is about to be accessed - # from the same node, and from different nodes. - - defmodule Worker do - @moduledoc false + # This is a GenServer created when the counter is about to be accessed from different nodes - it's running on the same node, + # where the :atomics variable is put, and processes from different nodes can ask it to modify the counter on their behalf. - # This is a GenServer created when the counter is about to be accessed from different nodes - it's running on the same node, - # where the :atomics variable is put, and processes from different nodes can ask it to modify the counter on their behalf. + use GenServer - use GenServer + @impl true + def init(parent_pid) do + Process.monitor(parent_pid) + {:ok, nil, :hibernate} + end - @impl true - def init(parent_pid) do - Process.monitor(parent_pid) - {:ok, nil, :hibernate} - end + @impl true + def handle_call({:add_get, atomic_ref, value}, _from, _state) do + result = :atomics.add_get(atomic_ref, 1, value) + {:reply, result, nil} + end - @impl true - def handle_call({:add_get, atomic_ref, value}, _from, _state) do - result = :atomics.add_get(atomic_ref, 1, value) - {:reply, result, nil} - end + @impl true + def handle_call({:get, atomic_ref}, _from, _state) do + result = :atomics.get(atomic_ref, 1) + {:reply, result, nil} + end - @impl true - def handle_cast({:sub, atomic_ref, value}, _state) do - :atomics.sub(atomic_ref, 1, value) - {:noreply, nil} - end + @impl true + def handle_cast({:sub, atomic_ref, value}, _state) do + :atomics.sub(atomic_ref, 1, value) + {:noreply, nil} + end - @impl true - def handle_info({:DOWN, _ref, :process, _object, _reason}, state) do - {:stop, :normal, state} - end + @impl true + def handle_info({:DOWN, _ref, :process, _object, _reason}, state) do + {:stop, :normal, state} end + end + + defmodule DistributedCounter do + @moduledoc false + + # A module providing a common interface to access and modify a counter used in the toilet implementation. + # The counter uses :atomics module under the hood. + # The module allows to create and modify the value of a counter in the same manner both when the counter is about to be accessed + # from the same node, and from different nodes. @type t :: {pid(), :atomics.atomics_ref()} @@ -75,57 +83,142 @@ defmodule Membrane.Core.Element.Toilet do end end - @opaque t :: - {__MODULE__, DistributedCounter.t(), pos_integer, Process.dest(), pos_integer(), - non_neg_integer()} + defmodule DistributedEffectiveFlowControl do + @moduledoc false + + @type t :: {pid(), :atomics.atomics_ref()} + + @spec new(Pad.effective_flow_control()) :: t + def new(initial_value) do + atomic_ref = :atomics.new(1, []) + + initial_value = effective_flow_control_to_int(initial_value) + :atomics.put(atomic_ref, 1, initial_value) + + {:ok, pid} = GenServer.start(Worker, self()) + {pid, atomic_ref} + end + + @spec get(t) :: Pad.effective_flow_control() + def get({pid, atomic_ref}) when node(pid) == node(self()) do + :atomics.get(atomic_ref, 1) + |> int_to_effective_flow_control() + end + + def get({pid, atomic_ref}) do + GenServer.call(pid, {:get, atomic_ref}) + |> int_to_effective_flow_control() + end + + # contains implementation only for caller being on this same node, as :atomics, + # because toilet is created on the receiver side of link and only receiver should + # call this function + @spec put(t, Pad.effective_flow_control()) :: :ok + def put({_pid, atomic_ref}, value) do + value = effective_flow_control_to_int(value) + :atomics.put(atomic_ref, 1, value) + end + + defp int_to_effective_flow_control(0), do: :undefined + defp int_to_effective_flow_control(1), do: :push + defp int_to_effective_flow_control(2), do: :pull + + defp effective_flow_control_to_int(:undefined), do: 0 + defp effective_flow_control_to_int(:push), do: 1 + defp effective_flow_control_to_int(:pull), do: 2 + end + + @type t :: %__MODULE__{ + counter: DistributedCounter.t(), + capacity: pos_integer(), + responsible_process: Process.dest(), + throttling_factor: pos_integer(), + unrinsed_buffers_size: non_neg_integer(), + receiver_effective_flow_control: DistributedEffectiveFlowControl.t() + } + + @enforce_keys [ + :counter, + :capacity, + :responsible_process, + :throttling_factor, + :receiver_effective_flow_control + ] + + defstruct @enforce_keys ++ [unrinsed_buffers_size: 0] @default_capacity_factor 200 @spec new( - pos_integer() | nil, - Membrane.Buffer.Metric.unit(), - Process.dest(), - pos_integer() + capacity :: pos_integer() | nil, + demand_unit :: Membrane.Buffer.Metric.unit(), + responsible_process :: Process.dest(), + throttling_factor :: pos_integer(), + receiver_effective_flow_control :: Pad.effective_flow_control() ) :: t - def new(capacity, demand_unit, responsible_process, throttling_factor) do + def new( + capacity, + demand_unit, + responsible_process, + throttling_factor, + receiver_effective_flow_control \\ :undefined + ) do default_capacity = Membrane.Buffer.Metric.from_unit(demand_unit).buffer_size_approximation() * @default_capacity_factor - toilet_ref = DistributedCounter.new() capacity = capacity || default_capacity - {__MODULE__, toilet_ref, capacity, responsible_process, throttling_factor, 0} + + receiver_effective_flow_control = + receiver_effective_flow_control + |> DistributedEffectiveFlowControl.new() + + %__MODULE__{ + counter: DistributedCounter.new(), + capacity: capacity, + responsible_process: responsible_process, + throttling_factor: throttling_factor, + receiver_effective_flow_control: receiver_effective_flow_control + } + end + + @spec set_receiver_effective_flow_control(t, Pad.effective_flow_control()) :: :ok + def set_receiver_effective_flow_control(%__MODULE__{} = toilet, value) do + DistributedEffectiveFlowControl.put( + toilet.receiver_effective_flow_control, + value + ) end @spec fill(t, non_neg_integer) :: {:ok | :overflow, t} - def fill( - {__MODULE__, counter, capacity, responsible_process, throttling_factor, - unrinsed_buffers_size}, - amount - ) do - if unrinsed_buffers_size + amount < throttling_factor do - {:ok, - {__MODULE__, counter, capacity, responsible_process, throttling_factor, - amount + unrinsed_buffers_size}} + def fill(%__MODULE__{} = toilet, amount) do + new_unrinsed_buffers_size = toilet.unrinsed_buffers_size + amount + + if new_unrinsed_buffers_size < toilet.throttling_factor do + {:ok, %{toilet | unrinsed_buffers_size: new_unrinsed_buffers_size}} else - size = DistributedCounter.add_get(counter, amount + unrinsed_buffers_size) + size = DistributedCounter.add_get(toilet.counter, new_unrinsed_buffers_size) - if size > capacity do - overflow(size, capacity, responsible_process) - {:overflow, {__MODULE__, counter, capacity, responsible_process, throttling_factor, 0}} - else - {:ok, {__MODULE__, counter, capacity, responsible_process, throttling_factor, 0}} + toilet.receiver_effective_flow_control + |> DistributedEffectiveFlowControl.get() + |> case do + :pull when size > toilet.capacity -> + overflow(size, toilet.capacity, toilet.responsible_process) + {:overflow, %{toilet | unrinsed_buffers_size: 0}} + + :undefined when size > 10 * toilet.capacity -> + overflow(size, 10 * toilet.capacity, toilet.responsible_process) + {:overflow, %{toilet | unrinsed_buffers_size: 0}} + + _ok -> + {:ok, %{toilet | unrinsed_buffers_size: 0}} end end end @spec drain(t, non_neg_integer) :: :ok - def drain( - {__MODULE__, counter, _capacity, _responsible_process, _throttling_factor, - _unrinsed_buff_size}, - amount - ) do - DistributedCounter.sub(counter, amount) + def drain(%__MODULE__{} = toilet, amount) do + DistributedCounter.sub(toilet.counter, amount) end defp overflow(size, capacity, responsible_process) do diff --git a/test/membrane/core/element/toilet_test.exs b/test/membrane/core/element/toilet_test.exs index 6aa557525..c5f9e4165 100644 --- a/test/membrane/core/element/toilet_test.exs +++ b/test/membrane/core/element/toilet_test.exs @@ -7,10 +7,9 @@ defmodule Membrane.Core.Element.ToiletTest do end test "if toilet is implemented as :atomics for elements put on the same node", context do - toilet = Toilet.new(100, :buffers, context.responsible_process, 1) + toilet = Toilet.new(100, :buffers, context.responsible_process, 1, :pull) - {_module, {_pid, atomic_ref}, _capacity, _responsible_process_pid, _throttling_factor, - _unrinsed_buffers} = toilet + %Toilet{counter: {_pid, atomic_ref}} = toilet Toilet.fill(toilet, 10) assert :atomics.get(atomic_ref, 1) == 10 @@ -20,10 +19,9 @@ defmodule Membrane.Core.Element.ToiletTest do test "if the receiving element uses toilet with :atomics and the sending element with a interprocess message, when the toilet is distributed", context do - toilet = Toilet.new(100, :buffers, context.responsible_process, 1) + toilet = Toilet.new(100, :buffers, context.responsible_process, 1, :pull) - {_module, {counter_pid, atomic_ref}, _capacity, _responsible_process_pid, _throttling_factor, - _unrinsed_buffers} = toilet + %Toilet{counter: {counter_pid, atomic_ref}} = toilet Toilet.fill(toilet, 10) assert GenServer.call(counter_pid, {:add_get, atomic_ref, 0}) == 10 @@ -34,16 +32,17 @@ defmodule Membrane.Core.Element.ToiletTest do end test "if throttling mechanism works properly", context do - toilet = Toilet.new(100, :buffers, context.responsible_process, 10) + toilet = Toilet.new(100, :buffers, context.responsible_process, 10, :pull) + {:ok, toilet} = Toilet.fill(toilet, 10) - assert {_module, _counter, _capacity, _pid, _throttling_factor, 0} = toilet + assert toilet.unrinsed_buffers_size == 0 {:ok, toilet} = Toilet.fill(toilet, 5) - assert {_module, _counter, _capacity, _pid, _throttling_factor, 5} = toilet + assert toilet.unrinsed_buffers_size == 5 {:ok, toilet} = Toilet.fill(toilet, 80) - assert {_module, _counter, _capacity, _pid, _throttling_factor, 0} = toilet + assert toilet.unrinsed_buffers_size == 0 {:ok, toilet} = Toilet.fill(toilet, 9) - assert {_module, _counter, _capacity, _pid, _throttling_factor, 9} = toilet + assert toilet.unrinsed_buffers_size == 9 {:overflow, toilet} = Toilet.fill(toilet, 11) - assert {_module, _counter, _capacity, _pid, _throttling_factor, 0} = toilet + assert toilet.unrinsed_buffers_size == 0 end end diff --git a/test/membrane/integration/effective_flow_control_resolution_test.exs b/test/membrane/integration/effective_flow_control_resolution_test.exs index 2a139c8fd..b7b5c41ad 100644 --- a/test/membrane/integration/effective_flow_control_resolution_test.exs +++ b/test/membrane/integration/effective_flow_control_resolution_test.exs @@ -72,8 +72,8 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do for idx <- 0..10, filter_type <- [:filter_a, :filter_b] do child = {filter_type, idx} child_pid = Testing.Pipeline.get_child_pid!(pipeline, child) - child_state = :sys.get_state(child_pid) + expected = if filter_type == :filter_a, do: :push, else: :pull assert child_state.effective_flow_control == expected end From ba8bb81d77d14dbbf05d34bf233519be38807659 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 6 Mar 2023 13:51:15 +0100 Subject: [PATCH 06/64] Fix credo issues --- .../effective_flow_control_controller.ex | 41 ++++++++----------- lib/membrane/core/element/pad_controller.ex | 2 +- ...effective_flow_control_resolution_test.exs | 2 +- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/lib/membrane/core/element/effective_flow_control_controller.ex b/lib/membrane/core/element/effective_flow_control_controller.ex index 902c5f0af..b3c7da33f 100644 --- a/lib/membrane/core/element/effective_flow_control_controller.ex +++ b/lib/membrane/core/element/effective_flow_control_controller.ex @@ -39,24 +39,17 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do pad_data = %{pad_data | other_effective_flow_control: other_effective_flow_control} state = PadModel.set_data!(state, my_pad_ref, pad_data) - cond do - state.playback == :stopped -> - state - - pad_data.direction == :output or pad_data.flow_control != :auto -> - state - - other_effective_flow_control == :undefined -> - state - - state.effective_flow_control == :undefined -> - resolve_effective_flow_control(state) - - state.effective_flow_control == :push and other_effective_flow_control == :pull -> - raise "dupa dupa 123" + if state.effective_flow_control == :push and pad_data.direction == :input and + other_effective_flow_control == :pull do + raise "dupa dupa 123" + end - state.effective_flow_control == :pull or other_effective_flow_control == :push -> - state + with :playing <- state.playback, + %{direction: :input, flow_control: :auto} <- pad_data, + mode when mode in [:push, :pull] <- other_effective_flow_control do + resolve_effective_flow_control(state) + else + _other -> state end end @@ -76,14 +69,12 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do end if effective_flow_control != :undefined do - Enum.each(state.pads_data, fn {_pad_ref, pad_data} -> - if pad_data.flow_control == :auto do - Message.send(pad_data.pid, :other_effective_flow_control, [ - pad_data.other_ref, - effective_flow_control - ]) - end - end) + for {_ref, %{flow_control: :auto} = pad_data} <- state.pads_data do + Message.send(pad_data.pid, :other_effective_flow_control, [ + pad_data.other_ref, + effective_flow_control + ]) + end end %{state | effective_flow_control: effective_flow_control} diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 040f880dc..b0de79d07 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -12,8 +12,8 @@ defmodule Membrane.Core.Element.PadController do ActionHandler, CallbackContext, DemandController, - EventController, EffectiveFlowControlController, + EventController, InputQueue, State, StreamFormatController, diff --git a/test/membrane/integration/effective_flow_control_resolution_test.exs b/test/membrane/integration/effective_flow_control_resolution_test.exs index b7b5c41ad..7e6275df6 100644 --- a/test/membrane/integration/effective_flow_control_resolution_test.exs +++ b/test/membrane/integration/effective_flow_control_resolution_test.exs @@ -25,7 +25,7 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do def_output_pad :pull_output, accepted_format: _any, flow_control: :manual @impl true - def handle_demand(_, _, _, _ctx, state), do: {[], state} + def handle_demand(_pad, _size, _unit, _ctx, state), do: {[], state} end @tag :dupa From 62315baa128df6b731c3fb8af39b30827b647d45 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 7 Mar 2023 18:02:20 +0100 Subject: [PATCH 07/64] Fill toilet always on sending buffers via push output pad --- .../core/element/buffer_controller.ex | 27 +++++++++++++---- .../core/element/demand_controller.ex | 22 +++++++------- lib/membrane/core/element/demand_handler.ex | 29 ++++++++++--------- .../effective_flow_control_controller.ex | 24 ++++++++++++--- lib/membrane/core/element/pad_controller.ex | 1 - .../core/element/action_handler_test.exs | 11 +++++++ ...effective_flow_control_resolution_test.exs | 3 +- 7 files changed, 79 insertions(+), 38 deletions(-) diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index eb9785c8e..47a727cd4 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -14,10 +14,12 @@ defmodule Membrane.Core.Element.BufferController do CallbackContext, DemandController, DemandHandler, + EffectiveFlowControlController, EventController, InputQueue, PlaybackQueue, - State + State, + Toilet } alias Membrane.Core.Telemetry @@ -58,10 +60,20 @@ defmodule Membrane.Core.Element.BufferController do State.t() defp do_handle_buffer(pad_ref, %{flow_control: :auto} = data, buffers, state) do %{demand: demand, demand_unit: demand_unit} = data - buf_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) - state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) - state = DemandController.send_auto_demand_if_needed(pad_ref, state) + + state = + EffectiveFlowControlController.pad_effective_flow_control(pad_ref, state) + |> case do + :push -> + if data.toilet, do: Toilet.drain(data.toilet, buf_size) + state + + :pull -> + state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) + DemandController.send_auto_demand_if_needed(pad_ref, state) + end + exec_buffer_callback(pad_ref, buffers, state) end @@ -77,7 +89,12 @@ defmodule Membrane.Core.Element.BufferController do end end - defp do_handle_buffer(pad_ref, _data, buffers, state) do + defp do_handle_buffer(pad_ref, %{flow_control: :push} = data, buffers, state) do + if data.toilet do + buf_size = Buffer.Metric.from_unit(data.demand_unit).buffers_size(buffers) + Toilet.drain(data.toilet, buf_size) + end + exec_buffer_callback(pad_ref, buffers, state) end diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 201d1b79e..9b7a94ac0 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -77,15 +77,9 @@ defmodule Membrane.Core.Element.DemandController do The demand should be sent when the current demand on the input pad is at most half of the demand request size and if there's positive demand on each of associated output pads. - - Also, the `demand_decrease` argument can be passed, decreasing the size of the - demand on the input pad before proceeding to the rest of the function logic. """ - # dupa: funkcja do wysylania demandow - # powinna zostac odpalona o ogarnieciu effective flow control - # albo demandy powinny byc obslugiwane po ogarnieciu effective flow control? - @spec send_auto_demand_if_needed(Pad.ref(), integer, State.t()) :: State.t() - def send_auto_demand_if_needed(pad_ref, demand_decrease \\ 0, state) do + @spec send_auto_demand_if_needed(Pad.ref(), State.t()) :: State.t() + def send_auto_demand_if_needed(pad_ref, %{effective_flow_control: :pull} = state) do data = PadModel.get_data!(state, pad_ref) %{ @@ -95,13 +89,13 @@ defmodule Membrane.Core.Element.DemandController do auto_demand_size: demand_request_size } = data - demand = demand - demand_decrease - demand = if demand <= div(demand_request_size, 2) and auto_demands_positive?(associated_pads, state) do - if toilet do + if data.other_effective_flow_control in [:push, :undefined] do Toilet.drain(toilet, demand_request_size - demand) - else + end + + if data.other_effective_flow_control in [:pull, :undefined] do Membrane.Logger.debug_verbose( "Sending auto demand of size #{demand_request_size - demand} on pad #{inspect(pad_ref)}" ) @@ -122,6 +116,10 @@ defmodule Membrane.Core.Element.DemandController do PadModel.set_data!(state, pad_ref, :demand, demand) end + def send_auto_demand_if_needed(_pad_ref, state) do + state + end + defp auto_demands_positive?(associated_pads, state) do Enum.all?(associated_pads, &(PadModel.get_data!(state, &1, :demand) > 0)) end diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index a0600f71e..8b6abd9e8 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -9,6 +9,7 @@ defmodule Membrane.Core.Element.DemandHandler do alias Membrane.Core.Element.{ BufferController, DemandController, + EffectiveFlowControlController, EventController, InputQueue, State, @@ -103,27 +104,22 @@ defmodule Membrane.Core.Element.DemandHandler do [Buffer.t()], State.t() ) :: State.t() - def handle_outgoing_buffers(pad_ref, %{flow_control: flow_control} = data, buffers, state) - when flow_control in [:auto, :manual] do + def handle_outgoing_buffers(pad_ref, data, buffers, state) do + EffectiveFlowControlController.pad_effective_flow_control(pad_ref, state) + |> do_handle_outgoing_buffers(pad_ref, data, buffers, state) + end + + defp do_handle_outgoing_buffers(:pull, pad_ref, data, buffers, state) do %{other_demand_unit: other_demand_unit, demand: demand} = data buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) end - def handle_outgoing_buffers( - pad_ref, - %{ - flow_control: :push, - toilet: toilet - } = data, - buffers, - state - ) - when toilet != nil do + defp do_handle_outgoing_buffers(:push, pad_ref, data, buffers, state) when data.toilet != nil do %{other_demand_unit: other_demand_unit} = data buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) - case Toilet.fill(toilet, buf_size) do + case Toilet.fill(data.toilet, buf_size) do {:ok, toilet} -> PadModel.set_data!(state, pad_ref, :toilet, toilet) @@ -134,10 +130,14 @@ defmodule Membrane.Core.Element.DemandHandler do end end - def handle_outgoing_buffers(_pad_ref, _pad_data, _buffers, state) do + defp do_handle_outgoing_buffers(:push, _ref, _data, _buffers, state) do state end + defp do_handle_outgoing_buffers(:undefined, _ref, _data, _buffers, _state) do + raise "not implemented yet" + end + defp update_demand(pad_ref, size, state) when is_integer(size) do PadModel.set_data!(state, pad_ref, :demand, size) end @@ -206,6 +206,7 @@ defmodule Membrane.Core.Element.DemandHandler do ) do state = PadModel.update_data!(state, pad_ref, :demand, &(&1 - outbound_metric_buf_size)) + # czyli wychodzi na to, ze toilet w manualnym pushu jest drainowany, w momencie zjadania buforow. if toilet = PadModel.get_data!(state, pad_ref, :toilet) do Toilet.drain(toilet, outbound_metric_buf_size) end diff --git a/lib/membrane/core/element/effective_flow_control_controller.ex b/lib/membrane/core/element/effective_flow_control_controller.ex index b3c7da33f..75868a5a4 100644 --- a/lib/membrane/core/element/effective_flow_control_controller.ex +++ b/lib/membrane/core/element/effective_flow_control_controller.ex @@ -1,7 +1,10 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do @moduledoc false - alias Membrane.Core.Element.State + alias Membrane.Core.Element.{ + State, + DemandController + } require Membrane.Core.Child.PadModel, as: PadModel require Membrane.Core.Message, as: Message @@ -41,7 +44,8 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do if state.effective_flow_control == :push and pad_data.direction == :input and other_effective_flow_control == :pull do - raise "dupa dupa 123" + # TODO: implement this + raise "not implemented yet" end with :playing <- state.playback, @@ -54,7 +58,7 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do end @spec resolve_effective_flow_control(State.t()) :: State.t() - def resolve_effective_flow_control(state) do + def resolve_effective_flow_control(%State{effective_flow_control: :undefined} = state) do input_auto_pads = Map.values(state.pads_data) |> Enum.filter(&(&1.direction == :input && &1.flow_control == :auto)) @@ -68,6 +72,8 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do %{} -> :undefined end + state = %{state | effective_flow_control: effective_flow_control} + if effective_flow_control != :undefined do for {_ref, %{flow_control: :auto} = pad_data} <- state.pads_data do Message.send(pad_data.pid, :other_effective_flow_control, [ @@ -77,6 +83,16 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do end end - %{state | effective_flow_control: effective_flow_control} + with %{effective_flow_control: :pull} <- state do + Enum.reduce(state.pads_data, state, fn + {pad_ref, %{flow_control: :auto, direction: :input}}, state -> + DemandController.send_auto_demand_if_needed(pad_ref, state) + + _pad_entry, state -> + state + end) + end end + + def resolve_effective_flow_control(state), do: state end diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index b0de79d07..ed5f5183e 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -340,7 +340,6 @@ defmodule Membrane.Core.Element.PadController do demand_pid: pid, demand_pad: other_ref, log_tag: inspect(ref), - # dupa: ogarnij te zmienną ponizej, to jest nietrywialne. ogolnie trzeba patrzec na effective_flow_control, mozna doda toilet?: enable_toilet?, target_size: props.target_queue_size, min_demand_factor: props.min_demand_factor diff --git a/test/membrane/core/element/action_handler_test.exs b/test/membrane/core/element/action_handler_test.exs index 89ba9af2b..9cfc601d8 100644 --- a/test/membrane/core/element/action_handler_test.exs +++ b/test/membrane/core/element/action_handler_test.exs @@ -38,6 +38,10 @@ defmodule Membrane.Core.Element.ActionHandlerTest do pid: self(), flow_control: :push ) + }, + pads_info: %{ + input: %{flow_control: :manual}, + input_push: %{flow_control: :push} } ) @@ -96,6 +100,10 @@ defmodule Membrane.Core.Element.ActionHandlerTest do end_of_stream?: false, flow_control: :push } + }, + pads_info: %{ + output: %{flow_control: :push}, + input: %{flow_control: :push} } ) @@ -481,6 +489,9 @@ defmodule Membrane.Core.Element.ActionHandlerTest do demand: 0 } }, + pads_info: %{ + output: %{flow_control: :manual} + }, playback: :playing ) diff --git a/test/membrane/integration/effective_flow_control_resolution_test.exs b/test/membrane/integration/effective_flow_control_resolution_test.exs index 7e6275df6..303e62c83 100644 --- a/test/membrane/integration/effective_flow_control_resolution_test.exs +++ b/test/membrane/integration/effective_flow_control_resolution_test.exs @@ -28,8 +28,7 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do def handle_demand(_pad, _size, _unit, _ctx, state), do: {[], state} end - @tag :dupa - test "effective_flow_control is resolved in simple scenario " do + test "effective_flow_control is properly resolved in simple scenario" do spec_beggining = [ child({:filter_a, 0}, DynamicFilter), child({:filter_b, 0}, DynamicFilter) From 7116b9448d7a04cb8720090f64f6d844147b1ce8 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 8 Mar 2023 14:01:29 +0100 Subject: [PATCH 08/64] Drain toilet only if it exists --- lib/membrane/core/element/demand_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 9b7a94ac0..2ef7f54c9 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -91,7 +91,7 @@ defmodule Membrane.Core.Element.DemandController do demand = if demand <= div(demand_request_size, 2) and auto_demands_positive?(associated_pads, state) do - if data.other_effective_flow_control in [:push, :undefined] do + if toilet != nil and data.other_effective_flow_control in [:push, :undefined] do Toilet.drain(toilet, demand_request_size - demand) end From a156819d3722368c0749a0519309e8845a6e19d9 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 8 Mar 2023 14:02:41 +0100 Subject: [PATCH 09/64] Fix credo issue --- .../core/element/effective_flow_control_controller.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/membrane/core/element/effective_flow_control_controller.ex b/lib/membrane/core/element/effective_flow_control_controller.ex index 75868a5a4..4cb6b0f16 100644 --- a/lib/membrane/core/element/effective_flow_control_controller.ex +++ b/lib/membrane/core/element/effective_flow_control_controller.ex @@ -2,8 +2,8 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do @moduledoc false alias Membrane.Core.Element.{ - State, - DemandController + DemandController, + State } require Membrane.Core.Child.PadModel, as: PadModel From eaab0bc6026733866c370d8452de11eed90f70f3 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 9 Mar 2023 11:38:15 +0100 Subject: [PATCH 10/64] Rename :undefined to :not_resolved --- lib/membrane/core/element/demand_controller.ex | 4 ++-- lib/membrane/core/element/demand_handler.ex | 3 +-- .../core/element/effective_flow_control_controller.ex | 6 +++--- lib/membrane/core/element/pad_controller.ex | 2 +- lib/membrane/core/element/state.ex | 2 +- lib/membrane/core/element/toilet.ex | 8 ++++---- lib/membrane/element/pad_data.ex | 2 +- lib/membrane/pad.ex | 2 +- .../effective_flow_control_resolution_test.exs | 2 +- 9 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 2ef7f54c9..ae811115c 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -91,11 +91,11 @@ defmodule Membrane.Core.Element.DemandController do demand = if demand <= div(demand_request_size, 2) and auto_demands_positive?(associated_pads, state) do - if toilet != nil and data.other_effective_flow_control in [:push, :undefined] do + if toilet != nil and data.other_effective_flow_control in [:push, :not_resolved] do Toilet.drain(toilet, demand_request_size - demand) end - if data.other_effective_flow_control in [:pull, :undefined] do + if data.other_effective_flow_control in [:pull, :not_resolved] do Membrane.Logger.debug_verbose( "Sending auto demand of size #{demand_request_size - demand} on pad #{inspect(pad_ref)}" ) diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 8b6abd9e8..8b7863a1b 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -134,7 +134,7 @@ defmodule Membrane.Core.Element.DemandHandler do state end - defp do_handle_outgoing_buffers(:undefined, _ref, _data, _buffers, _state) do + defp do_handle_outgoing_buffers(:not_resolved, _ref, _data, _buffers, _state) do raise "not implemented yet" end @@ -206,7 +206,6 @@ defmodule Membrane.Core.Element.DemandHandler do ) do state = PadModel.update_data!(state, pad_ref, :demand, &(&1 - outbound_metric_buf_size)) - # czyli wychodzi na to, ze toilet w manualnym pushu jest drainowany, w momencie zjadania buforow. if toilet = PadModel.get_data!(state, pad_ref, :toilet) do Toilet.drain(toilet, outbound_metric_buf_size) end diff --git a/lib/membrane/core/element/effective_flow_control_controller.ex b/lib/membrane/core/element/effective_flow_control_controller.ex index 4cb6b0f16..e54e09a8a 100644 --- a/lib/membrane/core/element/effective_flow_control_controller.ex +++ b/lib/membrane/core/element/effective_flow_control_controller.ex @@ -58,7 +58,7 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do end @spec resolve_effective_flow_control(State.t()) :: State.t() - def resolve_effective_flow_control(%State{effective_flow_control: :undefined} = state) do + def resolve_effective_flow_control(%State{effective_flow_control: :not_resolved} = state) do input_auto_pads = Map.values(state.pads_data) |> Enum.filter(&(&1.direction == :input && &1.flow_control == :auto)) @@ -69,12 +69,12 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do |> case do %{pull: _pads} -> :pull %{push: _pads} -> :push - %{} -> :undefined + %{} -> :not_resolved end state = %{state | effective_flow_control: effective_flow_control} - if effective_flow_control != :undefined do + if effective_flow_control != :not_resolved do for {_ref, %{flow_control: :auto} = pad_data} <- state.pads_data do Message.send(pad_data.pid, :other_effective_flow_control, [ pad_data.other_ref, diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index ed5f5183e..955676e6f 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -121,7 +121,7 @@ defmodule Membrane.Core.Element.PadController do other_endpoint, info, props.stream_format_validation_params, - :undefined, + :not_resolved, other_info, link_metadata, state diff --git a/lib/membrane/core/element/state.ex b/lib/membrane/core/element/state.ex index cfd794a5f..3b609dac2 100644 --- a/lib/membrane/core/element/state.ex +++ b/lib/membrane/core/element/state.ex @@ -97,7 +97,7 @@ defmodule Membrane.Core.Element.State do subprocess_supervisor: options.subprocess_supervisor, terminating?: false, setup_incomplete?: false, - effective_flow_control: :undefined + effective_flow_control: :not_resolved } |> PadSpecHandler.init_pads() end diff --git a/lib/membrane/core/element/toilet.ex b/lib/membrane/core/element/toilet.ex index 00ae222b2..7300cf85d 100644 --- a/lib/membrane/core/element/toilet.ex +++ b/lib/membrane/core/element/toilet.ex @@ -119,11 +119,11 @@ defmodule Membrane.Core.Element.Toilet do :atomics.put(atomic_ref, 1, value) end - defp int_to_effective_flow_control(0), do: :undefined + defp int_to_effective_flow_control(0), do: :not_resolved defp int_to_effective_flow_control(1), do: :push defp int_to_effective_flow_control(2), do: :pull - defp effective_flow_control_to_int(:undefined), do: 0 + defp effective_flow_control_to_int(:not_resolved), do: 0 defp effective_flow_control_to_int(:push), do: 1 defp effective_flow_control_to_int(:pull), do: 2 end @@ -161,7 +161,7 @@ defmodule Membrane.Core.Element.Toilet do demand_unit, responsible_process, throttling_factor, - receiver_effective_flow_control \\ :undefined + receiver_effective_flow_control \\ :not_resolved ) do default_capacity = Membrane.Buffer.Metric.from_unit(demand_unit).buffer_size_approximation() * @@ -206,7 +206,7 @@ defmodule Membrane.Core.Element.Toilet do overflow(size, toilet.capacity, toilet.responsible_process) {:overflow, %{toilet | unrinsed_buffers_size: 0}} - :undefined when size > 10 * toilet.capacity -> + :not_resolved when size > 10 * toilet.capacity -> overflow(size, 10 * toilet.capacity, toilet.responsible_process) {:overflow, %{toilet | unrinsed_buffers_size: 0}} diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index f0c310cda..643058bdf 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -75,6 +75,6 @@ defmodule Membrane.Element.PadData do sticky_events: [], stream_format_validation_params: [], other_demand_unit: nil, - other_effective_flow_control: :undefined + other_effective_flow_control: :not_resolved ] end diff --git a/lib/membrane/pad.ex b/lib/membrane/pad.ex index 10b2eb0b1..e057e2fb8 100644 --- a/lib/membrane/pad.ex +++ b/lib/membrane/pad.ex @@ -66,7 +66,7 @@ defmodule Membrane.Pad do """ @type flow_control :: :auto | :manual | :push - @type effective_flow_control :: :push | :pull | :undefined + @type effective_flow_control :: :push | :pull | :not_resolved @typedoc """ Values used when defining pad availability: diff --git a/test/membrane/integration/effective_flow_control_resolution_test.exs b/test/membrane/integration/effective_flow_control_resolution_test.exs index 303e62c83..67c4564a4 100644 --- a/test/membrane/integration/effective_flow_control_resolution_test.exs +++ b/test/membrane/integration/effective_flow_control_resolution_test.exs @@ -56,7 +56,7 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do child_pid = Testing.Pipeline.get_child_pid!(pipeline, child) child_state = :sys.get_state(child_pid) - assert child_state.effective_flow_control == :undefined + assert child_state.effective_flow_control == :not_resolved end Testing.Pipeline.execute_actions(pipeline, From a1dcfef2c4bbb7f5b03d88d8c20d9c5631f274e0 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 9 Mar 2023 11:58:05 +0100 Subject: [PATCH 11/64] Implement comments from PR --- lib/membrane/core/child/pad_model.ex | 3 +- lib/membrane/core/element.ex | 6 ++-- .../core/element/buffer_controller.ex | 4 +-- lib/membrane/core/element/demand_handler.ex | 4 +-- ...roller.ex => effective_flow_controller.ex} | 29 ++++++++++++------- .../core/element/lifecycle_controller.ex | 4 +-- lib/membrane/core/element/pad_controller.ex | 8 ++--- lib/membrane/core/element/state.ex | 3 +- lib/membrane/core/element/toilet.ex | 13 +++++---- lib/membrane/element/pad_data.ex | 2 +- lib/membrane/pad.ex | 2 -- ...effective_flow_control_resolution_test.exs | 20 +++++++------ 12 files changed, 55 insertions(+), 43 deletions(-) rename lib/membrane/core/element/{effective_flow_control_controller.ex => effective_flow_controller.ex} (80%) diff --git a/lib/membrane/core/child/pad_model.ex b/lib/membrane/core/child/pad_model.ex index b38591a8b..2b8da8de8 100644 --- a/lib/membrane/core/child/pad_model.ex +++ b/lib/membrane/core/child/pad_model.ex @@ -6,6 +6,7 @@ defmodule Membrane.Core.Child.PadModel do use Bunch alias Membrane.Core.Child + alias Membrane.Core.Element.EffectiveFlowController alias Membrane.{Pad, UnknownPadError} @type bin_pad_data :: %Membrane.Bin.PadData{ @@ -29,7 +30,7 @@ defmodule Membrane.Core.Child.PadModel do end_of_stream?: boolean(), direction: Pad.direction(), flow_control: Pad.flow_control(), - other_effective_flow_control: Pad.effective_flow_control(), + other_effective_flow_control: EffectiveFlowController.effective_flow_control() | nil, name: Pad.name(), ref: Pad.ref(), demand_unit: Membrane.Buffer.Metric.unit() | nil, diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index ecfedf8f5..44f32e27d 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -25,7 +25,7 @@ defmodule Membrane.Core.Element do alias Membrane.Core.Element.{ BufferController, DemandController, - EffectiveFlowControlController, + EffectiveFlowController, EventController, LifecycleController, PadController, @@ -217,11 +217,11 @@ defmodule Membrane.Core.Element do end defp do_handle_info( - Message.new(:other_effective_flow_control, [my_pad_ref, effective_flow_control]), + Message.new(:other_effective_flow_control_resolved, [my_pad_ref, effective_flow_control]), state ) do state = - EffectiveFlowControlController.handle_other_effective_flow_control( + EffectiveFlowController.handle_other_effective_flow_control( my_pad_ref, effective_flow_control, state diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index 47a727cd4..2d4874a52 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -14,7 +14,7 @@ defmodule Membrane.Core.Element.BufferController do CallbackContext, DemandController, DemandHandler, - EffectiveFlowControlController, + EffectiveFlowController, EventController, InputQueue, PlaybackQueue, @@ -63,7 +63,7 @@ defmodule Membrane.Core.Element.BufferController do buf_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) state = - EffectiveFlowControlController.pad_effective_flow_control(pad_ref, state) + EffectiveFlowController.pad_effective_flow_control(pad_ref, state) |> case do :push -> if data.toilet, do: Toilet.drain(data.toilet, buf_size) diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 8b7863a1b..08e1e03db 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -9,7 +9,7 @@ defmodule Membrane.Core.Element.DemandHandler do alias Membrane.Core.Element.{ BufferController, DemandController, - EffectiveFlowControlController, + EffectiveFlowController, EventController, InputQueue, State, @@ -105,7 +105,7 @@ defmodule Membrane.Core.Element.DemandHandler do State.t() ) :: State.t() def handle_outgoing_buffers(pad_ref, data, buffers, state) do - EffectiveFlowControlController.pad_effective_flow_control(pad_ref, state) + EffectiveFlowController.pad_effective_flow_control(pad_ref, state) |> do_handle_outgoing_buffers(pad_ref, data, buffers, state) end diff --git a/lib/membrane/core/element/effective_flow_control_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex similarity index 80% rename from lib/membrane/core/element/effective_flow_control_controller.ex rename to lib/membrane/core/element/effective_flow_controller.ex index e54e09a8a..6d05914fd 100644 --- a/lib/membrane/core/element/effective_flow_control_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -1,4 +1,4 @@ -defmodule Membrane.Core.Element.EffectiveFlowControlController do +defmodule Membrane.Core.Element.EffectiveFlowController do @moduledoc false alias Membrane.Core.Element.{ @@ -10,7 +10,9 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do require Membrane.Core.Message, as: Message require Membrane.Pad, as: Pad - @spec pad_effective_flow_control(Pad.ref(), State.t()) :: Pad.effective_flow_control() + @type effective_flow_control :: :push | :pull | :not_resolved + + @spec pad_effective_flow_control(Pad.ref(), State.t()) :: effective_flow_control() def pad_effective_flow_control(pad_ref, state) do pad_name = Pad.name_by_ref(pad_ref) @@ -34,7 +36,11 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do end end - @spec handle_other_effective_flow_control(Pad.ref(), Pad.effective_flow_control(), State.t()) :: + @spec handle_other_effective_flow_control( + Pad.ref(), + EffectiveFlowController.effective_flow_control(), + State.t() + ) :: State.t() def handle_other_effective_flow_control(my_pad_ref, other_effective_flow_control, state) do pad_data = PadModel.get_data!(state, my_pad_ref) @@ -64,19 +70,22 @@ defmodule Membrane.Core.Element.EffectiveFlowControlController do |> Enum.filter(&(&1.direction == :input && &1.flow_control == :auto)) effective_flow_control = - input_auto_pads - |> Enum.group_by(& &1.other_effective_flow_control) - |> case do - %{pull: _pads} -> :pull - %{push: _pads} -> :push - %{} -> :not_resolved + cond do + Enum.any?(input_auto_pads, &(&1.other_effective_flow_control == :pull)) -> + :pull + + Enum.any?(input_auto_pads, &(&1.other_effective_flow_control == :push)) -> + :push + + true -> + :not_resolved end state = %{state | effective_flow_control: effective_flow_control} if effective_flow_control != :not_resolved do for {_ref, %{flow_control: :auto} = pad_data} <- state.pads_data do - Message.send(pad_data.pid, :other_effective_flow_control, [ + Message.send(pad_data.pid, :other_effective_flow_control_resolved, [ pad_data.other_ref, effective_flow_control ]) diff --git a/lib/membrane/core/element/lifecycle_controller.ex b/lib/membrane/core/element/lifecycle_controller.ex index b9cd379bc..32b498af5 100644 --- a/lib/membrane/core/element/lifecycle_controller.ex +++ b/lib/membrane/core/element/lifecycle_controller.ex @@ -12,7 +12,7 @@ defmodule Membrane.Core.Element.LifecycleController do alias Membrane.Core.Element.{ ActionHandler, CallbackContext, - EffectiveFlowControlController, + EffectiveFlowController, PlaybackQueue, State } @@ -79,7 +79,7 @@ defmodule Membrane.Core.Element.LifecycleController do state = %State{state | playback: :playing} - |> EffectiveFlowControlController.resolve_effective_flow_control() + |> EffectiveFlowController.resolve_effective_flow_control() state = CallbackHandler.exec_and_handle_callback( diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 955676e6f..0806969ba 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -12,7 +12,7 @@ defmodule Membrane.Core.Element.PadController do ActionHandler, CallbackContext, DemandController, - EffectiveFlowControlController, + EffectiveFlowController, EventController, InputQueue, State, @@ -39,7 +39,7 @@ defmodule Membrane.Core.Element.PadController do link_metadata: %{toilet: Toilet.t() | nil}, stream_format_validation_params: StreamFormatController.stream_format_validation_params(), - other_effective_flow_control: Pad.effective_flow_control() + other_effective_flow_control: EffectiveFlowController.effective_flow_control() } @type link_call_reply_props :: @@ -89,7 +89,7 @@ defmodule Membrane.Core.Element.PadController do state ) do effective_flow_control = - EffectiveFlowControlController.pad_effective_flow_control(endpoint.pad_ref, state) + EffectiveFlowController.pad_effective_flow_control(endpoint.pad_ref, state) handle_link_response = Message.call(other_endpoint.pid, :handle_link, [ @@ -202,7 +202,7 @@ defmodule Membrane.Core.Element.PadController do state ) - state = EffectiveFlowControlController.handle_input_pad_added(endpoint.pad_ref, state) + state = EffectiveFlowController.handle_input_pad_added(endpoint.pad_ref, state) state = maybe_handle_pad_added(endpoint.pad_ref, state) {{:ok, {endpoint, info, link_metadata}}, state} end diff --git a/lib/membrane/core/element/state.ex b/lib/membrane/core/element/state.ex index 3b609dac2..31b953050 100644 --- a/lib/membrane/core/element/state.ex +++ b/lib/membrane/core/element/state.ex @@ -10,6 +10,7 @@ defmodule Membrane.Core.Element.State do alias Membrane.{Clock, Element, Pad, Sync} alias Membrane.Core.Timer alias Membrane.Core.Child.{PadModel, PadSpecHandler} + alias Membrane.Core.Element.EffectiveFlowController require Membrane.Pad @@ -37,7 +38,7 @@ defmodule Membrane.Core.Element.State do subprocess_supervisor: pid, terminating?: boolean(), setup_incomplete?: boolean(), - effective_flow_control: Pad.effective_flow_control() + effective_flow_control: EffectiveFlowController.effective_flow_control() } defstruct [ diff --git a/lib/membrane/core/element/toilet.ex b/lib/membrane/core/element/toilet.ex index 7300cf85d..3417fa2d6 100644 --- a/lib/membrane/core/element/toilet.ex +++ b/lib/membrane/core/element/toilet.ex @@ -5,7 +5,7 @@ defmodule Membrane.Core.Element.Toilet do # time and exceeds its capacity, it overflows by logging an error and killing # the responsible process (passed on the toilet creation). - alias Membrane.Pad + alias Membrane.Core.Element.EffectiveFlowController require Membrane.Logger @@ -88,7 +88,7 @@ defmodule Membrane.Core.Element.Toilet do @type t :: {pid(), :atomics.atomics_ref()} - @spec new(Pad.effective_flow_control()) :: t + @spec new(EffectiveFlowController.effective_flow_control()) :: t def new(initial_value) do atomic_ref = :atomics.new(1, []) @@ -99,7 +99,7 @@ defmodule Membrane.Core.Element.Toilet do {pid, atomic_ref} end - @spec get(t) :: Pad.effective_flow_control() + @spec get(t) :: EffectiveFlowController.effective_flow_control() def get({pid, atomic_ref}) when node(pid) == node(self()) do :atomics.get(atomic_ref, 1) |> int_to_effective_flow_control() @@ -113,7 +113,7 @@ defmodule Membrane.Core.Element.Toilet do # contains implementation only for caller being on this same node, as :atomics, # because toilet is created on the receiver side of link and only receiver should # call this function - @spec put(t, Pad.effective_flow_control()) :: :ok + @spec put(t, EffectiveFlowController.effective_flow_control()) :: :ok def put({_pid, atomic_ref}, value) do value = effective_flow_control_to_int(value) :atomics.put(atomic_ref, 1, value) @@ -154,7 +154,7 @@ defmodule Membrane.Core.Element.Toilet do demand_unit :: Membrane.Buffer.Metric.unit(), responsible_process :: Process.dest(), throttling_factor :: pos_integer(), - receiver_effective_flow_control :: Pad.effective_flow_control() + receiver_effective_flow_control :: EffectiveFlowController.effective_flow_control() ) :: t def new( capacity, @@ -182,7 +182,8 @@ defmodule Membrane.Core.Element.Toilet do } end - @spec set_receiver_effective_flow_control(t, Pad.effective_flow_control()) :: :ok + @spec set_receiver_effective_flow_control(t, EffectiveFlowController.effective_flow_control()) :: + :ok def set_receiver_effective_flow_control(%__MODULE__{} = toilet, value) do DistributedEffectiveFlowControl.put( toilet.receiver_effective_flow_control, diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 643058bdf..363d6a146 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -31,7 +31,7 @@ defmodule Membrane.Element.PadData do end_of_stream?: boolean(), direction: Pad.direction(), flow_control: Pad.flow_control(), - other_effective_flow_control: Pad.effective_flow_control(), + other_effective_flow_control: private_field(), name: Pad.name(), ref: Pad.ref(), options: %{optional(atom) => any}, diff --git a/lib/membrane/pad.ex b/lib/membrane/pad.ex index e057e2fb8..3e054239f 100644 --- a/lib/membrane/pad.ex +++ b/lib/membrane/pad.ex @@ -66,8 +66,6 @@ defmodule Membrane.Pad do """ @type flow_control :: :auto | :manual | :push - @type effective_flow_control :: :push | :pull | :not_resolved - @typedoc """ Values used when defining pad availability: diff --git a/test/membrane/integration/effective_flow_control_resolution_test.exs b/test/membrane/integration/effective_flow_control_resolution_test.exs index 67c4564a4..bcb04a138 100644 --- a/test/membrane/integration/effective_flow_control_resolution_test.exs +++ b/test/membrane/integration/effective_flow_control_resolution_test.exs @@ -52,11 +52,7 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do child = {filter_type, idx} assert_pipeline_notified(pipeline, child, :playing) - - child_pid = Testing.Pipeline.get_child_pid!(pipeline, child) - child_state = :sys.get_state(child_pid) - - assert child_state.effective_flow_control == :not_resolved + assert_child_effective_flow_control(pipeline, child, :not_resolved) end Testing.Pipeline.execute_actions(pipeline, @@ -70,11 +66,17 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do for idx <- 0..10, filter_type <- [:filter_a, :filter_b] do child = {filter_type, idx} - child_pid = Testing.Pipeline.get_child_pid!(pipeline, child) - child_state = :sys.get_state(child_pid) - expected = if filter_type == :filter_a, do: :push, else: :pull - assert child_state.effective_flow_control == expected + + assert_child_effective_flow_control(pipeline, child, expected) end end + + defp assert_child_effective_flow_control(pipeline, child_name, expected) do + child_state = + Testing.Pipeline.get_child_pid!(pipeline, child_name) + |> :sys.get_state() + + assert child_state.effective_flow_control == expected + end end From 012574b3a7c8e44746c5f9c79b93ee2a52aabb6d Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 9 Mar 2023 12:01:41 +0100 Subject: [PATCH 12/64] Fix dialyzer issues --- .../core/element/effective_flow_controller.ex | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 6d05914fd..b05aed4c2 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -38,7 +38,7 @@ defmodule Membrane.Core.Element.EffectiveFlowController do @spec handle_other_effective_flow_control( Pad.ref(), - EffectiveFlowController.effective_flow_control(), + effective_flow_control(), State.t() ) :: State.t() @@ -83,14 +83,15 @@ defmodule Membrane.Core.Element.EffectiveFlowController do state = %{state | effective_flow_control: effective_flow_control} - if effective_flow_control != :not_resolved do - for {_ref, %{flow_control: :auto} = pad_data} <- state.pads_data do - Message.send(pad_data.pid, :other_effective_flow_control_resolved, [ - pad_data.other_ref, - effective_flow_control - ]) + _ignored = + if effective_flow_control != :not_resolved do + for {_ref, %{flow_control: :auto} = pad_data} <- state.pads_data do + Message.send(pad_data.pid, :other_effective_flow_control_resolved, [ + pad_data.other_ref, + effective_flow_control + ]) + end end - end with %{effective_flow_control: :pull} <- state do Enum.reduce(state.pads_data, state, fn From e5c8305956809bce5e7680e49005151ad0a8a00a Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 9 Mar 2023 12:21:48 +0100 Subject: [PATCH 13/64] Refactor Element test --- test/membrane/element_test.exs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/membrane/element_test.exs b/test/membrane/element_test.exs index cfa5fc620..2d0cd4eb5 100644 --- a/test/membrane/element_test.exs +++ b/test/membrane/element_test.exs @@ -75,16 +75,16 @@ defmodule Membrane.ElementTest do [pipeline: pipeline] end - test "play", %{pipeline: _pipeline} do + test "play", %{} do TestFilter.assert_callback_called(:handle_playing) end describe "Start of stream" do - test "causes handle_start_of_stream/3 to be called", %{pipeline: _pipeline} do + test "causes handle_start_of_stream/3 to be called", %{} do TestFilter.assert_callback_called(:handle_start_of_stream) end - test "does not trigger calling callback handle_event/3", %{pipeline: _pipeline} do + test "does not trigger calling callback handle_event/3", %{} do TestFilter.refute_callback_called(:handle_event) end @@ -94,11 +94,11 @@ defmodule Membrane.ElementTest do end describe "End of stream" do - test "causes handle_end_of_stream/3 to be called", %{pipeline: _pipeline} do + test "causes handle_end_of_stream/3 to be called", %{} do TestFilter.assert_callback_called(:handle_end_of_stream) end - test "does not trigger calling callback handle_event/3", %{pipeline: _pipeline} do + test "does not trigger calling callback handle_event/3", %{} do TestFilter.refute_callback_called(:handle_event) end From 70f184267254fd4c0bcc95465a9e9b1c40ff33d5 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 15 Mar 2023 12:07:20 +0100 Subject: [PATCH 14/64] Implement full version of auto-push --- .../core/element/buffer_controller.ex | 15 +- .../core/element/demand_controller.ex | 37 ++--- lib/membrane/core/element/demand_handler.ex | 27 ++-- .../core/element/effective_flow_controller.ex | 92 ++++++----- lib/membrane/core/element/input_queue.ex | 2 +- lib/membrane/core/element/pad_controller.ex | 77 +++++---- lib/membrane/core/element/playback_queue.ex | 5 +- lib/membrane/core/element/state.ex | 2 +- lib/membrane/core/element/toilet.ex | 75 ++++++--- lib/membrane/element/pad_data.ex | 6 +- .../core/element/action_handler_test.exs | 10 +- test/membrane/core/element/toilet_test.exs | 6 +- .../integration/child_pad_removed_test.exs | 1 - ...effective_flow_control_resolution_test.exs | 148 +++++++++++++++++- test/membrane/testing/dynamic_source_test.exs | 16 +- 15 files changed, 348 insertions(+), 171 deletions(-) diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index 2d4874a52..5890eeee4 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -14,7 +14,6 @@ defmodule Membrane.Core.Element.BufferController do CallbackContext, DemandController, DemandHandler, - EffectiveFlowController, EventController, InputQueue, PlaybackQueue, @@ -62,18 +61,8 @@ defmodule Membrane.Core.Element.BufferController do %{demand: demand, demand_unit: demand_unit} = data buf_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) - state = - EffectiveFlowController.pad_effective_flow_control(pad_ref, state) - |> case do - :push -> - if data.toilet, do: Toilet.drain(data.toilet, buf_size) - state - - :pull -> - state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) - DemandController.send_auto_demand_if_needed(pad_ref, state) - end - + state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) + state = DemandController.send_auto_demand_if_needed(pad_ref, state) exec_buffer_callback(pad_ref, buffers, state) end diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index ae811115c..6404cf483 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -20,10 +20,8 @@ defmodule Membrane.Core.Element.DemandController do def handle_demand(pad_ref, size, state) do withl pad: {:ok, data} <- PadModel.get_data(state, pad_ref), playback: %State{playback: :playing} <- state do - %{direction: :output, flow_control: flow_control} = data - - if flow_control == :push, - do: raise("Pad with :push control mode cannot handle demand.") + if data.direction == :input, + do: raise("Input pad cannot handle demand.") do_handle_demand(pad_ref, size, data, state) else @@ -71,6 +69,10 @@ defmodule Membrane.Core.Element.DemandController do end end + defp do_handle_demand(_pad_ref, _size, %{flow_control: :push} = _data, state) do + state + end + @doc """ Sends auto demand to an input pad if it should be sent. @@ -79,10 +81,11 @@ defmodule Membrane.Core.Element.DemandController do associated output pads. """ @spec send_auto_demand_if_needed(Pad.ref(), State.t()) :: State.t() - def send_auto_demand_if_needed(pad_ref, %{effective_flow_control: :pull} = state) do + def send_auto_demand_if_needed(pad_ref, state) do data = PadModel.get_data!(state, pad_ref) %{ + flow_control: :auto, demand: demand, toilet: toilet, associated_pads: associated_pads, @@ -90,19 +93,17 @@ defmodule Membrane.Core.Element.DemandController do } = data demand = - if demand <= div(demand_request_size, 2) and auto_demands_positive?(associated_pads, state) do - if toilet != nil and data.other_effective_flow_control in [:push, :not_resolved] do - Toilet.drain(toilet, demand_request_size - demand) - end + if demand <= div(demand_request_size, 2) and + (state.effective_flow_control == :push or + auto_demands_positive?(associated_pads, state)) do + Membrane.Logger.debug_verbose( + "Sending auto demand of size #{demand_request_size - demand} on pad #{inspect(pad_ref)}" + ) - if data.other_effective_flow_control in [:pull, :not_resolved] do - Membrane.Logger.debug_verbose( - "Sending auto demand of size #{demand_request_size - demand} on pad #{inspect(pad_ref)}" - ) + %{pid: pid, other_ref: other_ref} = data + Message.send(pid, :demand, demand_request_size - demand, for_pad: other_ref) - %{pid: pid, other_ref: other_ref} = data - Message.send(pid, :demand, demand_request_size - demand, for_pad: other_ref) - end + if toilet, do: Toilet.drain(toilet, demand_request_size - demand) demand_request_size else @@ -116,10 +117,6 @@ defmodule Membrane.Core.Element.DemandController do PadModel.set_data!(state, pad_ref, :demand, demand) end - def send_auto_demand_if_needed(_pad_ref, state) do - state - end - defp auto_demands_positive?(associated_pads, state) do Enum.all?(associated_pads, &(PadModel.get_data!(state, &1, :demand) > 0)) end diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 08e1e03db..0f91ad1b1 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -9,7 +9,6 @@ defmodule Membrane.Core.Element.DemandHandler do alias Membrane.Core.Element.{ BufferController, DemandController, - EffectiveFlowController, EventController, InputQueue, State, @@ -104,22 +103,22 @@ defmodule Membrane.Core.Element.DemandHandler do [Buffer.t()], State.t() ) :: State.t() - def handle_outgoing_buffers(pad_ref, data, buffers, state) do - EffectiveFlowController.pad_effective_flow_control(pad_ref, state) - |> do_handle_outgoing_buffers(pad_ref, data, buffers, state) - end - - defp do_handle_outgoing_buffers(:pull, pad_ref, data, buffers, state) do + def handle_outgoing_buffers(pad_ref, %{flow_control: flow_control} = data, buffers, state) + when flow_control in [:auto, :manual] do %{other_demand_unit: other_demand_unit, demand: demand} = data buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) - PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) + state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) + fill_toilet_if_exists(pad_ref, data.toilet, buf_size, state) end - defp do_handle_outgoing_buffers(:push, pad_ref, data, buffers, state) when data.toilet != nil do + def handle_outgoing_buffers(pad_ref, %{flow_control: :push} = data, buffers, state) do %{other_demand_unit: other_demand_unit} = data buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) + fill_toilet_if_exists(pad_ref, data.toilet, buf_size, state) + end - case Toilet.fill(data.toilet, buf_size) do + defp fill_toilet_if_exists(pad_ref, toilet, buf_size, state) when toilet != nil do + case Toilet.fill(toilet, buf_size) do {:ok, toilet} -> PadModel.set_data!(state, pad_ref, :toilet, toilet) @@ -130,13 +129,7 @@ defmodule Membrane.Core.Element.DemandHandler do end end - defp do_handle_outgoing_buffers(:push, _ref, _data, _buffers, state) do - state - end - - defp do_handle_outgoing_buffers(:not_resolved, _ref, _data, _buffers, _state) do - raise "not implemented yet" - end + defp fill_toilet_if_exists(_pad_ref, nil, _buf_size, state), do: state defp update_demand(pad_ref, size, state) when is_integer(size) do PadModel.set_data!(state, pad_ref, :demand, size) diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index b05aed4c2..6e74cfa8b 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -1,6 +1,8 @@ defmodule Membrane.Core.Element.EffectiveFlowController do @moduledoc false + alias Membrane.Core.Element.Toilet + alias Membrane.Core.Element.{ DemandController, State @@ -8,9 +10,10 @@ defmodule Membrane.Core.Element.EffectiveFlowController do require Membrane.Core.Child.PadModel, as: PadModel require Membrane.Core.Message, as: Message + require Membrane.Logger require Membrane.Pad, as: Pad - @type effective_flow_control :: :push | :pull | :not_resolved + @type effective_flow_control :: :push | :pull @spec pad_effective_flow_control(Pad.ref(), State.t()) :: effective_flow_control() def pad_effective_flow_control(pad_ref, state) do @@ -44,65 +47,76 @@ defmodule Membrane.Core.Element.EffectiveFlowController do State.t() def handle_other_effective_flow_control(my_pad_ref, other_effective_flow_control, state) do pad_data = PadModel.get_data!(state, my_pad_ref) - pad_data = %{pad_data | other_effective_flow_control: other_effective_flow_control} state = PadModel.set_data!(state, my_pad_ref, pad_data) - if state.effective_flow_control == :push and pad_data.direction == :input and - other_effective_flow_control == :pull do - # TODO: implement this - raise "not implemented yet" - end - - with :playing <- state.playback, - %{direction: :input, flow_control: :auto} <- pad_data, - mode when mode in [:push, :pull] <- other_effective_flow_control do + if state.playback == :playing and pad_data.direction == :input and + pad_data.flow_control == :auto and + other_effective_flow_control != state.effective_flow_control do resolve_effective_flow_control(state) else - _other -> state + state end end @spec resolve_effective_flow_control(State.t()) :: State.t() - def resolve_effective_flow_control(%State{effective_flow_control: :not_resolved} = state) do - input_auto_pads = + def resolve_effective_flow_control(state) do + senders_flow_modes = Map.values(state.pads_data) |> Enum.filter(&(&1.direction == :input && &1.flow_control == :auto)) + |> Enum.map(& &1.other_effective_flow_control) - effective_flow_control = + new_effective_flow_control = cond do - Enum.any?(input_auto_pads, &(&1.other_effective_flow_control == :pull)) -> - :pull + Enum.member?(senders_flow_modes, :pull) -> :pull + Enum.member?(senders_flow_modes, :push) -> :push + true -> state.effective_flow_control + end - Enum.any?(input_auto_pads, &(&1.other_effective_flow_control == :push)) -> - :push + set_effective_flow_control(new_effective_flow_control, state) + end - true -> - :not_resolved - end + defp set_effective_flow_control( + effective_flow_control, + %{effective_flow_control: effective_flow_control} = state + ), + do: state + + defp set_effective_flow_control(new_effective_flow_control, state) do + Membrane.Logger.debug( + "Transiting `flow_control: :auto` pads to #{inspect(new_effective_flow_control)} effective flow control" + ) - state = %{state | effective_flow_control: effective_flow_control} + state = %{state | effective_flow_control: new_effective_flow_control} _ignored = - if effective_flow_control != :not_resolved do - for {_ref, %{flow_control: :auto} = pad_data} <- state.pads_data do - Message.send(pad_data.pid, :other_effective_flow_control_resolved, [ - pad_data.other_ref, - effective_flow_control - ]) + for {_ref, %{flow_control: :auto} = pad_data} <- state.pads_data do + Message.send(pad_data.pid, :other_effective_flow_control_resolved, [ + pad_data.other_ref, + new_effective_flow_control + ]) + + case pad_data.direction do + :input -> + Toilet.set_receiver_effective_flow_control( + pad_data.toilet, + new_effective_flow_control + ) + + :output -> + Toilet.set_sender_effective_flow_control( + pad_data.toilet, + new_effective_flow_control + ) end end - with %{effective_flow_control: :pull} <- state do - Enum.reduce(state.pads_data, state, fn - {pad_ref, %{flow_control: :auto, direction: :input}}, state -> - DemandController.send_auto_demand_if_needed(pad_ref, state) + Enum.reduce(state.pads_data, state, fn + {pad_ref, %{flow_control: :auto, direction: :input}}, state -> + DemandController.send_auto_demand_if_needed(pad_ref, state) - _pad_entry, state -> - state - end) - end + _pad_entry, state -> + state + end) end - - def resolve_effective_flow_control(state), do: state end diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index fe95e5e57..a7fb09167 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -310,7 +310,7 @@ defmodule Membrane.Core.Element.InputQueue do Sending demand of size #{inspect(to_demand)} to output #{inspect(linked_output_ref)} """ |> mk_log(input_queue) - |> Membrane.Logger.debug_verbose() + |> Membrane.Logger.debug() Message.send(demand_pid, :demand, to_demand, for_pad: linked_output_ref) %__MODULE__{input_queue | demand: demand - to_demand} diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 1844039b1..aec1e6d76 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -121,7 +121,7 @@ defmodule Membrane.Core.Element.PadController do other_endpoint, info, props.stream_format_validation_params, - :not_resolved, + :push, other_info, link_metadata, state @@ -171,15 +171,18 @@ defmodule Membrane.Core.Element.PadController do Map.put(link_metadata, :input_demand_unit, input_demand_unit) |> Map.put(:output_demand_unit, output_demand_unit) + my_effective_flow_control = + EffectiveFlowController.pad_effective_flow_control(endpoint.pad_ref, state) + toilet = - if input_demand_unit != nil, - do: - Toilet.new( - input_endpoint.pad_props.toilet_capacity, - input_demand_unit, - self(), - input_endpoint.pad_props.throttling_factor - ) + Toilet.new( + input_endpoint.pad_props.toilet_capacity, + input_demand_unit || :buffers, + self(), + input_endpoint.pad_props.throttling_factor, + other_effective_flow_control, + my_effective_flow_control + ) # The sibiling was an initiator, we don't need to use the pid of a task spawned for observability _metadata = Observability.setup_link(endpoint.pad_ref, link_metadata.observability_metadata) @@ -223,7 +226,14 @@ defmodule Membrane.Core.Element.PadController do state = generate_eos_if_needed(pad_ref, state) state = maybe_handle_pad_removed(pad_ref, state) state = remove_pad_associations(pad_ref, state) - PadModel.delete_data!(state, pad_ref) + {pad_data, state} = PadModel.pop_data!(state, pad_ref) + + with %{direction: :input, flow_control: :auto, other_effective_flow_control: :pull} <- + pad_data do + EffectiveFlowController.resolve_effective_flow_control(state) + else + _pad_data -> state + end else {:ok, %{availability: :always}} when state.terminating? -> state @@ -285,7 +295,8 @@ defmodule Membrane.Core.Element.PadController do stream_format: nil, start_of_stream?: false, end_of_stream?: false, - associated_pads: [] + associated_pads: [], + toilet: metadata.toilet }) data = data |> Map.merge(init_pad_direction_data(data, endpoint.pad_props, metadata, state)) @@ -328,7 +339,7 @@ defmodule Membrane.Core.Element.PadController do %{direction: :input, flow_control: :manual} = data, props, other_info, - metadata, + _metadata, %State{} ) do %{ref: ref, pid: pid, other_ref: other_ref, demand_unit: this_demand_unit} = data @@ -347,7 +358,7 @@ defmodule Membrane.Core.Element.PadController do min_demand_factor: props.min_demand_factor }) - %{input_queue: input_queue, demand: 0, toilet: metadata.toilet} + %{input_queue: input_queue, demand: 0} end defp init_pad_mode_data( @@ -363,8 +374,8 @@ defmodule Membrane.Core.Element.PadController do defp init_pad_mode_data( %{flow_control: :auto, direction: direction}, props, - other_info, - metadata, + _other_info, + _metadata, %State{} = state ) do associated_pads = @@ -373,12 +384,12 @@ defmodule Membrane.Core.Element.PadController do |> Enum.filter(&(&1.direction != direction and &1.flow_control == :auto)) |> Enum.map(& &1.ref) - toilet = - if direction == :input and other_info.flow_control == :push do - metadata.toilet - else - nil - end + # toilet = + # if direction == :input and other_info.flow_control == :push do + # metadata.toilet + # else + # nil + # end auto_demand_size = if direction == :input do @@ -392,21 +403,21 @@ defmodule Membrane.Core.Element.PadController do %{ demand: 0, associated_pads: associated_pads, - auto_demand_size: auto_demand_size, - toilet: toilet + auto_demand_size: auto_demand_size + # toilet: toilet } end - defp init_pad_mode_data( - %{flow_control: :push, direction: :output}, - _props, - %{flow_control: other_flow_control}, - metadata, - _state - ) - when other_flow_control in [:auto, :manual] do - %{toilet: metadata.toilet} - end + # defp init_pad_mode_data( + # %{flow_control: :push, direction: :output}, + # _props, + # %{flow_control: other_flow_control}, + # metadata, + # _state + # ) + # when other_flow_control in [:auto, :manual] do + # %{toilet: metadata.toilet} + # end defp init_pad_mode_data(_data, _props, _other_info, _metadata, _state), do: %{} diff --git a/lib/membrane/core/element/playback_queue.ex b/lib/membrane/core/element/playback_queue.ex index 52f6ad91d..495a2d09e 100644 --- a/lib/membrane/core/element/playback_queue.ex +++ b/lib/membrane/core/element/playback_queue.ex @@ -12,9 +12,12 @@ defmodule Membrane.Core.Element.PlaybackQueue do @spec eval(State.t()) :: State.t() def eval(%State{playback_queue: playback_queue} = state) do + require Membrane.Logger + state = playback_queue - |> List.foldr(state, fn function, state -> function.(state) end) + |> Enum.reverse() + |> Enum.reduce(state, fn function, state -> function.(state) end) %State{state | playback_queue: []} end diff --git a/lib/membrane/core/element/state.ex b/lib/membrane/core/element/state.ex index 31b953050..e8faf5143 100644 --- a/lib/membrane/core/element/state.ex +++ b/lib/membrane/core/element/state.ex @@ -98,7 +98,7 @@ defmodule Membrane.Core.Element.State do subprocess_supervisor: options.subprocess_supervisor, terminating?: false, setup_incomplete?: false, - effective_flow_control: :not_resolved + effective_flow_control: :push } |> PadSpecHandler.init_pads() end diff --git a/lib/membrane/core/element/toilet.ex b/lib/membrane/core/element/toilet.ex index 3417fa2d6..f0b5c8501 100644 --- a/lib/membrane/core/element/toilet.ex +++ b/lib/membrane/core/element/toilet.ex @@ -119,13 +119,11 @@ defmodule Membrane.Core.Element.Toilet do :atomics.put(atomic_ref, 1, value) end - defp int_to_effective_flow_control(0), do: :not_resolved - defp int_to_effective_flow_control(1), do: :push - defp int_to_effective_flow_control(2), do: :pull + defp int_to_effective_flow_control(0), do: :push + defp int_to_effective_flow_control(1), do: :pull - defp effective_flow_control_to_int(:not_resolved), do: 0 - defp effective_flow_control_to_int(:push), do: 1 - defp effective_flow_control_to_int(:pull), do: 2 + defp effective_flow_control_to_int(:push), do: 0 + defp effective_flow_control_to_int(:pull), do: 1 end @type t :: %__MODULE__{ @@ -134,7 +132,8 @@ defmodule Membrane.Core.Element.Toilet do responsible_process: Process.dest(), throttling_factor: pos_integer(), unrinsed_buffers_size: non_neg_integer(), - receiver_effective_flow_control: DistributedEffectiveFlowControl.t() + receiver_effective_flow_control: DistributedEffectiveFlowControl.t(), + sender_effective_flow_control: DistributedEffectiveFlowControl.t() } @enforce_keys [ @@ -142,7 +141,8 @@ defmodule Membrane.Core.Element.Toilet do :capacity, :responsible_process, :throttling_factor, - :receiver_effective_flow_control + :receiver_effective_flow_control, + :sender_effective_flow_control ] defstruct @enforce_keys ++ [unrinsed_buffers_size: 0] @@ -154,14 +154,16 @@ defmodule Membrane.Core.Element.Toilet do demand_unit :: Membrane.Buffer.Metric.unit(), responsible_process :: Process.dest(), throttling_factor :: pos_integer(), - receiver_effective_flow_control :: EffectiveFlowController.effective_flow_control() + receiver_effective_flow_control :: EffectiveFlowController.effective_flow_control(), + sender_effective_flow_control :: EffectiveFlowController.effective_flow_control() ) :: t def new( capacity, demand_unit, responsible_process, throttling_factor, - receiver_effective_flow_control \\ :not_resolved + sender_effective_flow_control, + receiver_effective_flow_control ) do default_capacity = Membrane.Buffer.Metric.from_unit(demand_unit).buffer_size_approximation() * @@ -173,12 +175,17 @@ defmodule Membrane.Core.Element.Toilet do receiver_effective_flow_control |> DistributedEffectiveFlowControl.new() + sender_effective_flow_control = + sender_effective_flow_control + |> DistributedEffectiveFlowControl.new() + %__MODULE__{ counter: DistributedCounter.new(), capacity: capacity, responsible_process: responsible_process, throttling_factor: throttling_factor, - receiver_effective_flow_control: receiver_effective_flow_control + receiver_effective_flow_control: receiver_effective_flow_control, + sender_effective_flow_control: sender_effective_flow_control } end @@ -191,6 +198,15 @@ defmodule Membrane.Core.Element.Toilet do ) end + @spec set_sender_effective_flow_control(t, EffectiveFlowController.effective_flow_control()) :: + :ok + def set_sender_effective_flow_control(%__MODULE__{} = toilet, value) do + DistributedEffectiveFlowControl.put( + toilet.sender_effective_flow_control, + value + ) + end + @spec fill(t, non_neg_integer) :: {:ok | :overflow, t} def fill(%__MODULE__{} = toilet, amount) do new_unrinsed_buffers_size = toilet.unrinsed_buffers_size + amount @@ -200,23 +216,36 @@ defmodule Membrane.Core.Element.Toilet do else size = DistributedCounter.add_get(toilet.counter, new_unrinsed_buffers_size) - toilet.receiver_effective_flow_control - |> DistributedEffectiveFlowControl.get() - |> case do - :pull when size > toilet.capacity -> - overflow(size, toilet.capacity, toilet.responsible_process) - {:overflow, %{toilet | unrinsed_buffers_size: 0}} + %{toilet | unrinsed_buffers_size: 0} + |> check_overflow(size) + end + end - :not_resolved when size > 10 * toilet.capacity -> - overflow(size, 10 * toilet.capacity, toilet.responsible_process) - {:overflow, %{toilet | unrinsed_buffers_size: 0}} + @spec check_overflow(t, integer) :: {:ok | :overflow, t} + defp check_overflow(%__MODULE__{} = toilet, size) do + endpoints_effective_flow_control(toilet) + |> case do + %{sender: :push, receiver: :pull} when size > toilet.capacity -> + overflow(size, toilet.capacity, toilet.responsible_process) + {:overflow, toilet} - _ok -> - {:ok, %{toilet | unrinsed_buffers_size: 0}} - end + %{} -> + {:ok, toilet} end end + @spec endpoints_effective_flow_control(t) :: map() + defp endpoints_effective_flow_control(%__MODULE__{} = toilet) do + %{ + sender: + toilet.sender_effective_flow_control + |> DistributedEffectiveFlowControl.get(), + receiver: + toilet.receiver_effective_flow_control + |> DistributedEffectiveFlowControl.get() + } + end + @spec drain(t, non_neg_integer) :: :ok def drain(%__MODULE__{} = toilet, amount) do DistributedCounter.sub(toilet.counter, amount) diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 363d6a146..9aa24efd2 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -31,7 +31,6 @@ defmodule Membrane.Element.PadData do end_of_stream?: boolean(), direction: Pad.direction(), flow_control: Pad.flow_control(), - other_effective_flow_control: private_field(), name: Pad.name(), ref: Pad.ref(), options: %{optional(atom) => any}, @@ -46,7 +45,8 @@ defmodule Membrane.Element.PadData do sticky_messages: private_field, toilet: private_field, associated_pads: private_field, - sticky_events: private_field + sticky_events: private_field, + other_effective_flow_control: private_field } @enforce_keys [ @@ -75,6 +75,6 @@ defmodule Membrane.Element.PadData do sticky_events: [], stream_format_validation_params: [], other_demand_unit: nil, - other_effective_flow_control: :not_resolved + other_effective_flow_control: :push ] end diff --git a/test/membrane/core/element/action_handler_test.exs b/test/membrane/core/element/action_handler_test.exs index 9cfc601d8..d363ffbf6 100644 --- a/test/membrane/core/element/action_handler_test.exs +++ b/test/membrane/core/element/action_handler_test.exs @@ -2,7 +2,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do use ExUnit.Case, async: true alias Membrane.{ActionError, Buffer, ElementError, PadDirectionError} - alias Membrane.Core.Element.State + alias Membrane.Core.Element.{State, Toilet} alias Membrane.Support.DemandsTest.Filter alias Membrane.Support.Element.{TrivialFilter, TrivialSource} @@ -72,6 +72,8 @@ defmodule Membrane.Core.Element.ActionHandlerTest do end defp trivial_filter_state(_context) do + toilet = Toilet.new(100, :buffers, spawn(fn -> nil end), 1, :push, :push) + state = struct(State, module: TrivialFilter, @@ -89,7 +91,8 @@ defmodule Membrane.Core.Element.ActionHandlerTest do other_demand_unit: :bytes, start_of_stream?: true, end_of_stream?: false, - flow_control: :push + flow_control: :push, + toilet: toilet }, input: %{ direction: :input, @@ -98,7 +101,8 @@ defmodule Membrane.Core.Element.ActionHandlerTest do stream_format: nil, start_of_stream?: true, end_of_stream?: false, - flow_control: :push + flow_control: :push, + toilet: :push } }, pads_info: %{ diff --git a/test/membrane/core/element/toilet_test.exs b/test/membrane/core/element/toilet_test.exs index c5f9e4165..5ac606277 100644 --- a/test/membrane/core/element/toilet_test.exs +++ b/test/membrane/core/element/toilet_test.exs @@ -7,7 +7,7 @@ defmodule Membrane.Core.Element.ToiletTest do end test "if toilet is implemented as :atomics for elements put on the same node", context do - toilet = Toilet.new(100, :buffers, context.responsible_process, 1, :pull) + toilet = Toilet.new(100, :buffers, context.responsible_process, 1, :push, :pull) %Toilet{counter: {_pid, atomic_ref}} = toilet @@ -19,7 +19,7 @@ defmodule Membrane.Core.Element.ToiletTest do test "if the receiving element uses toilet with :atomics and the sending element with a interprocess message, when the toilet is distributed", context do - toilet = Toilet.new(100, :buffers, context.responsible_process, 1, :pull) + toilet = Toilet.new(100, :buffers, context.responsible_process, 1, :push, :pull) %Toilet{counter: {counter_pid, atomic_ref}} = toilet @@ -32,7 +32,7 @@ defmodule Membrane.Core.Element.ToiletTest do end test "if throttling mechanism works properly", context do - toilet = Toilet.new(100, :buffers, context.responsible_process, 10, :pull) + toilet = Toilet.new(100, :buffers, context.responsible_process, 10, :push, :pull) {:ok, toilet} = Toilet.fill(toilet, 10) assert toilet.unrinsed_buffers_size == 0 diff --git a/test/membrane/integration/child_pad_removed_test.exs b/test/membrane/integration/child_pad_removed_test.exs index 5eb2b629f..5b29d833b 100644 --- a/test/membrane/integration/child_pad_removed_test.exs +++ b/test/membrane/integration/child_pad_removed_test.exs @@ -181,7 +181,6 @@ defmodule Membrane.Integration.ChildPadRemovedTest do end end - @tag :target test "and sibling linked via static pad is removed, pipeline is not raising" do for bin_actions <- [ [remove_children: :source], diff --git a/test/membrane/integration/effective_flow_control_resolution_test.exs b/test/membrane/integration/effective_flow_control_resolution_test.exs index bcb04a138..a6bd94d3c 100644 --- a/test/membrane/integration/effective_flow_control_resolution_test.exs +++ b/test/membrane/integration/effective_flow_control_resolution_test.exs @@ -6,19 +6,29 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do alias Membrane.Testing + require Membrane.Child, as: Child + defmodule DynamicFilter do use Membrane.Filter def_input_pad :input, availability: :on_request, accepted_format: _any def_output_pad :output, availability: :on_request, accepted_format: _any + def_options lazy?: [spec: boolean(), default: false] + @impl true def handle_playing(_ctx, state) do {[notify_parent: :playing], state} end + + @impl true + def handle_buffer(_pad, buffer, _ctx, state) do + if state.lazy?, do: Process.sleep(100) + {[forward: buffer], state} + end end - defmodule DynamicSource do + defmodule DoubleFlowControlSource do use Membrane.Source def_output_pad :push_output, accepted_format: _any, flow_control: :push @@ -28,6 +38,40 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do def handle_demand(_pad, _size, _unit, _ctx, state), do: {[], state} end + defmodule PushSource do + use Membrane.Source + + def_output_pad :output, accepted_format: _any, flow_control: :push + end + + defmodule PullSource do + use Membrane.Source + + def_output_pad :output, accepted_format: _any, flow_control: :manual + + @impl true + def handle_demand(_pad, _size, _unit, _ctx, state), do: {[], state} + end + + defmodule BatchingSource do + use Membrane.Source + + def_output_pad :output, accepted_format: _any, flow_control: :push + + @impl true + def handle_playing(_ctx, state) do + # buffers_barch is bigger than sum of default toilet capacity and default initial auto demand + buffers_batch = Enum.map(1..5_000, &%Membrane.Buffer{payload: inspect(&1)}) + + actions = [ + stream_format: {:output, %Membrane.StreamFormat.Mock{}}, + buffer: {:output, buffers_batch} + ] + + {actions, state} + end + end + test "effective_flow_control is properly resolved in simple scenario" do spec_beggining = [ child({:filter_a, 0}, DynamicFilter), @@ -52,12 +96,14 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do child = {filter_type, idx} assert_pipeline_notified(pipeline, child, :playing) - assert_child_effective_flow_control(pipeline, child, :not_resolved) + assert_child_effective_flow_control(pipeline, child, :push) end Testing.Pipeline.execute_actions(pipeline, spec: [ - child(:source, DynamicSource) |> via_out(:push_output) |> get_child({:filter_a, 0}), + child(:source, DoubleFlowControlSource) + |> via_out(:push_output) + |> get_child({:filter_a, 0}), get_child(:source) |> via_out(:pull_output) |> get_child({:filter_b, 0}) ] ) @@ -72,6 +118,102 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do end end + test "effective_flow_control switches between :push and :pull" do + spec = + Enum.reduce( + 1..10, + child(:push_source, PushSource), + fn idx, last_child -> last_child |> child({:filter, idx}, DynamicFilter) end + ) + + pipeline = Testing.Pipeline.start_link_supervised!(spec: spec) + + Process.sleep(500) + + for _i <- 1..5 do + Testing.Pipeline.execute_actions(pipeline, + spec: child(:pull_source, PullSource) |> get_child({:filter, 1}) + ) + + Process.sleep(500) + + for idx <- 1..10 do + assert_child_effective_flow_control(pipeline, {:filter, idx}, :pull) + end + + Testing.Pipeline.execute_actions(pipeline, remove_children: :pull_source) + Process.sleep(500) + + for idx <- 1..10 do + assert_child_effective_flow_control(pipeline, {:filter, idx}, :push) + end + end + + Testing.Pipeline.terminate(pipeline) + end + + test "effective_flow_control is :pull, only when it should be" do + spec = [ + child(:push_source, PushSource) + |> child(:filter_1, DynamicFilter) + |> child(:filter_2, DynamicFilter), + child(:pull_source, PullSource) + |> get_child(:filter_2) + ] + + pipeline = Testing.Pipeline.start_link_supervised!(spec: spec) + + Process.sleep(500) + + assert_child_effective_flow_control(pipeline, :filter_1, :push) + assert_child_effective_flow_control(pipeline, :filter_2, :pull) + end + + test "Toilet does not overflow, when input pad effective flow control is :push" do + spec = + child(:source, BatchingSource) + |> child(:filter, DynamicFilter) + + pipeline = Testing.Pipeline.start_supervised!(spec: spec) + Process.sleep(500) + + child_pid = Testing.Pipeline.get_child_pid!(pipeline, :filter) + assert Process.alive?(child_pid) + + Testing.Pipeline.terminate(pipeline) + end + + test "Toilet overflows, when it should" do + spec = { + child(:pull_source, PullSource) + |> child(:filter, %DynamicFilter{lazy?: true}), + group: :group, crash_group_mode: :temporary + } + + pipeline = Testing.Pipeline.start_link_supervised!(spec: spec) + Process.sleep(500) + + filter_ref = Child.ref(:filter, group: :group) + assert_child_effective_flow_control(pipeline, filter_ref, :pull) + + monitor_ref = + Testing.Pipeline.get_child_pid!(pipeline, filter_ref) + |> Process.monitor() + + Testing.Pipeline.execute_actions(pipeline, + spec: { + child(:batching_source, BatchingSource) + |> get_child(filter_ref), + group: :another_group, crash_group_mode: :temporary + } + ) + + # batch of buffers sent by BatchingSource should cause toilet overflow + assert_receive {:DOWN, ^monitor_ref, _process, _pid, :killed} + assert_pipeline_crash_group_down(pipeline, :group) + Testing.Pipeline.terminate(pipeline) + end + defp assert_child_effective_flow_control(pipeline, child_name, expected) do child_state = Testing.Pipeline.get_child_pid!(pipeline, child_name) diff --git a/test/membrane/testing/dynamic_source_test.exs b/test/membrane/testing/dynamic_source_test.exs index a95a87eab..1abdc69c0 100644 --- a/test/membrane/testing/dynamic_source_test.exs +++ b/test/membrane/testing/dynamic_source_test.exs @@ -50,16 +50,12 @@ defmodule Membrane.Testing.DynamicSourceTest do test "Source works properly when using generator function" do pipeline = Testing.Pipeline.start_link_supervised!( - spec: - [ - child(:source, Testing.DynamicSource), - child(:sink_1, Testing.Sink), - child(:sink_2, Testing.Sink) - ] ++ - [ - get_child(:source) |> get_child(:sink_1), - get_child(:source) |> get_child(:sink_2) - ] + spec: [ + child(:source, Testing.DynamicSource) + |> child(:sink_1, Testing.Sink), + get_child(:source) + |> child(:sink_2, Testing.Sink) + ] ) assert_sink_buffer(pipeline, :sink_1, %Buffer{payload: <<0::16>>}) From 38ff2a7e6a4d59d0c5be74cd1b204642f2d5cded Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 17 Mar 2023 14:44:57 +0100 Subject: [PATCH 15/64] Implement DemandCounter --- lib/membrane/core/element/demand_counter.ex | 286 ++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 lib/membrane/core/element/demand_counter.ex diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex new file mode 100644 index 000000000..74cce1931 --- /dev/null +++ b/lib/membrane/core/element/demand_counter.ex @@ -0,0 +1,286 @@ +defmodule Membrane.Core.Element.DemandCounter do + @moduledoc false + alias Membrane.Core.Element.EffectiveFlowController + + require Membrane.Logger + + defmodule Worker do + @moduledoc false + + # This is a GenServer created when the counter is about to be accessed from different nodes - it's running on the same node, + # where the :atomics variable is put, and processes from different nodes can ask it to modify the counter on their behalf. + + use GenServer + + @impl true + def init(parent_pid) do + Process.monitor(parent_pid) + {:ok, nil, :hibernate} + end + + @impl true + def handle_call({:add_get, atomic_ref, value}, _from, _state) do + result = :atomics.add_get(atomic_ref, 1, value) + {:reply, result, nil} + end + + @impl true + def handle_call({:sub_get, atomic_ref, value}, _from, _state) do + result = :atomics.sub_get(atomic_ref, 1, value) + {:sub_get, result, nil} + end + + @impl true + def handle_call({:get, atomic_ref}, _from, _state) do + result = :atomics.get(atomic_ref, 1) + {:sub_get, result, nil} + end + + @impl true + def handle_cast({:put, atomic_ref, value}, _state) do + :atomics.put(atomic_ref, 1, value) + {:noreply, nil} + end + + @impl true + def handle_info({:DOWN, _ref, :process, _object, _reason}, state) do + {:stop, :normal, state} + end + end + + defmodule DistributedAtomic do + @moduledoc false + + # A module providing a common interface to access and modify a counter used in the toilet implementation. + # The counter uses :atomics module under the hood. + # The module allows to create and modify the value of a counter in the same manner both when the counter is about to be accessed + # from the same node, and from different nodes. + + @type t :: {pid(), :atomics.atomics_ref()} + + @spec new(integer() | nil) :: t + def new(initial_value \\ nil) do + atomic_ref = :atomics.new(1, []) + {:ok, pid} = GenServer.start(Worker, self()) + if initial_value, do: put({pid, atomic_ref}, initial_value) + + {pid, atomic_ref} + end + + @spec add_get(t, integer()) :: integer() + def add_get({pid, atomic_ref}, value) when node(pid) == node(self()) do + :atomics.add_get(atomic_ref, 1, value) + end + + def add_get({pid, atomic_ref}, value) do + GenServer.call(pid, {:add_get, atomic_ref, value}) + end + + @spec sub_get(t, integer()) :: integer() + def sub_get({pid, atomic_ref}, value) when node(pid) == node(self()) do + :atomics.sub_get(atomic_ref, 1, value) + end + + def sub_get({pid, atomic_ref}, value) do + GenServer.cast(pid, {:sub_get, atomic_ref, value}) + end + + @spec put(t, integer()) :: :ok + def put({pid, atomic_ref}, value) when node(pid) == node(self()) do + :atomics.put(atomic_ref, 1, value) + end + + def put({pid, atomic_ref}, value) do + GenServer.cast(pid, {:put, atomic_ref, value}) + end + + @spec get(t) :: integer() + def get({pid, atomic_ref}) when node(pid) == node(self()) do + :atomics.get(atomic_ref, 1) + end + + def get({pid, atomic_ref}) do + GenServer.call(pid, {:get, atomic_ref}) + end + end + + defmodule DistributedEffectiveFlowControl do + @moduledoc false + + @type t :: DistributedAtomic.t() + + @spec new(EffectiveFlowController.effective_flow_control()) :: t + def new(initial_value) do + initial_value + |> effective_flow_control_to_int() + |> DistributedAtomic.new() + end + + @spec get(t) :: EffectiveFlowController.effective_flow_control() + def get(distributed_atomic) do + distributed_atomic + |> DistributedAtomic.get() + |> int_to_effective_flow_control() + end + + @spec put(t, EffectiveFlowController.effective_flow_control()) :: :ok + def put(distributed_atomic, value) do + value = effective_flow_control_to_int(value) + DistributedAtomic.put(distributed_atomic, value) + end + + defp int_to_effective_flow_control(0), do: :not_resolved + defp int_to_effective_flow_control(1), do: :push + defp int_to_effective_flow_control(2), do: :pull + + defp effective_flow_control_to_int(:not_resolved), do: 0 + defp effective_flow_control_to_int(:push), do: 1 + defp effective_flow_control_to_int(:pull), do: 2 + end + + @default_overflow_limit_factor -200 + @default_buffered_decrementation_limit 1 + # @default_capacity_factor 200 + + @type t :: %__MODULE__{ + counter: DistributedAtomic.t(), + receiver_mode: DistributedEffectiveFlowControl.t(), + receiver_process: Process.dest(), + overflow_limit: neg_integer(), + buffered_decrementation: non_neg_integer(), + buffered_decrementation_limit: pos_integer() + } + + @enforce_keys [ + :counter, + :receiver_mode, + :receiver_process + ] + + defstruct @enforce_keys ++ + [ + overflow_limit: -300, + buffered_decrementation: 0, + buffered_decrementation_limit: 1 + ] + + @spec new( + receiver_mode :: EffectiveFlowController.effective_flow_control(), + receiver_process :: Process.dest(), + receiver_demand_unit :: Membrane.Buffer.Metric.unit(), + overflow_limit :: neg_integer() | nil, + buffered_decrementation_limit :: pos_integer() + ) :: t + def new( + receiver_mode, + receiver_process, + receiver_demand_unit, + overflow_limit \\ nil, + buffered_decrementation_limit \\ @default_buffered_decrementation_limit + ) do + %__MODULE__{ + counter: DistributedAtomic.new(), + receiver_mode: DistributedEffectiveFlowControl.new(receiver_mode), + receiver_process: receiver_process, + overflow_limit: overflow_limit || default_overflow_limit(receiver_demand_unit), + buffered_decrementation_limit: buffered_decrementation_limit + } + end + + @spec set_receiver_mode(t, EffectiveFlowController.effective_flow_control()) :: :ok + def set_receiver_mode(%__MODULE__{} = demand_counter, mode) do + DistributedEffectiveFlowControl.put( + demand_counter.receiver_mode, + mode + ) + end + + @spec get_receiver_mode(t) :: EffectiveFlowController.effective_flow_control() + def get_receiver_mode(%__MODULE__{} = demand_counter) do + DistributedEffectiveFlowControl.get(demand_counter.receiver_mode) + end + + @spec increase(t, non_neg_integer()) :: integer() + def increase(%__MODULE__{} = demand_counter, value) do + DistributedAtomic.add_get(demand_counter.counter, value) + end + + @spec decrease(t, non_neg_integer()) :: {:ok | :overflow, t} + def decrease(%__MODULE__{} = demand_counter, value) do + demand_counter = %{ + demand_counter + | buffered_decrementation: demand_counter.buffered_decrementation + value + } + + if demand_counter.buffered_decrementation >= demand_counter.buffered_decrementation_limit do + flush_buffered_decrementation(demand_counter) + else + {:ok, demand_counter} + end + end + + defp flush_buffered_decrementation(demand_counter) do + counter_value = + DistributedAtomic.sub_get( + demand_counter.counter, + demand_counter.buffered_decrementation + ) + + demand_counter = %{demand_counter | buffered_decrementation: 0} + + if get_receiver_mode(demand_counter) == :pull and + counter_value < demand_counter.overflow_limit do + overflow(counter_value, demand_counter) + {:overflow, demand_counter} + else + {:ok, demand_counter} + end + end + + defp overflow(counter_value, demand_counter) do + Membrane.Logger.debug_verbose(~S""" + Toilet overflow + + ` ' ` + .'''. ' .'''. + .. ' ' .. + ' '.'.' ' + .'''.'.'''. + ' .''.'.''. ' + ;------ ' ------; + | ~~ .--'--// | + | / ' \ | + | / ' \ | + | | ' | | ,----. + | \ , ' , / | =|____|= + '---,###'###,---' (---( + /## ' ##\ )---) + |##, ' ,##| (---( + \'#####'/ `---` + \`"#"`/ + |`"`| + .-| |-. + / ' ' \ + '---------' + """) + + Membrane.Logger.error(""" + Toilet overflow. + + Reached the size of #{inspect(counter_value)}, which is below overflow limit (#{inspect(demand_counter.overflow_limit)}) + when storing data from output working in push mode. It means that some element in the pipeline + processes the stream too slow or doesn't process it at all. + To have control over amount of buffers being produced, consider using output in :auto or :manual + flow control mode. (see `Membrane.Pad.flow_control`). + You can also try changing the `toilet_capacity` in `Membrane.ChildrenSpec.via_in/3`. + """) + + _result = Process.exit(demand_counter.receiver_process, :kill) + :ok + end + + defp default_overflow_limit(demand_unit) do + Membrane.Buffer.Metric.from_unit(demand_unit).buffer_size_approximation() * + @default_overflow_limit_factor + end +end From a95ee9aae92b0911d0dc48212929ac694acf0972 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 17 Mar 2023 17:11:13 +0100 Subject: [PATCH 16/64] wip --- .../core/element/demand_controller.ex | 62 +++++++++++++++- lib/membrane/core/element/demand_counter.ex | 73 ++++++++++--------- .../core/element/effective_flow_controller.ex | 54 +++++++------- lib/membrane/element/pad_data.ex | 4 + 4 files changed, 133 insertions(+), 60 deletions(-) diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 6404cf483..40f9b5158 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -5,14 +5,74 @@ defmodule Membrane.Core.Element.DemandController do use Bunch + alias Membrane.Buffer alias Membrane.Core.{CallbackHandler, Message} alias Membrane.Core.Child.PadModel - alias Membrane.Core.Element.{ActionHandler, CallbackContext, PlaybackQueue, State, Toilet} + alias Membrane.Core.Element.{ActionHandler, CallbackContext, DemandCounter, EffectiveFlowController, PlaybackQueue, State, Toilet} alias Membrane.Pad require Membrane.Core.Child.PadModel require Membrane.Logger + # ----- NEW FUNCTIONALITIES + + @lacking_buffers_lowerbound 2000 + @lacking_buffers_upperbound 4000 + + @spec increase_demand_counter_if_needed(Pad.ref(), State.t()) :: State.t() + def increase_demand_counter_if_needed(pad_ref, state) do + cond do + PadModel.get_data!(state, pad_ref, :flow_control) == :manual -> + do_increase_demand_counter_if_needed(:manual, pad_ref, state) + + EffectiveFlowController.pad_effective_flow_control(pad_ref, state) == :pull -> + do_increase_demand_counter_if_needed(:auto_pull, pad_ref, state) + + true -> + state + end + end + + defp do_increase_demand_counter_if_needed(:manual, _pad_ref, state) do + state + end + + defp do_increase_demand_counter_if_needed(:auto_pull, pad_ref, state) do + %{ + demand_counter: demand_counter, + lacking_buffers: lacking_buffers, + associated_pads: associated_pads + } = pad_data = PadModel.get_data!(state, pad_ref) + + if lacking_buffers < @lacking_buffers_lowerbound and + Enum.all?(associated_pads, &DemandCounter.get(&1.demand_counter) > 0) do + diff = @lacking_buffers_upperbound - lacking_buffers + counter_value = DemandCounter.increase_get(demand_counter, diff) + + if counter_value - diff <= 0 do + Message.send(pad_data.pid, :demand_counter_increased, pad_data.other_ref) + end + + PadModel.set_data!(state, pad_ref, :lacking_buffers, @lacking_buffers_upperbound) + else + state + end + end + + @spec decrease_demand_counter_by_outgoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: State.t() + def decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) do + %{ + other_demand_unit: other_demand_unit, + demand_counter: demand_counter + } = PadModel.get_data!(state, pad_ref) + + buffers_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) + demand_counter = DemandCounter.decrease(demand_counter, buffers_size) + PadModel.set_data!(state, pad_ref, :demand_counter, demand_counter) + end + + # ----- OLD FUNCTIONALITIES + @doc """ Handles demand coming on an output pad. Updates demand value and executes `handle_demand` callback. """ diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 74cce1931..1de66bae3 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -104,65 +104,65 @@ defmodule Membrane.Core.Element.DemandCounter do end end - defmodule DistributedEffectiveFlowControl do + defmodule DistributedReceiverMode do @moduledoc false @type t :: DistributedAtomic.t() + @type receiver_mode_value :: + EffectiveFlowController.effective_flow_control() | :to_be_resolved - @spec new(EffectiveFlowController.effective_flow_control()) :: t + @spec new(receiver_mode_value) :: t def new(initial_value) do initial_value - |> effective_flow_control_to_int() + |> receiver_mode_to_int() |> DistributedAtomic.new() end - @spec get(t) :: EffectiveFlowController.effective_flow_control() + @spec get(t) :: receiver_mode_value() def get(distributed_atomic) do distributed_atomic |> DistributedAtomic.get() - |> int_to_effective_flow_control() + |> int_to_receiver_mode() end - @spec put(t, EffectiveFlowController.effective_flow_control()) :: :ok + @spec put(t, receiver_mode_value()) :: :ok def put(distributed_atomic, value) do - value = effective_flow_control_to_int(value) + value = receiver_mode_to_int(value) DistributedAtomic.put(distributed_atomic, value) end - defp int_to_effective_flow_control(0), do: :not_resolved - defp int_to_effective_flow_control(1), do: :push - defp int_to_effective_flow_control(2), do: :pull + defp int_to_receiver_mode(0), do: :to_be_resolved + defp int_to_receiver_mode(1), do: :push + defp int_to_receiver_mode(2), do: :pull - defp effective_flow_control_to_int(:not_resolved), do: 0 - defp effective_flow_control_to_int(:push), do: 1 - defp effective_flow_control_to_int(:pull), do: 2 + defp receiver_mode_to_int(:to_be_resolved), do: 0 + defp receiver_mode_to_int(:push), do: 1 + defp receiver_mode_to_int(:pull), do: 2 end @default_overflow_limit_factor -200 @default_buffered_decrementation_limit 1 - # @default_capacity_factor 200 @type t :: %__MODULE__{ counter: DistributedAtomic.t(), - receiver_mode: DistributedEffectiveFlowControl.t(), + receiver_mode: DistributedReceiverMode.t(), receiver_process: Process.dest(), overflow_limit: neg_integer(), buffered_decrementation: non_neg_integer(), buffered_decrementation_limit: pos_integer() } + @type receiver_mode :: DistributedReceiverMode.receiver_mode_value() + @enforce_keys [ :counter, :receiver_mode, - :receiver_process + :receiver_process, + :buffered_decrementation_limit, + :overflow_limit ] - defstruct @enforce_keys ++ - [ - overflow_limit: -300, - buffered_decrementation: 0, - buffered_decrementation_limit: 1 - ] + defstruct @enforce_keys ++ [buffered_decrementation: 0, toilet_overflowed?: false] @spec new( receiver_mode :: EffectiveFlowController.effective_flow_control(), @@ -180,7 +180,7 @@ defmodule Membrane.Core.Element.DemandCounter do ) do %__MODULE__{ counter: DistributedAtomic.new(), - receiver_mode: DistributedEffectiveFlowControl.new(receiver_mode), + receiver_mode: DistributedReceiverMode.new(receiver_mode), receiver_process: receiver_process, overflow_limit: overflow_limit || default_overflow_limit(receiver_demand_unit), buffered_decrementation_limit: buffered_decrementation_limit @@ -189,7 +189,7 @@ defmodule Membrane.Core.Element.DemandCounter do @spec set_receiver_mode(t, EffectiveFlowController.effective_flow_control()) :: :ok def set_receiver_mode(%__MODULE__{} = demand_counter, mode) do - DistributedEffectiveFlowControl.put( + DistributedReceiverMode.put( demand_counter.receiver_mode, mode ) @@ -197,15 +197,15 @@ defmodule Membrane.Core.Element.DemandCounter do @spec get_receiver_mode(t) :: EffectiveFlowController.effective_flow_control() def get_receiver_mode(%__MODULE__{} = demand_counter) do - DistributedEffectiveFlowControl.get(demand_counter.receiver_mode) + DistributedReceiverMode.get(demand_counter.receiver_mode) end - @spec increase(t, non_neg_integer()) :: integer() - def increase(%__MODULE__{} = demand_counter, value) do + @spec increase_get(t, non_neg_integer()) :: integer() + def increase_get(%__MODULE__{} = demand_counter, value) do DistributedAtomic.add_get(demand_counter.counter, value) end - @spec decrease(t, non_neg_integer()) :: {:ok | :overflow, t} + @spec decrease(t, non_neg_integer()) :: t def decrease(%__MODULE__{} = demand_counter, value) do demand_counter = %{ demand_counter @@ -215,10 +215,15 @@ defmodule Membrane.Core.Element.DemandCounter do if demand_counter.buffered_decrementation >= demand_counter.buffered_decrementation_limit do flush_buffered_decrementation(demand_counter) else - {:ok, demand_counter} + demand_counter end end + @spec get(t) :: integer() + def get(%__MODULE__{} = demand_counter) do + DistributedAtomic.get(demand_counter.counter) + end + defp flush_buffered_decrementation(demand_counter) do counter_value = DistributedAtomic.sub_get( @@ -228,12 +233,11 @@ defmodule Membrane.Core.Element.DemandCounter do demand_counter = %{demand_counter | buffered_decrementation: 0} - if get_receiver_mode(demand_counter) == :pull and + if not demand_counter.toilet_overflowed? and get_receiver_mode(demand_counter) == :pull and counter_value < demand_counter.overflow_limit do overflow(counter_value, demand_counter) - {:overflow, demand_counter} else - {:ok, demand_counter} + demand_counter end end @@ -275,8 +279,9 @@ defmodule Membrane.Core.Element.DemandCounter do You can also try changing the `toilet_capacity` in `Membrane.ChildrenSpec.via_in/3`. """) - _result = Process.exit(demand_counter.receiver_process, :kill) - :ok + Process.exit(demand_counter.receiver_process, :kill) + + %{demand_counter | toilet_overflowed?: true} end defp default_overflow_limit(demand_unit) do diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 6e74cfa8b..662b8e5ef 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -1,10 +1,9 @@ defmodule Membrane.Core.Element.EffectiveFlowController do @moduledoc false - alias Membrane.Core.Element.Toilet - alias Membrane.Core.Element.{ DemandController, + DemandCounter, State } @@ -30,7 +29,8 @@ defmodule Membrane.Core.Element.EffectiveFlowController do @spec handle_input_pad_added(Pad.ref(), State.t()) :: State.t() def handle_input_pad_added(pad_ref, state) do - with %{pads_data: %{^pad_ref => %{flow_control: :auto} = pad_data}} <- state do + with %{pads_data: %{^pad_ref => %{flow_control: :auto, direction: :input} = pad_data}} <- + state do handle_other_effective_flow_control( pad_ref, pad_data.other_effective_flow_control, @@ -50,12 +50,19 @@ defmodule Membrane.Core.Element.EffectiveFlowController do pad_data = %{pad_data | other_effective_flow_control: other_effective_flow_control} state = PadModel.set_data!(state, my_pad_ref, pad_data) - if state.playback == :playing and pad_data.direction == :input and - pad_data.flow_control == :auto and - other_effective_flow_control != state.effective_flow_control do - resolve_effective_flow_control(state) - else - state + cond do + state.playback != :playing or pad_data.direction != :input or pad_data.flow_control != :auto -> + state + + other_effective_flow_control == state.effective_flow_control -> + :ok = update_demand_counter_receiver_mode(my_pad_ref, state) + state + + other_effective_flow_control == :pull -> + set_effective_flow_control(:pull, state) + + other_effective_flow_control == :push -> + resolve_effective_flow_control(state) end end @@ -89,27 +96,19 @@ defmodule Membrane.Core.Element.EffectiveFlowController do state = %{state | effective_flow_control: new_effective_flow_control} - _ignored = - for {_ref, %{flow_control: :auto} = pad_data} <- state.pads_data do + Enum.each(state.pads_data, fn + {_ref, %{flow_control: :auto, direction: :output} = pad_data} -> Message.send(pad_data.pid, :other_effective_flow_control_resolved, [ pad_data.other_ref, new_effective_flow_control ]) - case pad_data.direction do - :input -> - Toilet.set_receiver_effective_flow_control( - pad_data.toilet, - new_effective_flow_control - ) - - :output -> - Toilet.set_sender_effective_flow_control( - pad_data.toilet, - new_effective_flow_control - ) - end - end + {_ref, %{flow_control: :auto, direction: :input} = pad_data} -> + :ok = update_demand_counter_receiver_mode(pad_data.ref, state) + + _pad_entry -> + :ok + end) Enum.reduce(state.pads_data, state, fn {pad_ref, %{flow_control: :auto, direction: :input}}, state -> @@ -119,4 +118,9 @@ defmodule Membrane.Core.Element.EffectiveFlowController do state end) end + + defp update_demand_counter_receiver_mode(pad_ref, state) do + PadModel.get_data!(state, pad_ref, [:demand_counter]) + |> DemandCounter.set_receiver_mode(state.effective_flow_control) + end end diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 9aa24efd2..79e6a5601 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -44,6 +44,8 @@ defmodule Membrane.Element.PadData do auto_demand_size: private_field, sticky_messages: private_field, toilet: private_field, + demand_counter: private_field, + lacking_buffers: private_field, associated_pads: private_field, sticky_events: private_field, other_effective_flow_control: private_field @@ -71,6 +73,8 @@ defmodule Membrane.Element.PadData do auto_demand_size: nil, sticky_messages: [], toilet: nil, + demand_counter: nil, + lacking_buffers: 0, associated_pads: [], sticky_events: [], stream_format_validation_params: [], From 73820a56ca8832b79e4002bef9adeef7326b3406 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 17 Mar 2023 18:13:33 +0100 Subject: [PATCH 17/64] wip --- lib/membrane/core/element/demand_controller.ex | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 40f9b5158..407e90bbb 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -59,6 +59,17 @@ defmodule Membrane.Core.Element.DemandController do end end + @spec handle_ingoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: State.t() + def handle_ingoing_buffers(pad_ref, buffers, state) do + %{ + demand_unit: demand_unit, + lacking_buffers: lacking_buffers + } = PadModel.get_data!(state, pad_ref) + + buffers_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) + PadModel.set_data!(state, pad_ref, :lacking_buffers, lacking_buffers - buffers_size) + end + @spec decrease_demand_counter_by_outgoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: State.t() def decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) do %{ From 6801f3c40aeeb97886bf8041032021e9adbedec8 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 20 Mar 2023 17:41:56 +0100 Subject: [PATCH 18/64] wip --- lib/membrane/core/element.ex | 5 + .../core/element/demand_controller.ex | 98 ++- lib/membrane/core/element/demand_counter.ex | 50 +- lib/membrane/core/element/demand_handler.ex | 1 - lib/membrane/core/element/input_queue.ex | 40 +- lib/membrane/core/element/pad_controller.ex | 64 +- lib/membrane/core/element/playback_queue.ex | 6 +- lib/membrane/core/element/state.ex | 6 + .../core/element/event_controller_test.exs | 2 +- .../core/element/input_queue_test.exs | 826 +++++++++--------- .../element/lifecycle_controller_test.exs | 114 +-- .../element/stream_format_controller_test.exs | 128 +-- test/membrane/core/element_test.exs | 2 +- 13 files changed, 716 insertions(+), 626 deletions(-) diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index 44f32e27d..28a73f424 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -178,6 +178,11 @@ defmodule Membrane.Core.Element do {:noreply, state} end + defp do_handle_info(Message.new(:demand_counter_increased, pad_ref) = msg, state) do + state = DemandController.handle_demand_counter_increased(pad_ref, state) + {:noreply, state} + end + defp do_handle_info(Message.new(:buffer, buffers, _opts) = msg, state) do pad_ref = Message.for_pad(msg) state = BufferController.handle_buffer(pad_ref, buffers, state) diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 407e90bbb..dad817056 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -8,7 +8,16 @@ defmodule Membrane.Core.Element.DemandController do alias Membrane.Buffer alias Membrane.Core.{CallbackHandler, Message} alias Membrane.Core.Child.PadModel - alias Membrane.Core.Element.{ActionHandler, CallbackContext, DemandCounter, EffectiveFlowController, PlaybackQueue, State, Toilet} + + alias Membrane.Core.Element.{ + ActionHandler, + CallbackContext, + DemandCounter, + PlaybackQueue, + State, + Toilet + } + alias Membrane.Pad require Membrane.Core.Child.PadModel @@ -19,39 +28,59 @@ defmodule Membrane.Core.Element.DemandController do @lacking_buffers_lowerbound 2000 @lacking_buffers_upperbound 4000 - @spec increase_demand_counter_if_needed(Pad.ref(), State.t()) :: State.t() - def increase_demand_counter_if_needed(pad_ref, state) do - cond do - PadModel.get_data!(state, pad_ref, :flow_control) == :manual -> - do_increase_demand_counter_if_needed(:manual, pad_ref, state) - - EffectiveFlowController.pad_effective_flow_control(pad_ref, state) == :pull -> - do_increase_demand_counter_if_needed(:auto_pull, pad_ref, state) + @spec handle_demand_counter_increased(Pad.ref(), State.t()) :: State.t() + def handle_demand_counter_increased(pad_ref, state) do + with {:ok, pad} <- PadModel.get_data(state, pad_ref), + %State{playback: :playing} <- state do + if pad.direction == :input, do: raise ":demand_counter_increased cannot arrive at input pad" - true -> + do_handle_demand_counter_increased(pad, state) + else + {:error, :unknown_pad} -> + # We've got a :demand_counter_increased message on already unlinked pad state - end - end - defp do_increase_demand_counter_if_needed(:manual, _pad_ref, state) do - state + %State{playback: :stopped} -> + PlaybackQueue.store(&handle_demand_counter_increased(pad_ref, &1), state) + end end - defp do_increase_demand_counter_if_needed(:auto_pull, pad_ref, state) do + defp do_handle_demand_counter_increased(%{flow_control: :auto} = pad_data, %{effective_flow_control: :pull} = state) do %{ demand_counter: demand_counter, - lacking_buffers: lacking_buffers, associated_pads: associated_pads - } = pad_data = PadModel.get_data!(state, pad_ref) + } = pad_data - if lacking_buffers < @lacking_buffers_lowerbound and - Enum.all?(associated_pads, &DemandCounter.get(&1.demand_counter) > 0) do - diff = @lacking_buffers_upperbound - lacking_buffers - counter_value = DemandCounter.increase_get(demand_counter, diff) + counter_value = demand_counter |> DemandCounter.get() - if counter_value - diff <= 0 do - Message.send(pad_data.pid, :demand_counter_increased, pad_data.other_ref) - end + if counter_value > 0 do + Enum.reduce(associated_pads, &increase_demand_counter_if_needed/2) + else + state + end + end + + defp do_handle_demand_counter_increased(%{flow_control: :manual} = pad_data, state) do + counter_value = pad_data.demand_counter |> DemandCounter.get() + + if counter_value > 0 do + register_pad_demand(pad_data.ref, counter_value, state) + else + state + end + end + + defp do_handle_demand_counter_increased(_pad_data, state) do + state + end + + @spec increase_demand_counter_if_needed(Pad.ref(), State.t()) :: State.t() + def increase_demand_counter_if_needed(pad_ref, state) do + pad_data = PadModel.get_data!(state, pad_ref) + + if increase_demand_counter?(pad_data, state) do + diff = @lacking_buffers_upperbound - pad_data.lacking_buffers + :ok = DemandCounter.increase(pad_data.demand_counter, diff) PadModel.set_data!(state, pad_ref, :lacking_buffers, @lacking_buffers_upperbound) else @@ -59,6 +88,24 @@ defmodule Membrane.Core.Element.DemandController do end end + @spec register_pad_demand(Pad.ref(), non_neg_integer(), State.t()) :: State.t() + def register_pad_demand(_pad_ref, _demand, state) do + state + end + + defp increase_demand_counter?(pad_data, state) do + %{ + flow_control: flow_control, + lacking_buffers: lacking_buffers, + associated_pads: associated_pads + } = pad_data + + flow_control == :auto and + state.effective_flow_control == :pull and + lacking_buffers < @lacking_buffers_lowerbound and + Enum.all?(associated_pads, &(DemandCounter.get(&1.demand_counter) > 0)) + end + @spec handle_ingoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: State.t() def handle_ingoing_buffers(pad_ref, buffers, state) do %{ @@ -70,7 +117,8 @@ defmodule Membrane.Core.Element.DemandController do PadModel.set_data!(state, pad_ref, :lacking_buffers, lacking_buffers - buffers_size) end - @spec decrease_demand_counter_by_outgoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: State.t() + @spec decrease_demand_counter_by_outgoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: + State.t() def decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) do %{ other_demand_unit: other_demand_unit, diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 1de66bae3..2879a744e 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -2,7 +2,9 @@ defmodule Membrane.Core.Element.DemandCounter do @moduledoc false alias Membrane.Core.Element.EffectiveFlowController + require Membrane.Core.Message, as: Message require Membrane.Logger + require Membrane.Pad, as: Pad defmodule Worker do @moduledoc false @@ -142,6 +144,7 @@ defmodule Membrane.Core.Element.DemandCounter do @default_overflow_limit_factor -200 @default_buffered_decrementation_limit 1 + @distributed_buffered_decrementation_limit 150 @type t :: %__MODULE__{ counter: DistributedAtomic.t(), @@ -158,6 +161,8 @@ defmodule Membrane.Core.Element.DemandCounter do :counter, :receiver_mode, :receiver_process, + :sender_process, + :sender_pad_ref, :buffered_decrementation_limit, :overflow_limit ] @@ -168,20 +173,31 @@ defmodule Membrane.Core.Element.DemandCounter do receiver_mode :: EffectiveFlowController.effective_flow_control(), receiver_process :: Process.dest(), receiver_demand_unit :: Membrane.Buffer.Metric.unit(), - overflow_limit :: neg_integer() | nil, - buffered_decrementation_limit :: pos_integer() + sender_process :: Process.dest(), + sender_pad_ref :: Pad.ref(), + overflow_limit :: neg_integer() | nil ) :: t def new( receiver_mode, receiver_process, receiver_demand_unit, - overflow_limit \\ nil, - buffered_decrementation_limit \\ @default_buffered_decrementation_limit + sender_process, + sender_pad_ref, + overflow_limit \\ nil ) do + {counter_pid, _atomic} = counter = DistributedAtomic.new() + + buffered_decrementation_limit = + if node(sender_process) == node(counter_pid), + do: @default_buffered_decrementation_limit, + else: @distributed_buffered_decrementation_limit + %__MODULE__{ - counter: DistributedAtomic.new(), + counter: counter, receiver_mode: DistributedReceiverMode.new(receiver_mode), receiver_process: receiver_process, + sender_process: sender_process, + sender_pad_ref: sender_pad_ref, overflow_limit: overflow_limit || default_overflow_limit(receiver_demand_unit), buffered_decrementation_limit: buffered_decrementation_limit } @@ -200,9 +216,20 @@ defmodule Membrane.Core.Element.DemandCounter do DistributedReceiverMode.get(demand_counter.receiver_mode) end - @spec increase_get(t, non_neg_integer()) :: integer() - def increase_get(%__MODULE__{} = demand_counter, value) do - DistributedAtomic.add_get(demand_counter.counter, value) + @spec increase(t, non_neg_integer()) :: :ok + def increase(%__MODULE__{} = demand_counter, value) do + new_counter_value = DistributedAtomic.add_get(demand_counter.counter, value) + old_counter_value = new_counter_value - value + + if old_counter_value <= 0 do + Message.send( + demand_counter.sender_process, + :demand_counter_increased, + demand_counter.sender_pad_ref + ) + end + + :ok end @spec decrease(t, non_neg_integer()) :: t @@ -224,7 +251,8 @@ defmodule Membrane.Core.Element.DemandCounter do DistributedAtomic.get(demand_counter.counter) end - defp flush_buffered_decrementation(demand_counter) do + @spec flush_buffered_decrementation(t) :: t + def flush_buffered_decrementation(demand_counter) do counter_value = DistributedAtomic.sub_get( demand_counter.counter, @@ -235,13 +263,13 @@ defmodule Membrane.Core.Element.DemandCounter do if not demand_counter.toilet_overflowed? and get_receiver_mode(demand_counter) == :pull and counter_value < demand_counter.overflow_limit do - overflow(counter_value, demand_counter) + overflow(demand_counter, counter_value) else demand_counter end end - defp overflow(counter_value, demand_counter) do + defp overflow(demand_counter, counter_value) do Membrane.Logger.debug_verbose(~S""" Toilet overflow diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 0f91ad1b1..35b6c0cb6 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -84,7 +84,6 @@ defmodule Membrane.Core.Element.DemandHandler do InputQueue.take_and_demand( pad_data.input_queue, pad_data.demand, - pad_data.pid, pad_data.other_ref ) diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index fe95e5e57..fbceaec08 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -8,11 +8,11 @@ defmodule Membrane.Core.Element.InputQueue do use Bunch + alias Membrane.Core.Element.DemandCounter alias Membrane.Buffer - alias Membrane.Core.{Message, Telemetry} + alias Membrane.Core.Telemetry alias Membrane.Pad - require Membrane.Core.Message require Membrane.Core.Telemetry require Membrane.Logger @@ -32,8 +32,7 @@ defmodule Membrane.Core.Element.InputQueue do demand: integer(), min_demand: pos_integer(), inbound_metric: module(), - outbound_metric: module(), - toilet?: boolean() + outbound_metric: module() } @enforce_keys [ @@ -42,10 +41,10 @@ defmodule Membrane.Core.Element.InputQueue do :target_size, :size, :demand, + :demand_counter, :min_demand, :inbound_metric, - :outbound_metric, - :toilet? + :outbound_metric ] defstruct @enforce_keys @@ -58,10 +57,9 @@ defmodule Membrane.Core.Element.InputQueue do @spec init(%{ inbound_demand_unit: Buffer.Metric.unit(), outbound_demand_unit: Buffer.Metric.unit(), - demand_pid: pid(), + demand_counter: DemandCounter.t(), demand_pad: Pad.ref(), log_tag: String.t(), - toilet?: boolean(), target_size: pos_integer() | nil, min_demand_factor: pos_integer() | nil }) :: t() @@ -69,10 +67,9 @@ defmodule Membrane.Core.Element.InputQueue do %{ inbound_demand_unit: inbound_demand_unit, outbound_demand_unit: outbound_demand_unit, - demand_pid: demand_pid, + demand_counter: demand_counter, demand_pad: demand_pad, log_tag: log_tag, - toilet?: toilet?, target_size: target_size, min_demand_factor: min_demand_factor } = config @@ -96,9 +93,9 @@ defmodule Membrane.Core.Element.InputQueue do min_demand: min_demand, inbound_metric: inbound_metric, outbound_metric: outbound_metric, - toilet?: toilet? + demand_counter: demand_counter } - |> send_demands(demand_pid, demand_pad) + |> send_demands(demand_pad) end @spec store(t(), atom(), any()) :: t() @@ -157,11 +154,10 @@ defmodule Membrane.Core.Element.InputQueue do } end - @spec take_and_demand(t(), non_neg_integer(), pid(), Pad.ref()) :: {output(), t()} + @spec take_and_demand(t(), non_neg_integer(), Pad.ref()) :: {output(), t()} def take_and_demand( %__MODULE__{} = input_queue, count, - demand_pid, demand_pad ) when count >= 0 do @@ -170,7 +166,7 @@ defmodule Membrane.Core.Element.InputQueue do |> Membrane.Logger.debug_verbose() {out, %__MODULE__{size: new_size} = input_queue} = do_take(input_queue, count) - input_queue = send_demands(input_queue, demand_pid, demand_pad) + input_queue = send_demands(input_queue, demand_pad) Telemetry.report_metric(:take_and_demand, new_size, input_queue.log_tag) {out, input_queue} end @@ -291,17 +287,16 @@ defmodule Membrane.Core.Element.InputQueue do end end - @spec send_demands(t(), pid(), Pad.ref()) :: t() + @spec send_demands(t(), Pad.ref()) :: t() defp send_demands( %__MODULE__{ - toilet?: false, size: size, target_size: target_size, demand: demand, + demand_counter: demand_counter, min_demand: min_demand } = input_queue, - demand_pid, - linked_output_ref + linked_output_ref ) when size < target_size and demand > 0 do to_demand = max(demand, min_demand) @@ -312,11 +307,11 @@ defmodule Membrane.Core.Element.InputQueue do |> mk_log(input_queue) |> Membrane.Logger.debug_verbose() - Message.send(demand_pid, :demand, to_demand, for_pad: linked_output_ref) + :ok = DemandCounter.increase(demand_counter, to_demand) %__MODULE__{input_queue | demand: demand - to_demand} end - defp send_demands(input_queue, _demand_pid, _linked_output_ref) do + defp send_demands(input_queue, _linked_output_ref) do input_queue end @@ -327,11 +322,10 @@ defmodule Membrane.Core.Element.InputQueue do log_tag: log_tag, size: size, target_size: target_size, - toilet?: toilet } = input_queue [ - "InputQueue #{log_tag}#{if toilet, do: " (toilet)", else: ""}: ", + "InputQueue #{log_tag}: ", message, "\n", "InputQueue size: #{inspect(size)}, target size: #{inspect(target_size)}" diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index aec1e6d76..23f8e501f 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -12,6 +12,7 @@ defmodule Membrane.Core.Element.PadController do ActionHandler, CallbackContext, DemandController, + DemandCounter, EffectiveFlowController, EventController, InputQueue, @@ -43,7 +44,7 @@ defmodule Membrane.Core.Element.PadController do } @type link_call_reply_props :: - {Endpoint.t(), PadModel.pad_info(), %{toilet: Toilet.t() | nil}} + {Endpoint.t(), PadModel.pad_info(), %{demand_counter: DemandCounter.t()}} @type link_call_reply :: :ok @@ -88,6 +89,9 @@ defmodule Membrane.Core.Element.PadController do %{initiator: :parent} = props, state ) do + + # IO.inspect(info.direction, label: "DUPAAAAAA PARENT") + effective_flow_control = EffectiveFlowController.pad_effective_flow_control(endpoint.pad_ref, state) @@ -160,34 +164,47 @@ defmodule Membrane.Core.Element.PadController do other_effective_flow_control: other_effective_flow_control } = link_props - {output_info, input_info, input_endpoint} = - if info.direction == :output, - do: {info, other_info, other_endpoint}, - else: {other_info, info, endpoint} + # if info.direction != :input do + IO.inspect(info.direction, label: "DUPAAAAAA SIBLING") + if info.direction != :input, do: raise "#{info.direction} is wrong" + # end + + true = (info.direction == :input) - {output_demand_unit, input_demand_unit} = resolve_demand_units(output_info, input_info) + + {output_demand_unit, input_demand_unit} = resolve_demand_units(other_info, info) link_metadata = Map.put(link_metadata, :input_demand_unit, input_demand_unit) |> Map.put(:output_demand_unit, output_demand_unit) - my_effective_flow_control = + pad_effective_flow_control = EffectiveFlowController.pad_effective_flow_control(endpoint.pad_ref, state) - toilet = - Toilet.new( - input_endpoint.pad_props.toilet_capacity, - input_demand_unit || :buffers, + # toilet = + # Toilet.new( + # input_endpoint.pad_props.toilet_capacity, + # input_demand_unit || :buffers, + # self(), + # input_endpoint.pad_props.throttling_factor, + # other_effective_flow_control, + # my_effective_flow_control + # ) + + demand_counter = + DemandCounter.new( + pad_effective_flow_control, self(), - input_endpoint.pad_props.throttling_factor, - other_effective_flow_control, - my_effective_flow_control + input_demand_unit || :buffers, + other_endpoint.pid, + other_endpoint.pad_ref, + -300 ) # The sibiling was an initiator, we don't need to use the pid of a task spawned for observability _metadata = Observability.setup_link(endpoint.pad_ref, link_metadata.observability_metadata) - link_metadata = Map.put(link_metadata, :toilet, toilet) + link_metadata = Map.put(link_metadata, :demand_counter, demand_counter) :ok = Child.PadController.validate_pad_mode!( @@ -296,7 +313,7 @@ defmodule Membrane.Core.Element.PadController do start_of_stream?: false, end_of_stream?: false, associated_pads: [], - toilet: metadata.toilet + demand_counter: metadata.demand_counter }) data = data |> Map.merge(init_pad_direction_data(data, endpoint.pad_props, metadata, state)) @@ -342,18 +359,15 @@ defmodule Membrane.Core.Element.PadController do _metadata, %State{} ) do - %{ref: ref, pid: pid, other_ref: other_ref, demand_unit: this_demand_unit} = data - - enable_toilet? = other_info.flow_control == :push + %{ref: ref, other_ref: other_ref, demand_unit: this_demand_unit, demand_counter: demand_counter} = data input_queue = InputQueue.init(%{ inbound_demand_unit: other_info[:demand_unit] || this_demand_unit, outbound_demand_unit: this_demand_unit, - demand_pid: pid, + demand_counter: demand_counter, demand_pad: other_ref, log_tag: inspect(ref), - toilet?: enable_toilet?, target_size: props.target_queue_size, min_demand_factor: props.min_demand_factor }) @@ -384,13 +398,6 @@ defmodule Membrane.Core.Element.PadController do |> Enum.filter(&(&1.direction != direction and &1.flow_control == :auto)) |> Enum.map(& &1.ref) - # toilet = - # if direction == :input and other_info.flow_control == :push do - # metadata.toilet - # else - # nil - # end - auto_demand_size = if direction == :input do props.auto_demand_size || @@ -404,7 +411,6 @@ defmodule Membrane.Core.Element.PadController do demand: 0, associated_pads: associated_pads, auto_demand_size: auto_demand_size - # toilet: toilet } end diff --git a/lib/membrane/core/element/playback_queue.ex b/lib/membrane/core/element/playback_queue.ex index 52f6ad91d..2a8876ea1 100644 --- a/lib/membrane/core/element/playback_queue.ex +++ b/lib/membrane/core/element/playback_queue.ex @@ -14,7 +14,11 @@ defmodule Membrane.Core.Element.PlaybackQueue do def eval(%State{playback_queue: playback_queue} = state) do state = playback_queue - |> List.foldr(state, fn function, state -> function.(state) end) + |> List.foldr(state, fn function, state -> + state = function.(state) + if state == :input, do: IO.inspect(state, label: "DUPA #{inspect(function)}") + state + end) %State{state | playback_queue: []} end diff --git a/lib/membrane/core/element/state.ex b/lib/membrane/core/element/state.ex index e8faf5143..866aa3897 100644 --- a/lib/membrane/core/element/state.ex +++ b/lib/membrane/core/element/state.ex @@ -24,6 +24,8 @@ defmodule Membrane.Core.Element.State do parent_pid: pid, supplying_demand?: boolean(), delayed_demands: MapSet.t({Pad.ref(), :supply | :redemand}), + supplying_output_demand?: boolean(), + delayed_output_demands: %{optional(Pad.ref()) => pos_integer()}, synchronization: %{ timers: %{Timer.id() => Timer.t()}, parent_clock: Clock.t(), @@ -51,6 +53,8 @@ defmodule Membrane.Core.Element.State do :parent_pid, :supplying_demand?, :delayed_demands, + :supplying_output_demand?, + :delayed_output_demands, :synchronization, :demand_size, :initialized?, @@ -84,6 +88,8 @@ defmodule Membrane.Core.Element.State do parent_pid: options.parent, supplying_demand?: false, delayed_demands: MapSet.new(), + supplying_output_demand?: false, + delayed_output_demands: %{}, synchronization: %{ parent_clock: options.parent_clock, timers: %{}, diff --git a/test/membrane/core/element/event_controller_test.exs b/test/membrane/core/element/event_controller_test.exs index d88396352..44bcd81a0 100644 --- a/test/membrane/core/element/event_controller_test.exs +++ b/test/membrane/core/element/event_controller_test.exs @@ -26,7 +26,7 @@ defmodule Membrane.Core.Element.EventControllerTest do demand_pid: self(), demand_pad: :some_pad, log_tag: "test", - toilet?: false, + demand_counter: :demand_counter, target_size: nil, min_demand_factor: nil }) diff --git a/test/membrane/core/element/input_queue_test.exs b/test/membrane/core/element/input_queue_test.exs index e4c2f7b14..a98f0f415 100644 --- a/test/membrane/core/element/input_queue_test.exs +++ b/test/membrane/core/element/input_queue_test.exs @@ -1,413 +1,413 @@ -defmodule Membrane.Core.Element.InputQueueTest do - use ExUnit.Case, async: true - - alias Membrane.Buffer - alias Membrane.Core.Element.InputQueue - alias Membrane.Core.Message - alias Membrane.Testing.Event - - require Message - - describe ".init/6 should" do - setup do - {:ok, - %{ - log_tag: "test", - target_queue_size: 100, - min_demand_factor: 0.1, - inbound_demand_unit: :bytes, - outbound_demand_unit: :bytes, - demand_pid: self(), - linked_output_ref: :output_pad_ref, - expected_metric: Buffer.Metric.from_unit(:bytes), - expected_min_demand: 10 - }} - end - - test "return InputQueue struct and send demand message", context do - assert InputQueue.init(%{ - inbound_demand_unit: context.inbound_demand_unit, - outbound_demand_unit: context.outbound_demand_unit, - demand_pid: context.demand_pid, - demand_pad: context.linked_output_ref, - log_tag: context.log_tag, - toilet?: false, - target_size: context.target_queue_size, - min_demand_factor: context.min_demand_factor - }) == %InputQueue{ - q: Qex.new(), - log_tag: context.log_tag, - target_size: context.target_queue_size, - size: 0, - demand: 0, - min_demand: context.expected_min_demand, - inbound_metric: context.expected_metric, - outbound_metric: context.expected_metric, - toilet?: false - } - - message = - Message.new(:demand, context.target_queue_size, for_pad: context.linked_output_ref) - - assert_received ^message - end - - test "not send the demand if toilet is enabled", context do - assert InputQueue.init(%{ - inbound_demand_unit: context.inbound_demand_unit, - outbound_demand_unit: context.outbound_demand_unit, - demand_pid: context.demand_pid, - demand_pad: context.linked_output_ref, - log_tag: context.log_tag, - toilet?: true, - target_size: context.target_queue_size, - min_demand_factor: context.min_demand_factor - }) == %InputQueue{ - q: Qex.new(), - log_tag: context.log_tag, - target_size: context.target_queue_size, - size: 0, - demand: context.target_queue_size, - min_demand: context.expected_min_demand, - inbound_metric: context.expected_metric, - outbound_metric: context.expected_metric, - toilet?: true - } - - refute_received Message.new(:demand, _) - end - end - - describe ".empty?/1 should" do - setup do - buffer = %Buffer{payload: <<1, 2, 3>>} - - input_queue = - struct(InputQueue, - size: 0, - inbound_metric: Buffer.Metric.Count, - outbound_metric: Buffer.Metric.Count, - q: Qex.new() - ) - - not_empty_input_queue = InputQueue.store(input_queue, :buffers, [buffer]) - - {:ok, - %{ - size: 0, - buffer: buffer, - input_queue: input_queue, - not_empty_input_queue: not_empty_input_queue - }} - end - - test "return true when pull buffer is empty", context do - assert InputQueue.empty?(context.input_queue) == true - end - - test "return false when pull buffer contains some buffers ", context do - assert InputQueue.empty?(context.not_empty_input_queue) == false - end - end - - describe ".store/3 should" do - setup do - {:ok, %{size: 10, q: Qex.new() |> Qex.push({:buffers, [], 3, 3}), payload: <<1, 2, 3>>}} - end - - test "increment `size` when `:metric` is `Count`", context do - input_queue = - struct(InputQueue, - size: context.size, - inbound_metric: Buffer.Metric.Count, - outbound_metric: Buffer.Metric.Count, - q: context.q - ) - - v = [%Buffer{payload: context.payload}] - %{size: new_size} = InputQueue.store(input_queue, :buffers, v) - assert new_size == context.size + 1 - end - - test "add payload size to `size` when `:metric` is `ByteSize`", context do - input_queue = - struct(InputQueue, - size: context.size, - inbound_metric: Buffer.Metric.ByteSize, - outbound_metric: Buffer.Metric.ByteSize, - q: context.q - ) - - v = [%Buffer{payload: context.payload}] - %{size: new_size} = InputQueue.store(input_queue, :buffers, v) - assert new_size == context.size + byte_size(context.payload) - end - - test "append buffer to the queue", context do - input_queue = - struct(InputQueue, - size: context.size, - inbound_metric: Buffer.Metric.ByteSize, - outbound_metric: Buffer.Metric.ByteSize, - q: context.q - ) - - v = [%Buffer{payload: context.payload}] - %{q: new_q} = InputQueue.store(input_queue, :buffers, v) - {{:value, last_elem}, remaining_q} = new_q |> Qex.pop_back() - assert remaining_q == context.q - assert last_elem == {:buffers, v, 3, 3} - end - - test "append event to the queue", context do - input_queue = - struct(InputQueue, - size: context.size, - inbound_metric: Buffer.Metric.ByteSize, - outbound_metric: Buffer.Metric.ByteSize, - q: context.q - ) - - v = %Event{} - %{q: new_q} = InputQueue.store(input_queue, :event, v) - {{:value, last_elem}, remaining_q} = new_q |> Qex.pop_back() - assert remaining_q == context.q - assert last_elem == {:non_buffer, :event, v} - end - - test "keep other fields unchanged after storing an event", context do - input_queue = - struct(InputQueue, - size: context.size, - inbound_metric: Buffer.Metric.ByteSize, - outbound_metric: Buffer.Metric.ByteSize, - q: context.q - ) - - v = %Event{} - new_input_queue = InputQueue.store(input_queue, :event, v) - assert %{new_input_queue | q: context.q} == input_queue - end - end - - describe ".take_and_demand/4 should" do - setup do - input_queue = - InputQueue.init(%{ - inbound_demand_unit: :buffers, - outbound_demand_unit: :buffers, - demand_pid: self(), - demand_pad: :pad, - log_tag: "test", - toilet?: false, - target_size: nil, - min_demand_factor: nil - }) - - assert_receive {Membrane.Core.Message, :demand, 40, [for_pad: :pad]} - - [input_queue: input_queue] - end - - test "return {:empty, []} when the queue is empty", %{input_queue: input_queue} do - assert {{:empty, []}, %InputQueue{size: 0, demand: 0}} = - InputQueue.take_and_demand(input_queue, 1, self(), :input) - - refute_receive {Membrane.Core.Message, :demand, 10, [for_pad: :pad]} - end - - test "send demands to the pid and updates demand", %{input_queue: input_queue} do - assert {{:value, [{:buffers, [1], 1, 1}]}, new_input_queue} = - input_queue - |> InputQueue.store(bufs(10)) - |> InputQueue.take_and_demand(1, self(), :pad) - - assert_receive {Membrane.Core.Message, :demand, 10, [for_pad: :pad]} - - assert new_input_queue.size == 9 - assert new_input_queue.demand == -9 - end - end - - describe ".take_and_demand/4 should also" do - setup do - size = 6 - buffers1 = {:buffers, [:b1, :b2, :b3], 3, 3} - buffers2 = {:buffers, [:b4, :b5, :b6], 3, 3} - q = Qex.new() |> Qex.push(buffers1) |> Qex.push(buffers2) - - input_queue = - struct(InputQueue, - size: size, - demand: 0, - min_demand: 0, - target_queue_size: 100, - toilet?: false, - inbound_metric: Buffer.Metric.Count, - outbound_metric: Buffer.Metric.Count, - q: q - ) - - {:ok, %{input_queue: input_queue, q: q, size: size, buffers1: buffers1, buffers2: buffers2}} - end - - test "return tuple {:ok, {:empty, buffers}} when there are not enough buffers", - context do - {result, _new_input_queue} = - InputQueue.take_and_demand( - context.input_queue, - 10, - self(), - :linked_output_ref - ) - - assert result == {:empty, [context.buffers1, context.buffers2]} - end - - test "set `size` to 0 when there are not enough buffers", context do - {_, %{size: new_size}} = - InputQueue.take_and_demand( - context.input_queue, - 10, - self(), - :linked_output_ref - ) - - assert new_size == 0 - end - - test "generate demand hen there are not enough buffers", context do - InputQueue.take_and_demand( - context.input_queue, - 10, - self(), - :linked_output_ref - ) - - expected_size = context.size - pad_ref = :linked_output_ref - message = Message.new(:demand, expected_size, for_pad: pad_ref) - assert_received ^message - end - - test "return `to_take` buffers from the queue when there are enough buffers and buffers dont have to be split", - context do - {result, %{q: new_q}} = - InputQueue.take_and_demand( - context.input_queue(), - 3, - self(), - :linked_output_ref - ) - - assert result == {:value, [context.buffers1()]} - - list = new_q |> Enum.into([]) - exp_list = Qex.new() |> Qex.push(context.buffers2()) |> Enum.into([]) - - assert list == exp_list - assert_received Message.new(:demand, _, _) - end - - test "return `to_take` buffers from the queue when there are enough buffers and buffers have to be split", - context do - {result, %{q: new_q}} = - InputQueue.take_and_demand( - context.input_queue, - 4, - self(), - :linked_output_ref - ) - - exp_buf2 = {:buffers, [:b4], 1, 1} - exp_rest = {:buffers, [:b5, :b6], 2, 2} - assert result == {:value, [context.buffers1, exp_buf2]} - - list = new_q |> Enum.into([]) - exp_list = Qex.new() |> Qex.push(exp_rest) |> Enum.into([]) - - assert list == exp_list - assert_received Message.new(:demand, _, _) - end - end - - test "if the queue works properly for :bytes input metric and :buffers output metric" do - queue = - InputQueue.init(%{ - inbound_demand_unit: :bytes, - outbound_demand_unit: :buffers, - demand_pid: self(), - demand_pad: :input, - log_tag: nil, - toilet?: false, - target_size: 10, - min_demand_factor: 1 - }) - - assert_receive {Membrane.Core.Message, :demand, 10, [for_pad: :input]} - assert queue.demand == 0 - queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) - assert queue.size == 4 - queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) - queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) - queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) - assert queue.size == 16 - assert queue.demand == 0 - {out, queue} = InputQueue.take_and_demand(queue, 2, self(), :input) - assert bufs_size(out, :buffers) == 2 - assert queue.size == 4 - assert queue.demand == 0 - assert_receive {Membrane.Core.Message, :demand, 12, [for_pad: :input]} - queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) - queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) - {out, queue} = InputQueue.take_and_demand(queue, 1, self(), :input) - assert bufs_size(out, :buffers) == 1 - assert queue.size == 8 - assert queue.demand == -8 - end - - test "if the queue works properly for :buffers input metric and :bytes output metric" do - queue = - InputQueue.init(%{ - inbound_demand_unit: :buffers, - outbound_demand_unit: :bytes, - demand_pid: self(), - demand_pad: :input, - log_tag: nil, - toilet?: false, - target_size: 3, - min_demand_factor: 1 - }) - - assert_receive {Membrane.Core.Message, :demand, 3, [for_pad: :input]} - assert queue.demand == 0 - queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) - assert queue.size == 1 - queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) - queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) - queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) - assert queue.size == 4 - assert queue.demand == 0 - {out, queue} = InputQueue.take_and_demand(queue, 2, self(), :input) - assert bufs_size(out, :bytes) == 2 - assert queue.size == 4 - assert queue.demand == 0 - refute_receive {Membrane.Core.Message, :demand, _size, [for_pad: :input]} - {out, queue} = InputQueue.take_and_demand(queue, 11, self(), :input) - assert bufs_size(out, :bytes) == 11 - assert queue.size == 2 - assert queue.demand == -1 - assert_receive {Membrane.Core.Message, :demand, 3, [for_pad: :input]} - end - - defp bufs_size(output, unit) do - {_state, bufs} = output - - Enum.flat_map(bufs, fn {:buffers, bufs_list, _inbound_metric_size, _outbound_metric_size} -> - bufs_list - end) - |> Membrane.Buffer.Metric.from_unit(unit).buffers_size() - end - - defp bufs(n), do: Enum.to_list(1..n) -end +# defmodule Membrane.Core.Element.InputQueueTest do +# use ExUnit.Case, async: true + +# alias Membrane.Buffer +# alias Membrane.Core.Element.InputQueue +# alias Membrane.Core.Message +# alias Membrane.Testing.Event + +# require Message + +# describe ".init/6 should" do +# setup do +# {:ok, +# %{ +# log_tag: "test", +# target_queue_size: 100, +# min_demand_factor: 0.1, +# inbound_demand_unit: :bytes, +# outbound_demand_unit: :bytes, +# demand_pid: self(), +# linked_output_ref: :output_pad_ref, +# expected_metric: Buffer.Metric.from_unit(:bytes), +# expected_min_demand: 10 +# }} +# end + +# test "return InputQueue struct and send demand message", context do +# assert InputQueue.init(%{ +# inbound_demand_unit: context.inbound_demand_unit, +# outbound_demand_unit: context.outbound_demand_unit, +# demand_pid: context.demand_pid, +# demand_pad: context.linked_output_ref, +# log_tag: context.log_tag, +# toilet?: false, +# target_size: context.target_queue_size, +# min_demand_factor: context.min_demand_factor +# }) == %InputQueue{ +# q: Qex.new(), +# log_tag: context.log_tag, +# target_size: context.target_queue_size, +# size: 0, +# demand: 0, +# min_demand: context.expected_min_demand, +# inbound_metric: context.expected_metric, +# outbound_metric: context.expected_metric, +# toilet?: false +# } + +# message = +# Message.new(:demand, context.target_queue_size, for_pad: context.linked_output_ref) + +# assert_received ^message +# end + +# test "not send the demand if toilet is enabled", context do +# assert InputQueue.init(%{ +# inbound_demand_unit: context.inbound_demand_unit, +# outbound_demand_unit: context.outbound_demand_unit, +# demand_pid: context.demand_pid, +# demand_pad: context.linked_output_ref, +# log_tag: context.log_tag, +# toilet?: true, +# target_size: context.target_queue_size, +# min_demand_factor: context.min_demand_factor +# }) == %InputQueue{ +# q: Qex.new(), +# log_tag: context.log_tag, +# target_size: context.target_queue_size, +# size: 0, +# demand: context.target_queue_size, +# min_demand: context.expected_min_demand, +# inbound_metric: context.expected_metric, +# outbound_metric: context.expected_metric, +# toilet?: true +# } + +# refute_received Message.new(:demand, _) +# end +# end + +# describe ".empty?/1 should" do +# setup do +# buffer = %Buffer{payload: <<1, 2, 3>>} + +# input_queue = +# struct(InputQueue, +# size: 0, +# inbound_metric: Buffer.Metric.Count, +# outbound_metric: Buffer.Metric.Count, +# q: Qex.new() +# ) + +# not_empty_input_queue = InputQueue.store(input_queue, :buffers, [buffer]) + +# {:ok, +# %{ +# size: 0, +# buffer: buffer, +# input_queue: input_queue, +# not_empty_input_queue: not_empty_input_queue +# }} +# end + +# test "return true when pull buffer is empty", context do +# assert InputQueue.empty?(context.input_queue) == true +# end + +# test "return false when pull buffer contains some buffers ", context do +# assert InputQueue.empty?(context.not_empty_input_queue) == false +# end +# end + +# describe ".store/3 should" do +# setup do +# {:ok, %{size: 10, q: Qex.new() |> Qex.push({:buffers, [], 3, 3}), payload: <<1, 2, 3>>}} +# end + +# test "increment `size` when `:metric` is `Count`", context do +# input_queue = +# struct(InputQueue, +# size: context.size, +# inbound_metric: Buffer.Metric.Count, +# outbound_metric: Buffer.Metric.Count, +# q: context.q +# ) + +# v = [%Buffer{payload: context.payload}] +# %{size: new_size} = InputQueue.store(input_queue, :buffers, v) +# assert new_size == context.size + 1 +# end + +# test "add payload size to `size` when `:metric` is `ByteSize`", context do +# input_queue = +# struct(InputQueue, +# size: context.size, +# inbound_metric: Buffer.Metric.ByteSize, +# outbound_metric: Buffer.Metric.ByteSize, +# q: context.q +# ) + +# v = [%Buffer{payload: context.payload}] +# %{size: new_size} = InputQueue.store(input_queue, :buffers, v) +# assert new_size == context.size + byte_size(context.payload) +# end + +# test "append buffer to the queue", context do +# input_queue = +# struct(InputQueue, +# size: context.size, +# inbound_metric: Buffer.Metric.ByteSize, +# outbound_metric: Buffer.Metric.ByteSize, +# q: context.q +# ) + +# v = [%Buffer{payload: context.payload}] +# %{q: new_q} = InputQueue.store(input_queue, :buffers, v) +# {{:value, last_elem}, remaining_q} = new_q |> Qex.pop_back() +# assert remaining_q == context.q +# assert last_elem == {:buffers, v, 3, 3} +# end + +# test "append event to the queue", context do +# input_queue = +# struct(InputQueue, +# size: context.size, +# inbound_metric: Buffer.Metric.ByteSize, +# outbound_metric: Buffer.Metric.ByteSize, +# q: context.q +# ) + +# v = %Event{} +# %{q: new_q} = InputQueue.store(input_queue, :event, v) +# {{:value, last_elem}, remaining_q} = new_q |> Qex.pop_back() +# assert remaining_q == context.q +# assert last_elem == {:non_buffer, :event, v} +# end + +# test "keep other fields unchanged after storing an event", context do +# input_queue = +# struct(InputQueue, +# size: context.size, +# inbound_metric: Buffer.Metric.ByteSize, +# outbound_metric: Buffer.Metric.ByteSize, +# q: context.q +# ) + +# v = %Event{} +# new_input_queue = InputQueue.store(input_queue, :event, v) +# assert %{new_input_queue | q: context.q} == input_queue +# end +# end + +# describe ".take_and_demand/4 should" do +# setup do +# input_queue = +# InputQueue.init(%{ +# inbound_demand_unit: :buffers, +# outbound_demand_unit: :buffers, +# demand_pid: self(), +# demand_pad: :pad, +# log_tag: "test", +# toilet?: false, +# target_size: nil, +# min_demand_factor: nil +# }) + +# assert_receive {Membrane.Core.Message, :demand, 40, [for_pad: :pad]} + +# [input_queue: input_queue] +# end + +# test "return {:empty, []} when the queue is empty", %{input_queue: input_queue} do +# assert {{:empty, []}, %InputQueue{size: 0, demand: 0}} = +# InputQueue.take_and_demand(input_queue, 1, self(), :input) + +# refute_receive {Membrane.Core.Message, :demand, 10, [for_pad: :pad]} +# end + +# test "send demands to the pid and updates demand", %{input_queue: input_queue} do +# assert {{:value, [{:buffers, [1], 1, 1}]}, new_input_queue} = +# input_queue +# |> InputQueue.store(bufs(10)) +# |> InputQueue.take_and_demand(1, self(), :pad) + +# assert_receive {Membrane.Core.Message, :demand, 10, [for_pad: :pad]} + +# assert new_input_queue.size == 9 +# assert new_input_queue.demand == -9 +# end +# end + +# describe ".take_and_demand/4 should also" do +# setup do +# size = 6 +# buffers1 = {:buffers, [:b1, :b2, :b3], 3, 3} +# buffers2 = {:buffers, [:b4, :b5, :b6], 3, 3} +# q = Qex.new() |> Qex.push(buffers1) |> Qex.push(buffers2) + +# input_queue = +# struct(InputQueue, +# size: size, +# demand: 0, +# min_demand: 0, +# target_queue_size: 100, +# toilet?: false, +# inbound_metric: Buffer.Metric.Count, +# outbound_metric: Buffer.Metric.Count, +# q: q +# ) + +# {:ok, %{input_queue: input_queue, q: q, size: size, buffers1: buffers1, buffers2: buffers2}} +# end + +# test "return tuple {:ok, {:empty, buffers}} when there are not enough buffers", +# context do +# {result, _new_input_queue} = +# InputQueue.take_and_demand( +# context.input_queue, +# 10, +# self(), +# :linked_output_ref +# ) + +# assert result == {:empty, [context.buffers1, context.buffers2]} +# end + +# test "set `size` to 0 when there are not enough buffers", context do +# {_, %{size: new_size}} = +# InputQueue.take_and_demand( +# context.input_queue, +# 10, +# self(), +# :linked_output_ref +# ) + +# assert new_size == 0 +# end + +# test "generate demand hen there are not enough buffers", context do +# InputQueue.take_and_demand( +# context.input_queue, +# 10, +# self(), +# :linked_output_ref +# ) + +# expected_size = context.size +# pad_ref = :linked_output_ref +# message = Message.new(:demand, expected_size, for_pad: pad_ref) +# assert_received ^message +# end + +# test "return `to_take` buffers from the queue when there are enough buffers and buffers dont have to be split", +# context do +# {result, %{q: new_q}} = +# InputQueue.take_and_demand( +# context.input_queue(), +# 3, +# self(), +# :linked_output_ref +# ) + +# assert result == {:value, [context.buffers1()]} + +# list = new_q |> Enum.into([]) +# exp_list = Qex.new() |> Qex.push(context.buffers2()) |> Enum.into([]) + +# assert list == exp_list +# assert_received Message.new(:demand, _, _) +# end + +# test "return `to_take` buffers from the queue when there are enough buffers and buffers have to be split", +# context do +# {result, %{q: new_q}} = +# InputQueue.take_and_demand( +# context.input_queue, +# 4, +# self(), +# :linked_output_ref +# ) + +# exp_buf2 = {:buffers, [:b4], 1, 1} +# exp_rest = {:buffers, [:b5, :b6], 2, 2} +# assert result == {:value, [context.buffers1, exp_buf2]} + +# list = new_q |> Enum.into([]) +# exp_list = Qex.new() |> Qex.push(exp_rest) |> Enum.into([]) + +# assert list == exp_list +# assert_received Message.new(:demand, _, _) +# end +# end + +# test "if the queue works properly for :bytes input metric and :buffers output metric" do +# queue = +# InputQueue.init(%{ +# inbound_demand_unit: :bytes, +# outbound_demand_unit: :buffers, +# demand_pid: self(), +# demand_pad: :input, +# log_tag: nil, +# toilet?: false, +# target_size: 10, +# min_demand_factor: 1 +# }) + +# assert_receive {Membrane.Core.Message, :demand, 10, [for_pad: :input]} +# assert queue.demand == 0 +# queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) +# assert queue.size == 4 +# queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) +# queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) +# queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) +# assert queue.size == 16 +# assert queue.demand == 0 +# {out, queue} = InputQueue.take_and_demand(queue, 2, self(), :input) +# assert bufs_size(out, :buffers) == 2 +# assert queue.size == 4 +# assert queue.demand == 0 +# assert_receive {Membrane.Core.Message, :demand, 12, [for_pad: :input]} +# queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) +# queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) +# {out, queue} = InputQueue.take_and_demand(queue, 1, self(), :input) +# assert bufs_size(out, :buffers) == 1 +# assert queue.size == 8 +# assert queue.demand == -8 +# end + +# test "if the queue works properly for :buffers input metric and :bytes output metric" do +# queue = +# InputQueue.init(%{ +# inbound_demand_unit: :buffers, +# outbound_demand_unit: :bytes, +# demand_pid: self(), +# demand_pad: :input, +# log_tag: nil, +# toilet?: false, +# target_size: 3, +# min_demand_factor: 1 +# }) + +# assert_receive {Membrane.Core.Message, :demand, 3, [for_pad: :input]} +# assert queue.demand == 0 +# queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) +# assert queue.size == 1 +# queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) +# queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) +# queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) +# assert queue.size == 4 +# assert queue.demand == 0 +# {out, queue} = InputQueue.take_and_demand(queue, 2, self(), :input) +# assert bufs_size(out, :bytes) == 2 +# assert queue.size == 4 +# assert queue.demand == 0 +# refute_receive {Membrane.Core.Message, :demand, _size, [for_pad: :input]} +# {out, queue} = InputQueue.take_and_demand(queue, 11, self(), :input) +# assert bufs_size(out, :bytes) == 11 +# assert queue.size == 2 +# assert queue.demand == -1 +# assert_receive {Membrane.Core.Message, :demand, 3, [for_pad: :input]} +# end + +# defp bufs_size(output, unit) do +# {_state, bufs} = output + +# Enum.flat_map(bufs, fn {:buffers, bufs_list, _inbound_metric_size, _outbound_metric_size} -> +# bufs_list +# end) +# |> Membrane.Buffer.Metric.from_unit(unit).buffers_size() +# end + +# defp bufs(n), do: Enum.to_list(1..n) +# end diff --git a/test/membrane/core/element/lifecycle_controller_test.exs b/test/membrane/core/element/lifecycle_controller_test.exs index 3a92d29c0..71a370338 100644 --- a/test/membrane/core/element/lifecycle_controller_test.exs +++ b/test/membrane/core/element/lifecycle_controller_test.exs @@ -1,65 +1,65 @@ -defmodule Membrane.Core.Element.LifecycleControllerTest do - use ExUnit.Case +# defmodule Membrane.Core.Element.LifecycleControllerTest do +# use ExUnit.Case - alias Membrane.Core.Element.{InputQueue, LifecycleController, State} - alias Membrane.Core.Message +# alias Membrane.Core.Element.{InputQueue, LifecycleController, State} +# alias Membrane.Core.Message - require Membrane.Core.Message +# require Membrane.Core.Message - defmodule DummyElement do - use Membrane.Filter - def_output_pad :output, flow_control: :manual, accepted_format: _any +# defmodule DummyElement do +# use Membrane.Filter +# def_output_pad :output, flow_control: :manual, accepted_format: _any - @impl true - def handle_terminate_request(_ctx, state) do - {[], state} - end - end +# @impl true +# def handle_terminate_request(_ctx, state) do +# {[], state} +# end +# end - setup do - input_queue = - InputQueue.init(%{ - inbound_demand_unit: :buffers, - outbound_demand_unit: :buffers, - demand_pid: self(), - demand_pad: :some_pad, - log_tag: "test", - toilet?: false, - target_size: nil, - min_demand_factor: nil - }) +# setup do +# input_queue = +# InputQueue.init(%{ +# inbound_demand_unit: :buffers, +# outbound_demand_unit: :buffers, +# demand_pid: self(), +# demand_pad: :some_pad, +# log_tag: "test", +# toilet?: false, +# target_size: nil, +# min_demand_factor: nil +# }) - state = - struct(State, - module: DummyElement, - name: :test_name, - type: :filter, - playback: :playing, - parent_pid: self(), - synchronization: %{clock: nil, parent_clock: nil}, - pads_data: %{ - input: - struct(Membrane.Element.PadData, - ref: :input, - direction: :input, - pid: self(), - flow_control: :manual, - start_of_stream?: true, - end_of_stream?: false, - input_queue: input_queue, - demand: 0 - ) - } - ) +# state = +# struct(State, +# module: DummyElement, +# name: :test_name, +# type: :filter, +# playback: :playing, +# parent_pid: self(), +# synchronization: %{clock: nil, parent_clock: nil}, +# pads_data: %{ +# input: +# struct(Membrane.Element.PadData, +# ref: :input, +# direction: :input, +# pid: self(), +# flow_control: :manual, +# start_of_stream?: true, +# end_of_stream?: false, +# input_queue: input_queue, +# demand: 0 +# ) +# } +# ) - assert_received Message.new(:demand, _size, for_pad: :some_pad) - [state: state] - end +# assert_received Message.new(:demand, _size, for_pad: :some_pad) +# [state: state] +# end - test "End of stream is generated upon termination", %{ - state: state - } do - state = LifecycleController.handle_terminate_request(state) - assert state.pads_data.input.end_of_stream? - end -end +# test "End of stream is generated upon termination", %{ +# state: state +# } do +# state = LifecycleController.handle_terminate_request(state) +# assert state.pads_data.input.end_of_stream? +# end +# end diff --git a/test/membrane/core/element/stream_format_controller_test.exs b/test/membrane/core/element/stream_format_controller_test.exs index 45e605e64..d1899e6a4 100644 --- a/test/membrane/core/element/stream_format_controller_test.exs +++ b/test/membrane/core/element/stream_format_controller_test.exs @@ -1,74 +1,74 @@ -defmodule Membrane.Core.Element.StreamFormatControllerTest do - use ExUnit.Case, async: true +# defmodule Membrane.Core.Element.StreamFormatControllerTest do +# use ExUnit.Case, async: true - alias Membrane.Buffer - alias Membrane.Core.Message - alias Membrane.Core.Element.{InputQueue, State} - alias Membrane.StreamFormat.Mock, as: MockStreamFormat - alias Membrane.Support.DemandsTest.Filter +# alias Membrane.Buffer +# alias Membrane.Core.Message +# alias Membrane.Core.Element.{InputQueue, State} +# alias Membrane.StreamFormat.Mock, as: MockStreamFormat +# alias Membrane.Support.DemandsTest.Filter - require Membrane.Core.Child.PadModel, as: PadModel - require Membrane.Core.Message, as: Message +# require Membrane.Core.Child.PadModel, as: PadModel +# require Membrane.Core.Message, as: Message - @module Membrane.Core.Element.StreamFormatController +# @module Membrane.Core.Element.StreamFormatController - setup do - input_queue = - InputQueue.init(%{ - inbound_demand_unit: :buffers, - outbound_demand_unit: :buffers, - demand_pid: self(), - demand_pad: :some_pad, - log_tag: "test", - toilet?: false, - target_size: nil, - min_demand_factor: nil - }) +# setup do +# input_queue = +# InputQueue.init(%{ +# inbound_demand_unit: :buffers, +# outbound_demand_unit: :buffers, +# demand_pid: self(), +# demand_pad: :some_pad, +# log_tag: "test", +# toilet?: false, +# target_size: nil, +# min_demand_factor: nil +# }) - state = - struct(State, - module: Filter, - name: :test_name, - parent: self(), - type: :filter, - playback: :playing, - synchronization: %{clock: nil, parent_clock: nil}, - pads_data: %{ - input: - struct(Membrane.Element.PadData, - direction: :input, - name: :input, - pid: self(), - flow_control: :manual, - input_queue: input_queue, - demand: 0 - ) - } - ) +# state = +# struct(State, +# module: Filter, +# name: :test_name, +# parent: self(), +# type: :filter, +# playback: :playing, +# synchronization: %{clock: nil, parent_clock: nil}, +# pads_data: %{ +# input: +# struct(Membrane.Element.PadData, +# direction: :input, +# name: :input, +# pid: self(), +# flow_control: :manual, +# input_queue: input_queue, +# demand: 0 +# ) +# } +# ) - assert_received Message.new(:demand, _size, for_pad: :some_pad) - [state: state] - end +# assert_received Message.new(:demand, _size, for_pad: :some_pad) +# [state: state] +# end - describe "handle_stream_format for pull pad" do - test "with empty input_queue", %{state: state} do - assert PadModel.set_data!(state, :input, :stream_format, %MockStreamFormat{}) == - @module.handle_stream_format(:input, %MockStreamFormat{}, state) - end +# describe "handle_stream_format for pull pad" do +# test "with empty input_queue", %{state: state} do +# assert PadModel.set_data!(state, :input, :stream_format, %MockStreamFormat{}) == +# @module.handle_stream_format(:input, %MockStreamFormat{}, state) +# end - test "with input_queue containing one buffer", %{state: state} do - state = - state - |> PadModel.update_data!( - :input, - :input_queue, - &InputQueue.store(&1, :buffer, %Buffer{payload: "aa"}) - ) +# test "with input_queue containing one buffer", %{state: state} do +# state = +# state +# |> PadModel.update_data!( +# :input, +# :input_queue, +# &InputQueue.store(&1, :buffer, %Buffer{payload: "aa"}) +# ) - state = @module.handle_stream_format(:input, %MockStreamFormat{}, state) +# state = @module.handle_stream_format(:input, %MockStreamFormat{}, state) - assert state.pads_data.input.input_queue.q |> Qex.last!() == - {:non_buffer, :stream_format, %MockStreamFormat{}} - end - end -end +# assert state.pads_data.input.input_queue.q |> Qex.last!() == +# {:non_buffer, :stream_format, %MockStreamFormat{}} +# end +# end +# end diff --git a/test/membrane/core/element_test.exs b/test/membrane/core/element_test.exs index 59e43f006..0fb132ac7 100644 --- a/test/membrane/core/element_test.exs +++ b/test/membrane/core/element_test.exs @@ -79,7 +79,7 @@ defmodule Membrane.Core.ElementTest do pad_props: %{options: [], toilet_capacity: nil, throttling_factor: nil} }, %{ - initiator: :sibling, + initiator: :parent, other_info: %{direction: :input, flow_control: :manual, demand_unit: :buffers}, link_metadata: %{toilet: nil, observability_metadata: %{}}, stream_format_validation_params: [], From 3fe1aa00b472d4a92dd1253590a1f488cd71b2a4 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 23 Mar 2023 20:36:46 +0100 Subject: [PATCH 19/64] wip --- lib/membrane/core/bin/pad_controller.ex | 3 +- lib/membrane/core/element.ex | 15 +- lib/membrane/core/element/action_handler.ex | 1 + .../core/element/buffer_controller.ex | 7 +- .../core/element/demand_controller.ex | 375 +++++++++++------- lib/membrane/core/element/demand_counter.ex | 81 ++-- lib/membrane/core/element/demand_handler.ex | 45 ++- .../core/element/effective_flow_controller.ex | 23 +- lib/membrane/core/element/input_queue.ex | 146 ++++--- lib/membrane/core/element/pad_controller.ex | 45 +-- lib/membrane/core/element/playback_queue.ex | 6 +- lib/membrane/core/element/state.ex | 15 +- lib/membrane/element/pad_data.ex | 4 +- .../core/element/event_controller_test.exs | 20 +- .../core/element/pad_controller_test.exs | 17 +- .../integration/auto_demands_test.exs | 1 + 16 files changed, 499 insertions(+), 305 deletions(-) diff --git a/lib/membrane/core/bin/pad_controller.ex b/lib/membrane/core/bin/pad_controller.ex index c5903b148..0e52dc247 100644 --- a/lib/membrane/core/bin/pad_controller.ex +++ b/lib/membrane/core/bin/pad_controller.ex @@ -293,10 +293,9 @@ defmodule Membrane.Core.Bin.PadController do def handle_unlink(pad_ref, state) do with {:ok, %{availability: :on_request}} <- PadModel.get_data(state, pad_ref) do state = maybe_handle_pad_removed(pad_ref, state) - endpoint = PadModel.get_data!(state, pad_ref, :endpoint) {pad_data, state} = PadModel.pop_data!(state, pad_ref) - if endpoint do + if endpoint = pad_data.endpoint do Message.send(endpoint.pid, :handle_unlink, endpoint.pad_ref) ChildLifeController.proceed_spec_startup(pad_data.spec_ref, state) else diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index 28a73f424..74050952b 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -172,14 +172,19 @@ defmodule Membrane.Core.Element do @compile {:inline, do_handle_info: 2} - defp do_handle_info(Message.new(:demand, size, _opts) = msg, state) do - pad_ref = Message.for_pad(msg) - state = DemandController.handle_demand(pad_ref, size, state) + # defp do_handle_info(Message.new(:demand, size, _opts) = msg, state) do + # pad_ref = Message.for_pad(msg) + # state = DemandController.handle_demand(pad_ref, size, state) + # {:noreply, state} + # end + + defp do_handle_info(Message.new(:demand_counter_increased, pad_ref), state) do + state = DemandController.check_demand_counter(pad_ref, state) {:noreply, state} end - defp do_handle_info(Message.new(:demand_counter_increased, pad_ref) = msg, state) do - state = DemandController.handle_demand_counter_increased(pad_ref, state) + defp do_handle_info(Message.new(:resume_handle_demand_loop), state) do + state = DemandController.exec_random_pad_handle_demand(state) {:noreply, state} end diff --git a/lib/membrane/core/element/action_handler.ex b/lib/membrane/core/element/action_handler.ex index 8b532de34..c02fceaf1 100644 --- a/lib/membrane/core/element/action_handler.ex +++ b/lib/membrane/core/element/action_handler.ex @@ -406,6 +406,7 @@ defmodule Membrane.Core.Element.ActionHandler do with %{direction: :output, flow_control: :manual} <- PadModel.get_data!(state, pad_ref) do DemandHandler.handle_redemand(pad_ref, state) + # Membrane.Core.Element.DemandController.redemand(pad_ref, state) else %{direction: :input} -> raise ElementError, "Tried to make a redemand on input pad #{inspect(pad_ref)}" diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index 5890eeee4..2593c2280 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -58,11 +58,12 @@ defmodule Membrane.Core.Element.BufferController do @spec do_handle_buffer(Pad.ref(), PadModel.pad_data(), [Buffer.t()] | Buffer.t(), State.t()) :: State.t() defp do_handle_buffer(pad_ref, %{flow_control: :auto} = data, buffers, state) do - %{demand: demand, demand_unit: demand_unit} = data + %{lacking_buffers: lacking_buffers, demand_unit: demand_unit} = data buf_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) - state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) - state = DemandController.send_auto_demand_if_needed(pad_ref, state) + state = PadModel.set_data!(state, pad_ref, :lacking_buffers, lacking_buffers - buf_size) + # state = DemandController.send_auto_demand_if_needed(pad_ref, state) + state = DemandController.increase_demand_counter_if_needed(pad_ref, state) exec_buffer_callback(pad_ref, buffers, state) end diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index dad817056..32b0b17f5 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -5,6 +5,7 @@ defmodule Membrane.Core.Element.DemandController do use Bunch + alias Membrane.Element.PadData alias Membrane.Buffer alias Membrane.Core.{CallbackHandler, Message} alias Membrane.Core.Child.PadModel @@ -14,8 +15,9 @@ defmodule Membrane.Core.Element.DemandController do CallbackContext, DemandCounter, PlaybackQueue, - State, - Toilet + # , + State + # Toilet } alias Membrane.Pad @@ -28,24 +30,30 @@ defmodule Membrane.Core.Element.DemandController do @lacking_buffers_lowerbound 2000 @lacking_buffers_upperbound 4000 - @spec handle_demand_counter_increased(Pad.ref(), State.t()) :: State.t() - def handle_demand_counter_increased(pad_ref, state) do + @handle_demand_loop_limit 20 + + @spec check_demand_counter(Pad.ref(), State.t()) :: State.t() + def check_demand_counter(pad_ref, state) do with {:ok, pad} <- PadModel.get_data(state, pad_ref), - %State{playback: :playing} <- state do - if pad.direction == :input, do: raise ":demand_counter_increased cannot arrive at input pad" + %State{playback: :playing} <- state do + if pad.direction == :input, + do: raise("cannot check demand counter in input pad") - do_handle_demand_counter_increased(pad, state) + do_check_demand_counter(pad, state) else {:error, :unknown_pad} -> # We've got a :demand_counter_increased message on already unlinked pad state %State{playback: :stopped} -> - PlaybackQueue.store(&handle_demand_counter_increased(pad_ref, &1), state) + PlaybackQueue.store(&check_demand_counter(pad_ref, &1), state) end end - defp do_handle_demand_counter_increased(%{flow_control: :auto} = pad_data, %{effective_flow_control: :pull} = state) do + defp do_check_demand_counter( + %{flow_control: :auto} = pad_data, + %{effective_flow_control: :pull} = state + ) do %{ demand_counter: demand_counter, associated_pads: associated_pads @@ -54,23 +62,19 @@ defmodule Membrane.Core.Element.DemandController do counter_value = demand_counter |> DemandCounter.get() if counter_value > 0 do - Enum.reduce(associated_pads, &increase_demand_counter_if_needed/2) + # todo: optimize lopp below + Enum.reduce(associated_pads, state, &increase_demand_counter_if_needed/2) else state end end - defp do_handle_demand_counter_increased(%{flow_control: :manual} = pad_data, state) do + defp do_check_demand_counter(%{flow_control: :manual} = pad_data, state) do counter_value = pad_data.demand_counter |> DemandCounter.get() - - if counter_value > 0 do - register_pad_demand(pad_data.ref, counter_value, state) - else - state - end + handle_manual_output_pad_demand(pad_data.ref, counter_value, state) end - defp do_handle_demand_counter_increased(_pad_data, state) do + defp do_check_demand_counter(_pad_data, state) do state end @@ -80,6 +84,9 @@ defmodule Membrane.Core.Element.DemandController do if increase_demand_counter?(pad_data, state) do diff = @lacking_buffers_upperbound - pad_data.lacking_buffers + + # IO.inspect(diff, label: "DEMAND CONTROLLER AUTO increasing counter by") + :ok = DemandCounter.increase(pad_data.demand_counter, diff) PadModel.set_data!(state, pad_ref, :lacking_buffers, @lacking_buffers_upperbound) @@ -88,158 +95,258 @@ defmodule Membrane.Core.Element.DemandController do end end - @spec register_pad_demand(Pad.ref(), non_neg_integer(), State.t()) :: State.t() - def register_pad_demand(_pad_ref, _demand, state) do - state - end - - defp increase_demand_counter?(pad_data, state) do - %{ - flow_control: flow_control, - lacking_buffers: lacking_buffers, - associated_pads: associated_pads - } = pad_data + @spec handle_manual_output_pad_demand(Pad.ref(), integer(), State.t()) :: State.t() + defp handle_manual_output_pad_demand(pad_ref, counter_value, state) when counter_value > 0 do + with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), + %State{playback: :playing} <- state do + pad_data = + cond do + counter_value > 0 and counter_value > pad_data.demand -> + %{pad_data | incoming_demand: counter_value - pad_data.demand, demand: counter_value} + + true -> + pad_data + end + + PadModel.set_data!(state, pad_ref, pad_data) + |> exec_random_pad_handle_demand() + else + {:error, :unknown_pad} -> + # We've got a :demand_counter_increased message on already unlinked pad + state - flow_control == :auto and - state.effective_flow_control == :pull and - lacking_buffers < @lacking_buffers_lowerbound and - Enum.all?(associated_pads, &(DemandCounter.get(&1.demand_counter) > 0)) + %State{playback: :stopped} -> + PlaybackQueue.store(&check_demand_counter(pad_ref, &1), state) + end end - @spec handle_ingoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: State.t() - def handle_ingoing_buffers(pad_ref, buffers, state) do - %{ - demand_unit: demand_unit, - lacking_buffers: lacking_buffers - } = PadModel.get_data!(state, pad_ref) + defp handle_manual_output_pad_demand(_pad_ref, _counter_value, state), do: state - buffers_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) - PadModel.set_data!(state, pad_ref, :lacking_buffers, lacking_buffers - buffers_size) + @spec exec_random_pad_handle_demand(State.t()) :: State.t() + def exec_random_pad_handle_demand(%{handle_demand_loop_counter: counter} = state) + when counter >= @handle_demand_loop_limit do + Message.send(self(), :resume_handle_demand_loop) + %{state | handle_demand_loop_counter: 0} end - @spec decrease_demand_counter_by_outgoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: - State.t() - def decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) do - %{ - other_demand_unit: other_demand_unit, - demand_counter: demand_counter - } = PadModel.get_data!(state, pad_ref) + def exec_random_pad_handle_demand(state) do + state = Map.update!(state, :handle_demand_loop_counter, &(&1 + 1)) - buffers_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) - demand_counter = DemandCounter.decrease(demand_counter, buffers_size) - PadModel.set_data!(state, pad_ref, :demand_counter, demand_counter) - end + pads_to_draw = + Map.values(state.pads_data) + |> Enum.filter(fn + %{direction: :output, flow_control: :manual, end_of_stream?: false, demand: demand} -> + demand > 0 - # ----- OLD FUNCTIONALITIES + _pad_data -> + false + end) - @doc """ - Handles demand coming on an output pad. Updates demand value and executes `handle_demand` callback. - """ - @spec handle_demand(Pad.ref(), non_neg_integer, State.t()) :: State.t() - def handle_demand(pad_ref, size, state) do - withl pad: {:ok, data} <- PadModel.get_data(state, pad_ref), - playback: %State{playback: :playing} <- state do - if data.direction == :input, - do: raise("Input pad cannot handle demand.") - - do_handle_demand(pad_ref, size, data, state) - else - pad: {:error, :unknown_pad} -> - # We've got a demand from already unlinked pad - state + case pads_to_draw do + [] -> + %{state | handle_demand_loop_counter: 0} - playback: _playback -> - PlaybackQueue.store(&handle_demand(pad_ref, size, &1), state) + pads_to_draw -> + Enum.random(pads_to_draw) + |> exec_handle_demand(state) end end - defp do_handle_demand(pad_ref, size, %{flow_control: :auto} = data, state) do - %{demand: old_demand, associated_pads: associated_pads} = data + @spec redemand(Pad.ref(), State.t()) :: State.t() + def redemand(pad_ref, state) do + case PadModel.get_data(state, pad_ref) do + {:ok, %{direction: :input}} -> + raise "Cannot redemand input pad #{inspect(pad_ref)}." - state = PadModel.set_data!(state, pad_ref, :demand, old_demand + size) + {:ok, %{demand: demand} = pad_data} when demand > 0 -> + exec_handle_demand(pad_data, state) - if old_demand <= 0 do - Enum.reduce(associated_pads, state, &send_auto_demand_if_needed/2) - else - state + _error_or_non_positive_demand -> + state end end - defp do_handle_demand(pad_ref, size, %{flow_control: :manual} = data, state) do - demand = data.demand + size - data = %{data | demand: demand} - state = PadModel.set_data!(state, pad_ref, data) - - if exec_handle_demand?(data) do - context = &CallbackContext.from_state(&1, incoming_demand: size) + @spec exec_handle_demand(PadData.t(), State.t()) :: State.t() + defp exec_handle_demand(pad_data, state) do + context = &CallbackContext.from_state(&1, incoming_demand: pad_data.incoming_demand) + state = CallbackHandler.exec_and_handle_callback( :handle_demand, ActionHandler, %{ - split_continuation_arbiter: &exec_handle_demand?(PadModel.get_data!(&1, pad_ref)), + split_continuation_arbiter: &exec_handle_demand?(PadModel.get_data!(&1, pad_data.ref)), context: context }, - [pad_ref, demand, data[:demand_unit]], + [pad_data.ref, pad_data.demand, pad_data.demand_unit], state ) - else - state - end - end - defp do_handle_demand(_pad_ref, _size, %{flow_control: :push} = _data, state) do - state + check_demand_counter(pad_data.ref, state) end - @doc """ - Sends auto demand to an input pad if it should be sent. + defp increase_demand_counter?(pad_data, state) do + %{ + flow_control: flow_control, + lacking_buffers: lacking_buffers, + associated_pads: associated_pads + } = pad_data - The demand should be sent when the current demand on the input pad is at most - half of the demand request size and if there's positive demand on each of - associated output pads. - """ - @spec send_auto_demand_if_needed(Pad.ref(), State.t()) :: State.t() - def send_auto_demand_if_needed(pad_ref, state) do - data = PadModel.get_data!(state, pad_ref) - %{ - flow_control: :auto, - demand: demand, - toilet: toilet, - associated_pads: associated_pads, - auto_demand_size: demand_request_size - } = data - - demand = - if demand <= div(demand_request_size, 2) and - (state.effective_flow_control == :push or - auto_demands_positive?(associated_pads, state)) do - Membrane.Logger.debug_verbose( - "Sending auto demand of size #{demand_request_size - demand} on pad #{inspect(pad_ref)}" - ) - - %{pid: pid, other_ref: other_ref} = data - Message.send(pid, :demand, demand_request_size - demand, for_pad: other_ref) - - if toilet, do: Toilet.drain(toilet, demand_request_size - demand) - - demand_request_size - else - Membrane.Logger.debug_verbose( - "Not sending auto demand on pad #{inspect(pad_ref)}, pads data: #{inspect(state.pads_data)}" - ) - - demand - end - - PadModel.set_data!(state, pad_ref, :demand, demand) + Membrane.Logger.warn("\n\nDUPA #{inspect(flow_control == :auto)}") + Membrane.Logger.warn("DUPA #{inspect(state.effective_flow_control)}") + Membrane.Logger.warn("DUPA #{inspect(lacking_buffers)}") + Membrane.Logger.warn("DUPA #{inspect(Enum.all?(associated_pads, &demand_counter_positive?(&1, state)))}") + + flow_control == :auto and + state.effective_flow_control == :pull and + lacking_buffers < @lacking_buffers_lowerbound and + Enum.all?(associated_pads, &demand_counter_positive?(&1, state)) end - defp auto_demands_positive?(associated_pads, state) do - Enum.all?(associated_pads, &(PadModel.get_data!(state, &1, :demand) > 0)) + defp demand_counter_positive?(pad_ref, state) do + PadModel.get_data!(state, pad_ref, :demand_counter) + |> DemandCounter.get() + |> then(&(&1 > 0)) end + # @spec handle_ingoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: State.t() + # def handle_ingoing_buffers(pad_ref, buffers, state) do + # %{ + # demand_unit: demand_unit, + # lacking_buffers: lacking_buffers + # } = PadModel.get_data!(state, pad_ref) + + # buffers_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) + # PadModel.set_data!(state, pad_ref, :lacking_buffers, lacking_buffers - buffers_size) + # end + + @spec decrease_demand_counter_by_outgoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: + State.t() + def decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) do + pad_data = PadModel.get_data!(state, pad_ref) + buffers_size = Buffer.Metric.from_unit(pad_data.other_demand_unit).buffers_size(buffers) + + pad_demand = pad_data.demand - buffers_size + demand_counter = DemandCounter.decrease(pad_data.demand_counter, buffers_size) + + PadModel.update_data!( + state, + pad_ref, + &%{&1 | demand: pad_demand, demand_counter: demand_counter} + ) + end + + # ----- OLD FUNCTIONALITIES + + # @doc """ + # Handles demand coming on an output pad. Updates demand value and executes `handle_demand` callback. + # """ + # @spec handle_demand(Pad.ref(), non_neg_integer, State.t()) :: State.t() + # def handle_demand(pad_ref, size, state) do + # withl pad: {:ok, data} <- PadModel.get_data(state, pad_ref), + # playback: %State{playback: :playing} <- state do + # if data.direction == :input, + # do: raise("Input pad cannot handle demand.") + + # do_handle_demand(pad_ref, size, data, state) + # else + # pad: {:error, :unknown_pad} -> + # # We've got a demand from already unlinked pad + # state + + # playback: _playback -> + # PlaybackQueue.store(&handle_demand(pad_ref, size, &1), state) + # end + # end + + # defp do_handle_demand(pad_ref, size, %{flow_control: :auto} = data, state) do + # %{demand: old_demand, associated_pads: associated_pads} = data + + # state = PadModel.set_data!(state, pad_ref, :demand, old_demand + size) + + # if old_demand <= 0 do + # Enum.reduce(associated_pads, state, &send_auto_demand_if_needed/2) + # else + # state + # end + # end + + # defp do_handle_demand(pad_ref, size, %{flow_control: :manual} = data, state) do + # demand = data.demand + size + # data = %{data | demand: demand} + # state = PadModel.set_data!(state, pad_ref, data) + + # if exec_handle_demand?(data) do + # context = &CallbackContext.from_state(&1, incoming_demand: size) + + # CallbackHandler.exec_and_handle_callback( + # :handle_demand, + # ActionHandler, + # %{ + # split_continuation_arbiter: &exec_handle_demand?(PadModel.get_data!(&1, pad_ref)), + # context: context + # }, + # [pad_ref, demand, data[:demand_unit]], + # state + # ) + # else + # state + # end + # end + + # defp do_handle_demand(_pad_ref, _size, %{flow_control: :push} = _data, state) do + # state + # end + + # @doc """ + # Sends auto demand to an input pad if it should be sent. + + # The demand should be sent when the current demand on the input pad is at most + # half of the demand request size and if there's positive demand on each of + # associated output pads. + # """ + # @spec send_auto_demand_if_needed(Pad.ref(), State.t()) :: State.t() + # def send_auto_demand_if_needed(pad_ref, state) do + # data = PadModel.get_data!(state, pad_ref) + + # %{ + # flow_control: :auto, + # demand: demand, + # toilet: toilet, + # associated_pads: associated_pads, + # auto_demand_size: demand_request_size + # } = data + + # demand = + # if demand <= div(demand_request_size, 2) and + # (state.effective_flow_control == :push or + # auto_demands_positive?(associated_pads, state)) do + # Membrane.Logger.debug_verbose( + # "Sending auto demand of size #{demand_request_size - demand} on pad #{inspect(pad_ref)}" + # ) + + # %{pid: pid, other_ref: other_ref} = data + # Message.send(pid, :demand, demand_request_size - demand, for_pad: other_ref) + + # if toilet, do: Toilet.drain(toilet, demand_request_size - demand) + + # demand_request_size + # else + # Membrane.Logger.debug_verbose( + # "Not sending auto demand on pad #{inspect(pad_ref)}, pads data: #{inspect(state.pads_data)}" + # ) + + # demand + # end + + # PadModel.set_data!(state, pad_ref, :demand, demand) + # end + + # defp auto_demands_positive?(associated_pads, state) do + # Enum.all?(associated_pads, &(PadModel.get_data!(state, &1, :demand) > 0)) + # end + defp exec_handle_demand?(%{end_of_stream?: true}) do Membrane.Logger.debug_verbose(""" Demand controller: not executing handle_demand as :end_of_stream action has already been returned diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 2879a744e..5238d5381 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -106,40 +106,40 @@ defmodule Membrane.Core.Element.DemandCounter do end end - defmodule DistributedReceiverMode do + defmodule DistributedFlowMode do @moduledoc false @type t :: DistributedAtomic.t() - @type receiver_mode_value :: + @type flow_mode_value :: EffectiveFlowController.effective_flow_control() | :to_be_resolved - @spec new(receiver_mode_value) :: t + @spec new(flow_mode_value) :: t def new(initial_value) do initial_value - |> receiver_mode_to_int() + |> flow_mode_to_int() |> DistributedAtomic.new() end - @spec get(t) :: receiver_mode_value() + @spec get(t) :: flow_mode_value() def get(distributed_atomic) do distributed_atomic |> DistributedAtomic.get() - |> int_to_receiver_mode() + |> int_to_flow_mode() end - @spec put(t, receiver_mode_value()) :: :ok + @spec put(t, flow_mode_value()) :: :ok def put(distributed_atomic, value) do - value = receiver_mode_to_int(value) + value = flow_mode_to_int(value) DistributedAtomic.put(distributed_atomic, value) end - defp int_to_receiver_mode(0), do: :to_be_resolved - defp int_to_receiver_mode(1), do: :push - defp int_to_receiver_mode(2), do: :pull + defp int_to_flow_mode(0), do: :to_be_resolved + defp int_to_flow_mode(1), do: :push + defp int_to_flow_mode(2), do: :pull - defp receiver_mode_to_int(:to_be_resolved), do: 0 - defp receiver_mode_to_int(:push), do: 1 - defp receiver_mode_to_int(:pull), do: 2 + defp flow_mode_to_int(:to_be_resolved), do: 0 + defp flow_mode_to_int(:push), do: 1 + defp flow_mode_to_int(:pull), do: 2 end @default_overflow_limit_factor -200 @@ -148,26 +148,27 @@ defmodule Membrane.Core.Element.DemandCounter do @type t :: %__MODULE__{ counter: DistributedAtomic.t(), - receiver_mode: DistributedReceiverMode.t(), + receiver_mode: DistributedFlowMode.t(), receiver_process: Process.dest(), overflow_limit: neg_integer(), buffered_decrementation: non_neg_integer(), buffered_decrementation_limit: pos_integer() } - @type receiver_mode :: DistributedReceiverMode.receiver_mode_value() + @type flow_mode :: DistributedFlowMode.flow_mode() @enforce_keys [ :counter, :receiver_mode, :receiver_process, + :sender_mode, :sender_process, :sender_pad_ref, :buffered_decrementation_limit, :overflow_limit ] - defstruct @enforce_keys ++ [buffered_decrementation: 0, toilet_overflowed?: false] + defstruct @enforce_keys ++ [ buffered_decrementation: 0, toilet_overflowed?: false] @spec new( receiver_mode :: EffectiveFlowController.effective_flow_control(), @@ -188,14 +189,16 @@ defmodule Membrane.Core.Element.DemandCounter do {counter_pid, _atomic} = counter = DistributedAtomic.new() buffered_decrementation_limit = - if node(sender_process) == node(counter_pid), - do: @default_buffered_decrementation_limit, - else: @distributed_buffered_decrementation_limit + if node(sender_process) == + node(counter_pid), + do: @default_buffered_decrementation_limit, + else: @distributed_buffered_decrementation_limit %__MODULE__{ counter: counter, - receiver_mode: DistributedReceiverMode.new(receiver_mode), + receiver_mode: DistributedFlowMode.new(receiver_mode), receiver_process: receiver_process, + sender_mode: DistributedFlowMode.new(:to_be_resolved), sender_process: sender_process, sender_pad_ref: sender_pad_ref, overflow_limit: overflow_limit || default_overflow_limit(receiver_demand_unit), @@ -203,17 +206,30 @@ defmodule Membrane.Core.Element.DemandCounter do } end - @spec set_receiver_mode(t, EffectiveFlowController.effective_flow_control()) :: :ok + @spec set_sender_mode(t, EffectiveFlowController.effective_flow_control()) :: :ok + def set_sender_mode(%__MODULE__{} = demand_counter, mode) do + DistributedFlowMode.put( + demand_counter.sender_mode, + mode + ) + end + + @spec get_sender_mode(t) :: flow_mode() + def get_sender_mode(%__MODULE__{} = demand_counter) do + DistributedFlowMode.get(demand_counter.sender_mode) + end + + @spec set_receiver_mode(t, flow_mode()) :: :ok def set_receiver_mode(%__MODULE__{} = demand_counter, mode) do - DistributedReceiverMode.put( + DistributedFlowMode.put( demand_counter.receiver_mode, mode ) end - @spec get_receiver_mode(t) :: EffectiveFlowController.effective_flow_control() + @spec get_receiver_mode(t) :: flow_mode() def get_receiver_mode(%__MODULE__{} = demand_counter) do - DistributedReceiverMode.get(demand_counter.receiver_mode) + DistributedFlowMode.get(demand_counter.receiver_mode) end @spec increase(t, non_neg_integer()) :: :ok @@ -221,6 +237,10 @@ defmodule Membrane.Core.Element.DemandCounter do new_counter_value = DistributedAtomic.add_get(demand_counter.counter, value) old_counter_value = new_counter_value - value + Membrane.Logger.warn( + "DEMAND COUNTER OLD NEW #{inspect({old_counter_value, new_counter_value})}" + ) + if old_counter_value <= 0 do Message.send( demand_counter.sender_process, @@ -239,11 +259,18 @@ defmodule Membrane.Core.Element.DemandCounter do | buffered_decrementation: demand_counter.buffered_decrementation + value } + xd = get(demand_counter) + + dc = if demand_counter.buffered_decrementation >= demand_counter.buffered_decrementation_limit do flush_buffered_decrementation(demand_counter) else demand_counter end + + Membrane.Logger.warn("DEMAND COUNTER AFTER DECREMENTATION #{inspect(get(dc))} old #{inspect(xd)}") + + dc end @spec get(t) :: integer() @@ -261,7 +288,9 @@ defmodule Membrane.Core.Element.DemandCounter do demand_counter = %{demand_counter | buffered_decrementation: 0} - if not demand_counter.toilet_overflowed? and get_receiver_mode(demand_counter) == :pull and + if not demand_counter.toilet_overflowed? and + get_receiver_mode(demand_counter) == :pull and + get_sender_mode(demand_counter) == :push and counter_value < demand_counter.overflow_limit do overflow(demand_counter, counter_value) else diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 35b6c0cb6..3aa368095 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -35,7 +35,7 @@ defmodule Membrane.Core.Element.DemandHandler do end def handle_redemand(pad_ref, state) do - DemandController.handle_demand(pad_ref, 0, state) + DemandController.redemand(pad_ref, state) end @doc """ @@ -81,10 +81,10 @@ defmodule Membrane.Core.Element.DemandHandler do pad_data = state |> PadModel.get_data!(pad_ref) {{_queue_status, data}, new_input_queue} = - InputQueue.take_and_demand( + InputQueue.take( pad_data.input_queue, - pad_data.demand, - pad_data.other_ref + pad_data.demand #, + # pad_data.other_ref ) state = PadModel.set_data!(state, pad_ref, :input_queue, new_input_queue) @@ -107,28 +107,33 @@ defmodule Membrane.Core.Element.DemandHandler do %{other_demand_unit: other_demand_unit, demand: demand} = data buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) - fill_toilet_if_exists(pad_ref, data.toilet, buf_size, state) - end - def handle_outgoing_buffers(pad_ref, %{flow_control: :push} = data, buffers, state) do - %{other_demand_unit: other_demand_unit} = data - buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) - fill_toilet_if_exists(pad_ref, data.toilet, buf_size, state) + # fill_toilet_if_exists(pad_ref, data.toilet, buf_size, state) + DemandController.decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) end - defp fill_toilet_if_exists(pad_ref, toilet, buf_size, state) when toilet != nil do - case Toilet.fill(toilet, buf_size) do - {:ok, toilet} -> - PadModel.set_data!(state, pad_ref, :toilet, toilet) + def handle_outgoing_buffers(pad_ref, %{flow_control: :push} = _data, buffers, state) do + # %{other_demand_unit: other_demand_unit} = data + # buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) + # fill_toilet_if_exists(pad_ref, data.toilet, buf_size, state) + + DemandController.decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) - {:overflow, _toilet} -> - # if the toilet has overflowed, we remove it so it didn't overflow again - # and let the parent handle that situation by unlinking this output pad or crashing - PadModel.set_data!(state, pad_ref, :toilet, nil) - end end - defp fill_toilet_if_exists(_pad_ref, nil, _buf_size, state), do: state + # defp fill_toilet_if_exists(pad_ref, toilet, buf_size, state) when toilet != nil do + # case Toilet.fill(toilet, buf_size) do + # {:ok, toilet} -> + # PadModel.set_data!(state, pad_ref, :toilet, toilet) + + # {:overflow, _toilet} -> + # # if the toilet has overflowed, we remove it so it didn't overflow again + # # and let the parent handle that situation by unlinking this output pad or crashing + # PadModel.set_data!(state, pad_ref, :toilet, nil) + # end + # end + + # defp fill_toilet_if_exists(_pad_ref, nil, _buf_size, state), do: state defp update_demand(pad_ref, size, state) when is_integer(size) do PadModel.set_data!(state, pad_ref, :demand, size) diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 662b8e5ef..4b8e9558b 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -55,7 +55,10 @@ defmodule Membrane.Core.Element.EffectiveFlowController do state other_effective_flow_control == state.effective_flow_control -> - :ok = update_demand_counter_receiver_mode(my_pad_ref, state) + :ok = + PadModel.get_data!(state, my_pad_ref, :demand_counter) + |> DemandCounter.set_receiver_mode(state.effective_flow_control) + state other_effective_flow_control == :pull -> @@ -98,13 +101,16 @@ defmodule Membrane.Core.Element.EffectiveFlowController do Enum.each(state.pads_data, fn {_ref, %{flow_control: :auto, direction: :output} = pad_data} -> + :ok = DemandCounter.set_sender_mode(pad_data.demand_counter, new_effective_flow_control) + :ok = DemandCounter.set_receiver_mode(pad_data.demand_counter, :to_be_resolved) + Message.send(pad_data.pid, :other_effective_flow_control_resolved, [ pad_data.other_ref, new_effective_flow_control ]) - {_ref, %{flow_control: :auto, direction: :input} = pad_data} -> - :ok = update_demand_counter_receiver_mode(pad_data.ref, state) + {_ref, %{flow_control: :auto, direction: :input, demand_counter: demand_counter}} -> + :ok = DemandCounter.set_receiver_mode(demand_counter, new_effective_flow_control) _pad_entry -> :ok @@ -112,15 +118,16 @@ defmodule Membrane.Core.Element.EffectiveFlowController do Enum.reduce(state.pads_data, state, fn {pad_ref, %{flow_control: :auto, direction: :input}}, state -> - DemandController.send_auto_demand_if_needed(pad_ref, state) + # DemandController.send_auto_demand_if_needed(pad_ref, state) + DemandController.increase_demand_counter_if_needed(pad_ref, state) _pad_entry, state -> state end) end - defp update_demand_counter_receiver_mode(pad_ref, state) do - PadModel.get_data!(state, pad_ref, [:demand_counter]) - |> DemandCounter.set_receiver_mode(state.effective_flow_control) - end + # defp update_demand_counter_receiver_mode(pad_ref, state) do + # PadModel.get_data!(state, pad_ref, [:demand_counter]) + # |> DemandCounter.set_receiver_mode(state.effective_flow_control) + # end end diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index fbceaec08..9e2d4f618 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -8,6 +8,7 @@ defmodule Membrane.Core.Element.InputQueue do use Bunch + alias Membrane.Event alias Membrane.Core.Element.DemandCounter alias Membrane.Buffer alias Membrane.Core.Telemetry @@ -24,30 +25,29 @@ defmodule Membrane.Core.Element.InputQueue do {:event | :stream_format, any} | {:buffers, list, pos_integer, pos_integer} @type output :: {:empty | :value, [output_value]} + @type queue_item() :: Buffer.t() | Event.t() | struct() | atom() + @type t :: %__MODULE__{ q: @qe.t(), log_tag: String.t(), target_size: pos_integer(), size: non_neg_integer(), - demand: integer(), - min_demand: pos_integer(), inbound_metric: module(), - outbound_metric: module() + outbound_metric: module(), + linked_output_ref: Pad.ref() } @enforce_keys [ :q, :log_tag, :target_size, - :size, - :demand, :demand_counter, - :min_demand, :inbound_metric, - :outbound_metric + :outbound_metric, + :linked_output_ref ] - defstruct @enforce_keys + defstruct @enforce_keys ++ [size: 0, lacking_buffers: 0] @default_target_size_factor 40 @@ -58,20 +58,18 @@ defmodule Membrane.Core.Element.InputQueue do inbound_demand_unit: Buffer.Metric.unit(), outbound_demand_unit: Buffer.Metric.unit(), demand_counter: DemandCounter.t(), - demand_pad: Pad.ref(), + linked_output_ref: Pad.ref(), log_tag: String.t(), - target_size: pos_integer() | nil, - min_demand_factor: pos_integer() | nil + target_size: pos_integer() | nil }) :: t() def init(config) do %{ inbound_demand_unit: inbound_demand_unit, outbound_demand_unit: outbound_demand_unit, demand_counter: demand_counter, - demand_pad: demand_pad, + linked_output_ref: linked_output_ref, log_tag: log_tag, - target_size: target_size, - min_demand_factor: min_demand_factor + target_size: target_size } = config inbound_metric = Buffer.Metric.from_unit(inbound_demand_unit) @@ -81,24 +79,19 @@ defmodule Membrane.Core.Element.InputQueue do target_size = target_size || default_target_size - min_demand = - (target_size * (min_demand_factor || default_min_demand_factor())) |> ceil() |> max(1) - %__MODULE__{ q: @qe.new(), log_tag: log_tag, target_size: target_size, - size: 0, - demand: target_size, - min_demand: min_demand, inbound_metric: inbound_metric, outbound_metric: outbound_metric, - demand_counter: demand_counter + demand_counter: demand_counter, + linked_output_ref: linked_output_ref } - |> send_demands(demand_pad) + |> maybe_increase_demand_counter() end - @spec store(t(), atom(), any()) :: t() + @spec store(t(), atom(), queue_item() | [queue_item()]) :: t() def store(input_queue, type \\ :buffers, v) def store(input_queue, :buffers, v) when is_list(v) do @@ -135,6 +128,7 @@ defmodule Membrane.Core.Element.InputQueue do %__MODULE__{ q: q, size: size, + lacking_buffers: lacking_buffers, inbound_metric: inbound_metric, outbound_metric: outbound_metric } = input_queue, @@ -150,24 +144,25 @@ defmodule Membrane.Core.Element.InputQueue do %__MODULE__{ input_queue | q: q |> @qe.push({:buffers, v, inbound_metric_buffer_size, outbound_metric_buffer_size}), - size: size + inbound_metric_buffer_size + size: size + inbound_metric_buffer_size, + lacking_buffers: lacking_buffers - inbound_metric_buffer_size } end - @spec take_and_demand(t(), non_neg_integer(), Pad.ref()) :: {output(), t()} - def take_and_demand( - %__MODULE__{} = input_queue, - count, - demand_pad - ) - when count >= 0 do + # w starej implementacji pole "demand" jest inkrementowane o tyle, ile wyciagniemy z kolejki + # wiec teraz trzeba podbic counter o tyle ile wyciagnelismy z kolejki + # wychodzi na to ze teraz pole "demand" jest wgl do wywalenia + + def take(%__MODULE__{} = input_queue, count) when count >= 0 do "Taking #{inspect(count)} #{inspect(input_queue.outbound_metric)}" |> mk_log(input_queue) |> Membrane.Logger.debug_verbose() - {out, %__MODULE__{size: new_size} = input_queue} = do_take(input_queue, count) - input_queue = send_demands(input_queue, demand_pad) - Telemetry.report_metric(:take_and_demand, new_size, input_queue.log_tag) + {out, input_queue} = do_take(input_queue, count) + input_queue = maybe_increase_demand_counter(input_queue) + + Telemetry.report_metric(:take, input_queue.size, input_queue.log_tag) + {out, input_queue} end @@ -176,23 +171,54 @@ defmodule Membrane.Core.Element.InputQueue do q: q, size: size, inbound_metric: inbound_metric, - outbound_metric: outbound_metric, - demand: demand + outbound_metric: outbound_metric } = input_queue, count ) do - {out, nq, new_queue_size} = q |> q_pop(count, inbound_metric, outbound_metric, size) - new_demand_size = demand + (size - new_queue_size) - - {out, - %__MODULE__{ - input_queue - | q: nq, - size: new_queue_size, - demand: new_demand_size - }} + {out, nq, new_size} = q |> q_pop(count, inbound_metric, outbound_metric, size) + input_queue = %{input_queue | q: nq, size: new_size} + {out, input_queue} end + # @spec take_and_demand(t(), non_neg_integer(), Pad.ref()) :: {output(), t()} + # def take_and_demand( + # %__MODULE__{} = input_queue, + # count, + # demand_pad + # ) + # when count >= 0 do + # "Taking #{inspect(count)} #{inspect(input_queue.outbound_metric)}" + # |> mk_log(input_queue) + # |> Membrane.Logger.debug_verbose() + + # {out, %__MODULE__{size: new_size} = input_queue} = do_take(input_queue, count) + # input_queue = increase_demand_counter(input_queue, demand_pad) + # Telemetry.report_metric(:take_and_demand, new_size, input_queue.log_tag) + # {out, input_queue} + # end + + # defp do_take( + # %__MODULE__{ + # q: q, + # size: size, + # inbound_metric: inbound_metric, + # outbound_metric: outbound_metric, + # demand: demand + # } = input_queue, + # count + # ) do + # {out, nq, new_queue_size} = q |> q_pop(count, inbound_metric, outbound_metric, size) + # new_demand_size = demand + (size - new_queue_size) + + # {out, + # %__MODULE__{ + # input_queue + # | q: nq, + # size: new_queue_size, + # demand: new_demand_size + # }} + # end + defp q_pop( q, size_to_take_in_outbound_metric, @@ -287,41 +313,39 @@ defmodule Membrane.Core.Element.InputQueue do end end - @spec send_demands(t(), Pad.ref()) :: t() - defp send_demands( + @spec maybe_increase_demand_counter(t()) :: t() + defp maybe_increase_demand_counter( %__MODULE__{ size: size, target_size: target_size, - demand: demand, demand_counter: demand_counter, - min_demand: min_demand - } = input_queue, - linked_output_ref + lacking_buffers: lacking_buffers + } = input_queue ) - when size < target_size and demand > 0 do - to_demand = max(demand, min_demand) + when target_size > size + lacking_buffers do + diff = target_size - size - lacking_buffers """ - Sending demand of size #{inspect(to_demand)} to output #{inspect(linked_output_ref)} + Increasing DemandCounter linked to #{inspect(input_queue.linked_output_ref)} by #{inspect(diff)} """ |> mk_log(input_queue) |> Membrane.Logger.debug_verbose() - :ok = DemandCounter.increase(demand_counter, to_demand) - %__MODULE__{input_queue | demand: demand - to_demand} - end + lacking_buffers = lacking_buffers + diff + :ok = DemandCounter.increase(demand_counter, diff) - defp send_demands(input_queue, _linked_output_ref) do - input_queue + %{input_queue | lacking_buffers: lacking_buffers} end + defp maybe_increase_demand_counter(%__MODULE__{} = input_queue), do: input_queue + # This function may be unused if particular logs are pruned @dialyzer {:no_unused, mk_log: 2} defp mk_log(message, input_queue) do %__MODULE__{ log_tag: log_tag, size: size, - target_size: target_size, + target_size: target_size } = input_queue [ diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 23f8e501f..c816385d8 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -89,9 +89,6 @@ defmodule Membrane.Core.Element.PadController do %{initiator: :parent} = props, state ) do - - # IO.inspect(info.direction, label: "DUPAAAAAA PARENT") - effective_flow_control = EffectiveFlowController.pad_effective_flow_control(endpoint.pad_ref, state) @@ -165,12 +162,10 @@ defmodule Membrane.Core.Element.PadController do } = link_props # if info.direction != :input do - IO.inspect(info.direction, label: "DUPAAAAAA SIBLING") - if info.direction != :input, do: raise "#{info.direction} is wrong" + if info.direction != :input, do: raise("pad direction #{inspect(info.direction)} is wrong") # end - true = (info.direction == :input) - + true = info.direction == :input {output_demand_unit, input_demand_unit} = resolve_demand_units(other_info, info) @@ -181,16 +176,6 @@ defmodule Membrane.Core.Element.PadController do pad_effective_flow_control = EffectiveFlowController.pad_effective_flow_control(endpoint.pad_ref, state) - # toilet = - # Toilet.new( - # input_endpoint.pad_props.toilet_capacity, - # input_demand_unit || :buffers, - # self(), - # input_endpoint.pad_props.throttling_factor, - # other_effective_flow_control, - # my_effective_flow_control - # ) - demand_counter = DemandCounter.new( pad_effective_flow_control, @@ -316,6 +301,12 @@ defmodule Membrane.Core.Element.PadController do demand_counter: metadata.demand_counter }) + :ok = + DemandCounter.set_sender_mode( + data.demand_counter, + EffectiveFlowController.pad_effective_flow_control(data.ref, state) + ) + data = data |> Map.merge(init_pad_direction_data(data, endpoint.pad_props, metadata, state)) data = @@ -334,7 +325,8 @@ defmodule Membrane.Core.Element.PadController do end) case data.direction do - :input -> DemandController.send_auto_demand_if_needed(endpoint.pad_ref, state) + # :input -> DemandController.send_auto_demand_if_needed(endpoint.pad_ref, state) + :input -> DemandController.increase_demand_counter_if_needed(endpoint.pad_ref, state) :output -> state end else @@ -359,17 +351,21 @@ defmodule Membrane.Core.Element.PadController do _metadata, %State{} ) do - %{ref: ref, other_ref: other_ref, demand_unit: this_demand_unit, demand_counter: demand_counter} = data + %{ + ref: ref, + other_ref: other_ref, + demand_unit: this_demand_unit, + demand_counter: demand_counter + } = data input_queue = InputQueue.init(%{ inbound_demand_unit: other_info[:demand_unit] || this_demand_unit, outbound_demand_unit: this_demand_unit, demand_counter: demand_counter, - demand_pad: other_ref, + linked_output_ref: other_ref, log_tag: inspect(ref), - target_size: props.target_queue_size, - min_demand_factor: props.min_demand_factor + target_size: props.target_queue_size }) %{input_queue: input_queue, demand: 0} @@ -456,10 +452,13 @@ defmodule Membrane.Core.Element.PadController do |> PadModel.set_data!(pad_ref, :associated_pads, []) if pad_data.direction == :output do + Membrane.Logger.warn("UNLINKING PADS ASSOCIATIONS #{inspect(pad_data.associated_pads)}") + Enum.reduce( pad_data.associated_pads, state, - &DemandController.send_auto_demand_if_needed/2 + # &DemandController.send_auto_demand_if_needed/2 + &DemandController.increase_demand_counter_if_needed/2 ) else state diff --git a/lib/membrane/core/element/playback_queue.ex b/lib/membrane/core/element/playback_queue.ex index 2a8876ea1..52f6ad91d 100644 --- a/lib/membrane/core/element/playback_queue.ex +++ b/lib/membrane/core/element/playback_queue.ex @@ -14,11 +14,7 @@ defmodule Membrane.Core.Element.PlaybackQueue do def eval(%State{playback_queue: playback_queue} = state) do state = playback_queue - |> List.foldr(state, fn function, state -> - state = function.(state) - if state == :input, do: IO.inspect(state, label: "DUPA #{inspect(function)}") - state - end) + |> List.foldr(state, fn function, state -> function.(state) end) %State{state | playback_queue: []} end diff --git a/lib/membrane/core/element/state.ex b/lib/membrane/core/element/state.ex index 866aa3897..1885952dd 100644 --- a/lib/membrane/core/element/state.ex +++ b/lib/membrane/core/element/state.ex @@ -24,8 +24,9 @@ defmodule Membrane.Core.Element.State do parent_pid: pid, supplying_demand?: boolean(), delayed_demands: MapSet.t({Pad.ref(), :supply | :redemand}), - supplying_output_demand?: boolean(), - delayed_output_demands: %{optional(Pad.ref()) => pos_integer()}, + # supplying_output_demand?: boolean(), + # output_demands: %{optional(Pad.ref()) => pos_integer()}, + handle_demand_loop_counter: non_neg_integer(), synchronization: %{ timers: %{Timer.id() => Timer.t()}, parent_clock: Clock.t(), @@ -53,8 +54,9 @@ defmodule Membrane.Core.Element.State do :parent_pid, :supplying_demand?, :delayed_demands, - :supplying_output_demand?, - :delayed_output_demands, + # :supplying_output_demand?, + # :output_demands, + :handle_demand_loop_counter, :synchronization, :demand_size, :initialized?, @@ -88,8 +90,9 @@ defmodule Membrane.Core.Element.State do parent_pid: options.parent, supplying_demand?: false, delayed_demands: MapSet.new(), - supplying_output_demand?: false, - delayed_output_demands: %{}, + # supplying_output_demand?: false, + # output_demands: %{}, + handle_demand_loop_counter: 0, synchronization: %{ parent_clock: options.parent_clock, timers: %{}, diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 79e6a5601..5452ff846 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -39,6 +39,7 @@ defmodule Membrane.Element.PadData do other_ref: private_field, input_queue: private_field, demand: integer() | nil, + incoming_demand: integer() | nil, demand_unit: private_field, other_demand_unit: private_field, auto_demand_size: private_field, @@ -66,7 +67,8 @@ defmodule Membrane.Element.PadData do defstruct @enforce_keys ++ [ input_queue: nil, - demand: nil, + demand: 0, + incoming_demand: nil, demand_unit: nil, start_of_stream?: false, end_of_stream?: false, diff --git a/test/membrane/core/element/event_controller_test.exs b/test/membrane/core/element/event_controller_test.exs index 44bcd81a0..5ae8b9ea9 100644 --- a/test/membrane/core/element/event_controller_test.exs +++ b/test/membrane/core/element/event_controller_test.exs @@ -1,8 +1,8 @@ defmodule Membrane.Core.Element.EventControllerTest do use ExUnit.Case - alias Membrane.Core.Element.{EventController, InputQueue, State} - alias Membrane.Core.{Events, Message} + alias Membrane.Core.Element.{DemandCounter, EventController, InputQueue, State} + alias Membrane.Core.Events alias Membrane.Event require Membrane.Core.Message @@ -19,14 +19,23 @@ defmodule Membrane.Core.Element.EventControllerTest do end setup do + demand_counter = + DemandCounter.new( + :pull, + spawn(fn -> :ok end), + :buffers, + spawn(fn -> :ok end), + :output, + -300 + ) + input_queue = InputQueue.init(%{ inbound_demand_unit: :buffers, outbound_demand_unit: :buffers, - demand_pid: self(), demand_pad: :some_pad, log_tag: "test", - demand_counter: :demand_counter, + demand_counter: demand_counter, target_size: nil, min_demand_factor: nil }) @@ -54,7 +63,8 @@ defmodule Membrane.Core.Element.EventControllerTest do } ) - assert_received Message.new(:demand, _size, for_pad: :some_pad) + assert DemandCounter.get(demand_counter) > 0 + [state: state] end diff --git a/test/membrane/core/element/pad_controller_test.exs b/test/membrane/core/element/pad_controller_test.exs index 55bc01821..40099ad19 100644 --- a/test/membrane/core/element/pad_controller_test.exs +++ b/test/membrane/core/element/pad_controller_test.exs @@ -30,17 +30,22 @@ defmodule Membrane.Core.Element.PadControllerTest do assert {{:ok, _pad_info}, new_state} = @module.handle_link( - :output, - %{pad_ref: :output, pid: self(), pad_props: %{options: []}, child: :a}, + :input, %{ - pad_ref: :other_input, - pid: nil, + pad_ref: :input, + pid: self(), + pad_props: %{min_demand_factor: 0.25, target_queue_size: 40, options: []}, + child: :a + }, + %{ + pad_ref: :other_output, + pid: spawn(fn -> :ok end), child: :b, pad_props: %{options: [], toilet_capacity: nil, throttling_factor: nil} }, %{ initiator: :sibling, - other_info: %{direction: :input, flow_control: :manual, demand_unit: :buffers}, + other_info: %{direction: :output, flow_control: :manual, demand_unit: :buffers}, link_metadata: %{toilet: make_ref(), observability_metadata: %{}}, stream_format_validation_params: [], other_effective_flow_control: :pull @@ -49,7 +54,7 @@ defmodule Membrane.Core.Element.PadControllerTest do ) assert %{new_state | pads_data: nil} == %{state | pads_data: nil} - assert PadModel.assert_instance(new_state, :output) == :ok + assert PadModel.assert_instance(new_state, :input) == :ok end test "when pad is does not exist in the element" do diff --git a/test/membrane/integration/auto_demands_test.exs b/test/membrane/integration/auto_demands_test.exs index fd6914239..981df8c3b 100644 --- a/test/membrane/integration/auto_demands_test.exs +++ b/test/membrane/integration/auto_demands_test.exs @@ -109,6 +109,7 @@ defmodule Membrane.Integration.AutoDemandsTest do refute_sink_buffer(pipeline, :left_sink, %{payload: 25_000}) end + @tag :dupa test "handle removed branch" do import Membrane.ChildrenSpec From bb520e956a09919a9a35caef3051b628db9d16ad Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 28 Mar 2023 13:18:40 +0200 Subject: [PATCH 20/64] wip --- lib/membrane/core/element.ex | 3 +- .../core/element/buffer_controller.ex | 7 + .../core/element/demand_controller.ex | 131 +++++++++++++----- lib/membrane/core/element/demand_counter.ex | 28 ++-- lib/membrane/core/element/demand_handler.ex | 67 +++++---- lib/membrane/element/pad_data.ex | 2 + lib/membrane/testing/source.ex | 3 + .../integration/auto_demands_test.exs | 12 +- test/membrane/integration/demands_test.exs | 5 +- test/support/demands_test/filter.ex | 9 ++ 10 files changed, 183 insertions(+), 84 deletions(-) diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index 74050952b..28b43c342 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -178,7 +178,8 @@ defmodule Membrane.Core.Element do # {:noreply, state} # end - defp do_handle_info(Message.new(:demand_counter_increased, pad_ref), state) do + defp do_handle_info(Message.new(:demand_counter_increased, [ref, pad_ref]) = msg, state) do + Membrane.Logger.warn("RECEIVING DC NOTIFICATION ON #{inspect(pad_ref)} #{inspect(msg)}") state = DemandController.check_demand_counter(pad_ref, state) {:noreply, state} end diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index 2593c2280..22df47333 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -69,9 +69,16 @@ defmodule Membrane.Core.Element.BufferController do defp do_handle_buffer(pad_ref, %{flow_control: :manual} = data, buffers, state) do %{input_queue: old_input_queue} = data + input_queue = InputQueue.store(old_input_queue, buffers) state = PadModel.set_data!(state, pad_ref, :input_queue, input_queue) + require Membrane.Logger + + Membrane.Logger.warn( + "HANDLE BUFFER #{inspect(Enum.count(buffers))} EMPTY QUEUE? #{inspect(old_input_queue |> InputQueue.empty?())}" + ) + if old_input_queue |> InputQueue.empty?() do DemandHandler.supply_demand(pad_ref, state) else diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 32b0b17f5..a05bdf115 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -30,7 +30,7 @@ defmodule Membrane.Core.Element.DemandController do @lacking_buffers_lowerbound 2000 @lacking_buffers_upperbound 4000 - @handle_demand_loop_limit 20 + @handle_demand_loop_limit 2 @spec check_demand_counter(Pad.ref(), State.t()) :: State.t() def check_demand_counter(pad_ref, state) do @@ -70,8 +70,12 @@ defmodule Membrane.Core.Element.DemandController do end defp do_check_demand_counter(%{flow_control: :manual} = pad_data, state) do - counter_value = pad_data.demand_counter |> DemandCounter.get() - handle_manual_output_pad_demand(pad_data.ref, counter_value, state) + # counter_value = pad_data.demand_counter |> DemandCounter.get() + # Membrane.Logger.debug("CHECKING DEMAND COUNTER #{counter_value}") + # handle_manual_output_pad_demand(pad_data.ref, counter_value, state) + + snapshot_demand_counter(pad_data, state) + |> exec_random_pad_handle_demand() end defp do_check_demand_counter(_pad_data, state) do @@ -95,36 +99,69 @@ defmodule Membrane.Core.Element.DemandController do end end - @spec handle_manual_output_pad_demand(Pad.ref(), integer(), State.t()) :: State.t() - defp handle_manual_output_pad_demand(pad_ref, counter_value, state) when counter_value > 0 do - with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), - %State{playback: :playing} <- state do - pad_data = - cond do - counter_value > 0 and counter_value > pad_data.demand -> - %{pad_data | incoming_demand: counter_value - pad_data.demand, demand: counter_value} + # @spec handle_manual_output_pad_demand(Pad.ref(), integer(), State.t()) :: State.t() + # defp handle_manual_output_pad_demand(pad_ref, counter_value, state) when counter_value > 0 do + # with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), + # %State{playback: :playing} <- state do + # pad_data = + # if counter_value > 0 and counter_value > pad_data.demand do + # %{ + # pad_data + # | incoming_demand: counter_value - pad_data.demand, + # demand: counter_value, + # handle_demand_executed?: false + # } + # else + # pad_data + # end + + # PadModel.set_data!(state, pad_ref, pad_data) + # |> exec_random_pad_handle_demand() + # else + # {:error, :unknown_pad} -> + # # We've got a :demand_counter_increased message on already unlinked pad + # state + + # %State{playback: :stopped} -> + # PlaybackQueue.store(&check_demand_counter(pad_ref, &1), state) + # end + # end + + # defp handle_manual_output_pad_demand(_pad_ref, _counter_value, state), do: state - true -> + defp snapshot_demand_counter(pad_data, state) do + d = pad_data.demand + + state = + pad_data.demand_counter + |> DemandCounter.get() + |> case do + counter_value when counter_value > 0 and counter_value > pad_data.demand -> + PadModel.set_data!(state, pad_data.ref, %{ pad_data - end + | incoming_demand: counter_value - pad_data.demand, + demand: counter_value, + handle_demand_executed?: false + }) - PadModel.set_data!(state, pad_ref, pad_data) - |> exec_random_pad_handle_demand() - else - {:error, :unknown_pad} -> - # We've got a :demand_counter_increased message on already unlinked pad - state + _counter_value -> + state + end - %State{playback: :stopped} -> - PlaybackQueue.store(&check_demand_counter(pad_ref, &1), state) - end - end + pad_data = PadModel.get_data!(state, pad_data.ref) + + Membrane.Logger.warn( + "PAD COUNTER DEMAND SNAPSHOT #{inspect(d)} -> #{inspect(pad_data.demand)} (handle_demand_executed?: #{pad_data.handle_demand_executed?})" + ) - defp handle_manual_output_pad_demand(_pad_ref, _counter_value, state), do: state + state + end @spec exec_random_pad_handle_demand(State.t()) :: State.t() def exec_random_pad_handle_demand(%{handle_demand_loop_counter: counter} = state) when counter >= @handle_demand_loop_limit do + Membrane.Logger.warn("HANDLE DEMAND LOOP COUNTER LIMIT") + Message.send(self(), :resume_handle_demand_loop) %{state | handle_demand_loop_counter: 0} end @@ -135,15 +172,31 @@ defmodule Membrane.Core.Element.DemandController do pads_to_draw = Map.values(state.pads_data) |> Enum.filter(fn - %{direction: :output, flow_control: :manual, end_of_stream?: false, demand: demand} -> + %{ + direction: :output, + flow_control: :manual, + end_of_stream?: false, + handle_demand_executed?: false, + demand: demand + } -> demand > 0 _pad_data -> false end) + # Membrane.Logger.warn("PADS TO DRAW #{inspect(pads_to_draw)}") + case pads_to_draw do [] -> + Membrane.Logger.warn("NO PAD TO DRAW") + + with {:ok, data} <- PadModel.get_data(state, :output) do + Membrane.Logger.warn( + "PAD :output handle_demand_executed? #{data.handle_demand_executed?} demand #{data.demand}" + ) + end + %{state | handle_demand_loop_counter: 0} pads_to_draw -> @@ -154,22 +207,36 @@ defmodule Membrane.Core.Element.DemandController do @spec redemand(Pad.ref(), State.t()) :: State.t() def redemand(pad_ref, state) do - case PadModel.get_data(state, pad_ref) do + with {:ok, %{direction: :output, flow_control: :manual} = pad_data} <- + PadModel.get_data(state, pad_ref) do + state = snapshot_demand_counter(pad_data, state) + pad_data = PadModel.get_data!(state, pad_ref) + + if exec_handle_demand?(pad_data) do + exec_handle_demand(pad_data, state) + else + state + end + else {:ok, %{direction: :input}} -> raise "Cannot redemand input pad #{inspect(pad_ref)}." - {:ok, %{demand: demand} = pad_data} when demand > 0 -> - exec_handle_demand(pad_data, state) + {:ok, %{flow_control: mode}} when mode != :manual -> + raise "Cannot redemand pad #{inspect(pad_ref)} because it has #{inspect(mode)} flow control." - _error_or_non_positive_demand -> + {:error, :unknown_pad} -> state end end @spec exec_handle_demand(PadData.t(), State.t()) :: State.t() defp exec_handle_demand(pad_data, state) do + Membrane.Logger.warn("EXEC HANDLE DEMAND #{inspect(pad_data.ref)}") + context = &CallbackContext.from_state(&1, incoming_demand: pad_data.incoming_demand) + state = PadModel.set_data!(state, pad_data.ref, :handle_demand_executed?, true) + state = CallbackHandler.exec_and_handle_callback( :handle_demand, @@ -192,11 +259,13 @@ defmodule Membrane.Core.Element.DemandController do associated_pads: associated_pads } = pad_data - Membrane.Logger.warn("\n\nDUPA #{inspect(flow_control == :auto)}") Membrane.Logger.warn("DUPA #{inspect(state.effective_flow_control)}") Membrane.Logger.warn("DUPA #{inspect(lacking_buffers)}") - Membrane.Logger.warn("DUPA #{inspect(Enum.all?(associated_pads, &demand_counter_positive?(&1, state)))}") + + Membrane.Logger.warn( + "DUPA #{inspect(Enum.all?(associated_pads, &demand_counter_positive?(&1, state)))}" + ) flow_control == :auto and state.effective_flow_control == :pull and diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 5238d5381..aef1a9b10 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -168,7 +168,7 @@ defmodule Membrane.Core.Element.DemandCounter do :overflow_limit ] - defstruct @enforce_keys ++ [ buffered_decrementation: 0, toilet_overflowed?: false] + defstruct @enforce_keys ++ [buffered_decrementation: 0, toilet_overflowed?: false] @spec new( receiver_mode :: EffectiveFlowController.effective_flow_control(), @@ -242,10 +242,16 @@ defmodule Membrane.Core.Element.DemandCounter do ) if old_counter_value <= 0 do + ref = make_ref() + + Membrane.Logger.warn( + "SENDING DC NOTIFICATION #{inspect(Message.new(:demand_counter_increased, [ref, demand_counter.sender_pad_ref]))}" + ) + Message.send( demand_counter.sender_process, :demand_counter_increased, - demand_counter.sender_pad_ref + [ref, demand_counter.sender_pad_ref] ) end @@ -262,13 +268,15 @@ defmodule Membrane.Core.Element.DemandCounter do xd = get(demand_counter) dc = - if demand_counter.buffered_decrementation >= demand_counter.buffered_decrementation_limit do - flush_buffered_decrementation(demand_counter) - else - demand_counter - end + if demand_counter.buffered_decrementation >= demand_counter.buffered_decrementation_limit do + flush_buffered_decrementation(demand_counter) + else + demand_counter + end - Membrane.Logger.warn("DEMAND COUNTER AFTER DECREMENTATION #{inspect(get(dc))} old #{inspect(xd)}") + Membrane.Logger.warn( + "DEMAND COUNTER AFTER DECREMENTATION #{inspect(get(dc))} old #{inspect(xd)}" + ) dc end @@ -289,8 +297,8 @@ defmodule Membrane.Core.Element.DemandCounter do demand_counter = %{demand_counter | buffered_decrementation: 0} if not demand_counter.toilet_overflowed? and - get_receiver_mode(demand_counter) == :pull and - get_sender_mode(demand_counter) == :push and + get_receiver_mode(demand_counter) == :pull and + get_sender_mode(demand_counter) == :push and counter_value < demand_counter.overflow_limit do overflow(demand_counter, counter_value) else diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 3aa368095..b4198e927 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -83,7 +83,8 @@ defmodule Membrane.Core.Element.DemandHandler do {{_queue_status, data}, new_input_queue} = InputQueue.take( pad_data.input_queue, - pad_data.demand #, + # , + pad_data.demand # pad_data.other_ref ) @@ -102,40 +103,42 @@ defmodule Membrane.Core.Element.DemandHandler do [Buffer.t()], State.t() ) :: State.t() - def handle_outgoing_buffers(pad_ref, %{flow_control: flow_control} = data, buffers, state) - when flow_control in [:auto, :manual] do - %{other_demand_unit: other_demand_unit, demand: demand} = data - buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) - state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) - - # fill_toilet_if_exists(pad_ref, data.toilet, buf_size, state) - DemandController.decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) - end - - def handle_outgoing_buffers(pad_ref, %{flow_control: :push} = _data, buffers, state) do - # %{other_demand_unit: other_demand_unit} = data - # buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) - # fill_toilet_if_exists(pad_ref, data.toilet, buf_size, state) - - DemandController.decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) - - end + # def handle_outgoing_buffers(pad_ref, %{flow_control: flow_control} = _data, buffers, state) + # when flow_control in [:auto, :manual] do + # # %{other_demand_unit: other_demand_unit, demand: demand} = data + # # buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) + # # state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) + + # # fill_toilet_if_exists(pad_ref, data.toilet, buf_size, state) + # state = DemandController.decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) + + # if PadModel.get_data!(state, pad_ref, :demand) <= 0 do + # DemandController.check_demand_counter(pad_ref, state) + # else + # state + # end + # end - # defp fill_toilet_if_exists(pad_ref, toilet, buf_size, state) when toilet != nil do - # case Toilet.fill(toilet, buf_size) do - # {:ok, toilet} -> - # PadModel.set_data!(state, pad_ref, :toilet, toilet) + # def handle_outgoing_buffers(pad_ref, %{flow_control: :push} = _data, buffers, state) do + # # %{other_demand_unit: other_demand_unit} = data + # # buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) + # # fill_toilet_if_exists(pad_ref, data.toilet, buf_size, state) - # {:overflow, _toilet} -> - # # if the toilet has overflowed, we remove it so it didn't overflow again - # # and let the parent handle that situation by unlinking this output pad or crashing - # PadModel.set_data!(state, pad_ref, :toilet, nil) - # end + # DemandController.decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) # end - # defp fill_toilet_if_exists(_pad_ref, nil, _buf_size, state), do: state + def handle_outgoing_buffers(pad_ref, data, buffers, state) do + state = DemandController.decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) + + if data.flow_control != :push and PadModel.get_data!(state, pad_ref, :demand) <= 0 do + DemandController.check_demand_counter(pad_ref, state) + else + state + end + end defp update_demand(pad_ref, size, state) when is_integer(size) do + # dupa: tutaj jest cos nie ten teges PadModel.set_data!(state, pad_ref, :demand, size) end @@ -180,6 +183,12 @@ defmodule Membrane.Core.Element.DemandHandler do State.t() ) :: State.t() defp handle_input_queue_output(pad_ref, data, state) do + count = PadModel.get_data!(state, pad_ref, :demand) + + Membrane.Logger.warn( + "HANDLE INPUT QUEUE OUTPUT SIZE #{inspect(Enum.count(data))} WHILE COUNT #{count}" + ) + Enum.reduce(data, state, fn v, state -> do_handle_input_queue_output(pad_ref, v, state) end) diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 5452ff846..5729a582a 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -39,6 +39,7 @@ defmodule Membrane.Element.PadData do other_ref: private_field, input_queue: private_field, demand: integer() | nil, + handle_demand_executed?: boolean(), incoming_demand: integer() | nil, demand_unit: private_field, other_demand_unit: private_field, @@ -68,6 +69,7 @@ defmodule Membrane.Element.PadData do [ input_queue: nil, demand: 0, + handle_demand_executed?: false, incoming_demand: nil, demand_unit: nil, start_of_stream?: false, diff --git a/lib/membrane/testing/source.ex b/lib/membrane/testing/source.ex index 1b1c01614..cd14eea11 100644 --- a/lib/membrane/testing/source.ex +++ b/lib/membrane/testing/source.ex @@ -86,6 +86,9 @@ defmodule Membrane.Testing.Source do @impl true def handle_demand(:output, size, :buffers, _ctx, state) do + require Membrane.Logger + Membrane.Logger.warn("HANDLE DEMAND #{size}") + get_actions(state, size) end diff --git a/test/membrane/integration/auto_demands_test.exs b/test/membrane/integration/auto_demands_test.exs index 981df8c3b..a46bbf6f9 100644 --- a/test/membrane/integration/auto_demands_test.exs +++ b/test/membrane/integration/auto_demands_test.exs @@ -1,6 +1,7 @@ defmodule Membrane.Integration.AutoDemandsTest do use ExUnit.Case, async: true + import Membrane.ChildrenSpec import Membrane.Testing.Assertions alias Membrane.Testing.{Pipeline, Sink, Source} @@ -51,8 +52,6 @@ defmodule Membrane.Integration.AutoDemandsTest do ] |> Enum.map(fn opts -> test "buffers pass through auto-demand filters; setup: #{inspect(opts)}" do - import Membrane.ChildrenSpec - %{payloads: payloads, factor: factor, direction: direction, filters: filters} = unquote(Macro.escape(opts)) @@ -109,10 +108,7 @@ defmodule Membrane.Integration.AutoDemandsTest do refute_sink_buffer(pipeline, :left_sink, %{payload: 25_000}) end - @tag :dupa test "handle removed branch" do - import Membrane.ChildrenSpec - pipeline = Pipeline.start_link_supervised!( spec: [ @@ -159,8 +155,6 @@ defmodule Membrane.Integration.AutoDemandsTest do ] |> Enum.map(fn opts -> test "buffers pass to auto-demand #{opts.name}" do - import Membrane.ChildrenSpec - %{name: name, module: module} = unquote(Macro.escape(opts)) payloads = Enum.map(1..1000, &inspect/1) @@ -202,8 +196,6 @@ defmodule Membrane.Integration.AutoDemandsTest do end test "toilet" do - import Membrane.ChildrenSpec - pipeline = Pipeline.start_link_supervised!( spec: @@ -230,8 +222,6 @@ defmodule Membrane.Integration.AutoDemandsTest do end test "toilet overflow" do - import Membrane.ChildrenSpec - pipeline = Pipeline.start_supervised!( spec: diff --git a/test/membrane/integration/demands_test.exs b/test/membrane/integration/demands_test.exs index 43fb0eca4..0f020cb1d 100644 --- a/test/membrane/integration/demands_test.exs +++ b/test/membrane/integration/demands_test.exs @@ -17,7 +17,7 @@ defmodule Membrane.Integration.DemandsTest do end defp test_pipeline(pid) do - pattern_gen = fn i -> %Buffer{payload: <> <> <<255>>} end + # pattern_gen = fn i -> %Buffer{payload: <> <> <<255>>} end demand = 500 Pipeline.message_child(pid, :sink, {:make_demand, demand}) @@ -25,7 +25,7 @@ defmodule Membrane.Integration.DemandsTest do 0..(demand - 1) |> assert_buffers_received(pid) - pattern = pattern_gen.(demand) + pattern = %Buffer{payload: <> <> <<255>>} refute_sink_buffer(pid, :sink, ^pattern, 0) Pipeline.message_child(pid, :sink, {:make_demand, demand}) @@ -43,6 +43,7 @@ defmodule Membrane.Integration.DemandsTest do test_pipeline(pid) end + @tag :dupa test "Pipeline with filter underestimating demand" do filter_demand_gen = fn _incoming_demand -> 2 end diff --git a/test/support/demands_test/filter.ex b/test/support/demands_test/filter.ex index 2becb5deb..6a18079d9 100644 --- a/test/support/demands_test/filter.ex +++ b/test/support/demands_test/filter.ex @@ -4,6 +4,8 @@ defmodule Membrane.Support.DemandsTest.Filter do alias Membrane.Buffer + require Membrane.Logger + def_output_pad :output, flow_control: :manual, accepted_format: _any def_input_pad :input, flow_control: :manual, demand_unit: :buffers, accepted_format: _any @@ -20,11 +22,18 @@ defmodule Membrane.Support.DemandsTest.Filter do @impl true def handle_demand(:output, size, _unit, _ctx, state) do + # state.demand_generator.(size) + # |> IO.inspect(label: "FILTER DEMANDING ") + + Membrane.Logger.warn("FILTER DEMADNING #{state.demand_generator.(size)}") + {[demand: {:input, state.demand_generator.(size)}], state} end @impl true def handle_buffer(:input, %Buffer{payload: payload}, _ctx, state) do + state = Map.update(state, :i, 0, &(&1 + 1)) + # IO.inspect(state.i, label: "FILTER HANDLE BUFFER NO") {[buffer: {:output, %Buffer{payload: payload <> <<255>>}}, redemand: :output], state} end From bdc2c69443f69f8a943308a435c90d11d0819450 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 30 Mar 2023 18:09:33 +0200 Subject: [PATCH 21/64] wip --- lib/membrane/core/element.ex | 5 +- lib/membrane/core/element/action_handler.ex | 2 +- .../core/element/buffer_controller.ex | 12 +- .../core/element/demand_controller.ex | 333 ++---------------- lib/membrane/core/element/demand_handler.ex | 141 ++++---- .../core/element/effective_flow_controller.ex | 6 - 6 files changed, 109 insertions(+), 390 deletions(-) diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index 28b43c342..a2f2fe241 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -18,6 +18,7 @@ defmodule Membrane.Core.Element do use Bunch use GenServer + alias Membrane.Core.Element.DemandHandler alias Membrane.{Clock, Core, ResourceGuard, Sync} alias Membrane.Core.{SubprocessSupervisor, TimerController} @@ -178,14 +179,14 @@ defmodule Membrane.Core.Element do # {:noreply, state} # end - defp do_handle_info(Message.new(:demand_counter_increased, [ref, pad_ref]) = msg, state) do + defp do_handle_info(Message.new(:demand_counter_increased, [_msg_ref, pad_ref]) = msg, state) do Membrane.Logger.warn("RECEIVING DC NOTIFICATION ON #{inspect(pad_ref)} #{inspect(msg)}") state = DemandController.check_demand_counter(pad_ref, state) {:noreply, state} end defp do_handle_info(Message.new(:resume_handle_demand_loop), state) do - state = DemandController.exec_random_pad_handle_demand(state) + state = DemandHandler.handle_delayed_demands(state) {:noreply, state} end diff --git a/lib/membrane/core/element/action_handler.ex b/lib/membrane/core/element/action_handler.ex index c02fceaf1..1781feb1b 100644 --- a/lib/membrane/core/element/action_handler.ex +++ b/lib/membrane/core/element/action_handler.ex @@ -307,7 +307,7 @@ defmodule Membrane.Core.Element.ActionHandler do } when stream_format != nil <- pad_data do state = - DemandHandler.handle_outgoing_buffers(pad_ref, pad_data, buffers, state) + DemandHandler.handle_outgoing_buffers(pad_ref, buffers, state) |> PadModel.set_data!(pad_ref, :start_of_stream?, true) Message.send(pid, :buffer, buffers, for_pad: other_ref) diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index 22df47333..7dccb3eed 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -106,13 +106,7 @@ defmodule Membrane.Core.Element.BufferController do def exec_buffer_callback(pad_ref, buffers, %State{type: :filter} = state) do Telemetry.report_metric("buffer", 1, inspect(pad_ref)) - CallbackHandler.exec_and_handle_callback( - :handle_buffers_batch, - ActionHandler, - %{context: &CallbackContext.from_state/1}, - [pad_ref, buffers], - state - ) + do_exec_buffer_callback(pad_ref, buffers, state) end def exec_buffer_callback(pad_ref, buffers, %State{type: type} = state) @@ -120,6 +114,10 @@ defmodule Membrane.Core.Element.BufferController do Telemetry.report_metric(:buffer, length(List.wrap(buffers))) Telemetry.report_bitrate(buffers) + do_exec_buffer_callback(pad_ref, buffers, state) + end + + defp do_exec_buffer_callback(pad_ref, buffers, state) do CallbackHandler.exec_and_handle_callback( :handle_buffers_batch, ActionHandler, diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index a05bdf115..4766790bd 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -5,19 +5,18 @@ defmodule Membrane.Core.Element.DemandController do use Bunch + alias Membrane.Core.Element.DemandHandler alias Membrane.Element.PadData - alias Membrane.Buffer - alias Membrane.Core.{CallbackHandler, Message} + alias Membrane.Core.CallbackHandler alias Membrane.Core.Child.PadModel alias Membrane.Core.Element.{ ActionHandler, CallbackContext, DemandCounter, + DemandHandler, PlaybackQueue, - # , State - # Toilet } alias Membrane.Pad @@ -25,13 +24,9 @@ defmodule Membrane.Core.Element.DemandController do require Membrane.Core.Child.PadModel require Membrane.Logger - # ----- NEW FUNCTIONALITIES - @lacking_buffers_lowerbound 2000 @lacking_buffers_upperbound 4000 - @handle_demand_loop_limit 2 - @spec check_demand_counter(Pad.ref(), State.t()) :: State.t() def check_demand_counter(pad_ref, state) do with {:ok, pad} <- PadModel.get_data(state, pad_ref), @@ -70,12 +65,7 @@ defmodule Membrane.Core.Element.DemandController do end defp do_check_demand_counter(%{flow_control: :manual} = pad_data, state) do - # counter_value = pad_data.demand_counter |> DemandCounter.get() - # Membrane.Logger.debug("CHECKING DEMAND COUNTER #{counter_value}") - # handle_manual_output_pad_demand(pad_data.ref, counter_value, state) - - snapshot_demand_counter(pad_data, state) - |> exec_random_pad_handle_demand() + DemandHandler.maybe_snapshot_demand_counter(pad_data.ref, state) end defp do_check_demand_counter(_pad_data, state) do @@ -88,9 +78,6 @@ defmodule Membrane.Core.Element.DemandController do if increase_demand_counter?(pad_data, state) do diff = @lacking_buffers_upperbound - pad_data.lacking_buffers - - # IO.inspect(diff, label: "DEMAND CONTROLLER AUTO increasing counter by") - :ok = DemandCounter.increase(pad_data.demand_counter, diff) PadModel.set_data!(state, pad_ref, :lacking_buffers, @lacking_buffers_upperbound) @@ -99,157 +86,32 @@ defmodule Membrane.Core.Element.DemandController do end end - # @spec handle_manual_output_pad_demand(Pad.ref(), integer(), State.t()) :: State.t() - # defp handle_manual_output_pad_demand(pad_ref, counter_value, state) when counter_value > 0 do - # with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), - # %State{playback: :playing} <- state do - # pad_data = - # if counter_value > 0 and counter_value > pad_data.demand do - # %{ - # pad_data - # | incoming_demand: counter_value - pad_data.demand, - # demand: counter_value, - # handle_demand_executed?: false - # } - # else - # pad_data - # end - - # PadModel.set_data!(state, pad_ref, pad_data) - # |> exec_random_pad_handle_demand() - # else - # {:error, :unknown_pad} -> - # # We've got a :demand_counter_increased message on already unlinked pad - # state - - # %State{playback: :stopped} -> - # PlaybackQueue.store(&check_demand_counter(pad_ref, &1), state) - # end - # end - - # defp handle_manual_output_pad_demand(_pad_ref, _counter_value, state), do: state - - defp snapshot_demand_counter(pad_data, state) do - d = pad_data.demand - - state = - pad_data.demand_counter - |> DemandCounter.get() - |> case do - counter_value when counter_value > 0 and counter_value > pad_data.demand -> - PadModel.set_data!(state, pad_data.ref, %{ - pad_data - | incoming_demand: counter_value - pad_data.demand, - demand: counter_value, - handle_demand_executed?: false - }) - - _counter_value -> - state - end - - pad_data = PadModel.get_data!(state, pad_data.ref) - - Membrane.Logger.warn( - "PAD COUNTER DEMAND SNAPSHOT #{inspect(d)} -> #{inspect(pad_data.demand)} (handle_demand_executed?: #{pad_data.handle_demand_executed?})" - ) - - state - end - - @spec exec_random_pad_handle_demand(State.t()) :: State.t() - def exec_random_pad_handle_demand(%{handle_demand_loop_counter: counter} = state) - when counter >= @handle_demand_loop_limit do - Membrane.Logger.warn("HANDLE DEMAND LOOP COUNTER LIMIT") - - Message.send(self(), :resume_handle_demand_loop) - %{state | handle_demand_loop_counter: 0} - end - - def exec_random_pad_handle_demand(state) do - state = Map.update!(state, :handle_demand_loop_counter, &(&1 + 1)) - - pads_to_draw = - Map.values(state.pads_data) - |> Enum.filter(fn - %{ - direction: :output, - flow_control: :manual, - end_of_stream?: false, - handle_demand_executed?: false, - demand: demand - } -> - demand > 0 - - _pad_data -> - false - end) - - # Membrane.Logger.warn("PADS TO DRAW #{inspect(pads_to_draw)}") - - case pads_to_draw do - [] -> - Membrane.Logger.warn("NO PAD TO DRAW") - - with {:ok, data} <- PadModel.get_data(state, :output) do - Membrane.Logger.warn( - "PAD :output handle_demand_executed? #{data.handle_demand_executed?} demand #{data.demand}" - ) - end - - %{state | handle_demand_loop_counter: 0} - - pads_to_draw -> - Enum.random(pads_to_draw) - |> exec_handle_demand(state) - end - end - - @spec redemand(Pad.ref(), State.t()) :: State.t() - def redemand(pad_ref, state) do - with {:ok, %{direction: :output, flow_control: :manual} = pad_data} <- - PadModel.get_data(state, pad_ref) do - state = snapshot_demand_counter(pad_data, state) - pad_data = PadModel.get_data!(state, pad_ref) - - if exec_handle_demand?(pad_data) do - exec_handle_demand(pad_data, state) - else - state - end + @spec exec_handle_demand(Pad.ref(), State.t()) :: State.t() + def exec_handle_demand(pad_ref, state) do + with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), + true <- exec_handle_demand?(pad_data) do + do_exec_handle_demand(pad_data, state) else - {:ok, %{direction: :input}} -> - raise "Cannot redemand input pad #{inspect(pad_ref)}." - - {:ok, %{flow_control: mode}} when mode != :manual -> - raise "Cannot redemand pad #{inspect(pad_ref)} because it has #{inspect(mode)} flow control." - - {:error, :unknown_pad} -> - state + _other -> state end end - @spec exec_handle_demand(PadData.t(), State.t()) :: State.t() - defp exec_handle_demand(pad_data, state) do + @spec do_exec_handle_demand(PadData.t(), State.t()) :: State.t() + defp do_exec_handle_demand(pad_data, state) do Membrane.Logger.warn("EXEC HANDLE DEMAND #{inspect(pad_data.ref)}") context = &CallbackContext.from_state(&1, incoming_demand: pad_data.incoming_demand) - state = PadModel.set_data!(state, pad_data.ref, :handle_demand_executed?, true) - - state = - CallbackHandler.exec_and_handle_callback( - :handle_demand, - ActionHandler, - %{ - split_continuation_arbiter: &exec_handle_demand?(PadModel.get_data!(&1, pad_data.ref)), - context: context - }, - [pad_data.ref, pad_data.demand, pad_data.demand_unit], - state - ) - - check_demand_counter(pad_data.ref, state) + CallbackHandler.exec_and_handle_callback( + :handle_demand, + ActionHandler, + %{ + split_continuation_arbiter: &exec_handle_demand?(PadModel.get_data!(&1, pad_data.ref)), + context: context + }, + [pad_data.ref, pad_data.demand, pad_data.demand_unit], + state + ) end defp increase_demand_counter?(pad_data, state) do @@ -259,14 +121,6 @@ defmodule Membrane.Core.Element.DemandController do associated_pads: associated_pads } = pad_data - Membrane.Logger.warn("\n\nDUPA #{inspect(flow_control == :auto)}") - Membrane.Logger.warn("DUPA #{inspect(state.effective_flow_control)}") - Membrane.Logger.warn("DUPA #{inspect(lacking_buffers)}") - - Membrane.Logger.warn( - "DUPA #{inspect(Enum.all?(associated_pads, &demand_counter_positive?(&1, state)))}" - ) - flow_control == :auto and state.effective_flow_control == :pull and lacking_buffers < @lacking_buffers_lowerbound and @@ -274,148 +128,13 @@ defmodule Membrane.Core.Element.DemandController do end defp demand_counter_positive?(pad_ref, state) do - PadModel.get_data!(state, pad_ref, :demand_counter) - |> DemandCounter.get() - |> then(&(&1 > 0)) - end - - # @spec handle_ingoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: State.t() - # def handle_ingoing_buffers(pad_ref, buffers, state) do - # %{ - # demand_unit: demand_unit, - # lacking_buffers: lacking_buffers - # } = PadModel.get_data!(state, pad_ref) - - # buffers_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) - # PadModel.set_data!(state, pad_ref, :lacking_buffers, lacking_buffers - buffers_size) - # end - - @spec decrease_demand_counter_by_outgoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: - State.t() - def decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) do - pad_data = PadModel.get_data!(state, pad_ref) - buffers_size = Buffer.Metric.from_unit(pad_data.other_demand_unit).buffers_size(buffers) - - pad_demand = pad_data.demand - buffers_size - demand_counter = DemandCounter.decrease(pad_data.demand_counter, buffers_size) + counter_value = + PadModel.get_data!(state, pad_ref, :demand_counter) + |> DemandCounter.get() - PadModel.update_data!( - state, - pad_ref, - &%{&1 | demand: pad_demand, demand_counter: demand_counter} - ) + counter_value > 0 end - # ----- OLD FUNCTIONALITIES - - # @doc """ - # Handles demand coming on an output pad. Updates demand value and executes `handle_demand` callback. - # """ - # @spec handle_demand(Pad.ref(), non_neg_integer, State.t()) :: State.t() - # def handle_demand(pad_ref, size, state) do - # withl pad: {:ok, data} <- PadModel.get_data(state, pad_ref), - # playback: %State{playback: :playing} <- state do - # if data.direction == :input, - # do: raise("Input pad cannot handle demand.") - - # do_handle_demand(pad_ref, size, data, state) - # else - # pad: {:error, :unknown_pad} -> - # # We've got a demand from already unlinked pad - # state - - # playback: _playback -> - # PlaybackQueue.store(&handle_demand(pad_ref, size, &1), state) - # end - # end - - # defp do_handle_demand(pad_ref, size, %{flow_control: :auto} = data, state) do - # %{demand: old_demand, associated_pads: associated_pads} = data - - # state = PadModel.set_data!(state, pad_ref, :demand, old_demand + size) - - # if old_demand <= 0 do - # Enum.reduce(associated_pads, state, &send_auto_demand_if_needed/2) - # else - # state - # end - # end - - # defp do_handle_demand(pad_ref, size, %{flow_control: :manual} = data, state) do - # demand = data.demand + size - # data = %{data | demand: demand} - # state = PadModel.set_data!(state, pad_ref, data) - - # if exec_handle_demand?(data) do - # context = &CallbackContext.from_state(&1, incoming_demand: size) - - # CallbackHandler.exec_and_handle_callback( - # :handle_demand, - # ActionHandler, - # %{ - # split_continuation_arbiter: &exec_handle_demand?(PadModel.get_data!(&1, pad_ref)), - # context: context - # }, - # [pad_ref, demand, data[:demand_unit]], - # state - # ) - # else - # state - # end - # end - - # defp do_handle_demand(_pad_ref, _size, %{flow_control: :push} = _data, state) do - # state - # end - - # @doc """ - # Sends auto demand to an input pad if it should be sent. - - # The demand should be sent when the current demand on the input pad is at most - # half of the demand request size and if there's positive demand on each of - # associated output pads. - # """ - # @spec send_auto_demand_if_needed(Pad.ref(), State.t()) :: State.t() - # def send_auto_demand_if_needed(pad_ref, state) do - # data = PadModel.get_data!(state, pad_ref) - - # %{ - # flow_control: :auto, - # demand: demand, - # toilet: toilet, - # associated_pads: associated_pads, - # auto_demand_size: demand_request_size - # } = data - - # demand = - # if demand <= div(demand_request_size, 2) and - # (state.effective_flow_control == :push or - # auto_demands_positive?(associated_pads, state)) do - # Membrane.Logger.debug_verbose( - # "Sending auto demand of size #{demand_request_size - demand} on pad #{inspect(pad_ref)}" - # ) - - # %{pid: pid, other_ref: other_ref} = data - # Message.send(pid, :demand, demand_request_size - demand, for_pad: other_ref) - - # if toilet, do: Toilet.drain(toilet, demand_request_size - demand) - - # demand_request_size - # else - # Membrane.Logger.debug_verbose( - # "Not sending auto demand on pad #{inspect(pad_ref)}, pads data: #{inspect(state.pads_data)}" - # ) - - # demand - # end - - # PadModel.set_data!(state, pad_ref, :demand, demand) - # end - - # defp auto_demands_positive?(associated_pads, state) do - # Enum.all?(associated_pads, &(PadModel.get_data!(state, &1, :demand) > 0)) - # end - defp exec_handle_demand?(%{end_of_stream?: true}) do Membrane.Logger.debug_verbose(""" Demand controller: not executing handle_demand as :end_of_stream action has already been returned diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index b4198e927..c0617c4c1 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -4,11 +4,11 @@ defmodule Membrane.Core.Element.DemandHandler do # Module handling demands requested on output pads. alias Membrane.Buffer - alias Membrane.Core.Child.PadModel alias Membrane.Core.Element.{ BufferController, DemandController, + DemandCounter, EventController, InputQueue, State, @@ -18,8 +18,8 @@ defmodule Membrane.Core.Element.DemandHandler do alias Membrane.Pad - require Membrane.Core.Child.PadModel - require Membrane.Core.Message + require Membrane.Core.Child.PadModel, as: PadModel + require Membrane.Core.Message, as: Message require Membrane.Logger @doc """ @@ -35,7 +35,8 @@ defmodule Membrane.Core.Element.DemandHandler do end def handle_redemand(pad_ref, state) do - DemandController.redemand(pad_ref, state) + DemandController.exec_handle_demand(pad_ref, state) + |> handle_delayed_demands() end @doc """ @@ -70,8 +71,8 @@ defmodule Membrane.Core.Element.DemandHandler do end def supply_demand(pad_ref, state) do - state = do_supply_demand(pad_ref, state) - handle_delayed_demands(state) + do_supply_demand(pad_ref, state) + |> handle_delayed_demands() end defp do_supply_demand(pad_ref, state) do @@ -80,16 +81,11 @@ defmodule Membrane.Core.Element.DemandHandler do pad_data = state |> PadModel.get_data!(pad_ref) - {{_queue_status, data}, new_input_queue} = - InputQueue.take( - pad_data.input_queue, - # , - pad_data.demand - # pad_data.other_ref - ) + {{_queue_status, popped_data}, new_input_queue} = + InputQueue.take(pad_data.input_queue, pad_data.demand) state = PadModel.set_data!(state, pad_ref, :input_queue, new_input_queue) - state = handle_input_queue_output(pad_ref, data, state) + state = handle_input_queue_output(pad_ref, popped_data, state) %State{state | supplying_demand?: false} end @@ -99,42 +95,24 @@ defmodule Membrane.Core.Element.DemandHandler do """ @spec handle_outgoing_buffers( Pad.ref(), - PadModel.pad_data(), [Buffer.t()], State.t() ) :: State.t() - # def handle_outgoing_buffers(pad_ref, %{flow_control: flow_control} = _data, buffers, state) - # when flow_control in [:auto, :manual] do - # # %{other_demand_unit: other_demand_unit, demand: demand} = data - # # buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) - # # state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) - - # # fill_toilet_if_exists(pad_ref, data.toilet, buf_size, state) - # state = DemandController.decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) - - # if PadModel.get_data!(state, pad_ref, :demand) <= 0 do - # DemandController.check_demand_counter(pad_ref, state) - # else - # state - # end - # end - - # def handle_outgoing_buffers(pad_ref, %{flow_control: :push} = _data, buffers, state) do - # # %{other_demand_unit: other_demand_unit} = data - # # buf_size = Buffer.Metric.from_unit(other_demand_unit).buffers_size(buffers) - # # fill_toilet_if_exists(pad_ref, data.toilet, buf_size, state) - - # DemandController.decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) - # end - - def handle_outgoing_buffers(pad_ref, data, buffers, state) do - state = DemandController.decrease_demand_counter_by_outgoing_buffers(pad_ref, buffers, state) - - if data.flow_control != :push and PadModel.get_data!(state, pad_ref, :demand) <= 0 do - DemandController.check_demand_counter(pad_ref, state) - else - state - end + def handle_outgoing_buffers(pad_ref, buffers, state) do + pad_data = PadModel.get_data!(state, pad_ref) + buffers_size = Buffer.Metric.from_unit(pad_data.other_demand_unit).buffers_size(buffers) + + demand = pad_data.demand - buffers_size + demand_counter = DemandCounter.decrease(pad_data.demand_counter, buffers_size) + + state = + PadModel.set_data!( + state, + pad_ref, + %{pad_data | demand: demand, demand_counter: demand_counter} + ) + + DemandController.check_demand_counter(pad_ref, state) end defp update_demand(pad_ref, size, state) when is_integer(size) do @@ -154,26 +132,61 @@ defmodule Membrane.Core.Element.DemandHandler do PadModel.set_data!(state, pad_ref, :demand, new_demand) end + @handle_demand_loop_limit 20 + + # dupa: upewnij sie, ze ta petla jest wykonywana tylko jak supplying_demand?: false @spec handle_delayed_demands(State.t()) :: State.t() - defp handle_delayed_demands(%State{delayed_demands: delayed_demands} = state) do + def handle_delayed_demands(%State{} = state) do # Taking random element of `:delayed_demands` is done to keep data flow # balanced among pads, i.e. to prevent situation where demands requested by # one pad are supplied right away while another one is waiting for buffers # potentially for a long time. - case Enum.take_random(state.delayed_demands, 1) do - [] -> - state - [{pad_ref, action} = entry] -> - state = %State{state | delayed_demands: MapSet.delete(delayed_demands, entry)} + # dupa: zalozenie do zweryfikowania: rzeczy wywolane w wyniku ponizszej petli bedą mialy `supplying_demand?: true` + + cond do + state.supplying_demand? -> + raise "dupa 007" + + state.handle_demand_loop_counter >= @handle_demand_loop_limit -> + Message.send(self(), :resume_handle_demand_loop) + %{state | handle_demand_loop_counter: 0} + + state.delayed_demands == MapSet.new() -> + # dupa: czy to czy ustawiam counter na 0, czy go nie dotykam w tym case, coś zmienia? + %{state | handle_demand_loop_counter: 0} + + true -> + [{pad_ref, action} = entry] = Enum.take_random(state.delayed_demands, 1) state = - case action do - :supply -> do_supply_demand(pad_ref, state) - :redemand -> handle_redemand(pad_ref, state) - end + state + |> Map.update!(:delayed_demands, &MapSet.delete(&1, entry)) + |> Map.update!(:handle_demand_loop_counter, &(&1 + 1)) + + case action do + :demand -> do_supply_demand(pad_ref, state) + :redemand -> handle_redemand(pad_ref, state) + end + end + end - handle_delayed_demands(state) + @spec maybe_snapshot_demand_counter(Pad.ref(), State.t()) :: State.t() + def maybe_snapshot_demand_counter(pad_ref, state) do + with {:ok, %{flow_control: :manual, demand: demand, demand_counter: demand_counter}} + when demand <= 0 <- PadModel.get_data(state, pad_ref, :demand), + counter_value when counter_value > 0 and counter_value > demand <- + DemandCounter.get(demand_counter) do + state = + PadModel.update_data!( + state, + pad_ref, + &%{&1 | demand: counter_value, incoming_demand: counter_value - &1.demand} + ) + + handle_redemand(pad_ref, state) + else + _other -> state end end @@ -182,15 +195,9 @@ defmodule Membrane.Core.Element.DemandHandler do [InputQueue.output_value()], State.t() ) :: State.t() - defp handle_input_queue_output(pad_ref, data, state) do - count = PadModel.get_data!(state, pad_ref, :demand) - - Membrane.Logger.warn( - "HANDLE INPUT QUEUE OUTPUT SIZE #{inspect(Enum.count(data))} WHILE COUNT #{count}" - ) - - Enum.reduce(data, state, fn v, state -> - do_handle_input_queue_output(pad_ref, v, state) + defp handle_input_queue_output(pad_ref, queue_output, state) do + Enum.reduce(queue_output, state, fn item, state -> + do_handle_input_queue_output(pad_ref, item, state) end) end diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 4b8e9558b..247a4deb2 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -118,16 +118,10 @@ defmodule Membrane.Core.Element.EffectiveFlowController do Enum.reduce(state.pads_data, state, fn {pad_ref, %{flow_control: :auto, direction: :input}}, state -> - # DemandController.send_auto_demand_if_needed(pad_ref, state) DemandController.increase_demand_counter_if_needed(pad_ref, state) _pad_entry, state -> state end) end - - # defp update_demand_counter_receiver_mode(pad_ref, state) do - # PadModel.get_data!(state, pad_ref, [:demand_counter]) - # |> DemandCounter.set_receiver_mode(state.effective_flow_control) - # end end From 0b41d56147042039e0cc59e87ce6b04991d00807 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 3 Apr 2023 14:13:11 +0200 Subject: [PATCH 22/64] Fix bugs in integration tests --- lib/membrane/core/element.ex | 1 + lib/membrane/core/element/action_handler.ex | 12 ++++- .../core/element/buffer_controller.ex | 24 +++++++++- .../core/element/demand_controller.ex | 1 + lib/membrane/core/element/demand_counter.ex | 8 ++-- lib/membrane/core/element/demand_handler.ex | 44 ++++++++++++------- lib/membrane/core/element/input_queue.ex | 1 + lib/membrane/testing/sink.ex | 2 +- lib/membrane/testing/source.ex | 3 ++ .../core/element/event_controller_test.exs | 2 +- test/membrane/integration/demands_test.exs | 3 -- test/support/demands_test/filter.ex | 4 -- 12 files changed, 71 insertions(+), 34 deletions(-) diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index a2f2fe241..e45f15c2a 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -186,6 +186,7 @@ defmodule Membrane.Core.Element do end defp do_handle_info(Message.new(:resume_handle_demand_loop), state) do + Membrane.Logger.warn("RECEIVING RESUME LOOP MSG") state = DemandHandler.handle_delayed_demands(state) {:noreply, state} end diff --git a/lib/membrane/core/element/action_handler.ex b/lib/membrane/core/element/action_handler.ex index 1781feb1b..12e767d72 100644 --- a/lib/membrane/core/element/action_handler.ex +++ b/lib/membrane/core/element/action_handler.ex @@ -20,7 +20,15 @@ defmodule Membrane.Core.Element.ActionHandler do } alias Membrane.Core.Child.PadModel - alias Membrane.Core.Element.{DemandHandler, PadController, State, StreamFormatController} + + alias Membrane.Core.Element.{ + DemandController, + DemandHandler, + PadController, + State, + StreamFormatController + } + alias Membrane.Core.{Events, Message, Telemetry, TimerController} alias Membrane.Element.Action @@ -311,7 +319,7 @@ defmodule Membrane.Core.Element.ActionHandler do |> PadModel.set_data!(pad_ref, :start_of_stream?, true) Message.send(pid, :buffer, buffers, for_pad: other_ref) - state + DemandController.check_demand_counter(pad_ref, state) else %{direction: :input} -> raise PadDirectionError, action: :buffer, direction: :input, pad: pad_ref diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index 7dccb3eed..5680efb95 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -5,6 +5,7 @@ defmodule Membrane.Core.Element.BufferController do use Bunch + alias Membrane.Core.Element.DemandCounter alias Membrane.{Buffer, Pad} alias Membrane.Core.{CallbackHandler, Telemetry} alias Membrane.Core.Child.PadModel @@ -62,7 +63,6 @@ defmodule Membrane.Core.Element.BufferController do buf_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) state = PadModel.set_data!(state, pad_ref, :lacking_buffers, lacking_buffers - buf_size) - # state = DemandController.send_auto_demand_if_needed(pad_ref, state) state = DemandController.increase_demand_counter_if_needed(pad_ref, state) exec_buffer_callback(pad_ref, buffers, state) end @@ -70,13 +70,33 @@ defmodule Membrane.Core.Element.BufferController do defp do_handle_buffer(pad_ref, %{flow_control: :manual} = data, buffers, state) do %{input_queue: old_input_queue} = data + require Membrane.Logger + + buf_num = + Enum.filter(buffers, &match?(%Membrane.Buffer{}, &1)) + |> Enum.count() + + Membrane.Logger.warn("STORING #{inspect(buf_num)} BUFFERS") + + for %Membrane.Buffer{payload: <> <> <<_::binary>>} <- buffers do + Membrane.Logger.warn("STORE IN INPUT_QUEUE BUFFER NO. #{inspect(i)}") + end + input_queue = InputQueue.store(old_input_queue, buffers) state = PadModel.set_data!(state, pad_ref, :input_queue, input_queue) require Membrane.Logger + warn_sufix = + with {:ok, %{demand: demand, demand_counter: dc}} <- PadModel.get_data(state, :output) do + " OUTPUT DEMAND SNAPSHOT #{inspect(demand)} COUNTER #{inspect(DemandCounter.get(dc))}" + else + _ -> "" + end + Membrane.Logger.warn( - "HANDLE BUFFER #{inspect(Enum.count(buffers))} EMPTY QUEUE? #{inspect(old_input_queue |> InputQueue.empty?())}" + "HANDLE BUFFER #{inspect(Enum.count(buffers))} EMPTY QUEUE? #{inspect(old_input_queue |> InputQueue.empty?())}" <> + warn_sufix ) if old_input_queue |> InputQueue.empty?() do diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 4766790bd..a5bad1263 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -56,6 +56,7 @@ defmodule Membrane.Core.Element.DemandController do counter_value = demand_counter |> DemandCounter.get() + # todo: maybe also check if pad_data.demand <= 0 ? if counter_value > 0 do # todo: optimize lopp below Enum.reduce(associated_pads, state, &increase_demand_counter_if_needed/2) diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index aef1a9b10..a9d3d5e57 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -29,13 +29,13 @@ defmodule Membrane.Core.Element.DemandCounter do @impl true def handle_call({:sub_get, atomic_ref, value}, _from, _state) do result = :atomics.sub_get(atomic_ref, 1, value) - {:sub_get, result, nil} + {:reply, result, nil} end @impl true def handle_call({:get, atomic_ref}, _from, _state) do result = :atomics.get(atomic_ref, 1) - {:sub_get, result, nil} + {:reply, result, nil} end @impl true @@ -274,9 +274,7 @@ defmodule Membrane.Core.Element.DemandCounter do demand_counter end - Membrane.Logger.warn( - "DEMAND COUNTER AFTER DECREMENTATION #{inspect(get(dc))} old #{inspect(xd)}" - ) + Membrane.Logger.warn("DEMAND COUNTER DECREMENTATION #{inspect(xd)} -> #{inspect(get(dc))}") dc end diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index c0617c4c1..0630db235 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -34,8 +34,11 @@ defmodule Membrane.Core.Element.DemandHandler do Map.update!(state, :delayed_demands, &MapSet.put(&1, {pad_ref, :redemand})) end - def handle_redemand(pad_ref, state) do - DemandController.exec_handle_demand(pad_ref, state) + def handle_redemand(pad_ref, %State{} = state) do + state + |> Map.put(:supplying_demand?, true) + |> then(&DemandController.exec_handle_demand(pad_ref, &1)) + |> Map.put(:supplying_demand?, false) |> handle_delayed_demands() end @@ -84,6 +87,10 @@ defmodule Membrane.Core.Element.DemandHandler do {{_queue_status, popped_data}, new_input_queue} = InputQueue.take(pad_data.input_queue, pad_data.demand) + Membrane.Logger.warn( + "TRYNIG TO TAKE #{pad_data.demand} from QUEUE, GOT #{inspect(popped_data, limit: :infinity, pretty: true)}" + ) + state = PadModel.set_data!(state, pad_ref, :input_queue, new_input_queue) state = handle_input_queue_output(pad_ref, popped_data, state) %State{state | supplying_demand?: false} @@ -105,18 +112,17 @@ defmodule Membrane.Core.Element.DemandHandler do demand = pad_data.demand - buffers_size demand_counter = DemandCounter.decrease(pad_data.demand_counter, buffers_size) - state = - PadModel.set_data!( - state, - pad_ref, - %{pad_data | demand: demand, demand_counter: demand_counter} - ) + # state = + PadModel.set_data!( + state, + pad_ref, + %{pad_data | demand: demand, demand_counter: demand_counter} + ) - DemandController.check_demand_counter(pad_ref, state) + # DemandController.check_demand_counter(pad_ref, state) end defp update_demand(pad_ref, size, state) when is_integer(size) do - # dupa: tutaj jest cos nie ten teges PadModel.set_data!(state, pad_ref, :demand, size) end @@ -134,7 +140,6 @@ defmodule Membrane.Core.Element.DemandHandler do @handle_demand_loop_limit 20 - # dupa: upewnij sie, ze ta petla jest wykonywana tylko jak supplying_demand?: false @spec handle_delayed_demands(State.t()) :: State.t() def handle_delayed_demands(%State{} = state) do # Taking random element of `:delayed_demands` is done to keep data flow @@ -142,18 +147,18 @@ defmodule Membrane.Core.Element.DemandHandler do # one pad are supplied right away while another one is waiting for buffers # potentially for a long time. - # dupa: zalozenie do zweryfikowania: rzeczy wywolane w wyniku ponizszej petli bedą mialy `supplying_demand?: true` + # Membrane.Logger.warn("HANDLE DELAYED DEMANDS #{inspect(state.delayed_demands)}") cond do state.supplying_demand? -> raise "dupa 007" state.handle_demand_loop_counter >= @handle_demand_loop_limit -> - Message.send(self(), :resume_handle_demand_loop) + Message.self(:resume_handle_demand_loop) + Membrane.Logger.warn("SENDING RESUME LOOP MSG") %{state | handle_demand_loop_counter: 0} state.delayed_demands == MapSet.new() -> - # dupa: czy to czy ustawiam counter na 0, czy go nie dotykam w tym case, coś zmienia? %{state | handle_demand_loop_counter: 0} true -> @@ -165,7 +170,7 @@ defmodule Membrane.Core.Element.DemandHandler do |> Map.update!(:handle_demand_loop_counter, &(&1 + 1)) case action do - :demand -> do_supply_demand(pad_ref, state) + :supply -> supply_demand(pad_ref, state) :redemand -> handle_redemand(pad_ref, state) end end @@ -173,10 +178,17 @@ defmodule Membrane.Core.Element.DemandHandler do @spec maybe_snapshot_demand_counter(Pad.ref(), State.t()) :: State.t() def maybe_snapshot_demand_counter(pad_ref, state) do + Membrane.Logger.warn("MAYBE SNAPSHOT DEMAND COUNTER #{inspect(pad_ref)}") + + # Membrane.Logger.warn(inspect(PadModel.get_data(state, pad_ref, :demand))) + # Membrane.Logger.warn("DEMAND COUNTER: ") + with {:ok, %{flow_control: :manual, demand: demand, demand_counter: demand_counter}} - when demand <= 0 <- PadModel.get_data(state, pad_ref, :demand), + when demand <= 0 <- PadModel.get_data(state, pad_ref), counter_value when counter_value > 0 and counter_value > demand <- DemandCounter.get(demand_counter) do + Membrane.Logger.warn("BUMPING DEMAND #{inspect(demand)} -> #{inspect(counter_value)}") + state = PadModel.update_data!( state, diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index 9e2d4f618..450c02366 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -324,6 +324,7 @@ defmodule Membrane.Core.Element.InputQueue do ) when target_size > size + lacking_buffers do diff = target_size - size - lacking_buffers + # diff = max(target_size - size - lacking_buffers, 10) """ Increasing DemandCounter linked to #{inspect(input_queue.linked_output_ref)} by #{inspect(diff)} diff --git a/lib/membrane/testing/sink.ex b/lib/membrane/testing/sink.ex index e9bf55d82..9b5923b24 100644 --- a/lib/membrane/testing/sink.ex +++ b/lib/membrane/testing/sink.ex @@ -71,7 +71,7 @@ defmodule Membrane.Testing.Sink do do: {notify({:end_of_stream, pad}), state} @impl true - def handle_stream_format(pad, stream_format, _context, state), + def handle_stream_format(pad, stream_format, _ctx, state), do: {notify({:stream_format, pad, stream_format}), state} @impl true diff --git a/lib/membrane/testing/source.ex b/lib/membrane/testing/source.ex index cd14eea11..dced1a50b 100644 --- a/lib/membrane/testing/source.ex +++ b/lib/membrane/testing/source.ex @@ -97,6 +97,9 @@ defmodule Membrane.Testing.Source do buffers = generator_state..(size + generator_state - 1) |> Enum.map(fn generator_state -> + require Membrane.Logger + Membrane.Logger.warn("GENERATING BUFFER NO. #{inspect(generator_state)}") + %Buffer{payload: <>} end) diff --git a/test/membrane/core/element/event_controller_test.exs b/test/membrane/core/element/event_controller_test.exs index 5ae8b9ea9..c3b74474b 100644 --- a/test/membrane/core/element/event_controller_test.exs +++ b/test/membrane/core/element/event_controller_test.exs @@ -33,7 +33,7 @@ defmodule Membrane.Core.Element.EventControllerTest do InputQueue.init(%{ inbound_demand_unit: :buffers, outbound_demand_unit: :buffers, - demand_pad: :some_pad, + linked_output_ref: :some_pad, log_tag: "test", demand_counter: demand_counter, target_size: nil, diff --git a/test/membrane/integration/demands_test.exs b/test/membrane/integration/demands_test.exs index 0f020cb1d..d2c3a0a27 100644 --- a/test/membrane/integration/demands_test.exs +++ b/test/membrane/integration/demands_test.exs @@ -17,8 +17,6 @@ defmodule Membrane.Integration.DemandsTest do end defp test_pipeline(pid) do - # pattern_gen = fn i -> %Buffer{payload: <> <> <<255>>} end - demand = 500 Pipeline.message_child(pid, :sink, {:make_demand, demand}) @@ -43,7 +41,6 @@ defmodule Membrane.Integration.DemandsTest do test_pipeline(pid) end - @tag :dupa test "Pipeline with filter underestimating demand" do filter_demand_gen = fn _incoming_demand -> 2 end diff --git a/test/support/demands_test/filter.ex b/test/support/demands_test/filter.ex index 6a18079d9..4266d8fc1 100644 --- a/test/support/demands_test/filter.ex +++ b/test/support/demands_test/filter.ex @@ -22,9 +22,6 @@ defmodule Membrane.Support.DemandsTest.Filter do @impl true def handle_demand(:output, size, _unit, _ctx, state) do - # state.demand_generator.(size) - # |> IO.inspect(label: "FILTER DEMANDING ") - Membrane.Logger.warn("FILTER DEMADNING #{state.demand_generator.(size)}") {[demand: {:input, state.demand_generator.(size)}], state} @@ -33,7 +30,6 @@ defmodule Membrane.Support.DemandsTest.Filter do @impl true def handle_buffer(:input, %Buffer{payload: payload}, _ctx, state) do state = Map.update(state, :i, 0, &(&1 + 1)) - # IO.inspect(state.i, label: "FILTER HANDLE BUFFER NO") {[buffer: {:output, %Buffer{payload: payload <> <<255>>}}, redemand: :output], state} end From e559e89f98aafc069bd342a3b5e615ec1125c4eb Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 3 Apr 2023 18:11:57 +0200 Subject: [PATCH 23/64] Fix all failing tests --- lib/membrane/core/element/demand_handler.ex | 4 +- .../core/element/action_handler_test.exs | 35 ++---- test/membrane/core/element_test.exs | 114 ++++++++++++++---- 3 files changed, 104 insertions(+), 49 deletions(-) diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 0630db235..c48c70c84 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -198,7 +198,9 @@ defmodule Membrane.Core.Element.DemandHandler do handle_redemand(pad_ref, state) else - _other -> state + other -> + Membrane.Logger.warn("NOT SNAPSHOTING BECAUSE #{inspect(other, pretty: true, limit: :infinity)}") + state end end diff --git a/test/membrane/core/element/action_handler_test.exs b/test/membrane/core/element/action_handler_test.exs index d363ffbf6..20f0d53dd 100644 --- a/test/membrane/core/element/action_handler_test.exs +++ b/test/membrane/core/element/action_handler_test.exs @@ -2,7 +2,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do use ExUnit.Case, async: true alias Membrane.{ActionError, Buffer, ElementError, PadDirectionError} - alias Membrane.Core.Element.{State, Toilet} + alias Membrane.Core.Element.{DemandCounter, State} alias Membrane.Support.DemandsTest.Filter alias Membrane.Support.Element.{TrivialFilter, TrivialSource} @@ -72,7 +72,11 @@ defmodule Membrane.Core.Element.ActionHandlerTest do end defp trivial_filter_state(_context) do - toilet = Toilet.new(100, :buffers, spawn(fn -> nil end), 1, :push, :push) + input_demand_counter = + DemandCounter.new(:push, self(), :buffers, spawn(fn -> :ok end), :output) + + output_demand_counter = + DemandCounter.new(:push, spawn(fn -> :ok end), :bytes, self(), :output) state = struct(State, @@ -92,7 +96,8 @@ defmodule Membrane.Core.Element.ActionHandlerTest do start_of_stream?: true, end_of_stream?: false, flow_control: :push, - toilet: toilet + demand_counter: output_demand_counter, + demand: 0 }, input: %{ direction: :input, @@ -102,7 +107,8 @@ defmodule Membrane.Core.Element.ActionHandlerTest do start_of_stream?: true, end_of_stream?: false, flow_control: :push, - toilet: :push + demand_counter: input_demand_counter, + demand: 0 } }, pads_info: %{ @@ -142,23 +148,6 @@ defmodule Membrane.Core.Element.ActionHandlerTest do end end - test "when element is moving to playing", %{state: state} do - state = - %{state | playback: :playing} - |> PadModel.set_data!(:output, :stream_format, @mock_stream_format) - - result = - @module.handle_action( - buffer_action(:output), - :handle_playing, - %{}, - state - ) - - assert result == state - assert_received Message.new(:buffer, [@mock_buffer], for_pad: :other_ref) - end - test "when element is playing", %{state: state} do state = %{state | playback: :playing} @@ -172,7 +161,9 @@ defmodule Membrane.Core.Element.ActionHandlerTest do state ) - assert result == state + assert result.pads_data.output.demand < 0 + assert DemandCounter.get(result.pads_data.output.demand_counter) < 0 + assert put_in(result, [:pads_data, :output, :demand], 0) == state assert_received Message.new(:buffer, [@mock_buffer], for_pad: :other_ref) end diff --git a/test/membrane/core/element_test.exs b/test/membrane/core/element_test.exs index 0fb132ac7..c72d72acf 100644 --- a/test/membrane/core/element_test.exs +++ b/test/membrane/core/element_test.exs @@ -65,23 +65,76 @@ defmodule Membrane.Core.ElementTest do state end + defmodule HelperServer do + use GenServer + + @spec start!() :: pid() + def start!() do + {:ok, pid} = GenServer.start(__MODULE__, self()) + pid + end + + @impl true + def init(owner) do + ref = Process.monitor(owner) + {:ok, %{monitor_ref: ref, owner: owner}} + end + + @impl true + def handle_cast({:set_reply, reply}, state), do: {:noreply, Map.put(state, :reply, reply)} + + @impl true + def handle_call(_msg, _from, state), do: {:reply, state.reply, state} + + @impl true + def handle_info({:DOWN, ref, _process, _pid, _reason}, %{monitor_ref: ref} = state) do + {:stop, :normal, state} + end + + @impl true + def handle_info(msg, %{owner: owner} = state) do + send(owner, msg) + {:noreply, state} + end + end + defp linked_state do - {:reply, {:ok, _reply}, state} = + helper_server = HelperServer.start!() + + output_other_endpoint = %Endpoint{ + pad_spec: :dynamic_input, + pad_ref: :dynamic_input, + pid: helper_server, + child: :other, + pad_props: %{options: [], toilet_capacity: nil, throttling_factor: nil} + } + + other_info = %{direction: :input, flow_control: :manual, demand_unit: :buffers} + + output_demand_counter = + Element.DemandCounter.new(:pull, helper_server, :buffers, self(), :output) + + reply_link_metadata = %{ + demand_counter: output_demand_counter, + observability_metadata: %{}, + input_demand_unit: :buffers, + output_demand_unit: :buffers + } + + handle_link_reply = {:ok, {output_other_endpoint, other_info, reply_link_metadata}} + + GenServer.cast(helper_server, {:set_reply, handle_link_reply}) + + {:reply, :ok, state} = Element.handle_call( Message.new(:handle_link, [ :output, %Endpoint{pad_spec: :output, pad_ref: :output, pad_props: %{options: []}, child: :this}, - %Endpoint{ - pad_spec: :dynamic_input, - pad_ref: :dynamic_input, - pid: self(), - child: :other, - pad_props: %{options: [], toilet_capacity: nil, throttling_factor: nil} - }, + output_other_endpoint, %{ initiator: :parent, - other_info: %{direction: :input, flow_control: :manual, demand_unit: :buffers}, - link_metadata: %{toilet: nil, observability_metadata: %{}}, + other_info: other_info, + link_metadata: %{demand_counter: output_demand_counter, observability_metadata: %{}}, stream_format_validation_params: [], other_effective_flow_control: :pull } @@ -151,9 +204,10 @@ defmodule Membrane.Core.ElementTest do test "should store demand/buffer/event/stream format when not playing" do initial_state = linked_state() + :ok = increase_output_demand_counter(initial_state, 10) [ - Message.new(:demand, 10, for_pad: :output), + Message.new(:demand_counter_increased, [:dupa, :output]), Message.new(:buffer, %Membrane.Buffer{payload: <<>>}, for_pad: :dynamic_input), Message.new(:stream_format, %StreamFormat{}, for_pad: :dynamic_input), Message.new(:event, %Membrane.Testing.Event{}, for_pad: :dynamic_input), @@ -168,8 +222,12 @@ defmodule Membrane.Core.ElementTest do end test "should update demand" do - msg = Message.new(:demand, 10, for_pad: :output) - assert {:noreply, state} = Element.handle_info(msg, playing_state()) + state = playing_state() + :ok = increase_output_demand_counter(state, 10) + + msg = Message.new(:demand_counter_increased, [:dupa, :output]) + assert {:noreply, state} = Element.handle_info(msg, state) + assert state.pads_data.output.demand == 10 end @@ -209,14 +267,14 @@ defmodule Membrane.Core.ElementTest do assert {:reply, {:ok, reply}, state} = Element.handle_call( Message.new(:handle_link, [ - :output, + :input, %{ - pad_ref: :output, - pad_props: %{options: [], toilet_capacity: nil}, + pad_ref: :dynamic_input, + pad_props: %{options: [], toilet_capacity: nil, target_queue_size: 40}, child: :this }, %{ - pad_ref: :dynamic_input, + pad_ref: :output, pid: pid, child: :other, pad_props: %{options: [], toilet_capacity: nil, throttling_factor: nil} @@ -237,23 +295,21 @@ defmodule Membrane.Core.ElementTest do get_state() ) - assert {%{child: :this, pad_props: %{options: []}, pad_ref: :output}, + assert {%{child: :this, pad_props: %{options: []}, pad_ref: :dynamic_input}, %{ - availability: :always, + availability: :on_request, flow_control: :manual, - direction: :output, - name: :output, + direction: :input, + name: :dynamic_input, options: nil }, - %{toilet: toilet, output_demand_unit: :buffers, input_demand_unit: :buffers}} = reply - - assert toilet != nil + %{demand_counter: %Element.DemandCounter{}, output_demand_unit: :buffers, input_demand_unit: :buffers}} = reply assert %Membrane.Element.PadData{ pid: ^pid, - other_ref: :dynamic_input, + other_ref: :output, other_demand_unit: :buffers - } = state.pads_data.output + } = state.pads_data.dynamic_input end test "should handle unlinking pads" do @@ -356,4 +412,10 @@ defmodule Membrane.Core.ElementTest do group: nil } end + + defp increase_output_demand_counter(state, value) do + :ok = + state.pads_data.output.demand_counter + |> Element.DemandCounter.increase(value) + end end From dc26072395383676437a0cf9dc5b76cedd7eacc2 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 4 Apr 2023 15:42:13 +0200 Subject: [PATCH 24/64] Write unit tests for DemandCounter --- .../core/element/buffer_controller.ex | 5 - lib/membrane/core/element/demand_counter.ex | 65 ++-- lib/membrane/core/element/demand_handler.ex | 13 +- lib/membrane/core/element/toilet.ex | 294 ------------------ .../core/element/demand_counter_test.exs | 134 ++++++++ test/membrane/core/element/toilet_test.exs | 48 --- test/membrane/core/element_test.exs | 6 +- 7 files changed, 187 insertions(+), 378 deletions(-) delete mode 100644 lib/membrane/core/element/toilet.ex create mode 100644 test/membrane/core/element/demand_counter_test.exs delete mode 100644 test/membrane/core/element/toilet_test.exs diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index 5680efb95..3852e1192 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -107,11 +107,6 @@ defmodule Membrane.Core.Element.BufferController do end defp do_handle_buffer(pad_ref, %{flow_control: :push} = data, buffers, state) do - if data.toilet do - buf_size = Buffer.Metric.from_unit(data.demand_unit).buffers_size(buffers) - Toilet.drain(data.toilet, buf_size) - end - exec_buffer_callback(pad_ref, buffers, state) end diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index a9d3d5e57..358a215ef 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -14,6 +14,11 @@ defmodule Membrane.Core.Element.DemandCounter do use GenServer + @type t :: pid() + + @spec start(pid()) :: {:ok, t} + def start(parent_pid), do: GenServer.start(__MODULE__, parent_pid) + @impl true def init(parent_pid) do Process.monitor(parent_pid) @@ -58,51 +63,67 @@ defmodule Membrane.Core.Element.DemandCounter do # The module allows to create and modify the value of a counter in the same manner both when the counter is about to be accessed # from the same node, and from different nodes. - @type t :: {pid(), :atomics.atomics_ref()} + @enforce_keys [:worker, :atomic_ref] + defstruct @enforce_keys + + @type t :: %__MODULE__{worker: Worker.t(), atomic_ref: :atomics.atomics_ref()} + + defguardp on_this_same_node_as_self(distributed_atomic) + when distributed_atomic.worker |> node() == self() |> node() @spec new(integer() | nil) :: t def new(initial_value \\ nil) do atomic_ref = :atomics.new(1, []) - {:ok, pid} = GenServer.start(Worker, self()) - if initial_value, do: put({pid, atomic_ref}, initial_value) + {:ok, worker} = Worker.start(self()) - {pid, atomic_ref} + distributed_atomic = %__MODULE__{ + atomic_ref: atomic_ref, + worker: worker + } + + if initial_value, do: put(distributed_atomic, initial_value) + + distributed_atomic end @spec add_get(t, integer()) :: integer() - def add_get({pid, atomic_ref}, value) when node(pid) == node(self()) do - :atomics.add_get(atomic_ref, 1, value) + def add_get(%__MODULE__{} = distributed_atomic, value) + when on_this_same_node_as_self(distributed_atomic) do + :atomics.add_get(distributed_atomic.atomic_ref, 1, value) end - def add_get({pid, atomic_ref}, value) do - GenServer.call(pid, {:add_get, atomic_ref, value}) + def add_get(%__MODULE__{} = distributed_atomic, value) do + GenServer.call(distributed_atomic.worker, {:add_get, distributed_atomic.atomic_ref, value}) end @spec sub_get(t, integer()) :: integer() - def sub_get({pid, atomic_ref}, value) when node(pid) == node(self()) do - :atomics.sub_get(atomic_ref, 1, value) + def sub_get(%__MODULE__{} = distributed_atomic, value) + when on_this_same_node_as_self(distributed_atomic) do + :atomics.sub_get(distributed_atomic.atomic_ref, 1, value) end - def sub_get({pid, atomic_ref}, value) do - GenServer.cast(pid, {:sub_get, atomic_ref, value}) + def sub_get(%__MODULE__{} = distributed_atomic, value) do + GenServer.cast(distributed_atomic.worker, {:sub_get, distributed_atomic.atomic_ref, value}) end @spec put(t, integer()) :: :ok - def put({pid, atomic_ref}, value) when node(pid) == node(self()) do - :atomics.put(atomic_ref, 1, value) + def put(%__MODULE__{} = distributed_atomic, value) + when on_this_same_node_as_self(distributed_atomic) do + :atomics.put(distributed_atomic.atomic_ref, 1, value) end - def put({pid, atomic_ref}, value) do - GenServer.cast(pid, {:put, atomic_ref, value}) + def put(%__MODULE__{} = distributed_atomic, value) do + GenServer.cast(distributed_atomic.worker, {:put, distributed_atomic.atomic_ref, value}) end @spec get(t) :: integer() - def get({pid, atomic_ref}) when node(pid) == node(self()) do - :atomics.get(atomic_ref, 1) + def get(%__MODULE__{} = distributed_atomic) + when on_this_same_node_as_self(distributed_atomic) do + :atomics.get(distributed_atomic.atomic_ref, 1) end - def get({pid, atomic_ref}) do - GenServer.call(pid, {:get, atomic_ref}) + def get(%__MODULE__{} = distributed_atomic) do + GenServer.call(distributed_atomic.worker, {:get, distributed_atomic.atomic_ref}) end end @@ -186,11 +207,11 @@ defmodule Membrane.Core.Element.DemandCounter do sender_pad_ref, overflow_limit \\ nil ) do - {counter_pid, _atomic} = counter = DistributedAtomic.new() + %DistributedAtomic{worker: worker} = counter = DistributedAtomic.new() buffered_decrementation_limit = if node(sender_process) == - node(counter_pid), + node(worker), do: @default_buffered_decrementation_limit, else: @distributed_buffered_decrementation_limit diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index c48c70c84..8502a68db 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -12,8 +12,7 @@ defmodule Membrane.Core.Element.DemandHandler do EventController, InputQueue, State, - StreamFormatController, - Toilet + StreamFormatController } alias Membrane.Pad @@ -199,7 +198,10 @@ defmodule Membrane.Core.Element.DemandHandler do handle_redemand(pad_ref, state) else other -> - Membrane.Logger.warn("NOT SNAPSHOTING BECAUSE #{inspect(other, pretty: true, limit: :infinity)}") + Membrane.Logger.warn( + "NOT SNAPSHOTING BECAUSE #{inspect(other, pretty: true, limit: :infinity)}" + ) + state end end @@ -232,11 +234,6 @@ defmodule Membrane.Core.Element.DemandHandler do state ) do state = PadModel.update_data!(state, pad_ref, :demand, &(&1 - outbound_metric_buf_size)) - - if toilet = PadModel.get_data!(state, pad_ref, :toilet) do - Toilet.drain(toilet, outbound_metric_buf_size) - end - BufferController.exec_buffer_callback(pad_ref, buffers, state) end end diff --git a/lib/membrane/core/element/toilet.ex b/lib/membrane/core/element/toilet.ex deleted file mode 100644 index f0b5c8501..000000000 --- a/lib/membrane/core/element/toilet.ex +++ /dev/null @@ -1,294 +0,0 @@ -defmodule Membrane.Core.Element.Toilet do - @moduledoc false - - # Toilet is an entity that can be filled and drained. If it's not drained on - # time and exceeds its capacity, it overflows by logging an error and killing - # the responsible process (passed on the toilet creation). - - alias Membrane.Core.Element.EffectiveFlowController - - require Membrane.Logger - - defmodule Worker do - @moduledoc false - - # This is a GenServer created when the counter is about to be accessed from different nodes - it's running on the same node, - # where the :atomics variable is put, and processes from different nodes can ask it to modify the counter on their behalf. - - use GenServer - - @impl true - def init(parent_pid) do - Process.monitor(parent_pid) - {:ok, nil, :hibernate} - end - - @impl true - def handle_call({:add_get, atomic_ref, value}, _from, _state) do - result = :atomics.add_get(atomic_ref, 1, value) - {:reply, result, nil} - end - - @impl true - def handle_call({:get, atomic_ref}, _from, _state) do - result = :atomics.get(atomic_ref, 1) - {:reply, result, nil} - end - - @impl true - def handle_cast({:sub, atomic_ref, value}, _state) do - :atomics.sub(atomic_ref, 1, value) - {:noreply, nil} - end - - @impl true - def handle_info({:DOWN, _ref, :process, _object, _reason}, state) do - {:stop, :normal, state} - end - end - - defmodule DistributedCounter do - @moduledoc false - - # A module providing a common interface to access and modify a counter used in the toilet implementation. - # The counter uses :atomics module under the hood. - # The module allows to create and modify the value of a counter in the same manner both when the counter is about to be accessed - # from the same node, and from different nodes. - - @type t :: {pid(), :atomics.atomics_ref()} - - @spec new() :: t - def new() do - atomic_ref = :atomics.new(1, []) - {:ok, pid} = GenServer.start(Worker, self()) - {pid, atomic_ref} - end - - @spec add_get(t, integer()) :: integer() - def add_get({pid, atomic_ref}, value) when node(pid) == node(self()) do - :atomics.add_get(atomic_ref, 1, value) - end - - def add_get({pid, atomic_ref}, value) do - GenServer.call(pid, {:add_get, atomic_ref, value}) - end - - @spec sub(t, integer()) :: :ok - def sub({pid, atomic_ref}, value) when node(pid) == node(self()) do - :atomics.sub(atomic_ref, 1, value) - end - - def sub({pid, atomic_ref}, value) do - GenServer.cast(pid, {:sub, atomic_ref, value}) - end - end - - defmodule DistributedEffectiveFlowControl do - @moduledoc false - - @type t :: {pid(), :atomics.atomics_ref()} - - @spec new(EffectiveFlowController.effective_flow_control()) :: t - def new(initial_value) do - atomic_ref = :atomics.new(1, []) - - initial_value = effective_flow_control_to_int(initial_value) - :atomics.put(atomic_ref, 1, initial_value) - - {:ok, pid} = GenServer.start(Worker, self()) - {pid, atomic_ref} - end - - @spec get(t) :: EffectiveFlowController.effective_flow_control() - def get({pid, atomic_ref}) when node(pid) == node(self()) do - :atomics.get(atomic_ref, 1) - |> int_to_effective_flow_control() - end - - def get({pid, atomic_ref}) do - GenServer.call(pid, {:get, atomic_ref}) - |> int_to_effective_flow_control() - end - - # contains implementation only for caller being on this same node, as :atomics, - # because toilet is created on the receiver side of link and only receiver should - # call this function - @spec put(t, EffectiveFlowController.effective_flow_control()) :: :ok - def put({_pid, atomic_ref}, value) do - value = effective_flow_control_to_int(value) - :atomics.put(atomic_ref, 1, value) - end - - defp int_to_effective_flow_control(0), do: :push - defp int_to_effective_flow_control(1), do: :pull - - defp effective_flow_control_to_int(:push), do: 0 - defp effective_flow_control_to_int(:pull), do: 1 - end - - @type t :: %__MODULE__{ - counter: DistributedCounter.t(), - capacity: pos_integer(), - responsible_process: Process.dest(), - throttling_factor: pos_integer(), - unrinsed_buffers_size: non_neg_integer(), - receiver_effective_flow_control: DistributedEffectiveFlowControl.t(), - sender_effective_flow_control: DistributedEffectiveFlowControl.t() - } - - @enforce_keys [ - :counter, - :capacity, - :responsible_process, - :throttling_factor, - :receiver_effective_flow_control, - :sender_effective_flow_control - ] - - defstruct @enforce_keys ++ [unrinsed_buffers_size: 0] - - @default_capacity_factor 200 - - @spec new( - capacity :: pos_integer() | nil, - demand_unit :: Membrane.Buffer.Metric.unit(), - responsible_process :: Process.dest(), - throttling_factor :: pos_integer(), - receiver_effective_flow_control :: EffectiveFlowController.effective_flow_control(), - sender_effective_flow_control :: EffectiveFlowController.effective_flow_control() - ) :: t - def new( - capacity, - demand_unit, - responsible_process, - throttling_factor, - sender_effective_flow_control, - receiver_effective_flow_control - ) do - default_capacity = - Membrane.Buffer.Metric.from_unit(demand_unit).buffer_size_approximation() * - @default_capacity_factor - - capacity = capacity || default_capacity - - receiver_effective_flow_control = - receiver_effective_flow_control - |> DistributedEffectiveFlowControl.new() - - sender_effective_flow_control = - sender_effective_flow_control - |> DistributedEffectiveFlowControl.new() - - %__MODULE__{ - counter: DistributedCounter.new(), - capacity: capacity, - responsible_process: responsible_process, - throttling_factor: throttling_factor, - receiver_effective_flow_control: receiver_effective_flow_control, - sender_effective_flow_control: sender_effective_flow_control - } - end - - @spec set_receiver_effective_flow_control(t, EffectiveFlowController.effective_flow_control()) :: - :ok - def set_receiver_effective_flow_control(%__MODULE__{} = toilet, value) do - DistributedEffectiveFlowControl.put( - toilet.receiver_effective_flow_control, - value - ) - end - - @spec set_sender_effective_flow_control(t, EffectiveFlowController.effective_flow_control()) :: - :ok - def set_sender_effective_flow_control(%__MODULE__{} = toilet, value) do - DistributedEffectiveFlowControl.put( - toilet.sender_effective_flow_control, - value - ) - end - - @spec fill(t, non_neg_integer) :: {:ok | :overflow, t} - def fill(%__MODULE__{} = toilet, amount) do - new_unrinsed_buffers_size = toilet.unrinsed_buffers_size + amount - - if new_unrinsed_buffers_size < toilet.throttling_factor do - {:ok, %{toilet | unrinsed_buffers_size: new_unrinsed_buffers_size}} - else - size = DistributedCounter.add_get(toilet.counter, new_unrinsed_buffers_size) - - %{toilet | unrinsed_buffers_size: 0} - |> check_overflow(size) - end - end - - @spec check_overflow(t, integer) :: {:ok | :overflow, t} - defp check_overflow(%__MODULE__{} = toilet, size) do - endpoints_effective_flow_control(toilet) - |> case do - %{sender: :push, receiver: :pull} when size > toilet.capacity -> - overflow(size, toilet.capacity, toilet.responsible_process) - {:overflow, toilet} - - %{} -> - {:ok, toilet} - end - end - - @spec endpoints_effective_flow_control(t) :: map() - defp endpoints_effective_flow_control(%__MODULE__{} = toilet) do - %{ - sender: - toilet.sender_effective_flow_control - |> DistributedEffectiveFlowControl.get(), - receiver: - toilet.receiver_effective_flow_control - |> DistributedEffectiveFlowControl.get() - } - end - - @spec drain(t, non_neg_integer) :: :ok - def drain(%__MODULE__{} = toilet, amount) do - DistributedCounter.sub(toilet.counter, amount) - end - - defp overflow(size, capacity, responsible_process) do - Membrane.Logger.debug_verbose(~S""" - Toilet overflow - - ` ' ` - .'''. ' .'''. - .. ' ' .. - ' '.'.' ' - .'''.'.'''. - ' .''.'.''. ' - ;------ ' ------; - | ~~ .--'--// | - | / ' \ | - | / ' \ | - | | ' | | ,----. - | \ , ' , / | =|____|= - '---,###'###,---' (---( - /## ' ##\ )---) - |##, ' ,##| (---( - \'#####'/ `---` - \`"#"`/ - |`"`| - .-| |-. - / ' ' \ - '---------' - """) - - Membrane.Logger.error(""" - Toilet overflow. - - Reached the size of #{inspect(size)}, which is above toilet capacity (#{inspect(capacity)}) - when storing data from output working in push mode. It means that some element in the pipeline - processes the stream too slow or doesn't process it at all. - To have control over amount of buffers being produced, consider using output in :auto or :manual - flow control mode. (see `Membrane.Pad.flow_control`). - You can also try changing the `toilet_capacity` in `Membrane.ChildrenSpec.via_in/3`. - """) - - Process.exit(responsible_process, :kill) - end -end diff --git a/test/membrane/core/element/demand_counter_test.exs b/test/membrane/core/element/demand_counter_test.exs new file mode 100644 index 000000000..096841106 --- /dev/null +++ b/test/membrane/core/element/demand_counter_test.exs @@ -0,0 +1,134 @@ +defmodule Membrane.Core.Element.DemandCounterTest do + use ExUnit.Case + + alias Membrane.Core.Element.DemandCounter + + test "if DemandCounter is implemented as :atomics for elements put on the same node" do + demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :output) + :ok = DemandCounter.increase(demand_counter, 10) + + assert get_atomic_value(demand_counter) == 10 + + demand_counter = DemandCounter.decrease(demand_counter, 15) + + assert demand_counter.buffered_decrementation == 0 + assert get_atomic_value(demand_counter) == -5 + assert DemandCounter.get(demand_counter) == -5 + end + + test "if the receiving element uses DemandCounter with :atomics and the sending element with a interprocess message, when the DemandCounter is distributed" do + demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :output) + :ok = DemandCounter.increase(demand_counter, 10) + + assert GenServer.call( + demand_counter.counter.worker, + {:get, demand_counter.counter.atomic_ref} + ) == 10 + + assert GenServer.call( + demand_counter.counter.worker, + {:sub_get, demand_counter.counter.atomic_ref, 15} + ) == -5 + + assert get_atomic_value(demand_counter) == -5 + + assert GenServer.call( + demand_counter.counter.worker, + {:add_get, demand_counter.counter.atomic_ref, 55} + ) == 50 + + assert get_atomic_value(demand_counter) == 50 + assert DemandCounter.get(demand_counter) == 50 + end + + test "if setting receiver and sender modes works properly" do + demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :output) + + :ok = DemandCounter.set_receiver_mode(demand_counter, :push) + assert DemandCounter.DistributedFlowMode.get(demand_counter.receiver_mode) == :push + + :ok = DemandCounter.set_receiver_mode(demand_counter, :pull) + assert DemandCounter.DistributedFlowMode.get(demand_counter.receiver_mode) == :pull + + :ok = DemandCounter.set_sender_mode(demand_counter, :push) + assert DemandCounter.DistributedFlowMode.get(demand_counter.sender_mode) == :push + + :ok = DemandCounter.set_sender_mode(demand_counter, :pull) + assert DemandCounter.DistributedFlowMode.get(demand_counter.sender_mode) == :pull + end + + test "if toilet overflows, only and only when it should" do + hour_in_millis = 60 * 60 * 1000 + sleeping_process = spawn(fn -> Process.sleep(hour_in_millis) end) + monitor_ref = Process.monitor(sleeping_process) + + demand_counter = DemandCounter.new(:pull, sleeping_process, :buffers, self(), :output) + + :ok = DemandCounter.set_sender_mode(demand_counter, :push) + demand_counter = DemandCounter.decrease(demand_counter, 100) + + refute_receive {:DOWN, ^monitor_ref, :process, _pid, _reason} + + possible_modes = [:push, :pull, :to_be_resolved] + + demand_counter = + for mode_1 <- possible_modes, mode_2 <- possible_modes do + {mode_1, mode_2} + end + |> List.delete({:push, :pull}) + |> Enum.reduce(demand_counter, fn {sender_mode, receiver_mode}, demand_counter -> + :ok = DemandCounter.set_sender_mode(demand_counter, sender_mode) + :ok = DemandCounter.set_receiver_mode(demand_counter, receiver_mode) + demand_counter = DemandCounter.decrease(demand_counter, 1000) + + refute_receive {:DOWN, ^monitor_ref, :process, _pid, _reason} + + demand_counter + end) + + :ok = DemandCounter.set_sender_mode(demand_counter, :push) + :ok = DemandCounter.set_receiver_mode(demand_counter, :pull) + _demand_counter = DemandCounter.decrease(demand_counter, 1000) + + assert_receive {:DOWN, ^monitor_ref, :process, _pid, _reason} + end + + test "if buffering decrementation works properly with distribution" do + another_node = setup_another_node() + pid_on_another_node = Node.spawn(another_node, fn -> :ok end) + demand_counter = DemandCounter.new(:push, self(), :buffers, pid_on_another_node, :output) + + assert %DemandCounter{buffered_decrementation_limit: 150} = demand_counter + + demand_counter = DemandCounter.decrease(demand_counter, 100) + + assert %DemandCounter{buffered_decrementation: 100} = demand_counter + assert get_atomic_value(demand_counter) == 0 + + demand_counter = DemandCounter.decrease(demand_counter, 49) + + assert %DemandCounter{buffered_decrementation: 149} = demand_counter + assert get_atomic_value(demand_counter) == 0 + + demand_counter = DemandCounter.decrease(demand_counter, 51) + + assert %DemandCounter{buffered_decrementation: 0} = demand_counter + assert get_atomic_value(demand_counter) == -200 + end + + defp setup_another_node() do + _cmd_result = System.cmd("epmd", ["-daemon"]) + _start_result = Node.start(:"my_node@127.0.0.1", :longnames) + {:ok, _pid, another_node} = :peer.start(%{host: ~c"127.0.0.1", name: :another_node}) + :rpc.block_call(another_node, :code, :add_paths, [:code.get_path()]) + + on_exit(fn -> :rpc.call(another_node, :init, :stop, []) end) + + another_node + end + + defp get_atomic_value(demand_counter) do + demand_counter.counter.atomic_ref + |> :atomics.get(1) + end +end diff --git a/test/membrane/core/element/toilet_test.exs b/test/membrane/core/element/toilet_test.exs deleted file mode 100644 index 5ac606277..000000000 --- a/test/membrane/core/element/toilet_test.exs +++ /dev/null @@ -1,48 +0,0 @@ -defmodule Membrane.Core.Element.ToiletTest do - use ExUnit.Case - alias Membrane.Core.Element.Toilet - - setup do - [responsible_process: spawn(fn -> nil end)] - end - - test "if toilet is implemented as :atomics for elements put on the same node", context do - toilet = Toilet.new(100, :buffers, context.responsible_process, 1, :push, :pull) - - %Toilet{counter: {_pid, atomic_ref}} = toilet - - Toilet.fill(toilet, 10) - assert :atomics.get(atomic_ref, 1) == 10 - Toilet.drain(toilet, 10) - assert :atomics.get(atomic_ref, 1) == 0 - end - - test "if the receiving element uses toilet with :atomics and the sending element with a interprocess message, when the toilet is distributed", - context do - toilet = Toilet.new(100, :buffers, context.responsible_process, 1, :push, :pull) - - %Toilet{counter: {counter_pid, atomic_ref}} = toilet - - Toilet.fill(toilet, 10) - assert GenServer.call(counter_pid, {:add_get, atomic_ref, 0}) == 10 - assert :atomics.get(atomic_ref, 1) == 10 - Toilet.drain(toilet, 10) - assert GenServer.call(counter_pid, {:add_get, atomic_ref, 0}) == 0 - assert :atomics.get(atomic_ref, 1) == 0 - end - - test "if throttling mechanism works properly", context do - toilet = Toilet.new(100, :buffers, context.responsible_process, 10, :push, :pull) - - {:ok, toilet} = Toilet.fill(toilet, 10) - assert toilet.unrinsed_buffers_size == 0 - {:ok, toilet} = Toilet.fill(toilet, 5) - assert toilet.unrinsed_buffers_size == 5 - {:ok, toilet} = Toilet.fill(toilet, 80) - assert toilet.unrinsed_buffers_size == 0 - {:ok, toilet} = Toilet.fill(toilet, 9) - assert toilet.unrinsed_buffers_size == 9 - {:overflow, toilet} = Toilet.fill(toilet, 11) - assert toilet.unrinsed_buffers_size == 0 - end -end diff --git a/test/membrane/core/element_test.exs b/test/membrane/core/element_test.exs index c72d72acf..7e1e391a0 100644 --- a/test/membrane/core/element_test.exs +++ b/test/membrane/core/element_test.exs @@ -303,7 +303,11 @@ defmodule Membrane.Core.ElementTest do name: :dynamic_input, options: nil }, - %{demand_counter: %Element.DemandCounter{}, output_demand_unit: :buffers, input_demand_unit: :buffers}} = reply + %{ + demand_counter: %Element.DemandCounter{}, + output_demand_unit: :buffers, + input_demand_unit: :buffers + }} = reply assert %Membrane.Element.PadData{ pid: ^pid, From a1f7380b337327fbeb26d372ba2e3ddf1356c4aa Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 5 Apr 2023 14:29:22 +0200 Subject: [PATCH 25/64] Write integration tests for toilet forwarding --- lib/membrane/core/element/demand_counter.ex | 8 +- lib/membrane/core/element/input_queue.ex | 3 +- .../integration/toilet_forwarding_test.exs | 245 ++++++++++++++++++ 3 files changed, 248 insertions(+), 8 deletions(-) create mode 100644 test/membrane/integration/toilet_forwarding_test.exs diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 358a215ef..668895e02 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -281,10 +281,7 @@ defmodule Membrane.Core.Element.DemandCounter do @spec decrease(t, non_neg_integer()) :: t def decrease(%__MODULE__{} = demand_counter, value) do - demand_counter = %{ - demand_counter - | buffered_decrementation: demand_counter.buffered_decrementation + value - } + demand_counter = Map.update!(demand_counter, :buffered_decrementation, &(&1 + value)) xd = get(demand_counter) @@ -305,8 +302,7 @@ defmodule Membrane.Core.Element.DemandCounter do DistributedAtomic.get(demand_counter.counter) end - @spec flush_buffered_decrementation(t) :: t - def flush_buffered_decrementation(demand_counter) do + defp flush_buffered_decrementation(demand_counter) do counter_value = DistributedAtomic.sub_get( demand_counter.counter, diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index 450c02366..92a2570e4 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -323,8 +323,7 @@ defmodule Membrane.Core.Element.InputQueue do } = input_queue ) when target_size > size + lacking_buffers do - diff = target_size - size - lacking_buffers - # diff = max(target_size - size - lacking_buffers, 10) + diff = max(target_size - size - lacking_buffers, div(target_size, 2)) """ Increasing DemandCounter linked to #{inspect(input_queue.linked_output_ref)} by #{inspect(diff)} diff --git a/test/membrane/integration/toilet_forwarding_test.exs b/test/membrane/integration/toilet_forwarding_test.exs new file mode 100644 index 000000000..302b197e8 --- /dev/null +++ b/test/membrane/integration/toilet_forwarding_test.exs @@ -0,0 +1,245 @@ +defmodule Membrane.Integration.ToiletForwardingTest do + use ExUnit.Case, async: true + + import Membrane.ChildrenSpec + import Membrane.Testing.Assertions + + alias Membrane.Testing + + require Membrane.Child, as: Child + require Membrane.Pad, as: Pad + + defmodule StreamFormat do + defstruct [] + end + + defmodule AutoFilter do + use Membrane.Filter + + def_input_pad :input, availability: :on_request, accepted_format: _any, flow_control: :auto + def_output_pad :output, availability: :on_request, accepted_format: _any, flow_control: :auto + + @impl true + def handle_buffer(_pad, buffer, _ctx, state) do + {[forward: buffer], state} + end + + @impl true + def handle_parent_notification({:execute_actions, actions}, _ctx, state) do + {actions, state} + end + end + + defmodule PushSink do + use Membrane.Sink + def_input_pad :input, accepted_format: _any, flow_control: :push + end + + defmodule AutoSink do + use Membrane.Sink + def_input_pad :input, accepted_format: _any, flow_control: :auto + + @impl true + def handle_parent_notification({:sleep, timeout}, _cts, state) do + Process.sleep(timeout) + {[], state} + end + + @impl true + def handle_buffer(_pad, buffer, _ctx, state) do + {[notify_parent: {:buffer, buffer}], state} + end + end + + defmodule PushSource do + use Membrane.Source + + def_output_pad :output, accepted_format: _any, flow_control: :push + + @impl true + def handle_playing(_ctx, state) do + {[stream_format: {:output, %StreamFormat{}}], state} + end + + @impl true + def handle_parent_notification({:forward_buffers, buffers}, _ctx, state) do + {[buffer: {:output, buffers}], state} + end + end + + defmodule PullSource do + use Membrane.Source + + defmodule StreamFormat do + defstruct [] + end + + def_output_pad :output, accepted_format: _any, flow_control: :manual + + @impl true + def handle_playing(_ctx, state) do + {[stream_format: {:output, %StreamFormat{}}], state} + end + + @impl true + def handle_parent_notification({:forward_buffers, buffers}, _ctx, state) do + {[buffer: {:output, buffers}], state} + end + + @impl true + def handle_demand(_pad, _demand, _unit, _ctx, state), do: {[], state} + end + + defmodule RedemandingFilter do + use Membrane.Filter + + def_input_pad :input, accepted_format: _any, flow_control: :manual, demand_unit: :buffers + def_output_pad :output, accepted_format: _any, flow_control: :manual, demand_unit: :buffers + + @impl true + def handle_demand(:output, _size, :buffers, _ctx, state) do + {[demand: :input], state} + end + + @impl true + def handle_buffer(:input, buffer, _ctx, state) do + {[buffer: {:output, buffer}, redemand: :output], state} + end + end + + test "toilet overflows only where it should" do + # because Membrane.Testing.Source output pad has :manual flow control, :filter will work in auto pull + + filter_ref = Child.ref(:filter, group: :filter_group) + sink_ref = Child.ref(:sink, group: :sink_group) + + spec = [ + {child(:filter, AutoFilter), group: :filter_group, crash_group_mode: :temporary}, + {child(:sink, %Testing.Sink{autodemand: false}), + group: :sink_group, crash_group_mode: :temporary}, + child(Testing.Source) + |> child(AutoFilter) + |> get_child(filter_ref) + |> get_child(sink_ref), + child(:push_source, PushSource) + |> child(AutoFilter) + |> get_child(filter_ref) + ] + + pipeline = Testing.Pipeline.start_link_supervised!(spec: spec) + + Process.sleep(500) + + buffers = + 1..10_000 + |> Enum.map(fn i -> %Membrane.Buffer{payload: <>} end) + + Testing.Pipeline.execute_actions(pipeline, + notify_child: {:push_source, {:forward_buffers, buffers}} + ) + + assert_pipeline_crash_group_down(pipeline, :filter_group) + + Testing.Pipeline.terminate(pipeline) + end + + test "toilet does not overflow on link between 2 pads in auto push" do + spec = + child(:source, PushSource) + |> child(AutoFilter) + |> child(:sink, AutoSink) + + pipeline = Testing.Pipeline.start_link_supervised!(spec: spec) + + Testing.Pipeline.execute_actions(pipeline, notify_child: {:sink, {:sleep, 1000}}) + Process.sleep(100) + + buffers = + 1..10_000 + |> Enum.map(fn i -> %Membrane.Buffer{payload: <>} end) + + Testing.Pipeline.execute_actions(pipeline, + notify_child: {:source, {:forward_buffers, buffers}} + ) + + refute_pipeline_notified(pipeline, :sink, {:buffer, _buffer}, 500) + + for i <- 1..10_000 do + assert_pipeline_notified( + pipeline, + :sink, + {:buffer, %Membrane.Buffer{payload: <>}} + ) + + assert buff_idx == i + end + + Testing.Pipeline.terminate(pipeline) + end + + test "toilet does not overflow on link between 2 pads in auto pull" do + spec = + child(PullSource) + |> child(:filter, AutoFilter) + |> via_out(Pad.ref(:output, 1)) + |> child(:sink, AutoSink) + + pipeline = Testing.Pipeline.start_link_supervised!(spec: spec) + + # time to propagate :pull effective flow control + Process.sleep(100) + + buffers = + 1..10_000 + |> Enum.map(fn i -> %Membrane.Buffer{payload: <>} end) + + filter_actions = [ + stream_format: {Pad.ref(:output, 1), %StreamFormat{}}, + buffer: {Pad.ref(:output, 1), buffers} + ] + + Testing.Pipeline.execute_actions(pipeline, + notify_child: {:filter, {:execute_actions, filter_actions}} + ) + + for i <- 1..10_000 do + assert_pipeline_notified( + pipeline, + :sink, + {:buffer, %Membrane.Buffer{payload: <>}} + ) + + assert buff_idx == i + end + + Testing.Pipeline.terminate(pipeline) + end + + test "elements in auto pull work properly with elements in manual pull" do + spec = + Enum.reduce(1..20, child(:source, PullSource), fn _i, spec -> + spec + |> child(AutoFilter) + |> child(RedemandingFilter) + end) + |> child(:sink, %Testing.Sink{autodemand: false}) + + pipeline = Testing.Pipeline.start_link_supervised!(spec: spec) + + buffers = + 1..4000 + |> Enum.map(fn i -> %Membrane.Buffer{payload: <>} end) + + Testing.Pipeline.execute_actions(pipeline, + notify_child: {:source, {:forward_buffers, buffers}}, + notify_child: {:sink, {:make_demand, 3000}} + ) + + for i <- 1..3000 do + assert_sink_buffer(pipeline, :sink, %Membrane.Buffer{payload: <>}) + assert buff_idx == i + end + + Testing.Pipeline.terminate(pipeline) + end +end From 3f1fefc0cb7c179c29e531370b6ebd29bd420be5 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 5 Apr 2023 14:49:00 +0200 Subject: [PATCH 26/64] Fix StreamFormatController and LifecycleController unit tests --- lib/membrane/core/element.ex | 2 +- lib/membrane/core/element/demand_counter.ex | 6 +- lib/membrane/core/element/input_queue.ex | 39 ------ .../element/lifecycle_controller_test.exs | 114 ++++++++-------- .../element/stream_format_controller_test.exs | 128 +++++++++--------- test/membrane/core/element_test.exs | 4 +- 6 files changed, 126 insertions(+), 167 deletions(-) diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index e45f15c2a..af0ddeff8 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -179,7 +179,7 @@ defmodule Membrane.Core.Element do # {:noreply, state} # end - defp do_handle_info(Message.new(:demand_counter_increased, [_msg_ref, pad_ref]) = msg, state) do + defp do_handle_info(Message.new(:demand_counter_increased, pad_ref) = msg, state) do Membrane.Logger.warn("RECEIVING DC NOTIFICATION ON #{inspect(pad_ref)} #{inspect(msg)}") state = DemandController.check_demand_counter(pad_ref, state) {:noreply, state} diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 668895e02..081d49bef 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -263,16 +263,14 @@ defmodule Membrane.Core.Element.DemandCounter do ) if old_counter_value <= 0 do - ref = make_ref() - Membrane.Logger.warn( - "SENDING DC NOTIFICATION #{inspect(Message.new(:demand_counter_increased, [ref, demand_counter.sender_pad_ref]))}" + "SENDING DC NOTIFICATION #{inspect(Message.new(:demand_counter_increased, demand_counter.sender_pad_ref))}" ) Message.send( demand_counter.sender_process, :demand_counter_increased, - [ref, demand_counter.sender_pad_ref] + demand_counter.sender_pad_ref ) end diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index 92a2570e4..404438764 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -180,45 +180,6 @@ defmodule Membrane.Core.Element.InputQueue do {out, input_queue} end - # @spec take_and_demand(t(), non_neg_integer(), Pad.ref()) :: {output(), t()} - # def take_and_demand( - # %__MODULE__{} = input_queue, - # count, - # demand_pad - # ) - # when count >= 0 do - # "Taking #{inspect(count)} #{inspect(input_queue.outbound_metric)}" - # |> mk_log(input_queue) - # |> Membrane.Logger.debug_verbose() - - # {out, %__MODULE__{size: new_size} = input_queue} = do_take(input_queue, count) - # input_queue = increase_demand_counter(input_queue, demand_pad) - # Telemetry.report_metric(:take_and_demand, new_size, input_queue.log_tag) - # {out, input_queue} - # end - - # defp do_take( - # %__MODULE__{ - # q: q, - # size: size, - # inbound_metric: inbound_metric, - # outbound_metric: outbound_metric, - # demand: demand - # } = input_queue, - # count - # ) do - # {out, nq, new_queue_size} = q |> q_pop(count, inbound_metric, outbound_metric, size) - # new_demand_size = demand + (size - new_queue_size) - - # {out, - # %__MODULE__{ - # input_queue - # | q: nq, - # size: new_queue_size, - # demand: new_demand_size - # }} - # end - defp q_pop( q, size_to_take_in_outbound_metric, diff --git a/test/membrane/core/element/lifecycle_controller_test.exs b/test/membrane/core/element/lifecycle_controller_test.exs index 71a370338..fd64a66d4 100644 --- a/test/membrane/core/element/lifecycle_controller_test.exs +++ b/test/membrane/core/element/lifecycle_controller_test.exs @@ -1,65 +1,65 @@ -# defmodule Membrane.Core.Element.LifecycleControllerTest do -# use ExUnit.Case +defmodule Membrane.Core.Element.LifecycleControllerTest do + use ExUnit.Case -# alias Membrane.Core.Element.{InputQueue, LifecycleController, State} -# alias Membrane.Core.Message + alias Membrane.Core.Element.{DemandCounter, InputQueue, LifecycleController, State} + alias Membrane.Core.Message -# require Membrane.Core.Message + require Membrane.Core.Message -# defmodule DummyElement do -# use Membrane.Filter -# def_output_pad :output, flow_control: :manual, accepted_format: _any + defmodule DummyElement do + use Membrane.Filter + def_output_pad :output, flow_control: :manual, accepted_format: _any -# @impl true -# def handle_terminate_request(_ctx, state) do -# {[], state} -# end -# end + @impl true + def handle_terminate_request(_ctx, state) do + {[], state} + end + end -# setup do -# input_queue = -# InputQueue.init(%{ -# inbound_demand_unit: :buffers, -# outbound_demand_unit: :buffers, -# demand_pid: self(), -# demand_pad: :some_pad, -# log_tag: "test", -# toilet?: false, -# target_size: nil, -# min_demand_factor: nil -# }) + setup do + demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :some_pad) -# state = -# struct(State, -# module: DummyElement, -# name: :test_name, -# type: :filter, -# playback: :playing, -# parent_pid: self(), -# synchronization: %{clock: nil, parent_clock: nil}, -# pads_data: %{ -# input: -# struct(Membrane.Element.PadData, -# ref: :input, -# direction: :input, -# pid: self(), -# flow_control: :manual, -# start_of_stream?: true, -# end_of_stream?: false, -# input_queue: input_queue, -# demand: 0 -# ) -# } -# ) + input_queue = + InputQueue.init(%{ + inbound_demand_unit: :buffers, + outbound_demand_unit: :buffers, + demand_counter: demand_counter, + linked_output_ref: :some_pad, + log_tag: "test", + target_size: nil + }) -# assert_received Message.new(:demand, _size, for_pad: :some_pad) -# [state: state] -# end + state = + struct(State, + module: DummyElement, + name: :test_name, + type: :filter, + playback: :playing, + parent_pid: self(), + synchronization: %{clock: nil, parent_clock: nil}, + pads_data: %{ + input: + struct(Membrane.Element.PadData, + ref: :input, + direction: :input, + pid: self(), + flow_control: :manual, + start_of_stream?: true, + end_of_stream?: false, + input_queue: input_queue, + demand: 0 + ) + } + ) -# test "End of stream is generated upon termination", %{ -# state: state -# } do -# state = LifecycleController.handle_terminate_request(state) -# assert state.pads_data.input.end_of_stream? -# end -# end + assert_received Message.new(:demand_counter_increased, :some_pad) + [state: state] + end + + test "End of stream is generated upon termination", %{ + state: state + } do + state = LifecycleController.handle_terminate_request(state) + assert state.pads_data.input.end_of_stream? + end +end diff --git a/test/membrane/core/element/stream_format_controller_test.exs b/test/membrane/core/element/stream_format_controller_test.exs index d1899e6a4..677497af7 100644 --- a/test/membrane/core/element/stream_format_controller_test.exs +++ b/test/membrane/core/element/stream_format_controller_test.exs @@ -1,74 +1,74 @@ -# defmodule Membrane.Core.Element.StreamFormatControllerTest do -# use ExUnit.Case, async: true +defmodule Membrane.Core.Element.StreamFormatControllerTest do + use ExUnit.Case, async: true -# alias Membrane.Buffer -# alias Membrane.Core.Message -# alias Membrane.Core.Element.{InputQueue, State} -# alias Membrane.StreamFormat.Mock, as: MockStreamFormat -# alias Membrane.Support.DemandsTest.Filter + alias Membrane.Buffer + alias Membrane.Core.Message + alias Membrane.Core.Element.{DemandCounter, InputQueue, State} + alias Membrane.StreamFormat.Mock, as: MockStreamFormat + alias Membrane.Support.DemandsTest.Filter -# require Membrane.Core.Child.PadModel, as: PadModel -# require Membrane.Core.Message, as: Message + require Membrane.Core.Child.PadModel, as: PadModel + require Membrane.Core.Message, as: Message -# @module Membrane.Core.Element.StreamFormatController + @module Membrane.Core.Element.StreamFormatController -# setup do -# input_queue = -# InputQueue.init(%{ -# inbound_demand_unit: :buffers, -# outbound_demand_unit: :buffers, -# demand_pid: self(), -# demand_pad: :some_pad, -# log_tag: "test", -# toilet?: false, -# target_size: nil, -# min_demand_factor: nil -# }) + setup do + demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :some_pad) -# state = -# struct(State, -# module: Filter, -# name: :test_name, -# parent: self(), -# type: :filter, -# playback: :playing, -# synchronization: %{clock: nil, parent_clock: nil}, -# pads_data: %{ -# input: -# struct(Membrane.Element.PadData, -# direction: :input, -# name: :input, -# pid: self(), -# flow_control: :manual, -# input_queue: input_queue, -# demand: 0 -# ) -# } -# ) + input_queue = + InputQueue.init(%{ + inbound_demand_unit: :buffers, + outbound_demand_unit: :buffers, + demand_counter: demand_counter, + linked_output_ref: :some_pad, + log_tag: "test", + target_size: nil, + }) -# assert_received Message.new(:demand, _size, for_pad: :some_pad) -# [state: state] -# end + state = + struct(State, + module: Filter, + name: :test_name, + parent: self(), + type: :filter, + playback: :playing, + synchronization: %{clock: nil, parent_clock: nil}, + pads_data: %{ + input: + struct(Membrane.Element.PadData, + direction: :input, + name: :input, + pid: self(), + flow_control: :manual, + input_queue: input_queue, + demand: 0 + ) + } + ) -# describe "handle_stream_format for pull pad" do -# test "with empty input_queue", %{state: state} do -# assert PadModel.set_data!(state, :input, :stream_format, %MockStreamFormat{}) == -# @module.handle_stream_format(:input, %MockStreamFormat{}, state) -# end + assert_received Message.new(:demand_counter_increased, :some_pad) + [state: state] + end -# test "with input_queue containing one buffer", %{state: state} do -# state = -# state -# |> PadModel.update_data!( -# :input, -# :input_queue, -# &InputQueue.store(&1, :buffer, %Buffer{payload: "aa"}) -# ) + describe "handle_stream_format for pull pad" do + test "with empty input_queue", %{state: state} do + assert PadModel.set_data!(state, :input, :stream_format, %MockStreamFormat{}) == + @module.handle_stream_format(:input, %MockStreamFormat{}, state) + end -# state = @module.handle_stream_format(:input, %MockStreamFormat{}, state) + test "with input_queue containing one buffer", %{state: state} do + state = + state + |> PadModel.update_data!( + :input, + :input_queue, + &InputQueue.store(&1, :buffer, %Buffer{payload: "aa"}) + ) -# assert state.pads_data.input.input_queue.q |> Qex.last!() == -# {:non_buffer, :stream_format, %MockStreamFormat{}} -# end -# end -# end + state = @module.handle_stream_format(:input, %MockStreamFormat{}, state) + + assert state.pads_data.input.input_queue.q |> Qex.last!() == + {:non_buffer, :stream_format, %MockStreamFormat{}} + end + end +end diff --git a/test/membrane/core/element_test.exs b/test/membrane/core/element_test.exs index 7e1e391a0..00ffc0edf 100644 --- a/test/membrane/core/element_test.exs +++ b/test/membrane/core/element_test.exs @@ -207,7 +207,7 @@ defmodule Membrane.Core.ElementTest do :ok = increase_output_demand_counter(initial_state, 10) [ - Message.new(:demand_counter_increased, [:dupa, :output]), + Message.new(:demand_counter_increased, :output), Message.new(:buffer, %Membrane.Buffer{payload: <<>>}, for_pad: :dynamic_input), Message.new(:stream_format, %StreamFormat{}, for_pad: :dynamic_input), Message.new(:event, %Membrane.Testing.Event{}, for_pad: :dynamic_input), @@ -225,7 +225,7 @@ defmodule Membrane.Core.ElementTest do state = playing_state() :ok = increase_output_demand_counter(state, 10) - msg = Message.new(:demand_counter_increased, [:dupa, :output]) + msg = Message.new(:demand_counter_increased, :output) assert {:noreply, state} = Element.handle_info(msg, state) assert state.pads_data.output.demand == 10 From dd47e4f4857e25e986ddfbda512b7a8e47cce8d1 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 5 Apr 2023 14:50:58 +0200 Subject: [PATCH 27/64] Fix DistributedPipelineTest --- .../element/stream_format_controller_test.exs | 2 +- .../integration/distributed_pipeline_test.exs | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/test/membrane/core/element/stream_format_controller_test.exs b/test/membrane/core/element/stream_format_controller_test.exs index 677497af7..6a61c0638 100644 --- a/test/membrane/core/element/stream_format_controller_test.exs +++ b/test/membrane/core/element/stream_format_controller_test.exs @@ -22,7 +22,7 @@ defmodule Membrane.Core.Element.StreamFormatControllerTest do demand_counter: demand_counter, linked_output_ref: :some_pad, log_tag: "test", - target_size: nil, + target_size: nil }) state = diff --git a/test/membrane/integration/distributed_pipeline_test.exs b/test/membrane/integration/distributed_pipeline_test.exs index 6204b8ee7..6096a9af6 100644 --- a/test/membrane/integration/distributed_pipeline_test.exs +++ b/test/membrane/integration/distributed_pipeline_test.exs @@ -4,39 +4,44 @@ defmodule Membrane.Integration.DistributedPipelineTest do import Membrane.Testing.Assertions setup do - hostname = start_nodes() - on_exit(fn -> kill_node(hostname) end) + {my_node, another_node} = start_nodes() + on_exit(fn -> kill_node(another_node) end) + [first_node: my_node, second_node: another_node] end - test "if distributed pipeline works properly" do + test "if distributed pipeline works properly", context do defmodule Pipeline do use Membrane.Pipeline alias Membrane.Support.Distributed.{Sink, Source} @impl true - def handle_init(_ctx, _opts) do + def handle_init(_ctx, opts) do + first_node = opts[:first_node] + second_node = opts[:second_node] + {[ spec: [ - {child(:source, %Source{output: [1, 2, 3, 4, 5]}), node: :"first@127.0.0.1"}, + {child(:source, %Source{output: [1, 2, 3, 4, 5]}), node: first_node}, {get_child(:source) |> via_in(:input, toilet_capacity: 100, throttling_factor: 50) - |> child(:sink, Sink), node: :"second@127.0.0.1"} + |> child(:sink, Sink), node: second_node} ] ], %{}} end end - pipeline = Membrane.Testing.Pipeline.start_link_supervised!(module: Pipeline) + pipeline = + Membrane.Testing.Pipeline.start_link_supervised!(module: Pipeline, custom_args: context) assert_end_of_stream(pipeline, :sink) end defp start_nodes() do System.cmd("epmd", ["-daemon"]) - {:ok, _pid} = Node.start(:"first@127.0.0.1", :longnames) + _start_result = Node.start(:"first@127.0.0.1", :longnames) {:ok, _pid, hostname} = :peer.start(%{host: ~c"127.0.0.1", name: :second}) :rpc.block_call(hostname, :code, :add_paths, [:code.get_path()]) - hostname + {node(self()), hostname} end defp kill_node(node) do From 3be90a39965028709d53c0ce318fb366a4d28234 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 6 Apr 2023 15:24:50 +0200 Subject: [PATCH 28/64] Fix input queue unit tests --- lib/membrane/core/element/input_queue.ex | 4 +- .../core/element/input_queue_test.exs | 798 +++++++++--------- 2 files changed, 388 insertions(+), 414 deletions(-) diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index 404438764..546221817 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -32,6 +32,7 @@ defmodule Membrane.Core.Element.InputQueue do log_tag: String.t(), target_size: pos_integer(), size: non_neg_integer(), + lacking_buffers: non_neg_integer(), inbound_metric: module(), outbound_metric: module(), linked_output_ref: Pad.ref() @@ -153,6 +154,7 @@ defmodule Membrane.Core.Element.InputQueue do # wiec teraz trzeba podbic counter o tyle ile wyciagnelismy z kolejki # wychodzi na to ze teraz pole "demand" jest wgl do wywalenia + @spec take(t, non_neg_integer()) :: {output(), t} def take(%__MODULE__{} = input_queue, count) when count >= 0 do "Taking #{inspect(count)} #{inspect(input_queue.outbound_metric)}" |> mk_log(input_queue) @@ -287,7 +289,7 @@ defmodule Membrane.Core.Element.InputQueue do diff = max(target_size - size - lacking_buffers, div(target_size, 2)) """ - Increasing DemandCounter linked to #{inspect(input_queue.linked_output_ref)} by #{inspect(diff)} + Increasing DemandCounter linked to #{inspect(input_queue.linked_output_ref)} by #{inspect(diff)} """ |> mk_log(input_queue) |> Membrane.Logger.debug_verbose() diff --git a/test/membrane/core/element/input_queue_test.exs b/test/membrane/core/element/input_queue_test.exs index a98f0f415..aa26e7b9c 100644 --- a/test/membrane/core/element/input_queue_test.exs +++ b/test/membrane/core/element/input_queue_test.exs @@ -1,413 +1,385 @@ -# defmodule Membrane.Core.Element.InputQueueTest do -# use ExUnit.Case, async: true - -# alias Membrane.Buffer -# alias Membrane.Core.Element.InputQueue -# alias Membrane.Core.Message -# alias Membrane.Testing.Event - -# require Message - -# describe ".init/6 should" do -# setup do -# {:ok, -# %{ -# log_tag: "test", -# target_queue_size: 100, -# min_demand_factor: 0.1, -# inbound_demand_unit: :bytes, -# outbound_demand_unit: :bytes, -# demand_pid: self(), -# linked_output_ref: :output_pad_ref, -# expected_metric: Buffer.Metric.from_unit(:bytes), -# expected_min_demand: 10 -# }} -# end - -# test "return InputQueue struct and send demand message", context do -# assert InputQueue.init(%{ -# inbound_demand_unit: context.inbound_demand_unit, -# outbound_demand_unit: context.outbound_demand_unit, -# demand_pid: context.demand_pid, -# demand_pad: context.linked_output_ref, -# log_tag: context.log_tag, -# toilet?: false, -# target_size: context.target_queue_size, -# min_demand_factor: context.min_demand_factor -# }) == %InputQueue{ -# q: Qex.new(), -# log_tag: context.log_tag, -# target_size: context.target_queue_size, -# size: 0, -# demand: 0, -# min_demand: context.expected_min_demand, -# inbound_metric: context.expected_metric, -# outbound_metric: context.expected_metric, -# toilet?: false -# } - -# message = -# Message.new(:demand, context.target_queue_size, for_pad: context.linked_output_ref) - -# assert_received ^message -# end - -# test "not send the demand if toilet is enabled", context do -# assert InputQueue.init(%{ -# inbound_demand_unit: context.inbound_demand_unit, -# outbound_demand_unit: context.outbound_demand_unit, -# demand_pid: context.demand_pid, -# demand_pad: context.linked_output_ref, -# log_tag: context.log_tag, -# toilet?: true, -# target_size: context.target_queue_size, -# min_demand_factor: context.min_demand_factor -# }) == %InputQueue{ -# q: Qex.new(), -# log_tag: context.log_tag, -# target_size: context.target_queue_size, -# size: 0, -# demand: context.target_queue_size, -# min_demand: context.expected_min_demand, -# inbound_metric: context.expected_metric, -# outbound_metric: context.expected_metric, -# toilet?: true -# } - -# refute_received Message.new(:demand, _) -# end -# end - -# describe ".empty?/1 should" do -# setup do -# buffer = %Buffer{payload: <<1, 2, 3>>} - -# input_queue = -# struct(InputQueue, -# size: 0, -# inbound_metric: Buffer.Metric.Count, -# outbound_metric: Buffer.Metric.Count, -# q: Qex.new() -# ) - -# not_empty_input_queue = InputQueue.store(input_queue, :buffers, [buffer]) - -# {:ok, -# %{ -# size: 0, -# buffer: buffer, -# input_queue: input_queue, -# not_empty_input_queue: not_empty_input_queue -# }} -# end - -# test "return true when pull buffer is empty", context do -# assert InputQueue.empty?(context.input_queue) == true -# end - -# test "return false when pull buffer contains some buffers ", context do -# assert InputQueue.empty?(context.not_empty_input_queue) == false -# end -# end - -# describe ".store/3 should" do -# setup do -# {:ok, %{size: 10, q: Qex.new() |> Qex.push({:buffers, [], 3, 3}), payload: <<1, 2, 3>>}} -# end - -# test "increment `size` when `:metric` is `Count`", context do -# input_queue = -# struct(InputQueue, -# size: context.size, -# inbound_metric: Buffer.Metric.Count, -# outbound_metric: Buffer.Metric.Count, -# q: context.q -# ) - -# v = [%Buffer{payload: context.payload}] -# %{size: new_size} = InputQueue.store(input_queue, :buffers, v) -# assert new_size == context.size + 1 -# end - -# test "add payload size to `size` when `:metric` is `ByteSize`", context do -# input_queue = -# struct(InputQueue, -# size: context.size, -# inbound_metric: Buffer.Metric.ByteSize, -# outbound_metric: Buffer.Metric.ByteSize, -# q: context.q -# ) - -# v = [%Buffer{payload: context.payload}] -# %{size: new_size} = InputQueue.store(input_queue, :buffers, v) -# assert new_size == context.size + byte_size(context.payload) -# end - -# test "append buffer to the queue", context do -# input_queue = -# struct(InputQueue, -# size: context.size, -# inbound_metric: Buffer.Metric.ByteSize, -# outbound_metric: Buffer.Metric.ByteSize, -# q: context.q -# ) - -# v = [%Buffer{payload: context.payload}] -# %{q: new_q} = InputQueue.store(input_queue, :buffers, v) -# {{:value, last_elem}, remaining_q} = new_q |> Qex.pop_back() -# assert remaining_q == context.q -# assert last_elem == {:buffers, v, 3, 3} -# end - -# test "append event to the queue", context do -# input_queue = -# struct(InputQueue, -# size: context.size, -# inbound_metric: Buffer.Metric.ByteSize, -# outbound_metric: Buffer.Metric.ByteSize, -# q: context.q -# ) - -# v = %Event{} -# %{q: new_q} = InputQueue.store(input_queue, :event, v) -# {{:value, last_elem}, remaining_q} = new_q |> Qex.pop_back() -# assert remaining_q == context.q -# assert last_elem == {:non_buffer, :event, v} -# end - -# test "keep other fields unchanged after storing an event", context do -# input_queue = -# struct(InputQueue, -# size: context.size, -# inbound_metric: Buffer.Metric.ByteSize, -# outbound_metric: Buffer.Metric.ByteSize, -# q: context.q -# ) - -# v = %Event{} -# new_input_queue = InputQueue.store(input_queue, :event, v) -# assert %{new_input_queue | q: context.q} == input_queue -# end -# end - -# describe ".take_and_demand/4 should" do -# setup do -# input_queue = -# InputQueue.init(%{ -# inbound_demand_unit: :buffers, -# outbound_demand_unit: :buffers, -# demand_pid: self(), -# demand_pad: :pad, -# log_tag: "test", -# toilet?: false, -# target_size: nil, -# min_demand_factor: nil -# }) - -# assert_receive {Membrane.Core.Message, :demand, 40, [for_pad: :pad]} - -# [input_queue: input_queue] -# end - -# test "return {:empty, []} when the queue is empty", %{input_queue: input_queue} do -# assert {{:empty, []}, %InputQueue{size: 0, demand: 0}} = -# InputQueue.take_and_demand(input_queue, 1, self(), :input) - -# refute_receive {Membrane.Core.Message, :demand, 10, [for_pad: :pad]} -# end - -# test "send demands to the pid and updates demand", %{input_queue: input_queue} do -# assert {{:value, [{:buffers, [1], 1, 1}]}, new_input_queue} = -# input_queue -# |> InputQueue.store(bufs(10)) -# |> InputQueue.take_and_demand(1, self(), :pad) - -# assert_receive {Membrane.Core.Message, :demand, 10, [for_pad: :pad]} - -# assert new_input_queue.size == 9 -# assert new_input_queue.demand == -9 -# end -# end - -# describe ".take_and_demand/4 should also" do -# setup do -# size = 6 -# buffers1 = {:buffers, [:b1, :b2, :b3], 3, 3} -# buffers2 = {:buffers, [:b4, :b5, :b6], 3, 3} -# q = Qex.new() |> Qex.push(buffers1) |> Qex.push(buffers2) - -# input_queue = -# struct(InputQueue, -# size: size, -# demand: 0, -# min_demand: 0, -# target_queue_size: 100, -# toilet?: false, -# inbound_metric: Buffer.Metric.Count, -# outbound_metric: Buffer.Metric.Count, -# q: q -# ) - -# {:ok, %{input_queue: input_queue, q: q, size: size, buffers1: buffers1, buffers2: buffers2}} -# end - -# test "return tuple {:ok, {:empty, buffers}} when there are not enough buffers", -# context do -# {result, _new_input_queue} = -# InputQueue.take_and_demand( -# context.input_queue, -# 10, -# self(), -# :linked_output_ref -# ) - -# assert result == {:empty, [context.buffers1, context.buffers2]} -# end - -# test "set `size` to 0 when there are not enough buffers", context do -# {_, %{size: new_size}} = -# InputQueue.take_and_demand( -# context.input_queue, -# 10, -# self(), -# :linked_output_ref -# ) - -# assert new_size == 0 -# end - -# test "generate demand hen there are not enough buffers", context do -# InputQueue.take_and_demand( -# context.input_queue, -# 10, -# self(), -# :linked_output_ref -# ) - -# expected_size = context.size -# pad_ref = :linked_output_ref -# message = Message.new(:demand, expected_size, for_pad: pad_ref) -# assert_received ^message -# end - -# test "return `to_take` buffers from the queue when there are enough buffers and buffers dont have to be split", -# context do -# {result, %{q: new_q}} = -# InputQueue.take_and_demand( -# context.input_queue(), -# 3, -# self(), -# :linked_output_ref -# ) - -# assert result == {:value, [context.buffers1()]} - -# list = new_q |> Enum.into([]) -# exp_list = Qex.new() |> Qex.push(context.buffers2()) |> Enum.into([]) - -# assert list == exp_list -# assert_received Message.new(:demand, _, _) -# end - -# test "return `to_take` buffers from the queue when there are enough buffers and buffers have to be split", -# context do -# {result, %{q: new_q}} = -# InputQueue.take_and_demand( -# context.input_queue, -# 4, -# self(), -# :linked_output_ref -# ) - -# exp_buf2 = {:buffers, [:b4], 1, 1} -# exp_rest = {:buffers, [:b5, :b6], 2, 2} -# assert result == {:value, [context.buffers1, exp_buf2]} - -# list = new_q |> Enum.into([]) -# exp_list = Qex.new() |> Qex.push(exp_rest) |> Enum.into([]) - -# assert list == exp_list -# assert_received Message.new(:demand, _, _) -# end -# end - -# test "if the queue works properly for :bytes input metric and :buffers output metric" do -# queue = -# InputQueue.init(%{ -# inbound_demand_unit: :bytes, -# outbound_demand_unit: :buffers, -# demand_pid: self(), -# demand_pad: :input, -# log_tag: nil, -# toilet?: false, -# target_size: 10, -# min_demand_factor: 1 -# }) - -# assert_receive {Membrane.Core.Message, :demand, 10, [for_pad: :input]} -# assert queue.demand == 0 -# queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) -# assert queue.size == 4 -# queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) -# queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) -# queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) -# assert queue.size == 16 -# assert queue.demand == 0 -# {out, queue} = InputQueue.take_and_demand(queue, 2, self(), :input) -# assert bufs_size(out, :buffers) == 2 -# assert queue.size == 4 -# assert queue.demand == 0 -# assert_receive {Membrane.Core.Message, :demand, 12, [for_pad: :input]} -# queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) -# queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) -# {out, queue} = InputQueue.take_and_demand(queue, 1, self(), :input) -# assert bufs_size(out, :buffers) == 1 -# assert queue.size == 8 -# assert queue.demand == -8 -# end - -# test "if the queue works properly for :buffers input metric and :bytes output metric" do -# queue = -# InputQueue.init(%{ -# inbound_demand_unit: :buffers, -# outbound_demand_unit: :bytes, -# demand_pid: self(), -# demand_pad: :input, -# log_tag: nil, -# toilet?: false, -# target_size: 3, -# min_demand_factor: 1 -# }) - -# assert_receive {Membrane.Core.Message, :demand, 3, [for_pad: :input]} -# assert queue.demand == 0 -# queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) -# assert queue.size == 1 -# queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) -# queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) -# queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) -# assert queue.size == 4 -# assert queue.demand == 0 -# {out, queue} = InputQueue.take_and_demand(queue, 2, self(), :input) -# assert bufs_size(out, :bytes) == 2 -# assert queue.size == 4 -# assert queue.demand == 0 -# refute_receive {Membrane.Core.Message, :demand, _size, [for_pad: :input]} -# {out, queue} = InputQueue.take_and_demand(queue, 11, self(), :input) -# assert bufs_size(out, :bytes) == 11 -# assert queue.size == 2 -# assert queue.demand == -1 -# assert_receive {Membrane.Core.Message, :demand, 3, [for_pad: :input]} -# end - -# defp bufs_size(output, unit) do -# {_state, bufs} = output - -# Enum.flat_map(bufs, fn {:buffers, bufs_list, _inbound_metric_size, _outbound_metric_size} -> -# bufs_list -# end) -# |> Membrane.Buffer.Metric.from_unit(unit).buffers_size() -# end - -# defp bufs(n), do: Enum.to_list(1..n) -# end +defmodule Membrane.Core.Element.InputQueueTest do + use ExUnit.Case, async: true + + alias Membrane.Buffer + alias Membrane.Core.Element.{DemandCounter, InputQueue} + alias Membrane.Core.Message + alias Membrane.Testing.Event + + require Message + + describe ".init/6 should" do + setup do + demand_counter = DemandCounter.new(:pull, self(), :bytes, self(), :output_pad_ref) + + {:ok, + %{ + log_tag: "test", + target_queue_size: 100, + inbound_demand_unit: :bytes, + outbound_demand_unit: :bytes, + linked_output_ref: :output_pad_ref, + demand_counter: demand_counter, + expected_metric: Buffer.Metric.from_unit(:bytes) + }} + end + + test "return InputQueue struct and send demand message", context do + assert InputQueue.init(%{ + inbound_demand_unit: context.inbound_demand_unit, + outbound_demand_unit: context.outbound_demand_unit, + linked_output_ref: context.linked_output_ref, + log_tag: context.log_tag, + demand_counter: context.demand_counter, + target_size: context.target_queue_size + }) == %InputQueue{ + q: Qex.new(), + log_tag: context.log_tag, + target_size: context.target_queue_size, + demand_counter: context.demand_counter, + inbound_metric: context.expected_metric, + outbound_metric: context.expected_metric, + linked_output_ref: context.linked_output_ref, + size: 0, + lacking_buffers: context.target_queue_size + } + + assert context.target_queue_size == context.demand_counter |> DemandCounter.get() + + expected_message = Message.new(:demand_counter_increased, context.linked_output_ref) + assert_received ^expected_message + end + end + + describe ".empty?/1 should" do + setup do + buffer = %Buffer{payload: <<1, 2, 3>>} + + input_queue = + struct(InputQueue, + size: 0, + inbound_metric: Buffer.Metric.Count, + outbound_metric: Buffer.Metric.Count, + q: Qex.new() + ) + + not_empty_input_queue = InputQueue.store(input_queue, :buffers, [buffer]) + + {:ok, + %{ + size: 0, + buffer: buffer, + input_queue: input_queue, + not_empty_input_queue: not_empty_input_queue + }} + end + + test "return true when pull buffer is empty", context do + assert InputQueue.empty?(context.input_queue) == true + end + + test "return false when pull buffer contains some buffers ", context do + assert InputQueue.empty?(context.not_empty_input_queue) == false + end + end + + describe ".store/3 should" do + setup do + {:ok, + %{ + lacking_buffers: 30, + size: 10, + q: Qex.new() |> Qex.push({:buffers, [], 3, 3}), + payload: <<1, 2, 3>> + }} + end + + test "updated properly `size` and `lacking_buffers` when `:metric` is `Buffer.Metric.Count`", + context do + input_queue = + struct(InputQueue, + size: context.size, + inbound_metric: Buffer.Metric.Count, + outbound_metric: Buffer.Metric.Count, + q: context.q, + lacking_buffers: context.lacking_buffers + ) + + v = [%Buffer{payload: context.payload}] + updated_input_queue = InputQueue.store(input_queue, :buffers, v) + + assert updated_input_queue.size == context.size + 1 + assert updated_input_queue.lacking_buffers == context.lacking_buffers - 1 + end + + test "updated properly `size` and `lacking_buffers` when `:metric` is `Buffer.Metric.ByteSize`", + context do + input_queue = + struct(InputQueue, + size: context.size, + inbound_metric: Buffer.Metric.ByteSize, + outbound_metric: Buffer.Metric.ByteSize, + q: context.q, + lacking_buffers: context.lacking_buffers + ) + + v = [%Buffer{payload: context.payload}] + updated_input_queue = InputQueue.store(input_queue, :buffers, v) + + assert updated_input_queue.size == context.size + byte_size(context.payload) + + assert updated_input_queue.lacking_buffers == + context.lacking_buffers - byte_size(context.payload) + end + + test "append buffer to the queue", context do + input_queue = + struct(InputQueue, + size: context.size, + inbound_metric: Buffer.Metric.ByteSize, + outbound_metric: Buffer.Metric.ByteSize, + q: context.q, + lacking_buffers: context.lacking_buffers + ) + + v = [%Buffer{payload: context.payload}] + %{q: new_q} = InputQueue.store(input_queue, :buffers, v) + {{:value, last_elem}, remaining_q} = new_q |> Qex.pop_back() + assert remaining_q == context.q + assert last_elem == {:buffers, v, 3, 3} + end + + test "append event to the queue", context do + input_queue = + struct(InputQueue, + size: context.size, + inbound_metric: Buffer.Metric.ByteSize, + outbound_metric: Buffer.Metric.ByteSize, + q: context.q, + lacking_buffers: context.lacking_buffers + ) + + v = %Event{} + %{q: new_q} = InputQueue.store(input_queue, :event, v) + {{:value, last_elem}, remaining_q} = new_q |> Qex.pop_back() + assert remaining_q == context.q + assert last_elem == {:non_buffer, :event, v} + end + + test "keep other fields unchanged after storing an event", context do + input_queue = + struct(InputQueue, + size: context.size, + inbound_metric: Buffer.Metric.ByteSize, + outbound_metric: Buffer.Metric.ByteSize, + q: context.q, + lacking_buffers: context.lacking_buffers + ) + + v = %Event{} + new_input_queue = InputQueue.store(input_queue, :event, v) + assert %{new_input_queue | q: context.q} == input_queue + end + end + + describe ".take/2 should" do + setup do + output_pad = :pad + demand_counter = DemandCounter.new(:pull, self(), :bytes, self(), output_pad) + + input_queue = + InputQueue.init(%{ + inbound_demand_unit: :buffers, + outbound_demand_unit: :buffers, + linked_output_ref: output_pad, + log_tag: "test", + demand_counter: demand_counter, + target_size: 40 + }) + + assert_received Message.new(:demand_counter_increased, ^output_pad) + + [input_queue: input_queue] + end + + test "return {:empty, []} when the queue is empty", %{input_queue: input_queue} do + old_counter_value = DemandCounter.get(input_queue.demand_counter) + old_lacking_buffers = input_queue.lacking_buffers + + assert {{:empty, []}, %InputQueue{size: 0, lacking_buffers: ^old_lacking_buffers}} = + InputQueue.take(input_queue, 1) + + assert old_counter_value == DemandCounter.get(input_queue.demand_counter) + end + + test "send demands to the pid and updates demand", %{input_queue: input_queue} do + assert {{:value, [{:buffers, [1], 1, 1}]}, new_input_queue} = + input_queue + |> InputQueue.store(bufs(10)) + |> InputQueue.take(1) + + assert new_input_queue.size == 9 + assert new_input_queue.lacking_buffers >= 31 + assert DemandCounter.get(new_input_queue.demand_counter) >= 41 + end + end + + describe ".take/2 should also" do + setup do + size = 6 + buffers1 = {:buffers, [:b1, :b2, :b3], 3, 3} + buffers2 = {:buffers, [:b4, :b5, :b6], 3, 3} + q = Qex.new() |> Qex.push(buffers1) |> Qex.push(buffers2) + output_pad = :pad + demand_counter = DemandCounter.new(:pull, self(), :bytes, self(), output_pad) + + :ok = DemandCounter.increase(demand_counter, 94) + assert_received Message.new(:demand_counter_increased, ^output_pad) + + input_queue = + struct(InputQueue, + size: size, + lacking_buffers: 94, + target_size: 100, + inbound_metric: Buffer.Metric.Count, + outbound_metric: Buffer.Metric.Count, + q: q, + linked_output_ref: output_pad, + demand_counter: demand_counter + ) + + {:ok, %{input_queue: input_queue, q: q, size: size, buffers1: buffers1, buffers2: buffers2}} + end + + test "return tuple {:ok, {:empty, buffers}} when there are not enough buffers", + context do + {result, _new_input_queue} = InputQueue.take(context.input_queue, 10) + assert result == {:empty, [context.buffers1, context.buffers2]} + end + + test "set `size` to 0 when there are not enough buffers", context do + {_, %{size: new_size}} = InputQueue.take(context.input_queue, 10) + + assert new_size == 0 + end + + test "increase DemandCounter hen there are not enough buffers", context do + old_counter_value = DemandCounter.get(context.input_queue.demand_counter) + old_lacking_buffers = context.input_queue.lacking_buffers + + {_output, input_queue} = InputQueue.take(context.input_queue, 10) + + assert old_counter_value < DemandCounter.get(input_queue.demand_counter) + assert old_lacking_buffers < input_queue.lacking_buffers + end + + test "return `to_take` buffers from the queue when there are enough buffers and buffers don't have to be split", + context do + {result, %{q: new_q}} = InputQueue.take(context.input_queue, 3) + + assert result == {:value, [context.buffers1]} + + list = new_q |> Enum.into([]) + exp_list = Qex.new() |> Qex.push(context.buffers2) |> Enum.into([]) + + assert list == exp_list + end + + test "return `to_take` buffers from the queue when there are enough buffers and buffers have to be split", + context do + {result, %{q: new_q}} = InputQueue.take(context.input_queue, 4) + + exp_buf2 = {:buffers, [:b4], 1, 1} + exp_rest = {:buffers, [:b5, :b6], 2, 2} + assert result == {:value, [context.buffers1, exp_buf2]} + + list = new_q |> Enum.into([]) + exp_list = Qex.new() |> Qex.push(exp_rest) |> Enum.into([]) + + assert list == exp_list + end + end + + test "if the queue works properly for :bytes input metric and :buffers output metric" do + demand_counter = DemandCounter.new(:pull, self(), :bytes, self(), :output_pad_ref) + + queue = + InputQueue.init(%{ + inbound_demand_unit: :bytes, + outbound_demand_unit: :buffers, + demand_counter: demand_counter, + linked_output_ref: :output_pad_ref, + log_tag: nil, + target_size: 10 + }) + + assert_receive Message.new(:demand_counter_increased, :output_pad_ref) + assert queue.lacking_buffers == 10 + queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) + assert queue.size == 4 + queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) + queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) + queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) + queue = Map.update!(queue, :demand_counter, &DemandCounter.decrease(&1, 16)) + assert queue.size == 16 + assert queue.lacking_buffers == -6 + {out, queue} = InputQueue.take(queue, 2) + assert bufs_size(out, :buffers) == 2 + assert queue.size == 4 + assert queue.lacking_buffers >= 6 + assert_receive Message.new(:demand_counter_increased, :output_pad_ref) + + queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) + queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) + {out, queue} = InputQueue.take(queue, 1) + assert bufs_size(out, :buffers) == 1 + assert queue.size == 8 + assert queue.lacking_buffers >= 2 + end + + test "if the queue works properly for :buffers input metric and :bytes output metric" do + demand_counter = DemandCounter.new(:pull, self(), :bytes, self(), :output_pad_ref) + + queue = + InputQueue.init(%{ + inbound_demand_unit: :buffers, + outbound_demand_unit: :bytes, + demand_counter: demand_counter, + linked_output_ref: :output_pad_ref, + log_tag: nil, + target_size: 3 + }) + + assert_receive Message.new(:demand_counter_increased, :output_pad_ref) + assert queue.lacking_buffers == 3 + queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) + assert queue.size == 1 + queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) + queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) + queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) + queue = Map.update!(queue, :demand_counter, &DemandCounter.decrease(&1, 4)) + assert queue.size == 4 + assert queue.lacking_buffers == -1 + {out, queue} = InputQueue.take(queue, 2) + assert bufs_size(out, :bytes) == 2 + assert queue.size == 4 + assert queue.lacking_buffers == -1 + refute_receive Message.new(:demand_counter_increased, :output_pad_ref) + {out, queue} = InputQueue.take(queue, 11) + assert bufs_size(out, :bytes) == 11 + assert queue.size == 2 + assert queue.lacking_buffers == 1 + assert_receive Message.new(:demand_counter_increased, :output_pad_ref) + end + + defp bufs_size(output, unit) do + {_state, bufs} = output + + Enum.flat_map(bufs, fn {:buffers, bufs_list, _inbound_metric_size, _outbound_metric_size} -> + bufs_list + end) + |> Membrane.Buffer.Metric.from_unit(unit).buffers_size() + end + + defp bufs(n), do: Enum.to_list(1..n) +end From 3e7deefad8c4f2d696f570b541f89fe184d0d6e5 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 6 Apr 2023 15:39:22 +0200 Subject: [PATCH 29/64] Remove unnecessary warns and comments --- lib/membrane/core/element.ex | 10 +----- .../core/element/buffer_controller.ex | 32 ++----------------- .../core/element/demand_controller.ex | 2 -- lib/membrane/core/element/demand_counter.ex | 25 +++------------ lib/membrane/core/element/demand_handler.ex | 26 ++------------- lib/membrane/core/element/input_queue.ex | 4 --- lib/membrane/core/element/pad_controller.ex | 17 ---------- lib/membrane/core/element/state.ex | 6 ---- lib/membrane/testing/source.ex | 10 +----- test/support/demands_test/filter.ex | 2 -- 10 files changed, 11 insertions(+), 123 deletions(-) diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index af0ddeff8..279f40da3 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -173,20 +173,12 @@ defmodule Membrane.Core.Element do @compile {:inline, do_handle_info: 2} - # defp do_handle_info(Message.new(:demand, size, _opts) = msg, state) do - # pad_ref = Message.for_pad(msg) - # state = DemandController.handle_demand(pad_ref, size, state) - # {:noreply, state} - # end - - defp do_handle_info(Message.new(:demand_counter_increased, pad_ref) = msg, state) do - Membrane.Logger.warn("RECEIVING DC NOTIFICATION ON #{inspect(pad_ref)} #{inspect(msg)}") + defp do_handle_info(Message.new(:demand_counter_increased, pad_ref), state) do state = DemandController.check_demand_counter(pad_ref, state) {:noreply, state} end defp do_handle_info(Message.new(:resume_handle_demand_loop), state) do - Membrane.Logger.warn("RECEIVING RESUME LOOP MSG") state = DemandHandler.handle_delayed_demands(state) {:noreply, state} end diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index 3852e1192..5e12d1b3d 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -5,7 +5,6 @@ defmodule Membrane.Core.Element.BufferController do use Bunch - alias Membrane.Core.Element.DemandCounter alias Membrane.{Buffer, Pad} alias Membrane.Core.{CallbackHandler, Telemetry} alias Membrane.Core.Child.PadModel @@ -18,8 +17,7 @@ defmodule Membrane.Core.Element.BufferController do EventController, InputQueue, PlaybackQueue, - State, - Toilet + State } alias Membrane.Core.Telemetry @@ -70,35 +68,9 @@ defmodule Membrane.Core.Element.BufferController do defp do_handle_buffer(pad_ref, %{flow_control: :manual} = data, buffers, state) do %{input_queue: old_input_queue} = data - require Membrane.Logger - - buf_num = - Enum.filter(buffers, &match?(%Membrane.Buffer{}, &1)) - |> Enum.count() - - Membrane.Logger.warn("STORING #{inspect(buf_num)} BUFFERS") - - for %Membrane.Buffer{payload: <> <> <<_::binary>>} <- buffers do - Membrane.Logger.warn("STORE IN INPUT_QUEUE BUFFER NO. #{inspect(i)}") - end - input_queue = InputQueue.store(old_input_queue, buffers) state = PadModel.set_data!(state, pad_ref, :input_queue, input_queue) - require Membrane.Logger - - warn_sufix = - with {:ok, %{demand: demand, demand_counter: dc}} <- PadModel.get_data(state, :output) do - " OUTPUT DEMAND SNAPSHOT #{inspect(demand)} COUNTER #{inspect(DemandCounter.get(dc))}" - else - _ -> "" - end - - Membrane.Logger.warn( - "HANDLE BUFFER #{inspect(Enum.count(buffers))} EMPTY QUEUE? #{inspect(old_input_queue |> InputQueue.empty?())}" <> - warn_sufix - ) - if old_input_queue |> InputQueue.empty?() do DemandHandler.supply_demand(pad_ref, state) else @@ -106,7 +78,7 @@ defmodule Membrane.Core.Element.BufferController do end end - defp do_handle_buffer(pad_ref, %{flow_control: :push} = data, buffers, state) do + defp do_handle_buffer(pad_ref, %{flow_control: :push}, buffers, state) do exec_buffer_callback(pad_ref, buffers, state) end diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index a5bad1263..5525fa728 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -99,8 +99,6 @@ defmodule Membrane.Core.Element.DemandController do @spec do_exec_handle_demand(PadData.t(), State.t()) :: State.t() defp do_exec_handle_demand(pad_data, state) do - Membrane.Logger.warn("EXEC HANDLE DEMAND #{inspect(pad_data.ref)}") - context = &CallbackContext.from_state(&1, incoming_demand: pad_data.incoming_demand) CallbackHandler.exec_and_handle_callback( diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 081d49bef..626ed346f 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -258,15 +258,7 @@ defmodule Membrane.Core.Element.DemandCounter do new_counter_value = DistributedAtomic.add_get(demand_counter.counter, value) old_counter_value = new_counter_value - value - Membrane.Logger.warn( - "DEMAND COUNTER OLD NEW #{inspect({old_counter_value, new_counter_value})}" - ) - if old_counter_value <= 0 do - Membrane.Logger.warn( - "SENDING DC NOTIFICATION #{inspect(Message.new(:demand_counter_increased, demand_counter.sender_pad_ref))}" - ) - Message.send( demand_counter.sender_process, :demand_counter_increased, @@ -281,18 +273,11 @@ defmodule Membrane.Core.Element.DemandCounter do def decrease(%__MODULE__{} = demand_counter, value) do demand_counter = Map.update!(demand_counter, :buffered_decrementation, &(&1 + value)) - xd = get(demand_counter) - - dc = - if demand_counter.buffered_decrementation >= demand_counter.buffered_decrementation_limit do - flush_buffered_decrementation(demand_counter) - else - demand_counter - end - - Membrane.Logger.warn("DEMAND COUNTER DECREMENTATION #{inspect(xd)} -> #{inspect(get(dc))}") - - dc + if demand_counter.buffered_decrementation >= demand_counter.buffered_decrementation_limit do + flush_buffered_decrementation(demand_counter) + else + demand_counter + end end @spec get(t) :: integer() diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 8502a68db..69ef00247 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -86,10 +86,6 @@ defmodule Membrane.Core.Element.DemandHandler do {{_queue_status, popped_data}, new_input_queue} = InputQueue.take(pad_data.input_queue, pad_data.demand) - Membrane.Logger.warn( - "TRYNIG TO TAKE #{pad_data.demand} from QUEUE, GOT #{inspect(popped_data, limit: :infinity, pretty: true)}" - ) - state = PadModel.set_data!(state, pad_ref, :input_queue, new_input_queue) state = handle_input_queue_output(pad_ref, popped_data, state) %State{state | supplying_demand?: false} @@ -111,14 +107,11 @@ defmodule Membrane.Core.Element.DemandHandler do demand = pad_data.demand - buffers_size demand_counter = DemandCounter.decrease(pad_data.demand_counter, buffers_size) - # state = PadModel.set_data!( state, pad_ref, %{pad_data | demand: demand, demand_counter: demand_counter} ) - - # DemandController.check_demand_counter(pad_ref, state) end defp update_demand(pad_ref, size, state) when is_integer(size) do @@ -146,15 +139,12 @@ defmodule Membrane.Core.Element.DemandHandler do # one pad are supplied right away while another one is waiting for buffers # potentially for a long time. - # Membrane.Logger.warn("HANDLE DELAYED DEMANDS #{inspect(state.delayed_demands)}") - cond do state.supplying_demand? -> - raise "dupa 007" + raise "Cannot handle delayed demands while already supplying demand" state.handle_demand_loop_counter >= @handle_demand_loop_limit -> Message.self(:resume_handle_demand_loop) - Membrane.Logger.warn("SENDING RESUME LOOP MSG") %{state | handle_demand_loop_counter: 0} state.delayed_demands == MapSet.new() -> @@ -177,17 +167,10 @@ defmodule Membrane.Core.Element.DemandHandler do @spec maybe_snapshot_demand_counter(Pad.ref(), State.t()) :: State.t() def maybe_snapshot_demand_counter(pad_ref, state) do - Membrane.Logger.warn("MAYBE SNAPSHOT DEMAND COUNTER #{inspect(pad_ref)}") - - # Membrane.Logger.warn(inspect(PadModel.get_data(state, pad_ref, :demand))) - # Membrane.Logger.warn("DEMAND COUNTER: ") - with {:ok, %{flow_control: :manual, demand: demand, demand_counter: demand_counter}} when demand <= 0 <- PadModel.get_data(state, pad_ref), counter_value when counter_value > 0 and counter_value > demand <- DemandCounter.get(demand_counter) do - Membrane.Logger.warn("BUMPING DEMAND #{inspect(demand)} -> #{inspect(counter_value)}") - state = PadModel.update_data!( state, @@ -197,12 +180,7 @@ defmodule Membrane.Core.Element.DemandHandler do handle_redemand(pad_ref, state) else - other -> - Membrane.Logger.warn( - "NOT SNAPSHOTING BECAUSE #{inspect(other, pretty: true, limit: :infinity)}" - ) - - state + _other -> state end end diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index 546221817..a36357bd6 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -150,10 +150,6 @@ defmodule Membrane.Core.Element.InputQueue do } end - # w starej implementacji pole "demand" jest inkrementowane o tyle, ile wyciagniemy z kolejki - # wiec teraz trzeba podbic counter o tyle ile wyciagnelismy z kolejki - # wychodzi na to ze teraz pole "demand" jest wgl do wywalenia - @spec take(t, non_neg_integer()) :: {output(), t} def take(%__MODULE__{} = input_queue, count) when count >= 0 do "Taking #{inspect(count)} #{inspect(input_queue.outbound_metric)}" diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index c816385d8..b6d1506ee 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -161,9 +161,7 @@ defmodule Membrane.Core.Element.PadController do other_effective_flow_control: other_effective_flow_control } = link_props - # if info.direction != :input do if info.direction != :input, do: raise("pad direction #{inspect(info.direction)} is wrong") - # end true = info.direction == :input @@ -325,7 +323,6 @@ defmodule Membrane.Core.Element.PadController do end) case data.direction do - # :input -> DemandController.send_auto_demand_if_needed(endpoint.pad_ref, state) :input -> DemandController.increase_demand_counter_if_needed(endpoint.pad_ref, state) :output -> state end @@ -410,17 +407,6 @@ defmodule Membrane.Core.Element.PadController do } end - # defp init_pad_mode_data( - # %{flow_control: :push, direction: :output}, - # _props, - # %{flow_control: other_flow_control}, - # metadata, - # _state - # ) - # when other_flow_control in [:auto, :manual] do - # %{toilet: metadata.toilet} - # end - defp init_pad_mode_data(_data, _props, _other_info, _metadata, _state), do: %{} @doc """ @@ -452,12 +438,9 @@ defmodule Membrane.Core.Element.PadController do |> PadModel.set_data!(pad_ref, :associated_pads, []) if pad_data.direction == :output do - Membrane.Logger.warn("UNLINKING PADS ASSOCIATIONS #{inspect(pad_data.associated_pads)}") - Enum.reduce( pad_data.associated_pads, state, - # &DemandController.send_auto_demand_if_needed/2 &DemandController.increase_demand_counter_if_needed/2 ) else diff --git a/lib/membrane/core/element/state.ex b/lib/membrane/core/element/state.ex index 1885952dd..f004c2833 100644 --- a/lib/membrane/core/element/state.ex +++ b/lib/membrane/core/element/state.ex @@ -24,8 +24,6 @@ defmodule Membrane.Core.Element.State do parent_pid: pid, supplying_demand?: boolean(), delayed_demands: MapSet.t({Pad.ref(), :supply | :redemand}), - # supplying_output_demand?: boolean(), - # output_demands: %{optional(Pad.ref()) => pos_integer()}, handle_demand_loop_counter: non_neg_integer(), synchronization: %{ timers: %{Timer.id() => Timer.t()}, @@ -54,8 +52,6 @@ defmodule Membrane.Core.Element.State do :parent_pid, :supplying_demand?, :delayed_demands, - # :supplying_output_demand?, - # :output_demands, :handle_demand_loop_counter, :synchronization, :demand_size, @@ -90,8 +86,6 @@ defmodule Membrane.Core.Element.State do parent_pid: options.parent, supplying_demand?: false, delayed_demands: MapSet.new(), - # supplying_output_demand?: false, - # output_demands: %{}, handle_demand_loop_counter: 0, synchronization: %{ parent_clock: options.parent_clock, diff --git a/lib/membrane/testing/source.ex b/lib/membrane/testing/source.ex index dced1a50b..1288b8f25 100644 --- a/lib/membrane/testing/source.ex +++ b/lib/membrane/testing/source.ex @@ -86,9 +86,6 @@ defmodule Membrane.Testing.Source do @impl true def handle_demand(:output, size, :buffers, _ctx, state) do - require Membrane.Logger - Membrane.Logger.warn("HANDLE DEMAND #{size}") - get_actions(state, size) end @@ -96,12 +93,7 @@ defmodule Membrane.Testing.Source do def default_buf_gen(generator_state, size) do buffers = generator_state..(size + generator_state - 1) - |> Enum.map(fn generator_state -> - require Membrane.Logger - Membrane.Logger.warn("GENERATING BUFFER NO. #{inspect(generator_state)}") - - %Buffer{payload: <>} - end) + |> Enum.map(&%Buffer{payload: <<&1::16>>}) action = [buffer: {:output, buffers}] {action, generator_state + size} diff --git a/test/support/demands_test/filter.ex b/test/support/demands_test/filter.ex index 4266d8fc1..30795279f 100644 --- a/test/support/demands_test/filter.ex +++ b/test/support/demands_test/filter.ex @@ -22,8 +22,6 @@ defmodule Membrane.Support.DemandsTest.Filter do @impl true def handle_demand(:output, size, _unit, _ctx, state) do - Membrane.Logger.warn("FILTER DEMADNING #{state.demand_generator.(size)}") - {[demand: {:input, state.demand_generator.(size)}], state} end From aa201a34cc07959acc3fdfbed95246683ab80edc Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 6 Apr 2023 15:46:42 +0200 Subject: [PATCH 30/64] Fix credo issues --- lib/membrane/core/element/demand_controller.ex | 3 +-- lib/membrane/core/element/input_queue.ex | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 5525fa728..10403b4d6 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -5,8 +5,6 @@ defmodule Membrane.Core.Element.DemandController do use Bunch - alias Membrane.Core.Element.DemandHandler - alias Membrane.Element.PadData alias Membrane.Core.CallbackHandler alias Membrane.Core.Child.PadModel @@ -19,6 +17,7 @@ defmodule Membrane.Core.Element.DemandController do State } + alias Membrane.Element.PadData alias Membrane.Pad require Membrane.Core.Child.PadModel diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index a36357bd6..140a761ce 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -8,10 +8,10 @@ defmodule Membrane.Core.Element.InputQueue do use Bunch - alias Membrane.Event - alias Membrane.Core.Element.DemandCounter alias Membrane.Buffer + alias Membrane.Core.Element.DemandCounter alias Membrane.Core.Telemetry + alias Membrane.Event alias Membrane.Pad require Membrane.Core.Telemetry From fc3b7d3863dfafa5b6b95a50ed753f4c95228b4f Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 6 Apr 2023 15:51:55 +0200 Subject: [PATCH 31/64] Fix dialyzer issues --- lib/membrane/core/child/pad_model.ex | 1 - lib/membrane/core/element/demand_counter.ex | 4 ++-- lib/membrane/core/element/demand_handler.ex | 3 +-- lib/membrane/core/element/pad_controller.ex | 5 ++--- lib/membrane/element/pad_data.ex | 2 -- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/membrane/core/child/pad_model.ex b/lib/membrane/core/child/pad_model.ex index 2b8da8de8..54ec1315b 100644 --- a/lib/membrane/core/child/pad_model.ex +++ b/lib/membrane/core/child/pad_model.ex @@ -40,7 +40,6 @@ defmodule Membrane.Core.Child.PadModel do sticky_messages: [Membrane.Event.t()], input_queue: Membrane.Core.Element.InputQueue.t() | nil, options: %{optional(atom) => any}, - toilet: Membrane.Core.Element.Toilet.t() | nil, auto_demand_size: pos_integer() | nil, associated_pads: [Pad.ref()] | nil, sticky_events: [Membrane.Event.t()] diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 626ed346f..97ce1dc2d 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -58,7 +58,7 @@ defmodule Membrane.Core.Element.DemandCounter do defmodule DistributedAtomic do @moduledoc false - # A module providing a common interface to access and modify a counter used in the toilet implementation. + # A module providing a common interface to access and modify a counter used in the DemandCounter implementation. # The counter uses :atomics module under the hood. # The module allows to create and modify the value of a counter in the same manner both when the counter is about to be accessed # from the same node, and from different nodes. @@ -176,7 +176,7 @@ defmodule Membrane.Core.Element.DemandCounter do buffered_decrementation_limit: pos_integer() } - @type flow_mode :: DistributedFlowMode.flow_mode() + @type flow_mode :: DistributedFlowMode.flow_mode_value() @enforce_keys [ :counter, diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 69ef00247..d86ffe67b 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -92,8 +92,7 @@ defmodule Membrane.Core.Element.DemandHandler do end @doc """ - Decreases the demand on the output by the size of outgoing buffers. Checks for the toilet - overflow if the toilet is enabled. + Decreases demand snapshot and demand counter on the output by the size of outgoing buffers. """ @spec handle_outgoing_buffers( Pad.ref(), diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index b6d1506ee..4fe9bdb8a 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -17,8 +17,7 @@ defmodule Membrane.Core.Element.PadController do EventController, InputQueue, State, - StreamFormatController, - Toilet + StreamFormatController } alias Membrane.Core.Parent.Link.Endpoint @@ -37,7 +36,7 @@ defmodule Membrane.Core.Element.PadController do | %{ initiator: :sibling, other_info: PadModel.pad_info() | nil, - link_metadata: %{toilet: Toilet.t() | nil}, + link_metadata: %{}, stream_format_validation_params: StreamFormatController.stream_format_validation_params(), other_effective_flow_control: EffectiveFlowController.effective_flow_control() diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 5729a582a..0a7d02776 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -45,7 +45,6 @@ defmodule Membrane.Element.PadData do other_demand_unit: private_field, auto_demand_size: private_field, sticky_messages: private_field, - toilet: private_field, demand_counter: private_field, lacking_buffers: private_field, associated_pads: private_field, @@ -76,7 +75,6 @@ defmodule Membrane.Element.PadData do end_of_stream?: false, auto_demand_size: nil, sticky_messages: [], - toilet: nil, demand_counter: nil, lacking_buffers: 0, associated_pads: [], From 37ca9e49a31c618b3cbba2062145d05427ff6e53 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 6 Apr 2023 17:02:31 +0200 Subject: [PATCH 32/64] Refactor DemandController and DemandHander --- lib/membrane/core/element/action_handler.ex | 3 +- .../core/element/demand_controller.ex | 41 +++++++++++++- lib/membrane/core/element/demand_handler.ex | 55 +++---------------- lib/membrane/core/element/pad_controller.ex | 2 - lib/membrane/element/pad_data.ex | 2 - 5 files changed, 46 insertions(+), 57 deletions(-) diff --git a/lib/membrane/core/element/action_handler.ex b/lib/membrane/core/element/action_handler.ex index 12e767d72..9eabb4764 100644 --- a/lib/membrane/core/element/action_handler.ex +++ b/lib/membrane/core/element/action_handler.ex @@ -315,7 +315,7 @@ defmodule Membrane.Core.Element.ActionHandler do } when stream_format != nil <- pad_data do state = - DemandHandler.handle_outgoing_buffers(pad_ref, buffers, state) + DemandController.handle_outgoing_buffers(pad_ref, buffers, state) |> PadModel.set_data!(pad_ref, :start_of_stream?, true) Message.send(pid, :buffer, buffers, for_pad: other_ref) @@ -414,7 +414,6 @@ defmodule Membrane.Core.Element.ActionHandler do with %{direction: :output, flow_control: :manual} <- PadModel.get_data!(state, pad_ref) do DemandHandler.handle_redemand(pad_ref, state) - # Membrane.Core.Element.DemandController.redemand(pad_ref, state) else %{direction: :input} -> raise ElementError, "Tried to make a redemand on input pad #{inspect(pad_ref)}" diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 10403b4d6..2b546c068 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -5,6 +5,7 @@ defmodule Membrane.Core.Element.DemandController do use Bunch + alias Membrane.Buffer alias Membrane.Core.CallbackHandler alias Membrane.Core.Child.PadModel @@ -55,9 +56,8 @@ defmodule Membrane.Core.Element.DemandController do counter_value = demand_counter |> DemandCounter.get() - # todo: maybe also check if pad_data.demand <= 0 ? if counter_value > 0 do - # todo: optimize lopp below + # TODO: think about optimizing lopp below Enum.reduce(associated_pads, state, &increase_demand_counter_if_needed/2) else state @@ -65,7 +65,20 @@ defmodule Membrane.Core.Element.DemandController do end defp do_check_demand_counter(%{flow_control: :manual} = pad_data, state) do - DemandHandler.maybe_snapshot_demand_counter(pad_data.ref, state) + with %{demand: demand, demand_counter: demand_counter} when demand <= 0 <- pad_data, + counter_value when counter_value > 0 and counter_value > demand <- + DemandCounter.get(demand_counter) do + state = + PadModel.update_data!( + state, + pad_data.ref, + &%{&1 | demand: counter_value, incoming_demand: counter_value - &1.demand} + ) + + DemandHandler.handle_redemand(pad_data.ref, state) + else + _other -> state + end end defp do_check_demand_counter(_pad_data, state) do @@ -86,6 +99,28 @@ defmodule Membrane.Core.Element.DemandController do end end + @doc """ + Decreases demand snapshot and demand counter on the output by the size of outgoing buffers. + """ + @spec handle_outgoing_buffers( + Pad.ref(), + [Buffer.t()], + State.t() + ) :: State.t() + def handle_outgoing_buffers(pad_ref, buffers, state) do + pad_data = PadModel.get_data!(state, pad_ref) + buffers_size = Buffer.Metric.from_unit(pad_data.other_demand_unit).buffers_size(buffers) + + demand = pad_data.demand - buffers_size + demand_counter = DemandCounter.decrease(pad_data.demand_counter, buffers_size) + + PadModel.set_data!( + state, + pad_ref, + %{pad_data | demand: demand, demand_counter: demand_counter} + ) + end + @spec exec_handle_demand(Pad.ref(), State.t()) :: State.t() def exec_handle_demand(pad_ref, state) do with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index d86ffe67b..5cc5bf0f5 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -3,12 +3,9 @@ defmodule Membrane.Core.Element.DemandHandler do # Module handling demands requested on output pads. - alias Membrane.Buffer - alias Membrane.Core.Element.{ BufferController, DemandController, - DemandCounter, EventController, InputQueue, State, @@ -34,13 +31,16 @@ defmodule Membrane.Core.Element.DemandHandler do end def handle_redemand(pad_ref, %State{} = state) do - state - |> Map.put(:supplying_demand?, true) - |> then(&DemandController.exec_handle_demand(pad_ref, &1)) - |> Map.put(:supplying_demand?, false) + do_handle_redemand(pad_ref, state) |> handle_delayed_demands() end + defp do_handle_redemand(pad_ref, state) do + state = %{state | supplying_demand?: true} + state = DemandController.exec_handle_demand(pad_ref, state) + %{state | supplying_demand?: false} + end + @doc """ If element is not supplying demand currently, this function supplies demand right away by taking buffers from the InputQueue of the given input pad @@ -91,28 +91,6 @@ defmodule Membrane.Core.Element.DemandHandler do %State{state | supplying_demand?: false} end - @doc """ - Decreases demand snapshot and demand counter on the output by the size of outgoing buffers. - """ - @spec handle_outgoing_buffers( - Pad.ref(), - [Buffer.t()], - State.t() - ) :: State.t() - def handle_outgoing_buffers(pad_ref, buffers, state) do - pad_data = PadModel.get_data!(state, pad_ref) - buffers_size = Buffer.Metric.from_unit(pad_data.other_demand_unit).buffers_size(buffers) - - demand = pad_data.demand - buffers_size - demand_counter = DemandCounter.decrease(pad_data.demand_counter, buffers_size) - - PadModel.set_data!( - state, - pad_ref, - %{pad_data | demand: demand, demand_counter: demand_counter} - ) - end - defp update_demand(pad_ref, size, state) when is_integer(size) do PadModel.set_data!(state, pad_ref, :demand, size) end @@ -164,25 +142,6 @@ defmodule Membrane.Core.Element.DemandHandler do end end - @spec maybe_snapshot_demand_counter(Pad.ref(), State.t()) :: State.t() - def maybe_snapshot_demand_counter(pad_ref, state) do - with {:ok, %{flow_control: :manual, demand: demand, demand_counter: demand_counter}} - when demand <= 0 <- PadModel.get_data(state, pad_ref), - counter_value when counter_value > 0 and counter_value > demand <- - DemandCounter.get(demand_counter) do - state = - PadModel.update_data!( - state, - pad_ref, - &%{&1 | demand: counter_value, incoming_demand: counter_value - &1.demand} - ) - - handle_redemand(pad_ref, state) - else - _other -> state - end - end - @spec handle_input_queue_output( Pad.ref(), [InputQueue.output_value()], diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 4fe9bdb8a..b5b82433b 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -162,8 +162,6 @@ defmodule Membrane.Core.Element.PadController do if info.direction != :input, do: raise("pad direction #{inspect(info.direction)} is wrong") - true = info.direction == :input - {output_demand_unit, input_demand_unit} = resolve_demand_units(other_info, info) link_metadata = diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 0a7d02776..c92d52887 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -39,7 +39,6 @@ defmodule Membrane.Element.PadData do other_ref: private_field, input_queue: private_field, demand: integer() | nil, - handle_demand_executed?: boolean(), incoming_demand: integer() | nil, demand_unit: private_field, other_demand_unit: private_field, @@ -68,7 +67,6 @@ defmodule Membrane.Element.PadData do [ input_queue: nil, demand: 0, - handle_demand_executed?: false, incoming_demand: nil, demand_unit: nil, start_of_stream?: false, From b60a4d0196c2ea96bd75c5c9885d62450d46bcdb Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 6 Apr 2023 17:07:25 +0200 Subject: [PATCH 33/64] Remove unnecesary require Membrane.Logger --- lib/membrane/bin.ex | 1 - lib/membrane/core/child/pad_controller.ex | 1 - lib/membrane/core/element/demand_handler.ex | 1 - lib/membrane/core/element/stream_format_controller.ex | 1 - lib/membrane/core/pipeline.ex | 1 - lib/membrane/core/pipeline/action_handler.ex | 2 -- lib/membrane/testing/pipeline.ex | 1 - test/support/demands_test/filter.ex | 2 -- test/support/log_metadata_test/pipeline.ex | 2 -- 9 files changed, 12 deletions(-) diff --git a/lib/membrane/bin.ex b/lib/membrane/bin.ex index eac5025cb..67b9038cd 100644 --- a/lib/membrane/bin.ex +++ b/lib/membrane/bin.ex @@ -18,7 +18,6 @@ defmodule Membrane.Bin do alias Membrane.Core.OptionsSpecs require Membrane.Core.Message - require Membrane.Logger @type state :: any() diff --git a/lib/membrane/core/child/pad_controller.ex b/lib/membrane/core/child/pad_controller.ex index e5ac48f45..19d5a2e39 100644 --- a/lib/membrane/core/child/pad_controller.ex +++ b/lib/membrane/core/child/pad_controller.ex @@ -5,7 +5,6 @@ defmodule Membrane.Core.Child.PadController do alias Membrane.{LinkError, Pad} require Membrane.Core.Child.PadModel - require Membrane.Logger @type state :: Membrane.Core.Bin.State.t() | Membrane.Core.Element.State.t() diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 5cc5bf0f5..ccfba6e83 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -16,7 +16,6 @@ defmodule Membrane.Core.Element.DemandHandler do require Membrane.Core.Child.PadModel, as: PadModel require Membrane.Core.Message, as: Message - require Membrane.Logger @doc """ Called when redemand action was returned. diff --git a/lib/membrane/core/element/stream_format_controller.ex b/lib/membrane/core/element/stream_format_controller.ex index cebcfd946..53c9c08ec 100644 --- a/lib/membrane/core/element/stream_format_controller.ex +++ b/lib/membrane/core/element/stream_format_controller.ex @@ -12,7 +12,6 @@ defmodule Membrane.Core.Element.StreamFormatController do require Membrane.Core.Child.PadModel require Membrane.Core.Telemetry - require Membrane.Logger @type stream_format_validation_param() :: {module(), Pad.name()} @type stream_format_validation_params() :: [stream_format_validation_param()] diff --git a/lib/membrane/core/pipeline.ex b/lib/membrane/core/pipeline.ex index 094f36932..a472cc2ce 100644 --- a/lib/membrane/core/pipeline.ex +++ b/lib/membrane/core/pipeline.ex @@ -11,7 +11,6 @@ defmodule Membrane.Core.Pipeline do require Membrane.Core.Message, as: Message require Membrane.Core.Telemetry, as: Telemetry - require Membrane.Logger require Membrane.Core.Component @impl GenServer diff --git a/lib/membrane/core/pipeline/action_handler.ex b/lib/membrane/core/pipeline/action_handler.ex index b078dc021..ea1196486 100644 --- a/lib/membrane/core/pipeline/action_handler.ex +++ b/lib/membrane/core/pipeline/action_handler.ex @@ -8,8 +8,6 @@ defmodule Membrane.Core.Pipeline.ActionHandler do alias Membrane.Core.Parent.LifecycleController alias Membrane.Core.Pipeline.State - require Membrane.Logger - @impl CallbackHandler def handle_action({action, _args}, :handle_init, _params, _state) when action != :spec do raise ActionError, action: action, reason: {:invalid_callback, :handle_init} diff --git a/lib/membrane/testing/pipeline.ex b/lib/membrane/testing/pipeline.ex index 7d79114e3..3589d98ad 100644 --- a/lib/membrane/testing/pipeline.ex +++ b/lib/membrane/testing/pipeline.ex @@ -85,7 +85,6 @@ defmodule Membrane.Testing.Pipeline do alias Membrane.{Element, Pipeline} alias Membrane.Testing.Notification - require Membrane.Logger require Membrane.Core.Message defmodule State do diff --git a/test/support/demands_test/filter.ex b/test/support/demands_test/filter.ex index 30795279f..fd52ed6bb 100644 --- a/test/support/demands_test/filter.ex +++ b/test/support/demands_test/filter.ex @@ -4,8 +4,6 @@ defmodule Membrane.Support.DemandsTest.Filter do alias Membrane.Buffer - require Membrane.Logger - def_output_pad :output, flow_control: :manual, accepted_format: _any def_input_pad :input, flow_control: :manual, demand_unit: :buffers, accepted_format: _any diff --git a/test/support/log_metadata_test/pipeline.ex b/test/support/log_metadata_test/pipeline.ex index fb7c0a581..0a2e3fa06 100644 --- a/test/support/log_metadata_test/pipeline.ex +++ b/test/support/log_metadata_test/pipeline.ex @@ -14,8 +14,6 @@ defmodule Membrane.Support.LogMetadataTest.Pipeline do import Membrane.ChildrenSpec - require Membrane.Logger - def_output_pad :output, flow_control: :manual, accepted_format: _any, From 2a5bb1070656fef4f64796a434542a9933d38280 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 6 Apr 2023 17:51:45 +0200 Subject: [PATCH 34/64] Reduce autodemands size --- lib/membrane/core/element/demand_controller.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 2b546c068..c4fc98f11 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -24,8 +24,8 @@ defmodule Membrane.Core.Element.DemandController do require Membrane.Core.Child.PadModel require Membrane.Logger - @lacking_buffers_lowerbound 2000 - @lacking_buffers_upperbound 4000 + @lacking_buffers_lowerbound 200 + @lacking_buffers_upperbound 400 @spec check_demand_counter(Pad.ref(), State.t()) :: State.t() def check_demand_counter(pad_ref, state) do From dd90dc8567a3192f4d58853ae080b5dc5081a60c Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 20 Apr 2023 18:32:14 +0200 Subject: [PATCH 35/64] Implement comments from PR - wip --- lib/membrane/core/bin/pad_controller.ex | 4 +- .../core/element/buffer_controller.ex | 6 +- .../core/element/demand_controller.ex | 22 ++- lib/membrane/core/element/demand_counter.ex | 182 ++---------------- .../demand_counter/distributed_atomic.ex | 75 ++++++++ .../demand_counter/distributed_flow_mode.ex | 38 ++++ .../core/element/demand_counter/worker.ex | 48 +++++ .../core/element/effective_flow_controller.ex | 4 +- lib/membrane/core/element/input_queue.ex | 27 +-- lib/membrane/core/element/pad_controller.ex | 6 +- lib/membrane/element/pad_data.ex | 4 +- .../core/element/input_queue_test.exs | 54 +++--- ...effective_flow_control_resolution_test.exs | 44 +++-- 13 files changed, 270 insertions(+), 244 deletions(-) create mode 100644 lib/membrane/core/element/demand_counter/distributed_atomic.ex create mode 100644 lib/membrane/core/element/demand_counter/distributed_flow_mode.ex create mode 100644 lib/membrane/core/element/demand_counter/worker.ex diff --git a/lib/membrane/core/bin/pad_controller.ex b/lib/membrane/core/bin/pad_controller.ex index 0e52dc247..8fc09737f 100644 --- a/lib/membrane/core/bin/pad_controller.ex +++ b/lib/membrane/core/bin/pad_controller.ex @@ -295,8 +295,8 @@ defmodule Membrane.Core.Bin.PadController do state = maybe_handle_pad_removed(pad_ref, state) {pad_data, state} = PadModel.pop_data!(state, pad_ref) - if endpoint = pad_data.endpoint do - Message.send(endpoint.pid, :handle_unlink, endpoint.pad_ref) + if pad_data.endpoint do + Message.send(pad_data.endpoint.pid, :handle_unlink, pad_data.endpoint.pad_ref) ChildLifeController.proceed_spec_startup(pad_data.spec_ref, state) else Membrane.Logger.debug(""" diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index 5e12d1b3d..eb06918fc 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -57,10 +57,12 @@ defmodule Membrane.Core.Element.BufferController do @spec do_handle_buffer(Pad.ref(), PadModel.pad_data(), [Buffer.t()] | Buffer.t(), State.t()) :: State.t() defp do_handle_buffer(pad_ref, %{flow_control: :auto} = data, buffers, state) do - %{lacking_buffers: lacking_buffers, demand_unit: demand_unit} = data + %{lacking_buffer_size: lacking_buffer_size, demand_unit: demand_unit} = data buf_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) - state = PadModel.set_data!(state, pad_ref, :lacking_buffers, lacking_buffers - buf_size) + state = + PadModel.set_data!(state, pad_ref, :lacking_buffer_size, lacking_buffer_size - buf_size) + state = DemandController.increase_demand_counter_if_needed(pad_ref, state) exec_buffer_callback(pad_ref, buffers, state) end diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index c4fc98f11..1683b591d 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -24,17 +24,17 @@ defmodule Membrane.Core.Element.DemandController do require Membrane.Core.Child.PadModel require Membrane.Logger - @lacking_buffers_lowerbound 200 - @lacking_buffers_upperbound 400 + @lacking_buffer_size_lowerbound 200 + @lacking_buffer_size_upperbound 400 @spec check_demand_counter(Pad.ref(), State.t()) :: State.t() def check_demand_counter(pad_ref, state) do - with {:ok, pad} <- PadModel.get_data(state, pad_ref), + with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), %State{playback: :playing} <- state do - if pad.direction == :input, + if pad_data.direction == :input, do: raise("cannot check demand counter in input pad") - do_check_demand_counter(pad, state) + do_check_demand_counter(pad_data, state) else {:error, :unknown_pad} -> # We've got a :demand_counter_increased message on already unlinked pad @@ -54,7 +54,7 @@ defmodule Membrane.Core.Element.DemandController do associated_pads: associated_pads } = pad_data - counter_value = demand_counter |> DemandCounter.get() + counter_value = DemandCounter.get(demand_counter) if counter_value > 0 do # TODO: think about optimizing lopp below @@ -68,6 +68,8 @@ defmodule Membrane.Core.Element.DemandController do with %{demand: demand, demand_counter: demand_counter} when demand <= 0 <- pad_data, counter_value when counter_value > 0 and counter_value > demand <- DemandCounter.get(demand_counter) do + # pole demand powinno brac uwage konwersję demand unitu + state = PadModel.update_data!( state, @@ -90,10 +92,10 @@ defmodule Membrane.Core.Element.DemandController do pad_data = PadModel.get_data!(state, pad_ref) if increase_demand_counter?(pad_data, state) do - diff = @lacking_buffers_upperbound - pad_data.lacking_buffers + diff = @lacking_buffer_size_upperbound - pad_data.lacking_buffer_size :ok = DemandCounter.increase(pad_data.demand_counter, diff) - PadModel.set_data!(state, pad_ref, :lacking_buffers, @lacking_buffers_upperbound) + PadModel.set_data!(state, pad_ref, :lacking_buffer_size, @lacking_buffer_size_upperbound) else state end @@ -150,13 +152,13 @@ defmodule Membrane.Core.Element.DemandController do defp increase_demand_counter?(pad_data, state) do %{ flow_control: flow_control, - lacking_buffers: lacking_buffers, + lacking_buffer_size: lacking_buffer_size, associated_pads: associated_pads } = pad_data flow_control == :auto and state.effective_flow_control == :pull and - lacking_buffers < @lacking_buffers_lowerbound and + lacking_buffer_size < @lacking_buffer_size_lowerbound and Enum.all?(associated_pads, &demand_counter_positive?(&1, state)) end diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 97ce1dc2d..1091d7d1b 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -2,179 +2,31 @@ defmodule Membrane.Core.Element.DemandCounter do @moduledoc false alias Membrane.Core.Element.EffectiveFlowController + alias __MODULE__.{ + DistributedAtomic, + DistributedFlowMode + } + require Membrane.Core.Message, as: Message require Membrane.Logger require Membrane.Pad, as: Pad - defmodule Worker do - @moduledoc false - - # This is a GenServer created when the counter is about to be accessed from different nodes - it's running on the same node, - # where the :atomics variable is put, and processes from different nodes can ask it to modify the counter on their behalf. - - use GenServer - - @type t :: pid() - - @spec start(pid()) :: {:ok, t} - def start(parent_pid), do: GenServer.start(__MODULE__, parent_pid) - - @impl true - def init(parent_pid) do - Process.monitor(parent_pid) - {:ok, nil, :hibernate} - end - - @impl true - def handle_call({:add_get, atomic_ref, value}, _from, _state) do - result = :atomics.add_get(atomic_ref, 1, value) - {:reply, result, nil} - end - - @impl true - def handle_call({:sub_get, atomic_ref, value}, _from, _state) do - result = :atomics.sub_get(atomic_ref, 1, value) - {:reply, result, nil} - end - - @impl true - def handle_call({:get, atomic_ref}, _from, _state) do - result = :atomics.get(atomic_ref, 1) - {:reply, result, nil} - end - - @impl true - def handle_cast({:put, atomic_ref, value}, _state) do - :atomics.put(atomic_ref, 1, value) - {:noreply, nil} - end - - @impl true - def handle_info({:DOWN, _ref, :process, _object, _reason}, state) do - {:stop, :normal, state} - end - end - - defmodule DistributedAtomic do - @moduledoc false - - # A module providing a common interface to access and modify a counter used in the DemandCounter implementation. - # The counter uses :atomics module under the hood. - # The module allows to create and modify the value of a counter in the same manner both when the counter is about to be accessed - # from the same node, and from different nodes. - - @enforce_keys [:worker, :atomic_ref] - defstruct @enforce_keys - - @type t :: %__MODULE__{worker: Worker.t(), atomic_ref: :atomics.atomics_ref()} - - defguardp on_this_same_node_as_self(distributed_atomic) - when distributed_atomic.worker |> node() == self() |> node() - - @spec new(integer() | nil) :: t - def new(initial_value \\ nil) do - atomic_ref = :atomics.new(1, []) - {:ok, worker} = Worker.start(self()) - - distributed_atomic = %__MODULE__{ - atomic_ref: atomic_ref, - worker: worker - } - - if initial_value, do: put(distributed_atomic, initial_value) - - distributed_atomic - end - - @spec add_get(t, integer()) :: integer() - def add_get(%__MODULE__{} = distributed_atomic, value) - when on_this_same_node_as_self(distributed_atomic) do - :atomics.add_get(distributed_atomic.atomic_ref, 1, value) - end - - def add_get(%__MODULE__{} = distributed_atomic, value) do - GenServer.call(distributed_atomic.worker, {:add_get, distributed_atomic.atomic_ref, value}) - end - - @spec sub_get(t, integer()) :: integer() - def sub_get(%__MODULE__{} = distributed_atomic, value) - when on_this_same_node_as_self(distributed_atomic) do - :atomics.sub_get(distributed_atomic.atomic_ref, 1, value) - end - - def sub_get(%__MODULE__{} = distributed_atomic, value) do - GenServer.cast(distributed_atomic.worker, {:sub_get, distributed_atomic.atomic_ref, value}) - end - - @spec put(t, integer()) :: :ok - def put(%__MODULE__{} = distributed_atomic, value) - when on_this_same_node_as_self(distributed_atomic) do - :atomics.put(distributed_atomic.atomic_ref, 1, value) - end - - def put(%__MODULE__{} = distributed_atomic, value) do - GenServer.cast(distributed_atomic.worker, {:put, distributed_atomic.atomic_ref, value}) - end - - @spec get(t) :: integer() - def get(%__MODULE__{} = distributed_atomic) - when on_this_same_node_as_self(distributed_atomic) do - :atomics.get(distributed_atomic.atomic_ref, 1) - end - - def get(%__MODULE__{} = distributed_atomic) do - GenServer.call(distributed_atomic.worker, {:get, distributed_atomic.atomic_ref}) - end - end - - defmodule DistributedFlowMode do - @moduledoc false - - @type t :: DistributedAtomic.t() - @type flow_mode_value :: - EffectiveFlowController.effective_flow_control() | :to_be_resolved - - @spec new(flow_mode_value) :: t - def new(initial_value) do - initial_value - |> flow_mode_to_int() - |> DistributedAtomic.new() - end - - @spec get(t) :: flow_mode_value() - def get(distributed_atomic) do - distributed_atomic - |> DistributedAtomic.get() - |> int_to_flow_mode() - end - - @spec put(t, flow_mode_value()) :: :ok - def put(distributed_atomic, value) do - value = flow_mode_to_int(value) - DistributedAtomic.put(distributed_atomic, value) - end - - defp int_to_flow_mode(0), do: :to_be_resolved - defp int_to_flow_mode(1), do: :push - defp int_to_flow_mode(2), do: :pull - - defp flow_mode_to_int(:to_be_resolved), do: 0 - defp flow_mode_to_int(:push), do: 1 - defp flow_mode_to_int(:pull), do: 2 - end - @default_overflow_limit_factor -200 @default_buffered_decrementation_limit 1 @distributed_buffered_decrementation_limit 150 - @type t :: %__MODULE__{ - counter: DistributedAtomic.t(), - receiver_mode: DistributedFlowMode.t(), - receiver_process: Process.dest(), - overflow_limit: neg_integer(), - buffered_decrementation: non_neg_integer(), - buffered_decrementation_limit: pos_integer() - } + @opaque t :: %__MODULE__{ + counter: DistributedAtomic.t(), + receiver_mode: DistributedFlowMode.t(), + receiver_process: Process.dest(), + sender_mode: DistributedFlowMode.t(), + sender_process: Process.dest(), + sender_pad_ref: Pad.ref(), + overflow_limit: neg_integer(), + buffered_decrementation: non_neg_integer(), + buffered_decrementation_limit: pos_integer(), + toilet_overflowed?: boolean() + } @type flow_mode :: DistributedFlowMode.flow_mode_value() diff --git a/lib/membrane/core/element/demand_counter/distributed_atomic.ex b/lib/membrane/core/element/demand_counter/distributed_atomic.ex new file mode 100644 index 000000000..9dd5bc90c --- /dev/null +++ b/lib/membrane/core/element/demand_counter/distributed_atomic.ex @@ -0,0 +1,75 @@ +defmodule Membrane.Core.Element.DemandCounter.DistributedAtomic do + @moduledoc false + + # A module providing a common interface to access and modify a counter used in the DemandCounter implementation. + # The counter uses :atomics module under the hood. + # The module allows to create and modify the value of a counter in the same manner both when the counter is about to be accessed + # from the same node, and from different nodes. + + alias Membrane.Core.Element.DemandCounter.Worker + + @enforce_keys [:worker, :atomic_ref] + defstruct @enforce_keys + + @type t :: %__MODULE__{worker: Worker.t(), atomic_ref: :atomics.atomics_ref()} + + defguardp on_the_same_node_as_self(distributed_atomic) + when distributed_atomic.worker |> node() == self() |> node() + + @spec new(integer() | nil) :: t + def new(initial_value \\ nil) do + atomic_ref = :atomics.new(1, []) + {:ok, worker} = Worker.start(self()) + + distributed_atomic = %__MODULE__{ + atomic_ref: atomic_ref, + worker: worker + } + + if initial_value != nil do + :ok = put(distributed_atomic, initial_value) + end + + distributed_atomic + end + + @spec add_get(t, integer()) :: integer() + def add_get(%__MODULE__{} = distributed_atomic, value) + when on_the_same_node_as_self(distributed_atomic) do + :atomics.add_get(distributed_atomic.atomic_ref, 1, value) + end + + def add_get(%__MODULE__{} = distributed_atomic, value) do + GenServer.call(distributed_atomic.worker, {:add_get, distributed_atomic.atomic_ref, value}) + end + + @spec sub_get(t, integer()) :: integer() + def sub_get(%__MODULE__{} = distributed_atomic, value) + when on_the_same_node_as_self(distributed_atomic) do + :atomics.sub_get(distributed_atomic.atomic_ref, 1, value) + end + + def sub_get(%__MODULE__{} = distributed_atomic, value) do + GenServer.cast(distributed_atomic.worker, {:sub_get, distributed_atomic.atomic_ref, value}) + end + + @spec put(t, integer()) :: :ok + def put(%__MODULE__{} = distributed_atomic, value) + when on_the_same_node_as_self(distributed_atomic) do + :atomics.put(distributed_atomic.atomic_ref, 1, value) + end + + def put(%__MODULE__{} = distributed_atomic, value) do + GenServer.cast(distributed_atomic.worker, {:put, distributed_atomic.atomic_ref, value}) + end + + @spec get(t) :: integer() + def get(%__MODULE__{} = distributed_atomic) + when on_the_same_node_as_self(distributed_atomic) do + :atomics.get(distributed_atomic.atomic_ref, 1) + end + + def get(%__MODULE__{} = distributed_atomic) do + GenServer.call(distributed_atomic.worker, {:get, distributed_atomic.atomic_ref}) + end +end diff --git a/lib/membrane/core/element/demand_counter/distributed_flow_mode.ex b/lib/membrane/core/element/demand_counter/distributed_flow_mode.ex new file mode 100644 index 000000000..a56369b60 --- /dev/null +++ b/lib/membrane/core/element/demand_counter/distributed_flow_mode.ex @@ -0,0 +1,38 @@ +defmodule Membrane.Core.Element.DemandCounter.DistributedFlowMode do + @moduledoc false + + alias Membrane.Core.Element.DemandCounter.DistributedAtomic + alias Membrane.Core.Element.EffectiveFlowController + + @type t :: DistributedAtomic.t() + @type flow_mode_value :: + EffectiveFlowController.effective_flow_control() | :to_be_resolved + + @spec new(flow_mode_value) :: t + def new(initial_value) do + initial_value + |> flow_mode_to_int() + |> DistributedAtomic.new() + end + + @spec get(t) :: flow_mode_value() + def get(distributed_atomic) do + distributed_atomic + |> DistributedAtomic.get() + |> int_to_flow_mode() + end + + @spec put(t, flow_mode_value()) :: :ok + def put(distributed_atomic, value) do + value = flow_mode_to_int(value) + DistributedAtomic.put(distributed_atomic, value) + end + + defp int_to_flow_mode(0), do: :to_be_resolved + defp int_to_flow_mode(1), do: :push + defp int_to_flow_mode(2), do: :pull + + defp flow_mode_to_int(:to_be_resolved), do: 0 + defp flow_mode_to_int(:push), do: 1 + defp flow_mode_to_int(:pull), do: 2 +end diff --git a/lib/membrane/core/element/demand_counter/worker.ex b/lib/membrane/core/element/demand_counter/worker.ex new file mode 100644 index 000000000..6ff8d5078 --- /dev/null +++ b/lib/membrane/core/element/demand_counter/worker.ex @@ -0,0 +1,48 @@ +defmodule Membrane.Core.Element.DemandCounter.Worker do + @moduledoc false + + # This is a GenServer created when the counter is about to be accessed from different nodes - it's running on the same node, + # where the :atomics variable is put, and processes from different nodes can ask it to modify the counter on their behalf. + + use GenServer + + @type t :: pid() + + @spec start(pid()) :: {:ok, t} + def start(parent_pid), do: GenServer.start(__MODULE__, parent_pid) + + @impl true + def init(parent_pid) do + Process.monitor(parent_pid) + {:ok, nil, :hibernate} + end + + @impl true + def handle_call({:add_get, atomic_ref, value}, _from, _state) do + result = :atomics.add_get(atomic_ref, 1, value) + {:reply, result, nil} + end + + @impl true + def handle_call({:sub_get, atomic_ref, value}, _from, _state) do + result = :atomics.sub_get(atomic_ref, 1, value) + {:reply, result, nil} + end + + @impl true + def handle_call({:get, atomic_ref}, _from, _state) do + result = :atomics.get(atomic_ref, 1) + {:reply, result, nil} + end + + @impl true + def handle_cast({:put, atomic_ref, value}, _state) do + :atomics.put(atomic_ref, 1, value) + {:noreply, nil} + end + + @impl true + def handle_info({:DOWN, _ref, :process, _object, _reason}, state) do + {:stop, :normal, state} + end +end diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 247a4deb2..1a5289db9 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -14,8 +14,8 @@ defmodule Membrane.Core.Element.EffectiveFlowController do @type effective_flow_control :: :push | :pull - @spec pad_effective_flow_control(Pad.ref(), State.t()) :: effective_flow_control() - def pad_effective_flow_control(pad_ref, state) do + @spec get_pad_effective_flow_control(Pad.ref(), State.t()) :: effective_flow_control() + def get_pad_effective_flow_control(pad_ref, state) do pad_name = Pad.name_by_ref(pad_ref) state.pads_info diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index 140a761ce..375daba06 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -10,11 +10,11 @@ defmodule Membrane.Core.Element.InputQueue do alias Membrane.Buffer alias Membrane.Core.Element.DemandCounter - alias Membrane.Core.Telemetry alias Membrane.Event alias Membrane.Pad + alias Membrane.StreamFormat - require Membrane.Core.Telemetry + require Membrane.Core.Telemetry, as: Telemetry require Membrane.Logger @qe Qex @@ -25,17 +25,18 @@ defmodule Membrane.Core.Element.InputQueue do {:event | :stream_format, any} | {:buffers, list, pos_integer, pos_integer} @type output :: {:empty | :value, [output_value]} - @type queue_item() :: Buffer.t() | Event.t() | struct() | atom() + @type queue_item() :: Buffer.t() | Event.t() | StreamFormat.t() | atom() @type t :: %__MODULE__{ q: @qe.t(), log_tag: String.t(), target_size: pos_integer(), size: non_neg_integer(), - lacking_buffers: non_neg_integer(), + lacking_buffer_size: non_neg_integer(), inbound_metric: module(), outbound_metric: module(), - linked_output_ref: Pad.ref() + linked_output_ref: Pad.ref(), + demand_counter: DemandCounter.t() } @enforce_keys [ @@ -48,7 +49,7 @@ defmodule Membrane.Core.Element.InputQueue do :linked_output_ref ] - defstruct @enforce_keys ++ [size: 0, lacking_buffers: 0] + defstruct @enforce_keys ++ [size: 0, lacking_buffer_size: 0] @default_target_size_factor 40 @@ -129,7 +130,7 @@ defmodule Membrane.Core.Element.InputQueue do %__MODULE__{ q: q, size: size, - lacking_buffers: lacking_buffers, + lacking_buffer_size: lacking_buffer_size, inbound_metric: inbound_metric, outbound_metric: outbound_metric } = input_queue, @@ -146,7 +147,7 @@ defmodule Membrane.Core.Element.InputQueue do input_queue | q: q |> @qe.push({:buffers, v, inbound_metric_buffer_size, outbound_metric_buffer_size}), size: size + inbound_metric_buffer_size, - lacking_buffers: lacking_buffers - inbound_metric_buffer_size + lacking_buffer_size: lacking_buffer_size - inbound_metric_buffer_size } end @@ -278,11 +279,11 @@ defmodule Membrane.Core.Element.InputQueue do size: size, target_size: target_size, demand_counter: demand_counter, - lacking_buffers: lacking_buffers + lacking_buffer_size: lacking_buffer_size } = input_queue ) - when target_size > size + lacking_buffers do - diff = max(target_size - size - lacking_buffers, div(target_size, 2)) + when target_size > size + lacking_buffer_size do + diff = max(target_size - size - lacking_buffer_size, div(target_size, 2)) """ Increasing DemandCounter linked to #{inspect(input_queue.linked_output_ref)} by #{inspect(diff)} @@ -290,10 +291,10 @@ defmodule Membrane.Core.Element.InputQueue do |> mk_log(input_queue) |> Membrane.Logger.debug_verbose() - lacking_buffers = lacking_buffers + diff + lacking_buffer_size = lacking_buffer_size + diff :ok = DemandCounter.increase(demand_counter, diff) - %{input_queue | lacking_buffers: lacking_buffers} + %{input_queue | lacking_buffer_size: lacking_buffer_size} end defp maybe_increase_demand_counter(%__MODULE__{} = input_queue), do: input_queue diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index b5b82433b..c952a9ff2 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -89,7 +89,7 @@ defmodule Membrane.Core.Element.PadController do state ) do effective_flow_control = - EffectiveFlowController.pad_effective_flow_control(endpoint.pad_ref, state) + EffectiveFlowController.get_pad_effective_flow_control(endpoint.pad_ref, state) handle_link_response = Message.call(other_endpoint.pid, :handle_link, [ @@ -169,7 +169,7 @@ defmodule Membrane.Core.Element.PadController do |> Map.put(:output_demand_unit, output_demand_unit) pad_effective_flow_control = - EffectiveFlowController.pad_effective_flow_control(endpoint.pad_ref, state) + EffectiveFlowController.get_pad_effective_flow_control(endpoint.pad_ref, state) demand_counter = DemandCounter.new( @@ -299,7 +299,7 @@ defmodule Membrane.Core.Element.PadController do :ok = DemandCounter.set_sender_mode( data.demand_counter, - EffectiveFlowController.pad_effective_flow_control(data.ref, state) + EffectiveFlowController.get_pad_effective_flow_control(data.ref, state) ) data = data |> Map.merge(init_pad_direction_data(data, endpoint.pad_props, metadata, state)) diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index c92d52887..5e546d6b2 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -45,7 +45,7 @@ defmodule Membrane.Element.PadData do auto_demand_size: private_field, sticky_messages: private_field, demand_counter: private_field, - lacking_buffers: private_field, + lacking_buffer_size: private_field, associated_pads: private_field, sticky_events: private_field, other_effective_flow_control: private_field @@ -74,7 +74,7 @@ defmodule Membrane.Element.PadData do auto_demand_size: nil, sticky_messages: [], demand_counter: nil, - lacking_buffers: 0, + lacking_buffer_size: 0, associated_pads: [], sticky_events: [], stream_format_validation_params: [], diff --git a/test/membrane/core/element/input_queue_test.exs b/test/membrane/core/element/input_queue_test.exs index aa26e7b9c..e250775da 100644 --- a/test/membrane/core/element/input_queue_test.exs +++ b/test/membrane/core/element/input_queue_test.exs @@ -41,10 +41,10 @@ defmodule Membrane.Core.Element.InputQueueTest do outbound_metric: context.expected_metric, linked_output_ref: context.linked_output_ref, size: 0, - lacking_buffers: context.target_queue_size + lacking_buffer_size: context.target_queue_size } - assert context.target_queue_size == context.demand_counter |> DemandCounter.get() + assert context.target_queue_size == DemandCounter.get(context.demand_counter) expected_message = Message.new(:demand_counter_increased, context.linked_output_ref) assert_received ^expected_message @@ -87,14 +87,14 @@ defmodule Membrane.Core.Element.InputQueueTest do setup do {:ok, %{ - lacking_buffers: 30, + lacking_buffer_size: 30, size: 10, q: Qex.new() |> Qex.push({:buffers, [], 3, 3}), payload: <<1, 2, 3>> }} end - test "updated properly `size` and `lacking_buffers` when `:metric` is `Buffer.Metric.Count`", + test "updated properly `size` and `lacking_buffer_size` when `:metric` is `Buffer.Metric.Count`", context do input_queue = struct(InputQueue, @@ -102,17 +102,17 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_metric: Buffer.Metric.Count, outbound_metric: Buffer.Metric.Count, q: context.q, - lacking_buffers: context.lacking_buffers + lacking_buffer_size: context.lacking_buffer_size ) v = [%Buffer{payload: context.payload}] updated_input_queue = InputQueue.store(input_queue, :buffers, v) assert updated_input_queue.size == context.size + 1 - assert updated_input_queue.lacking_buffers == context.lacking_buffers - 1 + assert updated_input_queue.lacking_buffer_size == context.lacking_buffer_size - 1 end - test "updated properly `size` and `lacking_buffers` when `:metric` is `Buffer.Metric.ByteSize`", + test "updated properly `size` and `lacking_buffer_size` when `:metric` is `Buffer.Metric.ByteSize`", context do input_queue = struct(InputQueue, @@ -120,7 +120,7 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_metric: Buffer.Metric.ByteSize, outbound_metric: Buffer.Metric.ByteSize, q: context.q, - lacking_buffers: context.lacking_buffers + lacking_buffer_size: context.lacking_buffer_size ) v = [%Buffer{payload: context.payload}] @@ -128,8 +128,8 @@ defmodule Membrane.Core.Element.InputQueueTest do assert updated_input_queue.size == context.size + byte_size(context.payload) - assert updated_input_queue.lacking_buffers == - context.lacking_buffers - byte_size(context.payload) + assert updated_input_queue.lacking_buffer_size == + context.lacking_buffer_size - byte_size(context.payload) end test "append buffer to the queue", context do @@ -139,7 +139,7 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_metric: Buffer.Metric.ByteSize, outbound_metric: Buffer.Metric.ByteSize, q: context.q, - lacking_buffers: context.lacking_buffers + lacking_buffer_size: context.lacking_buffer_size ) v = [%Buffer{payload: context.payload}] @@ -156,7 +156,7 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_metric: Buffer.Metric.ByteSize, outbound_metric: Buffer.Metric.ByteSize, q: context.q, - lacking_buffers: context.lacking_buffers + lacking_buffer_size: context.lacking_buffer_size ) v = %Event{} @@ -173,7 +173,7 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_metric: Buffer.Metric.ByteSize, outbound_metric: Buffer.Metric.ByteSize, q: context.q, - lacking_buffers: context.lacking_buffers + lacking_buffer_size: context.lacking_buffer_size ) v = %Event{} @@ -204,9 +204,9 @@ defmodule Membrane.Core.Element.InputQueueTest do test "return {:empty, []} when the queue is empty", %{input_queue: input_queue} do old_counter_value = DemandCounter.get(input_queue.demand_counter) - old_lacking_buffers = input_queue.lacking_buffers + old_lacking_buffer_size = input_queue.lacking_buffer_size - assert {{:empty, []}, %InputQueue{size: 0, lacking_buffers: ^old_lacking_buffers}} = + assert {{:empty, []}, %InputQueue{size: 0, lacking_buffer_size: ^old_lacking_buffer_size}} = InputQueue.take(input_queue, 1) assert old_counter_value == DemandCounter.get(input_queue.demand_counter) @@ -219,7 +219,7 @@ defmodule Membrane.Core.Element.InputQueueTest do |> InputQueue.take(1) assert new_input_queue.size == 9 - assert new_input_queue.lacking_buffers >= 31 + assert new_input_queue.lacking_buffer_size >= 31 assert DemandCounter.get(new_input_queue.demand_counter) >= 41 end end @@ -239,7 +239,7 @@ defmodule Membrane.Core.Element.InputQueueTest do input_queue = struct(InputQueue, size: size, - lacking_buffers: 94, + lacking_buffer_size: 94, target_size: 100, inbound_metric: Buffer.Metric.Count, outbound_metric: Buffer.Metric.Count, @@ -265,12 +265,12 @@ defmodule Membrane.Core.Element.InputQueueTest do test "increase DemandCounter hen there are not enough buffers", context do old_counter_value = DemandCounter.get(context.input_queue.demand_counter) - old_lacking_buffers = context.input_queue.lacking_buffers + old_lacking_buffer_size = context.input_queue.lacking_buffer_size {_output, input_queue} = InputQueue.take(context.input_queue, 10) assert old_counter_value < DemandCounter.get(input_queue.demand_counter) - assert old_lacking_buffers < input_queue.lacking_buffers + assert old_lacking_buffer_size < input_queue.lacking_buffer_size end test "return `to_take` buffers from the queue when there are enough buffers and buffers don't have to be split", @@ -314,7 +314,7 @@ defmodule Membrane.Core.Element.InputQueueTest do }) assert_receive Message.new(:demand_counter_increased, :output_pad_ref) - assert queue.lacking_buffers == 10 + assert queue.lacking_buffer_size == 10 queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) assert queue.size == 4 queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) @@ -322,11 +322,11 @@ defmodule Membrane.Core.Element.InputQueueTest do queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) queue = Map.update!(queue, :demand_counter, &DemandCounter.decrease(&1, 16)) assert queue.size == 16 - assert queue.lacking_buffers == -6 + assert queue.lacking_buffer_size == -6 {out, queue} = InputQueue.take(queue, 2) assert bufs_size(out, :buffers) == 2 assert queue.size == 4 - assert queue.lacking_buffers >= 6 + assert queue.lacking_buffer_size >= 6 assert_receive Message.new(:demand_counter_increased, :output_pad_ref) queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) @@ -334,7 +334,7 @@ defmodule Membrane.Core.Element.InputQueueTest do {out, queue} = InputQueue.take(queue, 1) assert bufs_size(out, :buffers) == 1 assert queue.size == 8 - assert queue.lacking_buffers >= 2 + assert queue.lacking_buffer_size >= 2 end test "if the queue works properly for :buffers input metric and :bytes output metric" do @@ -351,7 +351,7 @@ defmodule Membrane.Core.Element.InputQueueTest do }) assert_receive Message.new(:demand_counter_increased, :output_pad_ref) - assert queue.lacking_buffers == 3 + assert queue.lacking_buffer_size == 3 queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) assert queue.size == 1 queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) @@ -359,16 +359,16 @@ defmodule Membrane.Core.Element.InputQueueTest do queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) queue = Map.update!(queue, :demand_counter, &DemandCounter.decrease(&1, 4)) assert queue.size == 4 - assert queue.lacking_buffers == -1 + assert queue.lacking_buffer_size == -1 {out, queue} = InputQueue.take(queue, 2) assert bufs_size(out, :bytes) == 2 assert queue.size == 4 - assert queue.lacking_buffers == -1 + assert queue.lacking_buffer_size == -1 refute_receive Message.new(:demand_counter_increased, :output_pad_ref) {out, queue} = InputQueue.take(queue, 11) assert bufs_size(out, :bytes) == 11 assert queue.size == 2 - assert queue.lacking_buffers == 1 + assert queue.lacking_buffer_size == 1 assert_receive Message.new(:demand_counter_increased, :output_pad_ref) end diff --git a/test/membrane/integration/effective_flow_control_resolution_test.exs b/test/membrane/integration/effective_flow_control_resolution_test.exs index a6bd94d3c..6052515f5 100644 --- a/test/membrane/integration/effective_flow_control_resolution_test.exs +++ b/test/membrane/integration/effective_flow_control_resolution_test.exs @@ -8,7 +8,7 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do require Membrane.Child, as: Child - defmodule DynamicFilter do + defmodule AutoFilter do use Membrane.Filter def_input_pad :input, availability: :on_request, accepted_format: _any @@ -74,30 +74,35 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do test "effective_flow_control is properly resolved in simple scenario" do spec_beggining = [ - child({:filter_a, 0}, DynamicFilter), - child({:filter_b, 0}, DynamicFilter) + child({:filter_a, 0}, AutoFilter), + child({:filter_b, 0}, AutoFilter) ] spec = Enum.reduce(1..10, spec_beggining, fn idx, [predecessor_a, predecessor_b] -> [ predecessor_a - |> child({:filter_a, idx}, DynamicFilter), + |> child({:filter_a, idx}, AutoFilter), predecessor_b - |> child({:filter_b, idx}, DynamicFilter) + |> child({:filter_b, idx}, AutoFilter) ] end) pipeline = Testing.Pipeline.start_link_supervised!(spec: spec) - Process.sleep(1000) - - for idx <- 0..10, filter_type <- [:filter_a, :filter_b] do - child = {filter_type, idx} + children_names = + for filter_type <- [:filter_a, :filter_b], idx <- 0..10 do + {filter_type, idx} + end + children_names + |> Enum.map(fn child -> assert_pipeline_notified(pipeline, child, :playing) + child + end) + |> Enum.each(fn child -> assert_child_effective_flow_control(pipeline, child, :push) - end + end) Testing.Pipeline.execute_actions(pipeline, spec: [ @@ -110,9 +115,12 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do Process.sleep(1000) - for idx <- 0..10, filter_type <- [:filter_a, :filter_b] do - child = {filter_type, idx} - expected = if filter_type == :filter_a, do: :push, else: :pull + for child <- children_names do + expected = + case child do + {:filter_a, _idx} -> :push + {:filter_b, _idx} -> :pull + end assert_child_effective_flow_control(pipeline, child, expected) end @@ -123,7 +131,7 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do Enum.reduce( 1..10, child(:push_source, PushSource), - fn idx, last_child -> last_child |> child({:filter, idx}, DynamicFilter) end + fn idx, last_child -> last_child |> child({:filter, idx}, AutoFilter) end ) pipeline = Testing.Pipeline.start_link_supervised!(spec: spec) @@ -155,8 +163,8 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do test "effective_flow_control is :pull, only when it should be" do spec = [ child(:push_source, PushSource) - |> child(:filter_1, DynamicFilter) - |> child(:filter_2, DynamicFilter), + |> child(:filter_1, AutoFilter) + |> child(:filter_2, AutoFilter), child(:pull_source, PullSource) |> get_child(:filter_2) ] @@ -172,7 +180,7 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do test "Toilet does not overflow, when input pad effective flow control is :push" do spec = child(:source, BatchingSource) - |> child(:filter, DynamicFilter) + |> child(:filter, AutoFilter) pipeline = Testing.Pipeline.start_supervised!(spec: spec) Process.sleep(500) @@ -186,7 +194,7 @@ defmodule Membrane.Integration.EffectiveFlowControlResolutionTest do test "Toilet overflows, when it should" do spec = { child(:pull_source, PullSource) - |> child(:filter, %DynamicFilter{lazy?: true}), + |> child(:filter, %AutoFilter{lazy?: true}), group: :group, crash_group_mode: :temporary } From 40b1d08c114db608ee0ab45a555c1d2fc53bd153 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 20 Apr 2023 18:43:22 +0200 Subject: [PATCH 36/64] Improve log on stream format patter error --- lib/membrane/core/child/pad_model.ex | 1 + lib/membrane/core/element/action_handler.ex | 3 ++- .../core/element/stream_format_controller.ex | 12 ++++++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/membrane/core/child/pad_model.ex b/lib/membrane/core/child/pad_model.ex index 54ec1315b..aa2a5d288 100644 --- a/lib/membrane/core/child/pad_model.ex +++ b/lib/membrane/core/child/pad_model.ex @@ -53,6 +53,7 @@ defmodule Membrane.Core.Child.PadModel do required(:availability) => Pad.availability(), required(:direction) => Pad.direction(), required(:name) => Pad.name(), + required(:accepted_formats_str) => String.t(), optional(:flow_control) => Pad.flow_control(), optional(:demand_unit) => Membrane.Buffer.Metric.unit(), optional(:other_demand_unit) => Membrane.Buffer.Metric.unit() diff --git a/lib/membrane/core/element/action_handler.ex b/lib/membrane/core/element/action_handler.ex index 9eabb4764..ef5e1f266 100644 --- a/lib/membrane/core/element/action_handler.ex +++ b/lib/membrane/core/element/action_handler.ex @@ -362,7 +362,8 @@ defmodule Membrane.Core.Element.ActionHandler do StreamFormatController.validate_stream_format!( :output, stream_format_validation_params, - stream_format + stream_format, + state ) state = PadModel.set_data!(state, pad_ref, :stream_format, stream_format) diff --git a/lib/membrane/core/element/stream_format_controller.ex b/lib/membrane/core/element/stream_format_controller.ex index 53c9c08ec..e96b2f1a5 100644 --- a/lib/membrane/core/element/stream_format_controller.ex +++ b/lib/membrane/core/element/stream_format_controller.ex @@ -63,7 +63,8 @@ defmodule Membrane.Core.Element.StreamFormatController do validate_stream_format!( :input, [{state.module, pad_name} | stream_format_validation_params], - stream_format + stream_format, + state ) state = @@ -81,9 +82,10 @@ defmodule Membrane.Core.Element.StreamFormatController do @spec validate_stream_format!( Pad.direction(), stream_format_validation_params(), - StreamFormat.t() + StreamFormat.t(), + State.t() ) :: :ok - def validate_stream_format!(direction, params, stream_format) do + def validate_stream_format!(direction, params, stream_format, state) do unless is_struct(stream_format) do raise Membrane.StreamFormatError, """ Stream format must be defined as a struct, therefore it cannot be: #{inspect(stream_format)} @@ -92,8 +94,10 @@ defmodule Membrane.Core.Element.StreamFormatController do for {module, pad_name} <- params do unless module.membrane_stream_format_match?(pad_name, stream_format) do + pattern_string = get_in(state, [:pads_info, pad_name, :accepted_formats_str]) + raise Membrane.StreamFormatError, """ - Stream format: #{inspect(stream_format)} is not matching accepted format pattern in def_#{direction}_pad + Stream format: #{inspect(stream_format)} is not matching accepted format pattern "#{pattern_string}" in def_#{direction}_pad for pad #{inspect(pad_name)} in #{inspect(module)} """ end From 7d5620f552eb56b716655158522b29393808b059 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 21 Apr 2023 13:52:08 +0200 Subject: [PATCH 37/64] Remove info about initiator from handle_link --- lib/membrane/core/bin/pad_controller.ex | 4 +--- lib/membrane/core/element/pad_controller.ex | 21 +++---------------- .../child_life_controller/link_utils.ex | 2 +- .../core/element/pad_controller_test.exs | 3 +-- test/membrane/core/element_test.exs | 3 --- 5 files changed, 6 insertions(+), 27 deletions(-) diff --git a/lib/membrane/core/bin/pad_controller.ex b/lib/membrane/core/bin/pad_controller.ex index 8fc09737f..00e8c5b09 100644 --- a/lib/membrane/core/bin/pad_controller.ex +++ b/lib/membrane/core/bin/pad_controller.ex @@ -196,12 +196,10 @@ defmodule Membrane.Core.Bin.PadController do SpecificationParser.raw_endpoint(), SpecificationParser.raw_endpoint(), %{ - initiator: :parent, stream_format_validation_params: StreamFormatController.stream_format_validation_params() } | %{ - initiator: :sibling, other_info: PadModel.pad_info() | nil, link_metadata: map, stream_format_validation_params: @@ -234,7 +232,7 @@ defmodule Membrane.Core.Bin.PadController do child_endpoint = %{child_endpoint | pad_props: pad_props} - if params.initiator == :sibling do + if direction == :input do :ok = Child.PadController.validate_pad_mode!( {endpoint.pad_ref, pad_data}, diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index c952a9ff2..ae626ada3 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -29,12 +29,10 @@ defmodule Membrane.Core.Element.PadController do @type link_call_props :: %{ - initiator: :parent, stream_format_validation_params: StreamFormatController.stream_format_validation_params() } | %{ - initiator: :sibling, other_info: PadModel.pad_info() | nil, link_metadata: %{}, stream_format_validation_params: @@ -78,16 +76,10 @@ defmodule Membrane.Core.Element.PadController do :ok = Child.PadController.validate_pad_being_linked!(direction, info) - do_handle_link(endpoint, other_endpoint, info, link_props, state) + do_handle_link(direction, endpoint, other_endpoint, info, link_props, state) end - defp do_handle_link( - endpoint, - other_endpoint, - info, - %{initiator: :parent} = props, - state - ) do + defp do_handle_link(:output, endpoint, other_endpoint, info, props, state) do effective_flow_control = EffectiveFlowController.get_pad_effective_flow_control(endpoint.pad_ref, state) @@ -97,7 +89,6 @@ defmodule Membrane.Core.Element.PadController do other_endpoint, endpoint, %{ - initiator: :sibling, other_info: info, link_metadata: %{ observability_metadata: Observability.setup_link(endpoint.pad_ref) @@ -146,13 +137,7 @@ defmodule Membrane.Core.Element.PadController do end end - defp do_handle_link( - endpoint, - other_endpoint, - info, - %{initiator: :sibling} = link_props, - state - ) do + defp do_handle_link(:input, endpoint, other_endpoint, info, link_props, state) do %{ other_info: other_info, link_metadata: link_metadata, diff --git a/lib/membrane/core/parent/child_life_controller/link_utils.ex b/lib/membrane/core/parent/child_life_controller/link_utils.ex index 4d4855557..98c704c0f 100644 --- a/lib/membrane/core/parent/child_life_controller/link_utils.ex +++ b/lib/membrane/core/parent/child_life_controller/link_utils.ex @@ -277,7 +277,7 @@ defmodule Membrane.Core.Parent.ChildLifeController.LinkUtils do if {Membrane.Bin, :itself} in [from.child, to.child] do state else - params = %{initiator: :parent, stream_format_validation_params: []} + params = %{stream_format_validation_params: []} case Message.call(from.pid, :handle_link, [:output, from, to, params]) do :ok -> diff --git a/test/membrane/core/element/pad_controller_test.exs b/test/membrane/core/element/pad_controller_test.exs index 40099ad19..94c827752 100644 --- a/test/membrane/core/element/pad_controller_test.exs +++ b/test/membrane/core/element/pad_controller_test.exs @@ -44,7 +44,6 @@ defmodule Membrane.Core.Element.PadControllerTest do pad_props: %{options: [], toilet_capacity: nil, throttling_factor: nil} }, %{ - initiator: :sibling, other_info: %{direction: :output, flow_control: :manual, demand_unit: :buffers}, link_metadata: %{toilet: make_ref(), observability_metadata: %{}}, stream_format_validation_params: [], @@ -65,7 +64,7 @@ defmodule Membrane.Core.Element.PadControllerTest do :output, %{pad_ref: :invalid_pad_ref, child: :a}, %{pad_ref: :x, child: :b}, - %{link_initiator: :parent, stream_format_validation_params: []}, + %{stream_format_validation_params: []}, state ) end diff --git a/test/membrane/core/element_test.exs b/test/membrane/core/element_test.exs index 00ffc0edf..f297f9e3b 100644 --- a/test/membrane/core/element_test.exs +++ b/test/membrane/core/element_test.exs @@ -132,7 +132,6 @@ defmodule Membrane.Core.ElementTest do %Endpoint{pad_spec: :output, pad_ref: :output, pad_props: %{options: []}, child: :this}, output_other_endpoint, %{ - initiator: :parent, other_info: other_info, link_metadata: %{demand_counter: output_demand_counter, observability_metadata: %{}}, stream_format_validation_params: [], @@ -163,7 +162,6 @@ defmodule Membrane.Core.ElementTest do }, %Endpoint{pad_spec: :output, pad_ref: :output, pid: self(), child: :other}, %{ - initiator: :sibling, other_info: %{direction: :output, flow_control: :manual}, link_metadata: %{toilet: nil, observability_metadata: %{}}, stream_format_validation_params: [], @@ -280,7 +278,6 @@ defmodule Membrane.Core.ElementTest do pad_props: %{options: [], toilet_capacity: nil, throttling_factor: nil} }, %{ - initiator: :sibling, other_info: %{ direction: :input, demand_unit: :buffers, From 3476724ff1c1e28b278bf976ddc6232648c69455 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 21 Apr 2023 14:58:28 +0200 Subject: [PATCH 38/64] Rename PadData.demand to PadData.demand_snapshot --- lib/membrane/core/child/pad_model.ex | 2 +- .../core/element/demand_controller.ex | 30 +++++++++++-------- lib/membrane/core/element/demand_handler.ex | 16 +++++----- lib/membrane/core/element/pad_controller.ex | 6 ++-- .../core/filter_aggregator/context.ex | 2 +- lib/membrane/element/pad_data.ex | 4 +-- .../core/element/action_handler_test.exs | 14 ++++----- test/membrane/core/element/pad_model_test.exs | 22 +++++++------- test/membrane/core/element_test.exs | 2 +- test/membrane/filter_aggregator/unit_test.exs | 4 +-- test/support/bin/test_bins.ex | 2 +- 11 files changed, 56 insertions(+), 48 deletions(-) diff --git a/lib/membrane/core/child/pad_model.ex b/lib/membrane/core/child/pad_model.ex index aa2a5d288..e03c69f87 100644 --- a/lib/membrane/core/child/pad_model.ex +++ b/lib/membrane/core/child/pad_model.ex @@ -25,7 +25,7 @@ defmodule Membrane.Core.Child.PadModel do @type element_pad_data :: %Membrane.Element.PadData{ availability: Pad.availability(), stream_format: Membrane.StreamFormat.t() | nil, - demand: integer() | nil, + demand_snapshot: integer() | nil, start_of_stream?: boolean(), end_of_stream?: boolean(), direction: Pad.direction(), diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 1683b591d..4f52bebe1 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -65,16 +65,22 @@ defmodule Membrane.Core.Element.DemandController do end defp do_check_demand_counter(%{flow_control: :manual} = pad_data, state) do - with %{demand: demand, demand_counter: demand_counter} when demand <= 0 <- pad_data, - counter_value when counter_value > 0 and counter_value > demand <- + with %{demand_snapshot: demand_snapshot, demand_counter: demand_counter} + when demand_snapshot <= 0 <- pad_data, + demand_counter_value + when demand_counter_value > 0 and demand_counter_value > demand_snapshot <- DemandCounter.get(demand_counter) do - # pole demand powinno brac uwage konwersję demand unitu + # pole demand_snapshot powinno brac uwage konwersję demand unitu state = PadModel.update_data!( state, pad_data.ref, - &%{&1 | demand: counter_value, incoming_demand: counter_value - &1.demand} + &%{ + &1 + | demand_snapshot: demand_counter_value, + incoming_demand: demand_counter_value - &1.demand_snapshot + } ) DemandHandler.handle_redemand(pad_data.ref, state) @@ -113,13 +119,13 @@ defmodule Membrane.Core.Element.DemandController do pad_data = PadModel.get_data!(state, pad_ref) buffers_size = Buffer.Metric.from_unit(pad_data.other_demand_unit).buffers_size(buffers) - demand = pad_data.demand - buffers_size + demand_snapshot = pad_data.demand_snapshot - buffers_size demand_counter = DemandCounter.decrease(pad_data.demand_counter, buffers_size) PadModel.set_data!( state, pad_ref, - %{pad_data | demand: demand, demand_counter: demand_counter} + %{pad_data | demand_snapshot: demand_snapshot, demand_counter: demand_counter} ) end @@ -144,7 +150,7 @@ defmodule Membrane.Core.Element.DemandController do split_continuation_arbiter: &exec_handle_demand?(PadModel.get_data!(&1, pad_data.ref)), context: context }, - [pad_data.ref, pad_data.demand, pad_data.demand_unit], + [pad_data.ref, pad_data.demand_snapshot, pad_data.demand_unit], state ) end @@ -163,11 +169,11 @@ defmodule Membrane.Core.Element.DemandController do end defp demand_counter_positive?(pad_ref, state) do - counter_value = + demand_counter_value = PadModel.get_data!(state, pad_ref, :demand_counter) |> DemandCounter.get() - counter_value > 0 + demand_counter_value > 0 end defp exec_handle_demand?(%{end_of_stream?: true}) do @@ -178,10 +184,10 @@ defmodule Membrane.Core.Element.DemandController do false end - defp exec_handle_demand?(%{demand: demand}) when demand <= 0 do + defp exec_handle_demand?(%{demand_snapshot: demand_snapshot}) when demand_snapshot <= 0 do Membrane.Logger.debug_verbose(""" - Demand controller: not executing handle_demand as demand is not greater than 0, - demand: #{inspect(demand)} + Demand controller: not executing handle_demand as demand_snapshot is not greater than 0, + demand_snapshot: #{inspect(demand_snapshot)} """) false diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 78c92f988..5d5cc38c6 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -85,7 +85,7 @@ defmodule Membrane.Core.Element.DemandHandler do pad_data = state |> PadModel.get_data!(pad_ref) {{_queue_status, popped_data}, new_input_queue} = - InputQueue.take(pad_data.input_queue, pad_data.demand) + InputQueue.take(pad_data.input_queue, pad_data.demand_snapshot) state = PadModel.set_data!(state, pad_ref, :input_queue, new_input_queue) state = handle_input_queue_output(pad_ref, popped_data, state) @@ -93,19 +93,19 @@ defmodule Membrane.Core.Element.DemandHandler do end defp update_demand(pad_ref, size, state) when is_integer(size) do - PadModel.set_data!(state, pad_ref, :demand, size) + PadModel.set_data!(state, pad_ref, :demand_snapshot, size) end defp update_demand(pad_ref, size_fun, state) when is_function(size_fun) do - demand = PadModel.get_data!(state, pad_ref, :demand) - new_demand = size_fun.(demand) + demand_snapshot = PadModel.get_data!(state, pad_ref, :demand_snapshot) + new_demand_snapshot = size_fun.(demand_snapshot) - if new_demand < 0 do + if new_demand_snapshot < 0 do raise Membrane.ElementError, "Demand altering function requested negative demand on pad #{inspect(pad_ref)} in #{state.module}" end - PadModel.set_data!(state, pad_ref, :demand, new_demand) + PadModel.set_data!(state, pad_ref, :demand_snapshot, new_demand_snapshot) end @spec handle_delayed_demands(State.t()) :: State.t() @@ -168,7 +168,9 @@ defmodule Membrane.Core.Element.DemandHandler do {:buffers, buffers, _inbound_metric_buf_size, outbound_metric_buf_size}, state ) do - state = PadModel.update_data!(state, pad_ref, :demand, &(&1 - outbound_metric_buf_size)) + state = + PadModel.update_data!(state, pad_ref, :demand_snapshot, &(&1 - outbound_metric_buf_size)) + BufferController.exec_buffer_callback(pad_ref, buffers, state) end end diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index ae626ada3..58ff47d1a 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -347,7 +347,7 @@ defmodule Membrane.Core.Element.PadController do target_size: props.target_queue_size }) - %{input_queue: input_queue, demand: 0} + %{input_queue: input_queue, demand_snapshot: 0} end defp init_pad_mode_data( @@ -357,7 +357,7 @@ defmodule Membrane.Core.Element.PadController do _metadata, _state ) do - %{demand: 0} + %{demand_snapshot: 0} end defp init_pad_mode_data( @@ -383,7 +383,7 @@ defmodule Membrane.Core.Element.PadController do end %{ - demand: 0, + demand_snapshot: 0, associated_pads: associated_pads, auto_demand_size: auto_demand_size } diff --git a/lib/membrane/core/filter_aggregator/context.ex b/lib/membrane/core/filter_aggregator/context.ex index e1e42463f..21ce6be3c 100644 --- a/lib/membrane/core/filter_aggregator/context.ex +++ b/lib/membrane/core/filter_aggregator/context.ex @@ -64,7 +64,7 @@ defmodule Membrane.Core.FilterAggregator.Context do |> Map.delete(:accepted_formats_str) |> Map.merge(%{ stream_format: nil, - demand: nil, + demand_snapshot: nil, start_of_stream?: false, end_of_stream?: false, ref: pad_description.name, diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 5e546d6b2..240d5ce8f 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -38,7 +38,7 @@ defmodule Membrane.Element.PadData do pid: private_field, other_ref: private_field, input_queue: private_field, - demand: integer() | nil, + demand_snapshot: integer() | nil, incoming_demand: integer() | nil, demand_unit: private_field, other_demand_unit: private_field, @@ -66,7 +66,7 @@ defmodule Membrane.Element.PadData do defstruct @enforce_keys ++ [ input_queue: nil, - demand: 0, + demand_snapshot: 0, incoming_demand: nil, demand_unit: nil, start_of_stream?: false, diff --git a/test/membrane/core/element/action_handler_test.exs b/test/membrane/core/element/action_handler_test.exs index 20f0d53dd..e160ffb87 100644 --- a/test/membrane/core/element/action_handler_test.exs +++ b/test/membrane/core/element/action_handler_test.exs @@ -30,7 +30,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do direction: :input, pid: self(), flow_control: :manual, - demand: 0 + demand_snapshot: 0 ), input_push: struct(Membrane.Element.PadData, @@ -54,7 +54,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do test "delaying demand", %{state: state} do state = %{state | playback: :playing, supplying_demand?: true} state = @module.handle_action({:demand, {:input, 10}}, :handle_info, %{}, state) - assert state.pads_data.input.demand == 10 + assert state.pads_data.input.demand_snapshot == 10 assert MapSet.new([{:input, :supply}]) == state.delayed_demands end @@ -97,7 +97,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do end_of_stream?: false, flow_control: :push, demand_counter: output_demand_counter, - demand: 0 + demand_snapshot: 0 }, input: %{ direction: :input, @@ -108,7 +108,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do end_of_stream?: false, flow_control: :push, demand_counter: input_demand_counter, - demand: 0 + demand_snapshot: 0 } }, pads_info: %{ @@ -161,9 +161,9 @@ defmodule Membrane.Core.Element.ActionHandlerTest do state ) - assert result.pads_data.output.demand < 0 + assert result.pads_data.output.demand_snapshot < 0 assert DemandCounter.get(result.pads_data.output.demand_counter) < 0 - assert put_in(result, [:pads_data, :output, :demand], 0) == state + assert put_in(result, [:pads_data, :output, :demand_snapshot], 0) == state assert_received Message.new(:buffer, [@mock_buffer], for_pad: :other_ref) end @@ -481,7 +481,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do direction: :output, pid: self(), flow_control: :manual, - demand: 0 + demand_snapshot: 0 } }, pads_info: %{ diff --git a/test/membrane/core/element/pad_model_test.exs b/test/membrane/core/element/pad_model_test.exs index 6c63df615..cf747500f 100644 --- a/test/membrane/core/element/pad_model_test.exs +++ b/test/membrane/core/element/pad_model_test.exs @@ -9,7 +9,7 @@ defmodule Membrane.Core.Child.PadModelTest do defp setup_element_state(_ctx) do state = %Membrane.Core.Element.State{ - pads_data: %{:input => struct(Membrane.Element.PadData, demand: 1)}, + pads_data: %{:input => struct(Membrane.Element.PadData, demand_snapshot: 1)}, pads_info: %{} } @@ -32,11 +32,11 @@ defmodule Membrane.Core.Child.PadModelTest do setup :setup_element_state test "is {:ok, value} when the pad is present", ctx do - assert {:ok, 1} = PadModel.get_data(ctx.state, :input, :demand) + assert {:ok, 1} = PadModel.get_data(ctx.state, :input, :demand_snapshot) end test "is :unknown_pad when the pad is not present", ctx do - assert PadModel.get_data(ctx.state, :output, :demand) == + assert PadModel.get_data(ctx.state, :output, :demand_snapshot) == {:error, :unknown_pad} end end @@ -45,12 +45,12 @@ defmodule Membrane.Core.Child.PadModelTest do setup :setup_element_state test "is value when the pad is present", ctx do - assert 1 = PadModel.get_data!(ctx.state, :input, :demand) + assert 1 = PadModel.get_data!(ctx.state, :input, :demand_snapshot) end test "is :unknown_pad when the pad is not present", ctx do assert_raise @unknown_pad_error_module, fn -> - PadModel.get_data!(ctx.state, :output, :demand) + PadModel.get_data!(ctx.state, :output, :demand_snapshot) end end end @@ -90,12 +90,12 @@ defmodule Membrane.Core.Child.PadModelTest do setup :setup_element_state test "updates the pad data with the given function when present", ctx do - assert PadModel.update_data(ctx.state, :input, :demand, &{:ok, &1 + 5}) == - {:ok, put_in(ctx.state, [:pads_data, :input, :demand], 6)} + assert PadModel.update_data(ctx.state, :input, :demand_snapshot, &{:ok, &1 + 5}) == + {:ok, put_in(ctx.state, [:pads_data, :input, :demand_snapshot], 6)} end test "is :unknown_pad and original state when the pad is not present", ctx do - assert PadModel.update_data(ctx.state, :output, :demand, &{:ok, &1 + 1}) == + assert PadModel.update_data(ctx.state, :output, :demand_snapshot, &{:ok, &1 + 1}) == {{:error, :unknown_pad}, ctx.state} end end @@ -104,13 +104,13 @@ defmodule Membrane.Core.Child.PadModelTest do setup :setup_element_state test "updates the pad data with the given function when present", ctx do - assert PadModel.update_data!(ctx.state, :input, :demand, &(&1 + 5)) == - put_in(ctx.state, [:pads_data, :input, :demand], 6) + assert PadModel.update_data!(ctx.state, :input, :demand_snapshot, &(&1 + 5)) == + put_in(ctx.state, [:pads_data, :input, :demand_snapshot], 6) end test "raises when the pad is not present", ctx do assert_raise @unknown_pad_error_module, fn -> - PadModel.update_data!(ctx.state, :other_input, :demand, &(&1 + 5)) + PadModel.update_data!(ctx.state, :other_input, :demand_snapshot, &(&1 + 5)) end end end diff --git a/test/membrane/core/element_test.exs b/test/membrane/core/element_test.exs index f297f9e3b..37ae6da9a 100644 --- a/test/membrane/core/element_test.exs +++ b/test/membrane/core/element_test.exs @@ -226,7 +226,7 @@ defmodule Membrane.Core.ElementTest do msg = Message.new(:demand_counter_increased, :output) assert {:noreply, state} = Element.handle_info(msg, state) - assert state.pads_data.output.demand == 10 + assert state.pads_data.output.demand_snapshot == 10 end test "should store incoming buffers in dynamic_input buffer" do diff --git a/test/membrane/filter_aggregator/unit_test.exs b/test/membrane/filter_aggregator/unit_test.exs index 32a39488f..e5b42d48b 100644 --- a/test/membrane/filter_aggregator/unit_test.exs +++ b/test/membrane/filter_aggregator/unit_test.exs @@ -45,7 +45,7 @@ defmodule Membrane.FilterAggregator.UnitTest do pad_description_template |> Map.merge(%{ stream_format: nil, - demand: 0, + demand_snapshot: 0, ref: nil, other_ref: nil, other_demand_unit: :buffers, @@ -160,7 +160,7 @@ defmodule Membrane.FilterAggregator.UnitTest do |> Enum.flat_map(& &1.pads) |> Enum.each(fn {pad, pad_data} -> assert pad_data.availability == :always - assert pad_data.demand == nil + assert pad_data.demand_snapshot == nil assert pad_data.direction == pad assert pad_data.start_of_stream? == false assert pad_data.end_of_stream? == false diff --git a/test/support/bin/test_bins.ex b/test/support/bin/test_bins.ex index aa2dbd9f4..0603ff173 100644 --- a/test/support/bin/test_bins.ex +++ b/test/support/bin/test_bins.ex @@ -46,7 +46,7 @@ defmodule Membrane.Support.Bin.TestBins do ctx.pads |> Map.values() |> Enum.filter(&(&1.direction == :output)) - |> Enum.map(& &1.demand) + |> Enum.map(& &1.demand_snapshot) |> Enum.min() demands = From 5a09bfe84a1351dc253a32da9af997a7948db223 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 21 Apr 2023 15:54:35 +0200 Subject: [PATCH 39/64] Rename check_demand_counter to snpashot_demand_counter --- lib/membrane/core/element.ex | 2 +- lib/membrane/core/element/action_handler.ex | 2 +- .../core/element/demand_controller.ex | 20 +++++++++---------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index 279f40da3..85232ef91 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -174,7 +174,7 @@ defmodule Membrane.Core.Element do @compile {:inline, do_handle_info: 2} defp do_handle_info(Message.new(:demand_counter_increased, pad_ref), state) do - state = DemandController.check_demand_counter(pad_ref, state) + state = DemandController.snapshot_demand_counter(pad_ref, state) {:noreply, state} end diff --git a/lib/membrane/core/element/action_handler.ex b/lib/membrane/core/element/action_handler.ex index ef5e1f266..766794d1f 100644 --- a/lib/membrane/core/element/action_handler.ex +++ b/lib/membrane/core/element/action_handler.ex @@ -319,7 +319,7 @@ defmodule Membrane.Core.Element.ActionHandler do |> PadModel.set_data!(pad_ref, :start_of_stream?, true) Message.send(pid, :buffer, buffers, for_pad: other_ref) - DemandController.check_demand_counter(pad_ref, state) + DemandController.snapshot_demand_counter(pad_ref, state) else %{direction: :input} -> raise PadDirectionError, action: :buffer, direction: :input, pad: pad_ref diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 4f52bebe1..0187d7758 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -27,25 +27,25 @@ defmodule Membrane.Core.Element.DemandController do @lacking_buffer_size_lowerbound 200 @lacking_buffer_size_upperbound 400 - @spec check_demand_counter(Pad.ref(), State.t()) :: State.t() - def check_demand_counter(pad_ref, state) do + @spec snapshot_demand_counter(Pad.ref(), State.t()) :: State.t() + def snapshot_demand_counter(pad_ref, state) do with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), %State{playback: :playing} <- state do if pad_data.direction == :input, do: raise("cannot check demand counter in input pad") - do_check_demand_counter(pad_data, state) + do_snapshot_demand_counter(pad_data, state) else {:error, :unknown_pad} -> # We've got a :demand_counter_increased message on already unlinked pad state %State{playback: :stopped} -> - PlaybackQueue.store(&check_demand_counter(pad_ref, &1), state) + PlaybackQueue.store(&snapshot_demand_counter(pad_ref, &1), state) end end - defp do_check_demand_counter( + defp do_snapshot_demand_counter( %{flow_control: :auto} = pad_data, %{effective_flow_control: :pull} = state ) do @@ -54,17 +54,15 @@ defmodule Membrane.Core.Element.DemandController do associated_pads: associated_pads } = pad_data - counter_value = DemandCounter.get(demand_counter) - - if counter_value > 0 do - # TODO: think about optimizing lopp below + if DemandCounter.get(demand_counter) > 0 do + # TODO: think about optimizing loop below Enum.reduce(associated_pads, state, &increase_demand_counter_if_needed/2) else state end end - defp do_check_demand_counter(%{flow_control: :manual} = pad_data, state) do + defp do_snapshot_demand_counter(%{flow_control: :manual} = pad_data, state) do with %{demand_snapshot: demand_snapshot, demand_counter: demand_counter} when demand_snapshot <= 0 <- pad_data, demand_counter_value @@ -89,7 +87,7 @@ defmodule Membrane.Core.Element.DemandController do end end - defp do_check_demand_counter(_pad_data, state) do + defp do_snapshot_demand_counter(_pad_data, state) do state end From 3970934c270e931fc93ac1cb4c54a7dc7345e9ab Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 21 Apr 2023 18:37:25 +0200 Subject: [PATCH 40/64] Refactor DemandController and DemandHandler --- lib/membrane/core/element/action_handler.ex | 2 +- .../core/element/buffer_controller.ex | 4 +- .../core/element/demand_controller.ex | 114 ++---------------- .../demand_controller/auto_flow_utils.ex | 78 ++++++++++++ lib/membrane/core/element/demand_handler.ex | 55 ++++++++- .../core/element/effective_flow_controller.ex | 16 +-- lib/membrane/core/element/pad_controller.ex | 11 +- 7 files changed, 153 insertions(+), 127 deletions(-) create mode 100644 lib/membrane/core/element/demand_controller/auto_flow_utils.ex diff --git a/lib/membrane/core/element/action_handler.ex b/lib/membrane/core/element/action_handler.ex index 766794d1f..6f5e06847 100644 --- a/lib/membrane/core/element/action_handler.ex +++ b/lib/membrane/core/element/action_handler.ex @@ -315,7 +315,7 @@ defmodule Membrane.Core.Element.ActionHandler do } when stream_format != nil <- pad_data do state = - DemandController.handle_outgoing_buffers(pad_ref, buffers, state) + DemandController.decrease_demand_by_outgoing_buffers(pad_ref, buffers, state) |> PadModel.set_data!(pad_ref, :start_of_stream?, true) Message.send(pid, :buffer, buffers, for_pad: other_ref) diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index eb06918fc..2722d2855 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -12,7 +12,6 @@ defmodule Membrane.Core.Element.BufferController do alias Membrane.Core.Element.{ ActionHandler, CallbackContext, - DemandController, DemandHandler, EventController, InputQueue, @@ -20,6 +19,7 @@ defmodule Membrane.Core.Element.BufferController do State } + alias Membrane.Core.Element.DemandController.AutoFlowUtils alias Membrane.Core.Telemetry require Membrane.Core.Child.PadModel @@ -63,7 +63,7 @@ defmodule Membrane.Core.Element.BufferController do state = PadModel.set_data!(state, pad_ref, :lacking_buffer_size, lacking_buffer_size - buf_size) - state = DemandController.increase_demand_counter_if_needed(pad_ref, state) + state = AutoFlowUtils.increase_demand_counter_if_needed(pad_ref, state) exec_buffer_callback(pad_ref, buffers, state) end diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 0187d7758..5db1bec3a 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -1,32 +1,27 @@ defmodule Membrane.Core.Element.DemandController do @moduledoc false - # Module handling demands incoming through output pads. + # Module handling changes in values of output pads demand counters use Bunch + alias __MODULE__.AutoFlowUtils + alias Membrane.Buffer - alias Membrane.Core.CallbackHandler alias Membrane.Core.Child.PadModel alias Membrane.Core.Element.{ - ActionHandler, - CallbackContext, DemandCounter, DemandHandler, PlaybackQueue, State } - alias Membrane.Element.PadData alias Membrane.Pad require Membrane.Core.Child.PadModel require Membrane.Logger - @lacking_buffer_size_lowerbound 200 - @lacking_buffer_size_upperbound 400 - @spec snapshot_demand_counter(Pad.ref(), State.t()) :: State.t() def snapshot_demand_counter(pad_ref, state) do with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), @@ -55,8 +50,7 @@ defmodule Membrane.Core.Element.DemandController do } = pad_data if DemandCounter.get(demand_counter) > 0 do - # TODO: think about optimizing loop below - Enum.reduce(associated_pads, state, &increase_demand_counter_if_needed/2) + AutoFlowUtils.increase_demand_counter_if_needed(associated_pads, state) else state end @@ -91,107 +85,21 @@ defmodule Membrane.Core.Element.DemandController do state end - @spec increase_demand_counter_if_needed(Pad.ref(), State.t()) :: State.t() - def increase_demand_counter_if_needed(pad_ref, state) do - pad_data = PadModel.get_data!(state, pad_ref) - - if increase_demand_counter?(pad_data, state) do - diff = @lacking_buffer_size_upperbound - pad_data.lacking_buffer_size - :ok = DemandCounter.increase(pad_data.demand_counter, diff) - - PadModel.set_data!(state, pad_ref, :lacking_buffer_size, @lacking_buffer_size_upperbound) - else - state - end - end - @doc """ Decreases demand snapshot and demand counter on the output by the size of outgoing buffers. """ - @spec handle_outgoing_buffers( - Pad.ref(), - [Buffer.t()], - State.t() - ) :: State.t() - def handle_outgoing_buffers(pad_ref, buffers, state) do + @spec decrease_demand_by_outgoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: State.t() + def decrease_demand_by_outgoing_buffers(pad_ref, buffers, state) do pad_data = PadModel.get_data!(state, pad_ref) buffers_size = Buffer.Metric.from_unit(pad_data.other_demand_unit).buffers_size(buffers) demand_snapshot = pad_data.demand_snapshot - buffers_size demand_counter = DemandCounter.decrease(pad_data.demand_counter, buffers_size) - PadModel.set_data!( - state, - pad_ref, - %{pad_data | demand_snapshot: demand_snapshot, demand_counter: demand_counter} - ) - end - - @spec exec_handle_demand(Pad.ref(), State.t()) :: State.t() - def exec_handle_demand(pad_ref, state) do - with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), - true <- exec_handle_demand?(pad_data) do - do_exec_handle_demand(pad_data, state) - else - _other -> state - end - end - - @spec do_exec_handle_demand(PadData.t(), State.t()) :: State.t() - defp do_exec_handle_demand(pad_data, state) do - context = &CallbackContext.from_state(&1, incoming_demand: pad_data.incoming_demand) - - CallbackHandler.exec_and_handle_callback( - :handle_demand, - ActionHandler, - %{ - split_continuation_arbiter: &exec_handle_demand?(PadModel.get_data!(&1, pad_data.ref)), - context: context - }, - [pad_data.ref, pad_data.demand_snapshot, pad_data.demand_unit], - state - ) - end - - defp increase_demand_counter?(pad_data, state) do - %{ - flow_control: flow_control, - lacking_buffer_size: lacking_buffer_size, - associated_pads: associated_pads - } = pad_data - - flow_control == :auto and - state.effective_flow_control == :pull and - lacking_buffer_size < @lacking_buffer_size_lowerbound and - Enum.all?(associated_pads, &demand_counter_positive?(&1, state)) - end - - defp demand_counter_positive?(pad_ref, state) do - demand_counter_value = - PadModel.get_data!(state, pad_ref, :demand_counter) - |> DemandCounter.get() - - demand_counter_value > 0 - end - - defp exec_handle_demand?(%{end_of_stream?: true}) do - Membrane.Logger.debug_verbose(""" - Demand controller: not executing handle_demand as :end_of_stream action has already been returned - """) - - false - end - - defp exec_handle_demand?(%{demand_snapshot: demand_snapshot}) when demand_snapshot <= 0 do - Membrane.Logger.debug_verbose(""" - Demand controller: not executing handle_demand as demand_snapshot is not greater than 0, - demand_snapshot: #{inspect(demand_snapshot)} - """) - - false - end - - defp exec_handle_demand?(_pad_data) do - true + PadModel.set_data!(state, pad_ref, %{ + pad_data + | demand_snapshot: demand_snapshot, + demand_counter: demand_counter + }) end end diff --git a/lib/membrane/core/element/demand_controller/auto_flow_utils.ex b/lib/membrane/core/element/demand_controller/auto_flow_utils.ex new file mode 100644 index 000000000..3ac190ff0 --- /dev/null +++ b/lib/membrane/core/element/demand_controller/auto_flow_utils.ex @@ -0,0 +1,78 @@ +defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do + @moduledoc false + + alias Membrane.Core.Element.{ + DemandCounter, + State + } + + require Membrane.Core.Child.PadModel, as: PadModel + require Membrane.Pad, as: Pad + + @lacking_buffer_size_lowerbound 200 + @lacking_buffer_size_upperbound 400 + + defguardp is_input_auto_pad_data(pad_data) + when is_map(pad_data) and is_map_key(pad_data, :flow_control) and + pad_data.flow_control == :auto and is_map_key(pad_data, :direction) and + pad_data.direction == :input + + @spec increase_demand_counter_if_needed(Pad.ref() | [Pad.ref()], State.t()) :: State.t() + def increase_demand_counter_if_needed(pad_ref_list, state) when is_list(pad_ref_list) do + Enum.reduce(pad_ref_list, state, fn pad_ref, state -> + case PadModel.get_data(state, pad_ref) do + {:ok, pad_data} when is_input_auto_pad_data(pad_data) -> + do_increase_demand_counter_if_needed(pad_data, state) + + _other -> + state + end + end) + end + + def increase_demand_counter_if_needed(pad_ref, state) when Pad.is_pad_ref(pad_ref) do + with {:ok, pad_data} when is_input_auto_pad_data(pad_data) <- + PadModel.get_data(state, pad_ref) do + do_increase_demand_counter_if_needed(pad_data, state) + else + _other -> state + end + end + + defp do_increase_demand_counter_if_needed(pad_data, state) do + if increase_demand_counter?(pad_data, state) do + diff = @lacking_buffer_size_upperbound - pad_data.lacking_buffer_size + :ok = DemandCounter.increase(pad_data.demand_counter, diff) + + PadModel.set_data!( + state, + pad_data.ref, + :lacking_buffer_size, + @lacking_buffer_size_upperbound + ) + else + state + end + end + + defp increase_demand_counter?(pad_data, state) do + %{ + flow_control: flow_control, + lacking_buffer_size: lacking_buffer_size, + associated_pads: associated_pads + } = pad_data + + flow_control == :auto and + state.effective_flow_control == :pull and + lacking_buffer_size < @lacking_buffer_size_lowerbound and + Enum.all?(associated_pads, &demand_counter_positive?(&1, state)) + end + + defp demand_counter_positive?(pad_ref, state) do + demand_counter_value = + PadModel.get_data!(state, pad_ref, :demand_counter) + |> DemandCounter.get() + + demand_counter_value > 0 + end +end diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 5d5cc38c6..6c739cb65 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -3,9 +3,12 @@ defmodule Membrane.Core.Element.DemandHandler do # Module handling demands requested on output pads. + alias Membrane.Core.CallbackHandler + alias Membrane.Core.Element.{ + ActionHandler, BufferController, - DemandController, + CallbackContext, EventController, InputQueue, State, @@ -16,6 +19,7 @@ defmodule Membrane.Core.Element.DemandHandler do require Membrane.Core.Child.PadModel, as: PadModel require Membrane.Core.Message, as: Message + require Membrane.Logger @handle_demand_loop_limit 20 @@ -38,7 +42,7 @@ defmodule Membrane.Core.Element.DemandHandler do defp do_handle_redemand(pad_ref, state) do state = %{state | supplying_demand?: true} - state = DemandController.exec_handle_demand(pad_ref, state) + state = exec_handle_demand(pad_ref, state) %{state | supplying_demand?: false} end @@ -173,4 +177,51 @@ defmodule Membrane.Core.Element.DemandHandler do BufferController.exec_buffer_callback(pad_ref, buffers, state) end + + @spec exec_handle_demand(Pad.ref(), State.t()) :: State.t() + defp exec_handle_demand(pad_ref, state) do + with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), + true <- exec_handle_demand?(pad_data) do + do_exec_handle_demand(pad_data, state) + else + _other -> state + end + end + + @spec do_exec_handle_demand(PadData.t(), State.t()) :: State.t() + defp do_exec_handle_demand(pad_data, state) do + context = &CallbackContext.from_state(&1, incoming_demand: pad_data.incoming_demand) + + CallbackHandler.exec_and_handle_callback( + :handle_demand, + ActionHandler, + %{ + split_continuation_arbiter: &exec_handle_demand?(PadModel.get_data!(&1, pad_data.ref)), + context: context + }, + [pad_data.ref, pad_data.demand_snapshot, pad_data.demand_unit], + state + ) + end + + defp exec_handle_demand?(%{end_of_stream?: true}) do + Membrane.Logger.debug_verbose(""" + Demand controller: not executing handle_demand as :end_of_stream action has already been returned + """) + + false + end + + defp exec_handle_demand?(%{demand_snapshot: demand_snapshot}) when demand_snapshot <= 0 do + Membrane.Logger.debug_verbose(""" + Demand controller: not executing handle_demand as demand_snapshot is not greater than 0, + demand_snapshot: #{inspect(demand_snapshot)} + """) + + false + end + + defp exec_handle_demand?(_pad_data) do + true + end end diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 1a5289db9..b57d38993 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -1,11 +1,8 @@ defmodule Membrane.Core.Element.EffectiveFlowController do @moduledoc false - alias Membrane.Core.Element.{ - DemandController, - DemandCounter, - State - } + alias Membrane.Core.Element.{DemandCounter, State} + alias Membrane.Core.Element.DemandController.AutoFlowUtils require Membrane.Core.Child.PadModel, as: PadModel require Membrane.Core.Message, as: Message @@ -116,12 +113,7 @@ defmodule Membrane.Core.Element.EffectiveFlowController do :ok end) - Enum.reduce(state.pads_data, state, fn - {pad_ref, %{flow_control: :auto, direction: :input}}, state -> - DemandController.increase_demand_counter_if_needed(pad_ref, state) - - _pad_entry, state -> - state - end) + Map.keys(state.pads_data) + |> AutoFlowUtils.increase_demand_counter_if_needed(state) end end diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 58ff47d1a..1c1f25d80 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -11,7 +11,6 @@ defmodule Membrane.Core.Element.PadController do alias Membrane.Core.Element.{ ActionHandler, CallbackContext, - DemandController, DemandCounter, EffectiveFlowController, EventController, @@ -20,6 +19,8 @@ defmodule Membrane.Core.Element.PadController do StreamFormatController } + alias Membrane.Core.Element.DemandController.AutoFlowUtils + alias Membrane.Core.Parent.Link.Endpoint require Membrane.Core.Child.PadModel @@ -305,7 +306,7 @@ defmodule Membrane.Core.Element.PadController do end) case data.direction do - :input -> DemandController.increase_demand_counter_if_needed(endpoint.pad_ref, state) + :input -> AutoFlowUtils.increase_demand_counter_if_needed(endpoint.pad_ref, state) :output -> state end else @@ -420,11 +421,7 @@ defmodule Membrane.Core.Element.PadController do |> PadModel.set_data!(pad_ref, :associated_pads, []) if pad_data.direction == :output do - Enum.reduce( - pad_data.associated_pads, - state, - &DemandController.increase_demand_counter_if_needed/2 - ) + AutoFlowUtils.increase_demand_counter_if_needed(pad_data.associated_pads, state) else state end From f18195de0d8945cae44daa91cdbd48363a4a25d0 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 24 Apr 2023 14:46:24 +0200 Subject: [PATCH 41/64] Rename overflow_limit in DemandCounter to toilet_capacity --- lib/membrane/children_spec.ex | 2 +- lib/membrane/core/element/demand_counter.ex | 35 ++++++++++--------- lib/membrane/core/element/pad_controller.ex | 2 +- .../core/element/demand_counter_test.exs | 2 +- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/lib/membrane/children_spec.ex b/lib/membrane/children_spec.ex index 0e1afa6d4..d096463bd 100644 --- a/lib/membrane/children_spec.ex +++ b/lib/membrane/children_spec.ex @@ -538,7 +538,7 @@ defmodule Membrane.ChildrenSpec do """ @spec via_in(builder(), Pad.name() | Pad.ref(), options: pad_options(), - toilet_capacity: number | nil, + toilet_capacity: non_neg_integer() | float() | nil, target_queue_size: number | nil, min_demand_factor: number | nil, auto_demand_size: number | nil, diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 1091d7d1b..52ef82ebc 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -11,7 +11,7 @@ defmodule Membrane.Core.Element.DemandCounter do require Membrane.Logger require Membrane.Pad, as: Pad - @default_overflow_limit_factor -200 + @default_toilet_capacity_factor -200 @default_buffered_decrementation_limit 1 @distributed_buffered_decrementation_limit 150 @@ -22,10 +22,11 @@ defmodule Membrane.Core.Element.DemandCounter do sender_mode: DistributedFlowMode.t(), sender_process: Process.dest(), sender_pad_ref: Pad.ref(), - overflow_limit: neg_integer(), + toilet_capacity: neg_integer(), buffered_decrementation: non_neg_integer(), buffered_decrementation_limit: pos_integer(), - toilet_overflowed?: boolean() + toilet_overflowed?: boolean(), + receiver_demand_unit: Membrane.Buffer.Metric.unit() } @type flow_mode :: DistributedFlowMode.flow_mode_value() @@ -38,7 +39,8 @@ defmodule Membrane.Core.Element.DemandCounter do :sender_process, :sender_pad_ref, :buffered_decrementation_limit, - :overflow_limit + :toilet_capacity, + :receiver_demand_unit ] defstruct @enforce_keys ++ [buffered_decrementation: 0, toilet_overflowed?: false] @@ -49,7 +51,7 @@ defmodule Membrane.Core.Element.DemandCounter do receiver_demand_unit :: Membrane.Buffer.Metric.unit(), sender_process :: Process.dest(), sender_pad_ref :: Pad.ref(), - overflow_limit :: neg_integer() | nil + toilet_capacity :: neg_integer() | nil ) :: t def new( receiver_mode, @@ -57,15 +59,14 @@ defmodule Membrane.Core.Element.DemandCounter do receiver_demand_unit, sender_process, sender_pad_ref, - overflow_limit \\ nil + toilet_capacity \\ nil ) do %DistributedAtomic{worker: worker} = counter = DistributedAtomic.new() buffered_decrementation_limit = - if node(sender_process) == - node(worker), - do: @default_buffered_decrementation_limit, - else: @distributed_buffered_decrementation_limit + if node(sender_process) == node(worker), + do: @default_buffered_decrementation_limit, + else: @distributed_buffered_decrementation_limit %__MODULE__{ counter: counter, @@ -74,8 +75,9 @@ defmodule Membrane.Core.Element.DemandCounter do sender_mode: DistributedFlowMode.new(:to_be_resolved), sender_process: sender_process, sender_pad_ref: sender_pad_ref, - overflow_limit: overflow_limit || default_overflow_limit(receiver_demand_unit), - buffered_decrementation_limit: buffered_decrementation_limit + toilet_capacity: toilet_capacity || default_toilet_capacity(receiver_demand_unit), + buffered_decrementation_limit: buffered_decrementation_limit, + receiver_demand_unit: receiver_demand_unit } end @@ -149,7 +151,7 @@ defmodule Membrane.Core.Element.DemandCounter do if not demand_counter.toilet_overflowed? and get_receiver_mode(demand_counter) == :pull and get_sender_mode(demand_counter) == :push and - counter_value < demand_counter.overflow_limit do + counter_value < demand_counter.toilet_capacity do overflow(demand_counter, counter_value) else demand_counter @@ -186,7 +188,8 @@ defmodule Membrane.Core.Element.DemandCounter do Membrane.Logger.error(""" Toilet overflow. - Reached the size of #{inspect(counter_value)}, which is below overflow limit (#{inspect(demand_counter.overflow_limit)}) + Demand counter reached the size of #{inspect(counter_value)}, which means that there are #{inspect(-1 * counter_value)} + #{demand_counter.receiver_demand_unit} sent without demanding it, which is above toilet capacity (#{inspect(demand_counter.toilet_capacity)}) when storing data from output working in push mode. It means that some element in the pipeline processes the stream too slow or doesn't process it at all. To have control over amount of buffers being produced, consider using output in :auto or :manual @@ -199,8 +202,8 @@ defmodule Membrane.Core.Element.DemandCounter do %{demand_counter | toilet_overflowed?: true} end - defp default_overflow_limit(demand_unit) do + defp default_toilet_capacity(demand_unit) do Membrane.Buffer.Metric.from_unit(demand_unit).buffer_size_approximation() * - @default_overflow_limit_factor + @default_toilet_capacity_factor end end diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 1c1f25d80..df09fd784 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -164,7 +164,7 @@ defmodule Membrane.Core.Element.PadController do input_demand_unit || :buffers, other_endpoint.pid, other_endpoint.pad_ref, - -300 + endpoint.pad_props[:toilet_capacity] ) # The sibiling was an initiator, we don't need to use the pid of a task spawned for observability diff --git a/test/membrane/core/element/demand_counter_test.exs b/test/membrane/core/element/demand_counter_test.exs index 096841106..d2620f7b1 100644 --- a/test/membrane/core/element/demand_counter_test.exs +++ b/test/membrane/core/element/demand_counter_test.exs @@ -16,7 +16,7 @@ defmodule Membrane.Core.Element.DemandCounterTest do assert DemandCounter.get(demand_counter) == -5 end - test "if the receiving element uses DemandCounter with :atomics and the sending element with a interprocess message, when the DemandCounter is distributed" do + test "if DemandCounter.Worker works properly " do demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :output) :ok = DemandCounter.increase(demand_counter, 10) From 1eef4555a5c5d75360655d4e9e965150fae48ae7 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 25 Apr 2023 15:26:17 +0200 Subject: [PATCH 42/64] Sort aliases --- lib/membrane/core/element/effective_flow_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index b57d38993..c96a3deae 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -1,8 +1,8 @@ defmodule Membrane.Core.Element.EffectiveFlowController do @moduledoc false - alias Membrane.Core.Element.{DemandCounter, State} alias Membrane.Core.Element.DemandController.AutoFlowUtils + alias Membrane.Core.Element.{DemandCounter, State} require Membrane.Core.Child.PadModel, as: PadModel require Membrane.Core.Message, as: Message From ca7fef74bd79291d5d3341fc8fdde8e4ea8d4b8e Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 25 Apr 2023 16:15:07 +0200 Subject: [PATCH 43/64] Add PadData fields description --- lib/membrane/children_spec.ex | 2 +- lib/membrane/core/element/demand_counter.ex | 28 +++++++++++-------- lib/membrane/core/element/pad_controller.ex | 3 +- lib/membrane/element/pad_data.ex | 8 +++++- .../core/element/demand_counter_test.exs | 2 +- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/lib/membrane/children_spec.ex b/lib/membrane/children_spec.ex index d096463bd..6d1e250eb 100644 --- a/lib/membrane/children_spec.ex +++ b/lib/membrane/children_spec.ex @@ -567,7 +567,7 @@ defmodule Membrane.ChildrenSpec do min_demand_factor: [default: nil], auto_demand_size: [default: nil], toilet_capacity: [default: nil], - throttling_factor: [default: 1] + throttling_factor: [default: nil] ) |> case do {:ok, props} -> diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 52ef82ebc..6aa6f2d40 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -12,8 +12,8 @@ defmodule Membrane.Core.Element.DemandCounter do require Membrane.Pad, as: Pad @default_toilet_capacity_factor -200 - @default_buffered_decrementation_limit 1 - @distributed_buffered_decrementation_limit 150 + @default_throttling_factor 1 + @distributed_default_throttling_factor 150 @opaque t :: %__MODULE__{ counter: DistributedAtomic.t(), @@ -24,7 +24,7 @@ defmodule Membrane.Core.Element.DemandCounter do sender_pad_ref: Pad.ref(), toilet_capacity: neg_integer(), buffered_decrementation: non_neg_integer(), - buffered_decrementation_limit: pos_integer(), + throttling_factor: pos_integer(), toilet_overflowed?: boolean(), receiver_demand_unit: Membrane.Buffer.Metric.unit() } @@ -38,7 +38,7 @@ defmodule Membrane.Core.Element.DemandCounter do :sender_mode, :sender_process, :sender_pad_ref, - :buffered_decrementation_limit, + :throttling_factor, :toilet_capacity, :receiver_demand_unit ] @@ -51,7 +51,8 @@ defmodule Membrane.Core.Element.DemandCounter do receiver_demand_unit :: Membrane.Buffer.Metric.unit(), sender_process :: Process.dest(), sender_pad_ref :: Pad.ref(), - toilet_capacity :: neg_integer() | nil + toilet_capacity :: non_neg_integer() | float() | nil, + throttling_factor :: pos_integer() | nil ) :: t def new( receiver_mode, @@ -59,14 +60,17 @@ defmodule Membrane.Core.Element.DemandCounter do receiver_demand_unit, sender_process, sender_pad_ref, - toilet_capacity \\ nil + toilet_capacity \\ nil, + throttling_factor \\ nil ) do %DistributedAtomic{worker: worker} = counter = DistributedAtomic.new() - buffered_decrementation_limit = - if node(sender_process) == node(worker), - do: @default_buffered_decrementation_limit, - else: @distributed_buffered_decrementation_limit + throttling_factor = + cond do + throttling_factor != nil -> throttling_factor + node(sender_process) == node(worker) -> @default_throttling_factor + true -> @distributed_default_throttling_factor + end %__MODULE__{ counter: counter, @@ -76,7 +80,7 @@ defmodule Membrane.Core.Element.DemandCounter do sender_process: sender_process, sender_pad_ref: sender_pad_ref, toilet_capacity: toilet_capacity || default_toilet_capacity(receiver_demand_unit), - buffered_decrementation_limit: buffered_decrementation_limit, + throttling_factor: throttling_factor, receiver_demand_unit: receiver_demand_unit } end @@ -127,7 +131,7 @@ defmodule Membrane.Core.Element.DemandCounter do def decrease(%__MODULE__{} = demand_counter, value) do demand_counter = Map.update!(demand_counter, :buffered_decrementation, &(&1 + value)) - if demand_counter.buffered_decrementation >= demand_counter.buffered_decrementation_limit do + if demand_counter.buffered_decrementation >= demand_counter.throttling_factor do flush_buffered_decrementation(demand_counter) else demand_counter diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index df09fd784..047e3c392 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -164,7 +164,8 @@ defmodule Membrane.Core.Element.PadController do input_demand_unit || :buffers, other_endpoint.pid, other_endpoint.pad_ref, - endpoint.pad_props[:toilet_capacity] + endpoint.pad_props[:toilet_capacity], + endpoint.pad_props[:throttling_factor] ) # The sibiling was an initiator, we don't need to use the pid of a task spawned for observability diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 240d5ce8f..5aa87a858 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -6,7 +6,6 @@ defmodule Membrane.Element.PadData do - `:availability` - see `t:Membrane.Pad.availability/0` - `:stream_format` - the most recent `t:Membrane.StreamFormat.t/0` that have been sent (output) or received (input) on the pad. May be `nil` if not yet set. - - `:demand` - current demand requested on the pad working in `:auto` or `:manual` flow control mode. - `:direction` - see `t:Membrane.Pad.direction/0` - `:end_of_stream?` - flag determining whether the stream processing via the pad has been finished - `:flow_control` - see `t:Membrane.Pad.flow_control/0`. @@ -44,7 +43,14 @@ defmodule Membrane.Element.PadData do other_demand_unit: private_field, auto_demand_size: private_field, sticky_messages: private_field, + + # Instance of DemandCounter shared by both sides of link. Holds amount of data, that has been demanded by the element + # with input pad, but hasn't been sent yet by the element with output pad. Detects toilet overflow as well. demand_counter: private_field, + + # Field used in DemandController.AutoFlowUtils and InputQueue, to caluclate, how much DemandCounter should be increased. + # Contains amount of data (:buffers/:bytes), that has been demanded from the element on the other side of link, but + # hasn't arrived yet. Unused for output pads. lacking_buffer_size: private_field, associated_pads: private_field, sticky_events: private_field, diff --git a/test/membrane/core/element/demand_counter_test.exs b/test/membrane/core/element/demand_counter_test.exs index d2620f7b1..ff1c38f6d 100644 --- a/test/membrane/core/element/demand_counter_test.exs +++ b/test/membrane/core/element/demand_counter_test.exs @@ -98,7 +98,7 @@ defmodule Membrane.Core.Element.DemandCounterTest do pid_on_another_node = Node.spawn(another_node, fn -> :ok end) demand_counter = DemandCounter.new(:push, self(), :buffers, pid_on_another_node, :output) - assert %DemandCounter{buffered_decrementation_limit: 150} = demand_counter + assert %DemandCounter{throttling_factor: 150} = demand_counter demand_counter = DemandCounter.decrease(demand_counter, 100) From 9f3e04649aa8b7259e6e18895b2e06d95274bdf2 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 25 Apr 2023 16:46:25 +0200 Subject: [PATCH 44/64] Add comments about effective flow control resolution --- .../core/element/effective_flow_controller.ex | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index c96a3deae..5ac04d63f 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -1,6 +1,23 @@ defmodule Membrane.Core.Element.EffectiveFlowController do @moduledoc false + # Module responsible for the mechanism of resolving effective flow control in elements with pads with auto flow control. + # Effective flow control of the element determines if the element's pads with auto flow control work in :push or in + # :pull mode. If the element's effective flow control is set to :push, then all of its auto pads work in :push. Analogically, + # if the element effective flow control is set to :pull, auto pads also work in :pull. + + # If element A is linked via its input auto pads only to the :push output pads, then effective flow control of + # element A will be set to :push. Otherwise, if element A is linked via its input auto pads to at least one + # :pull output pad, element A will set itss effective flow control to :pull and will forward this information + # via its output auto pads. + + # Resolving effective flow control is performed on + # - entering playing playback + # - adding and removing pad + # - receiving information, that neighbour element effective flow control has changed + + # Effective flow control of a single element can switch between :push and :pull many times during the element's lifetime. + alias Membrane.Core.Element.DemandController.AutoFlowUtils alias Membrane.Core.Element.{DemandCounter, State} From 456aafa36bbb396ef26c4813a33a72d9583bf058 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 25 Apr 2023 16:49:52 +0200 Subject: [PATCH 45/64] Add lacking alias --- lib/membrane/core/element/demand_handler.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index 6c739cb65..a61aad064 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -15,6 +15,7 @@ defmodule Membrane.Core.Element.DemandHandler do StreamFormatController } + alias Membrane.Element.PadData alias Membrane.Pad require Membrane.Core.Child.PadModel, as: PadModel From 241d807fde12fcb4ea656d36b875e3030acacec8 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 12 May 2023 16:24:37 +0200 Subject: [PATCH 46/64] Implement suggestions from CR wip --- lib/membrane/children_spec.ex | 10 ++-- lib/membrane/core/element.ex | 7 ++- .../core/element/demand_controller.ex | 2 +- .../demand_controller/auto_flow_utils.ex | 10 +--- lib/membrane/core/element/demand_counter.ex | 2 +- .../demand_counter/distributed_atomic.ex | 4 +- .../{ => distributed_atomic}/worker.ex | 14 ++---- lib/membrane/core/element/demand_handler.ex | 2 +- .../core/element/effective_flow_controller.ex | 50 +++++++------------ lib/membrane/core/element/input_queue.ex | 4 +- lib/membrane/core/element/pad_controller.ex | 15 +++++- .../core/element/demand_counter_test.exs | 2 +- 12 files changed, 55 insertions(+), 67 deletions(-) rename lib/membrane/core/element/demand_counter/{ => distributed_atomic}/worker.ex (74%) diff --git a/lib/membrane/children_spec.ex b/lib/membrane/children_spec.ex index 6d1e250eb..3c1b8c783 100644 --- a/lib/membrane/children_spec.ex +++ b/lib/membrane/children_spec.ex @@ -538,11 +538,11 @@ defmodule Membrane.ChildrenSpec do """ @spec via_in(builder(), Pad.name() | Pad.ref(), options: pad_options(), - toilet_capacity: non_neg_integer() | float() | nil, - target_queue_size: number | nil, - min_demand_factor: number | nil, - auto_demand_size: number | nil, - throttling_factor: number | nil + toilet_capacity: non_neg_integer() | nil, + target_queue_size: non_neg_integer() | nil, + min_demand_factor: non_neg_integer() | nil, + auto_demand_size: non_neg_integer() | nil, + throttling_factor: non_neg_integer() | nil ) :: builder() | no_return def via_in(builder, pad, props \\ []) diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index 85232ef91..eaab68f92 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -222,12 +222,15 @@ defmodule Membrane.Core.Element do end defp do_handle_info( - Message.new(:other_effective_flow_control_resolved, [my_pad_ref, effective_flow_control]), + Message.new(:other_effective_flow_control_resolved, [ + input_pad_ref, + effective_flow_control + ]), state ) do state = EffectiveFlowController.handle_other_effective_flow_control( - my_pad_ref, + input_pad_ref, effective_flow_control, state ) diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 5db1bec3a..9e8fa6c58 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -27,7 +27,7 @@ defmodule Membrane.Core.Element.DemandController do with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), %State{playback: :playing} <- state do if pad_data.direction == :input, - do: raise("cannot check demand counter in input pad") + do: raise("cannot snapshot demand counter in input pad") do_snapshot_demand_counter(pad_data, state) else diff --git a/lib/membrane/core/element/demand_controller/auto_flow_utils.ex b/lib/membrane/core/element/demand_controller/auto_flow_utils.ex index 3ac190ff0..883448adf 100644 --- a/lib/membrane/core/element/demand_controller/auto_flow_utils.ex +++ b/lib/membrane/core/element/demand_controller/auto_flow_utils.ex @@ -19,15 +19,7 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do @spec increase_demand_counter_if_needed(Pad.ref() | [Pad.ref()], State.t()) :: State.t() def increase_demand_counter_if_needed(pad_ref_list, state) when is_list(pad_ref_list) do - Enum.reduce(pad_ref_list, state, fn pad_ref, state -> - case PadModel.get_data(state, pad_ref) do - {:ok, pad_data} when is_input_auto_pad_data(pad_data) -> - do_increase_demand_counter_if_needed(pad_data, state) - - _other -> - state - end - end) + Enum.reduce(pad_ref_list, state, &increase_demand_counter_if_needed/2) end def increase_demand_counter_if_needed(pad_ref, state) when Pad.is_pad_ref(pad_ref) do diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 6aa6f2d40..6b89d0aff 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -51,7 +51,7 @@ defmodule Membrane.Core.Element.DemandCounter do receiver_demand_unit :: Membrane.Buffer.Metric.unit(), sender_process :: Process.dest(), sender_pad_ref :: Pad.ref(), - toilet_capacity :: non_neg_integer() | float() | nil, + toilet_capacity :: non_neg_integer() | nil, throttling_factor :: pos_integer() | nil ) :: t def new( diff --git a/lib/membrane/core/element/demand_counter/distributed_atomic.ex b/lib/membrane/core/element/demand_counter/distributed_atomic.ex index 9dd5bc90c..0af560f89 100644 --- a/lib/membrane/core/element/demand_counter/distributed_atomic.ex +++ b/lib/membrane/core/element/demand_counter/distributed_atomic.ex @@ -6,7 +6,7 @@ defmodule Membrane.Core.Element.DemandCounter.DistributedAtomic do # The module allows to create and modify the value of a counter in the same manner both when the counter is about to be accessed # from the same node, and from different nodes. - alias Membrane.Core.Element.DemandCounter.Worker + alias __MODULE__.Worker @enforce_keys [:worker, :atomic_ref] defstruct @enforce_keys @@ -19,7 +19,7 @@ defmodule Membrane.Core.Element.DemandCounter.DistributedAtomic do @spec new(integer() | nil) :: t def new(initial_value \\ nil) do atomic_ref = :atomics.new(1, []) - {:ok, worker} = Worker.start(self()) + {:ok, worker} = Worker.start_link() distributed_atomic = %__MODULE__{ atomic_ref: atomic_ref, diff --git a/lib/membrane/core/element/demand_counter/worker.ex b/lib/membrane/core/element/demand_counter/distributed_atomic/worker.ex similarity index 74% rename from lib/membrane/core/element/demand_counter/worker.ex rename to lib/membrane/core/element/demand_counter/distributed_atomic/worker.ex index 6ff8d5078..b5957d35f 100644 --- a/lib/membrane/core/element/demand_counter/worker.ex +++ b/lib/membrane/core/element/demand_counter/distributed_atomic/worker.ex @@ -1,4 +1,4 @@ -defmodule Membrane.Core.Element.DemandCounter.Worker do +defmodule Membrane.Core.Element.DemandCounter.DistributedAtomic.Worker do @moduledoc false # This is a GenServer created when the counter is about to be accessed from different nodes - it's running on the same node, @@ -8,12 +8,11 @@ defmodule Membrane.Core.Element.DemandCounter.Worker do @type t :: pid() - @spec start(pid()) :: {:ok, t} - def start(parent_pid), do: GenServer.start(__MODULE__, parent_pid) + @spec start_link() :: {:ok, t} + def start_link(), do: GenServer.start_link(__MODULE__, nil) @impl true - def init(parent_pid) do - Process.monitor(parent_pid) + def init(_arg) do {:ok, nil, :hibernate} end @@ -40,9 +39,4 @@ defmodule Membrane.Core.Element.DemandCounter.Worker do :atomics.put(atomic_ref, 1, value) {:noreply, nil} end - - @impl true - def handle_info({:DOWN, _ref, :process, _object, _reason}, state) do - {:stop, :normal, state} - end end diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index a61aad064..b4dd199a9 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -128,7 +128,7 @@ defmodule Membrane.Core.Element.DemandHandler do Message.self(:resume_handle_demand_loop) %{state | handle_demand_loop_counter: 0} - state.delayed_demands == MapSet.new() -> + Enum.empty?(state.delayed_demands) -> %{state | handle_demand_loop_counter: 0} true -> diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 5ac04d63f..91fa37267 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -6,8 +6,8 @@ defmodule Membrane.Core.Element.EffectiveFlowController do # :pull mode. If the element's effective flow control is set to :push, then all of its auto pads work in :push. Analogically, # if the element effective flow control is set to :pull, auto pads also work in :pull. - # If element A is linked via its input auto pads only to the :push output pads, then effective flow control of - # element A will be set to :push. Otherwise, if element A is linked via its input auto pads to at least one + # If element A is linked via its input auto pads only to the :push output pads of other elements, then effective flow + # control of element A will be set to :push. Otherwise, if element A is linked via its input auto pads to at least one # :pull output pad, element A will set itss effective flow control to :pull and will forward this information # via its output auto pads. @@ -41,28 +41,16 @@ defmodule Membrane.Core.Element.EffectiveFlowController do end end - @spec handle_input_pad_added(Pad.ref(), State.t()) :: State.t() - def handle_input_pad_added(pad_ref, state) do - with %{pads_data: %{^pad_ref => %{flow_control: :auto, direction: :input} = pad_data}} <- - state do - handle_other_effective_flow_control( - pad_ref, - pad_data.other_effective_flow_control, - state - ) - end - end - @spec handle_other_effective_flow_control( Pad.ref(), effective_flow_control(), State.t() ) :: State.t() - def handle_other_effective_flow_control(my_pad_ref, other_effective_flow_control, state) do - pad_data = PadModel.get_data!(state, my_pad_ref) + def handle_other_effective_flow_control(input_pad_ref, other_effective_flow_control, state) do + pad_data = PadModel.get_data!(state, input_pad_ref) pad_data = %{pad_data | other_effective_flow_control: other_effective_flow_control} - state = PadModel.set_data!(state, my_pad_ref, pad_data) + state = PadModel.set_data!(state, input_pad_ref, pad_data) cond do state.playback != :playing or pad_data.direction != :input or pad_data.flow_control != :auto -> @@ -70,7 +58,7 @@ defmodule Membrane.Core.Element.EffectiveFlowController do other_effective_flow_control == state.effective_flow_control -> :ok = - PadModel.get_data!(state, my_pad_ref, :demand_counter) + PadModel.get_data!(state, input_pad_ref, :demand_counter) |> DemandCounter.set_receiver_mode(state.effective_flow_control) state @@ -113,24 +101,24 @@ defmodule Membrane.Core.Element.EffectiveFlowController do state = %{state | effective_flow_control: new_effective_flow_control} - Enum.each(state.pads_data, fn - {_ref, %{flow_control: :auto, direction: :output} = pad_data} -> + state.pads_data + |> Enum.filter(fn {_ref, %{flow_control: flow_control}} -> flow_control == :auto end) + |> Enum.reduce(state, fn + {_ref, %{direction: :output} = pad_data}, state -> :ok = DemandCounter.set_sender_mode(pad_data.demand_counter, new_effective_flow_control) :ok = DemandCounter.set_receiver_mode(pad_data.demand_counter, :to_be_resolved) - Message.send(pad_data.pid, :other_effective_flow_control_resolved, [ - pad_data.other_ref, - new_effective_flow_control - ]) + Message.send( + pad_data.pid, + :other_effective_flow_control_resolved, + [pad_data.other_ref, new_effective_flow_control] + ) - {_ref, %{flow_control: :auto, direction: :input, demand_counter: demand_counter}} -> - :ok = DemandCounter.set_receiver_mode(demand_counter, new_effective_flow_control) + state - _pad_entry -> - :ok + {pad_ref, %{direction: :input} = pad_data}, state -> + :ok = DemandCounter.set_receiver_mode(pad_data.demand_counter, new_effective_flow_control) + AutoFlowUtils.increase_demand_counter_if_needed(pad_ref, state) end) - - Map.keys(state.pads_data) - |> AutoFlowUtils.increase_demand_counter_if_needed(state) end end diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index 375daba06..334bbcae1 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -291,10 +291,8 @@ defmodule Membrane.Core.Element.InputQueue do |> mk_log(input_queue) |> Membrane.Logger.debug_verbose() - lacking_buffer_size = lacking_buffer_size + diff :ok = DemandCounter.increase(demand_counter, diff) - - %{input_queue | lacking_buffer_size: lacking_buffer_size} + %{input_queue | lacking_buffer_size: lacking_buffer_size + diff} end defp maybe_increase_demand_counter(%__MODULE__{} = input_queue), do: input_queue diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 047e3c392..917e4e731 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -4,6 +4,7 @@ defmodule Membrane.Core.Element.PadController do # Module handling linking and unlinking pads. use Bunch + alias Membrane.Core.Element.PadController alias Membrane.{LinkError, Pad} alias Membrane.Core.{CallbackHandler, Child, Events, Message, Observability} alias Membrane.Core.Child.PadModel @@ -191,7 +192,7 @@ defmodule Membrane.Core.Element.PadController do state ) - state = EffectiveFlowController.handle_input_pad_added(endpoint.pad_ref, state) + state = PadController.handle_input_pad_added(endpoint.pad_ref, state) state = maybe_handle_pad_added(endpoint.pad_ref, state) {{:ok, {endpoint, info, link_metadata}}, state} end @@ -241,6 +242,18 @@ defmodule Membrane.Core.Element.PadController do end end + @spec handle_input_pad_added(Pad.ref(), State.t()) :: State.t() + def handle_input_pad_added(pad_ref, state) do + with %{pads_data: %{^pad_ref => %{flow_control: :auto, direction: :input} = pad_data}} <- + state do + EffectiveFlowController.handle_other_effective_flow_control( + pad_ref, + pad_data.other_effective_flow_control, + state + ) + end + end + defp resolve_demand_units(output_info, input_info) do output_demand_unit = if output_info[:flow_control] == :push, diff --git a/test/membrane/core/element/demand_counter_test.exs b/test/membrane/core/element/demand_counter_test.exs index ff1c38f6d..f4c308ea7 100644 --- a/test/membrane/core/element/demand_counter_test.exs +++ b/test/membrane/core/element/demand_counter_test.exs @@ -16,7 +16,7 @@ defmodule Membrane.Core.Element.DemandCounterTest do assert DemandCounter.get(demand_counter) == -5 end - test "if DemandCounter.Worker works properly " do + test "if DemandCounter.DistributedAtomic.Worker works properly " do demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :output) :ok = DemandCounter.increase(demand_counter, 10) From dd4be64166edcec751e7711eab1b35745f8e06ed Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 15 May 2023 17:21:46 +0200 Subject: [PATCH 47/64] Implement suggestions from CR wip --- .../core/element/buffer_controller.ex | 2 +- .../core/element/demand_controller.ex | 2 +- .../demand_controller/auto_flow_utils.ex | 35 ++++++++----------- lib/membrane/core/element/demand_counter.ex | 4 +-- .../core/element/effective_flow_controller.ex | 2 +- lib/membrane/core/element/pad_controller.ex | 15 ++++---- 6 files changed, 25 insertions(+), 35 deletions(-) diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index 2722d2855..15f32561e 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -63,7 +63,7 @@ defmodule Membrane.Core.Element.BufferController do state = PadModel.set_data!(state, pad_ref, :lacking_buffer_size, lacking_buffer_size - buf_size) - state = AutoFlowUtils.increase_demand_counter_if_needed(pad_ref, state) + state = AutoFlowUtils.auto_adjust_demand_counter(pad_ref, state) exec_buffer_callback(pad_ref, buffers, state) end diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 9e8fa6c58..c2855e928 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -50,7 +50,7 @@ defmodule Membrane.Core.Element.DemandController do } = pad_data if DemandCounter.get(demand_counter) > 0 do - AutoFlowUtils.increase_demand_counter_if_needed(associated_pads, state) + AutoFlowUtils.auto_adjust_demand_counter(associated_pads, state) else state end diff --git a/lib/membrane/core/element/demand_controller/auto_flow_utils.ex b/lib/membrane/core/element/demand_controller/auto_flow_utils.ex index 883448adf..2fd58db5b 100644 --- a/lib/membrane/core/element/demand_controller/auto_flow_utils.ex +++ b/lib/membrane/core/element/demand_controller/auto_flow_utils.ex @@ -17,21 +17,17 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do pad_data.flow_control == :auto and is_map_key(pad_data, :direction) and pad_data.direction == :input - @spec increase_demand_counter_if_needed(Pad.ref() | [Pad.ref()], State.t()) :: State.t() - def increase_demand_counter_if_needed(pad_ref_list, state) when is_list(pad_ref_list) do - Enum.reduce(pad_ref_list, state, &increase_demand_counter_if_needed/2) + @spec auto_adjust_demand_counter(Pad.ref() | [Pad.ref()], State.t()) :: State.t() + def auto_adjust_demand_counter(pad_ref_list, state) when is_list(pad_ref_list) do + Enum.reduce(pad_ref_list, state, &auto_adjust_demand_counter/2) end - def increase_demand_counter_if_needed(pad_ref, state) when Pad.is_pad_ref(pad_ref) do - with {:ok, pad_data} when is_input_auto_pad_data(pad_data) <- - PadModel.get_data(state, pad_ref) do - do_increase_demand_counter_if_needed(pad_data, state) - else - _other -> state - end + def auto_adjust_demand_counter(pad_ref, state) when Pad.is_pad_ref(pad_ref) do + PadModel.get_data!(state, pad_ref) + |> do_auto_adjust_demand_counter(state) end - defp do_increase_demand_counter_if_needed(pad_data, state) do + defp do_auto_adjust_demand_counter(pad_data, state) when is_input_auto_pad_data(pad_data) do if increase_demand_counter?(pad_data, state) do diff = @lacking_buffer_size_upperbound - pad_data.lacking_buffer_size :ok = DemandCounter.increase(pad_data.demand_counter, diff) @@ -47,17 +43,14 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do end end - defp increase_demand_counter?(pad_data, state) do - %{ - flow_control: flow_control, - lacking_buffer_size: lacking_buffer_size, - associated_pads: associated_pads - } = pad_data + defp do_auto_adjust_demand_counter(%{ref: ref}, _state) do + raise "#{__MODULE__}.auto_adjust_demand_counter/2 can be called only for auto input pads, while #{inspect(ref)} is not such a pad." + end - flow_control == :auto and - state.effective_flow_control == :pull and - lacking_buffer_size < @lacking_buffer_size_lowerbound and - Enum.all?(associated_pads, &demand_counter_positive?(&1, state)) + defp increase_demand_counter?(pad_data, state) do + state.effective_flow_control == :pull and + pad_data.lacking_buffer_size < @lacking_buffer_size_lowerbound and + Enum.all?(pad_data.associated_pads, &demand_counter_positive?(&1, state)) end defp demand_counter_positive?(pad_ref, state) do diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index 6b89d0aff..dcdf5bf9d 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -11,7 +11,7 @@ defmodule Membrane.Core.Element.DemandCounter do require Membrane.Logger require Membrane.Pad, as: Pad - @default_toilet_capacity_factor -200 + @default_toilet_capacity_factor 200 @default_throttling_factor 1 @distributed_default_throttling_factor 150 @@ -155,7 +155,7 @@ defmodule Membrane.Core.Element.DemandCounter do if not demand_counter.toilet_overflowed? and get_receiver_mode(demand_counter) == :pull and get_sender_mode(demand_counter) == :push and - counter_value < demand_counter.toilet_capacity do + -1 * counter_value > demand_counter.toilet_capacity do overflow(demand_counter, counter_value) else demand_counter diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 91fa37267..5512984d2 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -118,7 +118,7 @@ defmodule Membrane.Core.Element.EffectiveFlowController do {pad_ref, %{direction: :input} = pad_data}, state -> :ok = DemandCounter.set_receiver_mode(pad_data.demand_counter, new_effective_flow_control) - AutoFlowUtils.increase_demand_counter_if_needed(pad_ref, state) + AutoFlowUtils.auto_adjust_demand_counter(pad_ref, state) end) end end diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 917e4e731..5936ebaef 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -319,10 +319,9 @@ defmodule Membrane.Core.Element.PadController do PadModel.update_data!(state, other_data.ref, :associated_pads, &[data.ref | &1]) end) - case data.direction do - :input -> AutoFlowUtils.increase_demand_counter_if_needed(endpoint.pad_ref, state) - :output -> state - end + if data.direction == :input, + do: AutoFlowUtils.auto_adjust_demand_counter(endpoint.pad_ref, state), + else: state else state end @@ -434,11 +433,9 @@ defmodule Membrane.Core.Element.PadController do end) |> PadModel.set_data!(pad_ref, :associated_pads, []) - if pad_data.direction == :output do - AutoFlowUtils.increase_demand_counter_if_needed(pad_data.associated_pads, state) - else - state - end + if pad_data.direction == :output, + do: AutoFlowUtils.auto_adjust_demand_counter(pad_data.associated_pads, state), + else: state _pad_data -> state From 91375b294b9e5599022a0c2b925e687d18825604 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 15 May 2023 17:40:59 +0200 Subject: [PATCH 48/64] Implement suggestions from CR --- .../core/element/demand_controller/auto_flow_utils.ex | 9 +++------ lib/membrane/core/element/pad_controller.ex | 2 +- lib/membrane/element/pad_data.ex | 6 +++++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/membrane/core/element/demand_controller/auto_flow_utils.ex b/lib/membrane/core/element/demand_controller/auto_flow_utils.ex index 2fd58db5b..c41183b7c 100644 --- a/lib/membrane/core/element/demand_controller/auto_flow_utils.ex +++ b/lib/membrane/core/element/demand_controller/auto_flow_utils.ex @@ -9,9 +9,6 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do require Membrane.Core.Child.PadModel, as: PadModel require Membrane.Pad, as: Pad - @lacking_buffer_size_lowerbound 200 - @lacking_buffer_size_upperbound 400 - defguardp is_input_auto_pad_data(pad_data) when is_map(pad_data) and is_map_key(pad_data, :flow_control) and pad_data.flow_control == :auto and is_map_key(pad_data, :direction) and @@ -29,14 +26,14 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do defp do_auto_adjust_demand_counter(pad_data, state) when is_input_auto_pad_data(pad_data) do if increase_demand_counter?(pad_data, state) do - diff = @lacking_buffer_size_upperbound - pad_data.lacking_buffer_size + diff = pad_data.auto_demand_size - pad_data.lacking_buffer_size :ok = DemandCounter.increase(pad_data.demand_counter, diff) PadModel.set_data!( state, pad_data.ref, :lacking_buffer_size, - @lacking_buffer_size_upperbound + pad_data.auto_demand_size ) else state @@ -49,7 +46,7 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do defp increase_demand_counter?(pad_data, state) do state.effective_flow_control == :pull and - pad_data.lacking_buffer_size < @lacking_buffer_size_lowerbound and + pad_data.lacking_buffer_size < pad_data.auto_demand_size / 2 and Enum.all?(pad_data.associated_pads, &demand_counter_positive?(&1, state)) end diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 5936ebaef..8ba9cdbed 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -52,7 +52,7 @@ defmodule Membrane.Core.Element.PadController do | {:error, {:neighbor_child_dead, reason :: any()}} | {:error, {:unknown_pad, name :: Membrane.Child.name(), pad_ref :: Pad.ref()}} - @default_auto_demand_size_factor 4000 + @default_auto_demand_size_factor 400 @doc """ Verifies linked pad, initializes it's data. diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 5aa87a858..7569451be 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -37,13 +37,17 @@ defmodule Membrane.Element.PadData do pid: private_field, other_ref: private_field, input_queue: private_field, - demand_snapshot: integer() | nil, incoming_demand: integer() | nil, demand_unit: private_field, other_demand_unit: private_field, auto_demand_size: private_field, sticky_messages: private_field, + # Used only for output pads with :pull or :auto flow control. Holds the last captured value of DemandCounter, + # decreased by the size of buffers sent via specific pad since the last capture, expressed in the appropriate metric. + # Moment, when demand_snapshot value drops to 0 or less, triggers another capture of DemandCounter value. + demand_snapshot: integer() | nil, + # Instance of DemandCounter shared by both sides of link. Holds amount of data, that has been demanded by the element # with input pad, but hasn't been sent yet by the element with output pad. Detects toilet overflow as well. demand_counter: private_field, From bdbc08e1d763f3cf37a4cd8bd6b94b3f83a6ea62 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 15 May 2023 18:24:59 +0200 Subject: [PATCH 49/64] Implement suggestion from CR wip --- lib/membrane/core/child/pad_model.ex | 1 + lib/membrane/core/element/demand_handler.ex | 14 +++++++------- lib/membrane/element/pad_data.ex | 2 ++ test/membrane/core/element/action_handler_test.exs | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/membrane/core/child/pad_model.ex b/lib/membrane/core/child/pad_model.ex index e03c69f87..7c0a4d10f 100644 --- a/lib/membrane/core/child/pad_model.ex +++ b/lib/membrane/core/child/pad_model.ex @@ -26,6 +26,7 @@ defmodule Membrane.Core.Child.PadModel do availability: Pad.availability(), stream_format: Membrane.StreamFormat.t() | nil, demand_snapshot: integer() | nil, + manual_demand_size: integer(), start_of_stream?: boolean(), end_of_stream?: boolean(), direction: Pad.direction(), diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index b4dd199a9..bd5dd2c54 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -90,7 +90,7 @@ defmodule Membrane.Core.Element.DemandHandler do pad_data = state |> PadModel.get_data!(pad_ref) {{_queue_status, popped_data}, new_input_queue} = - InputQueue.take(pad_data.input_queue, pad_data.demand_snapshot) + InputQueue.take(pad_data.input_queue, pad_data.manual_demand_size) state = PadModel.set_data!(state, pad_ref, :input_queue, new_input_queue) state = handle_input_queue_output(pad_ref, popped_data, state) @@ -98,19 +98,19 @@ defmodule Membrane.Core.Element.DemandHandler do end defp update_demand(pad_ref, size, state) when is_integer(size) do - PadModel.set_data!(state, pad_ref, :demand_snapshot, size) + PadModel.set_data!(state, pad_ref, :manual_demand_size, size) end defp update_demand(pad_ref, size_fun, state) when is_function(size_fun) do - demand_snapshot = PadModel.get_data!(state, pad_ref, :demand_snapshot) - new_demand_snapshot = size_fun.(demand_snapshot) + manual_demand_size = PadModel.get_data!(state, pad_ref, :manual_demand_size) + new_manual_demand_size = size_fun.(manual_demand_size) - if new_demand_snapshot < 0 do + if new_manual_demand_size < 0 do raise Membrane.ElementError, "Demand altering function requested negative demand on pad #{inspect(pad_ref)} in #{state.module}" end - PadModel.set_data!(state, pad_ref, :demand_snapshot, new_demand_snapshot) + PadModel.set_data!(state, pad_ref, :manual_demand_size, new_manual_demand_size) end @spec handle_delayed_demands(State.t()) :: State.t() @@ -174,7 +174,7 @@ defmodule Membrane.Core.Element.DemandHandler do state ) do state = - PadModel.update_data!(state, pad_ref, :demand_snapshot, &(&1 - outbound_metric_buf_size)) + PadModel.update_data!(state, pad_ref, :manual_demand_size, &(&1 - outbound_metric_buf_size)) BufferController.exec_buffer_callback(pad_ref, buffers, state) end diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 7569451be..3825dd246 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -56,6 +56,7 @@ defmodule Membrane.Element.PadData do # Contains amount of data (:buffers/:bytes), that has been demanded from the element on the other side of link, but # hasn't arrived yet. Unused for output pads. lacking_buffer_size: private_field, + manual_demand_size: private_field, associated_pads: private_field, sticky_events: private_field, other_effective_flow_control: private_field @@ -85,6 +86,7 @@ defmodule Membrane.Element.PadData do sticky_messages: [], demand_counter: nil, lacking_buffer_size: 0, + manual_demand_size: 0, associated_pads: [], sticky_events: [], stream_format_validation_params: [], diff --git a/test/membrane/core/element/action_handler_test.exs b/test/membrane/core/element/action_handler_test.exs index e160ffb87..f60341a18 100644 --- a/test/membrane/core/element/action_handler_test.exs +++ b/test/membrane/core/element/action_handler_test.exs @@ -54,7 +54,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do test "delaying demand", %{state: state} do state = %{state | playback: :playing, supplying_demand?: true} state = @module.handle_action({:demand, {:input, 10}}, :handle_info, %{}, state) - assert state.pads_data.input.demand_snapshot == 10 + assert state.pads_data.input.manual_demand_size == 10 assert MapSet.new([{:input, :supply}]) == state.delayed_demands end From 72e8f47ea122db9632f343f7eb6f34f81838b8b4 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 16 May 2023 12:21:17 +0200 Subject: [PATCH 50/64] Implement suggestion from CR --- .../core/element/demand_controller.ex | 6 ++--- lib/membrane/core/element/pad_controller.ex | 25 ++++++++++++------- .../core/element/action_handler_test.exs | 1 + 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index c2855e928..cbfafc5eb 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -62,8 +62,6 @@ defmodule Membrane.Core.Element.DemandController do demand_counter_value when demand_counter_value > 0 and demand_counter_value > demand_snapshot <- DemandCounter.get(demand_counter) do - # pole demand_snapshot powinno brac uwage konwersję demand unitu - state = PadModel.update_data!( state, @@ -91,7 +89,9 @@ defmodule Membrane.Core.Element.DemandController do @spec decrease_demand_by_outgoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: State.t() def decrease_demand_by_outgoing_buffers(pad_ref, buffers, state) do pad_data = PadModel.get_data!(state, pad_ref) - buffers_size = Buffer.Metric.from_unit(pad_data.other_demand_unit).buffers_size(buffers) + + demand_unit = pad_data.demand_unit || pad_data.other_demand_unit || :buffers + buffers_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) demand_snapshot = pad_data.demand_snapshot - buffers_size demand_counter = DemandCounter.decrease(pad_data.demand_counter, buffers_size) diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 8ba9cdbed..9ab3b9516 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -152,8 +152,10 @@ defmodule Membrane.Core.Element.PadController do {output_demand_unit, input_demand_unit} = resolve_demand_units(other_info, info) link_metadata = - Map.put(link_metadata, :input_demand_unit, input_demand_unit) - |> Map.put(:output_demand_unit, output_demand_unit) + Map.merge(link_metadata, %{ + input_demand_unit: input_demand_unit, + output_demand_unit: output_demand_unit + }) pad_effective_flow_control = EffectiveFlowController.get_pad_effective_flow_control(endpoint.pad_ref, state) @@ -375,7 +377,7 @@ defmodule Membrane.Core.Element.PadController do end defp init_pad_mode_data( - %{flow_control: :auto, direction: direction}, + %{flow_control: :auto, direction: direction} = data, props, _other_info, _metadata, @@ -388,12 +390,17 @@ defmodule Membrane.Core.Element.PadController do |> Enum.map(& &1.ref) auto_demand_size = - if direction == :input do - props.auto_demand_size || - Membrane.Buffer.Metric.Count.buffer_size_approximation() * - @default_auto_demand_size_factor - else - nil + cond do + direction == :output -> + nil + + props.auto_demand_size != nil -> + props.auto_demand_size + + true -> + demand_unit = data.other_demand_unit || data.demand_unit || :buffers + metric = Membrane.Buffer.Metric.from_unit(demand_unit) + metric.buffer_size_approximation() * @default_auto_demand_size_factor end %{ diff --git a/test/membrane/core/element/action_handler_test.exs b/test/membrane/core/element/action_handler_test.exs index f60341a18..ae7d4e057 100644 --- a/test/membrane/core/element/action_handler_test.exs +++ b/test/membrane/core/element/action_handler_test.exs @@ -92,6 +92,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do pid: self(), other_ref: :other_ref, stream_format: nil, + demand_unit: :bytes, other_demand_unit: :bytes, start_of_stream?: true, end_of_stream?: false, From 97d0b22dc373a57f5b9ecd23ce0d47ec2b8315e9 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 16 May 2023 17:03:39 +0200 Subject: [PATCH 51/64] Terminate distributed atomic worker after it's creator death --- .../element/demand_counter/distributed_atomic.ex | 2 +- .../demand_counter/distributed_atomic/worker.ex | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/membrane/core/element/demand_counter/distributed_atomic.ex b/lib/membrane/core/element/demand_counter/distributed_atomic.ex index 0af560f89..8f9cf81fc 100644 --- a/lib/membrane/core/element/demand_counter/distributed_atomic.ex +++ b/lib/membrane/core/element/demand_counter/distributed_atomic.ex @@ -19,7 +19,7 @@ defmodule Membrane.Core.Element.DemandCounter.DistributedAtomic do @spec new(integer() | nil) :: t def new(initial_value \\ nil) do atomic_ref = :atomics.new(1, []) - {:ok, worker} = Worker.start_link() + {:ok, worker} = Worker.start_link(self()) distributed_atomic = %__MODULE__{ atomic_ref: atomic_ref, diff --git a/lib/membrane/core/element/demand_counter/distributed_atomic/worker.ex b/lib/membrane/core/element/demand_counter/distributed_atomic/worker.ex index b5957d35f..e50b1d2ca 100644 --- a/lib/membrane/core/element/demand_counter/distributed_atomic/worker.ex +++ b/lib/membrane/core/element/demand_counter/distributed_atomic/worker.ex @@ -8,12 +8,13 @@ defmodule Membrane.Core.Element.DemandCounter.DistributedAtomic.Worker do @type t :: pid() - @spec start_link() :: {:ok, t} - def start_link(), do: GenServer.start_link(__MODULE__, nil) + @spec start_link(pid()) :: {:ok, t} + def start_link(owner_pid), do: GenServer.start_link(__MODULE__, owner_pid) @impl true - def init(_arg) do - {:ok, nil, :hibernate} + def init(owner_pid) do + ref = Process.monitor(owner_pid) + {:ok, %{ref: ref}, :hibernate} end @impl true @@ -39,4 +40,9 @@ defmodule Membrane.Core.Element.DemandCounter.DistributedAtomic.Worker do :atomics.put(atomic_ref, 1, value) {:noreply, nil} end + + @impl true + def handle_info({:DOWN, ref, _process, _pid, _reason}, %{ref: ref} = state) do + {:stop, :normal, state} + end end From 47964a5d01e3a633d70b81e59de4f4493a237616 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 17 May 2023 11:23:42 +0200 Subject: [PATCH 52/64] Update algorithm of changing flags in DemandCounter --- lib/membrane/core/element.ex | 4 +-- .../core/element/effective_flow_controller.ex | 28 +++++++++++++------ lib/membrane/core/element/pad_controller.ex | 2 +- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index eaab68f92..e4b67a4c9 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -222,14 +222,14 @@ defmodule Membrane.Core.Element do end defp do_handle_info( - Message.new(:other_effective_flow_control_resolved, [ + Message.new(:sender_effective_flow_control_resolved, [ input_pad_ref, effective_flow_control ]), state ) do state = - EffectiveFlowController.handle_other_effective_flow_control( + EffectiveFlowController.handle_sender_effective_flow_control( input_pad_ref, effective_flow_control, state diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 5512984d2..017e9a6e6 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -41,13 +41,13 @@ defmodule Membrane.Core.Element.EffectiveFlowController do end end - @spec handle_other_effective_flow_control( + @spec handle_sender_effective_flow_control( Pad.ref(), effective_flow_control(), State.t() ) :: State.t() - def handle_other_effective_flow_control(input_pad_ref, other_effective_flow_control, state) do + def handle_sender_effective_flow_control(input_pad_ref, other_effective_flow_control, state) do pad_data = PadModel.get_data!(state, input_pad_ref) pad_data = %{pad_data | other_effective_flow_control: other_effective_flow_control} state = PadModel.set_data!(state, input_pad_ref, pad_data) @@ -64,15 +64,15 @@ defmodule Membrane.Core.Element.EffectiveFlowController do state other_effective_flow_control == :pull -> - set_effective_flow_control(:pull, state) + set_effective_flow_control(:pull, input_pad_ref, state) other_effective_flow_control == :push -> - resolve_effective_flow_control(state) + resolve_effective_flow_control(input_pad_ref, state) end end - @spec resolve_effective_flow_control(State.t()) :: State.t() - def resolve_effective_flow_control(state) do + @spec resolve_effective_flow_control(Pad.ref(), State.t()) :: State.t() + def resolve_effective_flow_control(last_changed_pad \\ nil, state) do senders_flow_modes = Map.values(state.pads_data) |> Enum.filter(&(&1.direction == :input && &1.flow_control == :auto)) @@ -85,16 +85,17 @@ defmodule Membrane.Core.Element.EffectiveFlowController do true -> state.effective_flow_control end - set_effective_flow_control(new_effective_flow_control, state) + set_effective_flow_control(new_effective_flow_control, last_changed_pad, state) end defp set_effective_flow_control( effective_flow_control, + _last_changed_pad, %{effective_flow_control: effective_flow_control} = state ), do: state - defp set_effective_flow_control(new_effective_flow_control, state) do + defp set_effective_flow_control(new_effective_flow_control, last_changed_pad, state) do Membrane.Logger.debug( "Transiting `flow_control: :auto` pads to #{inspect(new_effective_flow_control)} effective flow control" ) @@ -110,12 +111,21 @@ defmodule Membrane.Core.Element.EffectiveFlowController do Message.send( pad_data.pid, - :other_effective_flow_control_resolved, + :sender_effective_flow_control_resolved, [pad_data.other_ref, new_effective_flow_control] ) state + {pad_ref, %{direction: :input} = pad_data}, state when last_changed_pad != nil -> + if pad_ref == last_changed_pad or + DemandCounter.get_receiver_mode(pad_data.demand_counter) != :to_be_resolved do + :ok = + DemandCounter.set_receiver_mode(pad_data.demand_counter, new_effective_flow_control) + end + + AutoFlowUtils.auto_adjust_demand_counter(pad_ref, state) + {pad_ref, %{direction: :input} = pad_data}, state -> :ok = DemandCounter.set_receiver_mode(pad_data.demand_counter, new_effective_flow_control) AutoFlowUtils.auto_adjust_demand_counter(pad_ref, state) diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 9ab3b9516..f53631014 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -248,7 +248,7 @@ defmodule Membrane.Core.Element.PadController do def handle_input_pad_added(pad_ref, state) do with %{pads_data: %{^pad_ref => %{flow_control: :auto, direction: :input} = pad_data}} <- state do - EffectiveFlowController.handle_other_effective_flow_control( + EffectiveFlowController.handle_sender_effective_flow_control( pad_ref, pad_data.other_effective_flow_control, state From 27bc14c1ad36b8c9864bc3f06db82d83447cee43 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Wed, 17 May 2023 16:57:25 +0200 Subject: [PATCH 53/64] Implement suggestion from CR wip --- lib/membrane/core/element/demand_counter.ex | 55 ++++++++++--------- .../demand_counter/distributed_flow_mode.ex | 38 ------------- .../demand_counter/distributed_flow_status.ex | 37 +++++++++++++ .../core/element/effective_flow_controller.ex | 25 +++++++-- lib/membrane/core/element/pad_controller.ex | 30 +++++----- .../core/element/demand_counter_test.exs | 44 +++++++++------ 6 files changed, 126 insertions(+), 103 deletions(-) delete mode 100644 lib/membrane/core/element/demand_counter/distributed_flow_mode.ex create mode 100644 lib/membrane/core/element/demand_counter/distributed_flow_status.ex diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/demand_counter.ex index dcdf5bf9d..adb6c930c 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/demand_counter.ex @@ -1,10 +1,11 @@ defmodule Membrane.Core.Element.DemandCounter do @moduledoc false + alias Membrane.Core.Element.DemandCounter.DistributedFlowStatus alias Membrane.Core.Element.EffectiveFlowController alias __MODULE__.{ DistributedAtomic, - DistributedFlowMode + DistributedFlowStatus } require Membrane.Core.Message, as: Message @@ -17,9 +18,9 @@ defmodule Membrane.Core.Element.DemandCounter do @opaque t :: %__MODULE__{ counter: DistributedAtomic.t(), - receiver_mode: DistributedFlowMode.t(), + receiver_status: DistributedFlowStatus.t(), receiver_process: Process.dest(), - sender_mode: DistributedFlowMode.t(), + sender_status: DistributedFlowStatus.t(), sender_process: Process.dest(), sender_pad_ref: Pad.ref(), toilet_capacity: neg_integer(), @@ -29,13 +30,13 @@ defmodule Membrane.Core.Element.DemandCounter do receiver_demand_unit: Membrane.Buffer.Metric.unit() } - @type flow_mode :: DistributedFlowMode.flow_mode_value() + @type flow_mode :: DistributedFlowStatus.value() @enforce_keys [ :counter, - :receiver_mode, + :receiver_status, :receiver_process, - :sender_mode, + :sender_status, :sender_process, :sender_pad_ref, :throttling_factor, @@ -46,7 +47,7 @@ defmodule Membrane.Core.Element.DemandCounter do defstruct @enforce_keys ++ [buffered_decrementation: 0, toilet_overflowed?: false] @spec new( - receiver_mode :: EffectiveFlowController.effective_flow_control(), + receiver_effective_flow_control :: EffectiveFlowController.effective_flow_control(), receiver_process :: Process.dest(), receiver_demand_unit :: Membrane.Buffer.Metric.unit(), sender_process :: Process.dest(), @@ -55,7 +56,7 @@ defmodule Membrane.Core.Element.DemandCounter do throttling_factor :: pos_integer() | nil ) :: t def new( - receiver_mode, + receiver_effective_flow_control, receiver_process, receiver_demand_unit, sender_process, @@ -72,11 +73,13 @@ defmodule Membrane.Core.Element.DemandCounter do true -> @distributed_default_throttling_factor end + receiver_status = DistributedFlowStatus.new({:resolved, receiver_effective_flow_control}) + %__MODULE__{ counter: counter, - receiver_mode: DistributedFlowMode.new(receiver_mode), + receiver_status: receiver_status, receiver_process: receiver_process, - sender_mode: DistributedFlowMode.new(:to_be_resolved), + sender_status: DistributedFlowStatus.new(:to_be_resolved), sender_process: sender_process, sender_pad_ref: sender_pad_ref, toilet_capacity: toilet_capacity || default_toilet_capacity(receiver_demand_unit), @@ -85,30 +88,30 @@ defmodule Membrane.Core.Element.DemandCounter do } end - @spec set_sender_mode(t, EffectiveFlowController.effective_flow_control()) :: :ok - def set_sender_mode(%__MODULE__{} = demand_counter, mode) do - DistributedFlowMode.put( - demand_counter.sender_mode, + @spec set_sender_status(t, DistributedFlowStatus.value()) :: :ok + def set_sender_status(%__MODULE__{} = demand_counter, mode) do + DistributedFlowStatus.put( + demand_counter.sender_status, mode ) end - @spec get_sender_mode(t) :: flow_mode() - def get_sender_mode(%__MODULE__{} = demand_counter) do - DistributedFlowMode.get(demand_counter.sender_mode) + @spec get_sender_status(t) :: DistributedFlowStatus.value() + def get_sender_status(%__MODULE__{} = demand_counter) do + DistributedFlowStatus.get(demand_counter.sender_status) end - @spec set_receiver_mode(t, flow_mode()) :: :ok - def set_receiver_mode(%__MODULE__{} = demand_counter, mode) do - DistributedFlowMode.put( - demand_counter.receiver_mode, + @spec set_receiver_status(t, DistributedFlowStatus.value()) :: :ok + def set_receiver_status(%__MODULE__{} = demand_counter, mode) do + DistributedFlowStatus.put( + demand_counter.receiver_status, mode ) end - @spec get_receiver_mode(t) :: flow_mode() - def get_receiver_mode(%__MODULE__{} = demand_counter) do - DistributedFlowMode.get(demand_counter.receiver_mode) + @spec get_receiver_status(t) :: DistributedFlowStatus.value() + def get_receiver_status(%__MODULE__{} = demand_counter) do + DistributedFlowStatus.get(demand_counter.receiver_status) end @spec increase(t, non_neg_integer()) :: :ok @@ -153,8 +156,8 @@ defmodule Membrane.Core.Element.DemandCounter do demand_counter = %{demand_counter | buffered_decrementation: 0} if not demand_counter.toilet_overflowed? and - get_receiver_mode(demand_counter) == :pull and - get_sender_mode(demand_counter) == :push and + get_receiver_status(demand_counter) == {:resolved, :pull} and + get_sender_status(demand_counter) == {:resolved, :push} and -1 * counter_value > demand_counter.toilet_capacity do overflow(demand_counter, counter_value) else diff --git a/lib/membrane/core/element/demand_counter/distributed_flow_mode.ex b/lib/membrane/core/element/demand_counter/distributed_flow_mode.ex deleted file mode 100644 index a56369b60..000000000 --- a/lib/membrane/core/element/demand_counter/distributed_flow_mode.ex +++ /dev/null @@ -1,38 +0,0 @@ -defmodule Membrane.Core.Element.DemandCounter.DistributedFlowMode do - @moduledoc false - - alias Membrane.Core.Element.DemandCounter.DistributedAtomic - alias Membrane.Core.Element.EffectiveFlowController - - @type t :: DistributedAtomic.t() - @type flow_mode_value :: - EffectiveFlowController.effective_flow_control() | :to_be_resolved - - @spec new(flow_mode_value) :: t - def new(initial_value) do - initial_value - |> flow_mode_to_int() - |> DistributedAtomic.new() - end - - @spec get(t) :: flow_mode_value() - def get(distributed_atomic) do - distributed_atomic - |> DistributedAtomic.get() - |> int_to_flow_mode() - end - - @spec put(t, flow_mode_value()) :: :ok - def put(distributed_atomic, value) do - value = flow_mode_to_int(value) - DistributedAtomic.put(distributed_atomic, value) - end - - defp int_to_flow_mode(0), do: :to_be_resolved - defp int_to_flow_mode(1), do: :push - defp int_to_flow_mode(2), do: :pull - - defp flow_mode_to_int(:to_be_resolved), do: 0 - defp flow_mode_to_int(:push), do: 1 - defp flow_mode_to_int(:pull), do: 2 -end diff --git a/lib/membrane/core/element/demand_counter/distributed_flow_status.ex b/lib/membrane/core/element/demand_counter/distributed_flow_status.ex new file mode 100644 index 000000000..adcc38597 --- /dev/null +++ b/lib/membrane/core/element/demand_counter/distributed_flow_status.ex @@ -0,0 +1,37 @@ +defmodule Membrane.Core.Element.DemandCounter.DistributedFlowStatus do + @moduledoc false + + alias Membrane.Core.Element.DemandCounter.DistributedAtomic + alias Membrane.Core.Element.EffectiveFlowController + + @type t :: DistributedAtomic.t() + @type value :: {:resolved, EffectiveFlowController.effective_flow_control()} | :to_be_resolved + + @spec new(value) :: t + def new(initial_value) do + initial_value + |> flow_status_to_int() + |> DistributedAtomic.new() + end + + @spec get(t) :: value() + def get(distributed_atomic) do + distributed_atomic + |> DistributedAtomic.get() + |> int_to_flow_status() + end + + @spec put(t, value()) :: :ok + def put(distributed_atomic, value) do + value = flow_status_to_int(value) + DistributedAtomic.put(distributed_atomic, value) + end + + defp int_to_flow_status(0), do: :to_be_resolved + defp int_to_flow_status(1), do: {:resolved, :push} + defp int_to_flow_status(2), do: {:resolved, :pull} + + defp flow_status_to_int(:to_be_resolved), do: 0 + defp flow_status_to_int({:resolved, :push}), do: 1 + defp flow_status_to_int({:resolved, :pull}), do: 2 +end diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 017e9a6e6..925f85d13 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -59,7 +59,7 @@ defmodule Membrane.Core.Element.EffectiveFlowController do other_effective_flow_control == state.effective_flow_control -> :ok = PadModel.get_data!(state, input_pad_ref, :demand_counter) - |> DemandCounter.set_receiver_mode(state.effective_flow_control) + |> DemandCounter.set_receiver_status({:resolved, state.effective_flow_control}) state @@ -106,8 +106,13 @@ defmodule Membrane.Core.Element.EffectiveFlowController do |> Enum.filter(fn {_ref, %{flow_control: flow_control}} -> flow_control == :auto end) |> Enum.reduce(state, fn {_ref, %{direction: :output} = pad_data}, state -> - :ok = DemandCounter.set_sender_mode(pad_data.demand_counter, new_effective_flow_control) - :ok = DemandCounter.set_receiver_mode(pad_data.demand_counter, :to_be_resolved) + :ok = + DemandCounter.set_sender_status( + pad_data.demand_counter, + {:resolved, new_effective_flow_control} + ) + + :ok = DemandCounter.set_receiver_status(pad_data.demand_counter, :to_be_resolved) Message.send( pad_data.pid, @@ -119,15 +124,23 @@ defmodule Membrane.Core.Element.EffectiveFlowController do {pad_ref, %{direction: :input} = pad_data}, state when last_changed_pad != nil -> if pad_ref == last_changed_pad or - DemandCounter.get_receiver_mode(pad_data.demand_counter) != :to_be_resolved do + DemandCounter.get_receiver_status(pad_data.demand_counter) != :to_be_resolved do :ok = - DemandCounter.set_receiver_mode(pad_data.demand_counter, new_effective_flow_control) + DemandCounter.set_receiver_status( + pad_data.demand_counter, + {:resolved, new_effective_flow_control} + ) end AutoFlowUtils.auto_adjust_demand_counter(pad_ref, state) {pad_ref, %{direction: :input} = pad_data}, state -> - :ok = DemandCounter.set_receiver_mode(pad_data.demand_counter, new_effective_flow_control) + :ok = + DemandCounter.set_receiver_status( + pad_data.demand_counter, + {:resolved, new_effective_flow_control} + ) + AutoFlowUtils.auto_adjust_demand_counter(pad_ref, state) end) end diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index f53631014..3ec94a7b2 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -194,7 +194,19 @@ defmodule Membrane.Core.Element.PadController do state ) - state = PadController.handle_input_pad_added(endpoint.pad_ref, state) + state = + case PadModel.get_data!(state, endpoint.pad_ref) do + %{flow_control: :auto, direction: :input} = pad_data -> + EffectiveFlowController.handle_sender_effective_flow_control( + pad_data.ref, + pad_data.other_effective_flow_control, + state + ) + + _pad_data -> + state + end + state = maybe_handle_pad_added(endpoint.pad_ref, state) {{:ok, {endpoint, info, link_metadata}}, state} end @@ -244,18 +256,6 @@ defmodule Membrane.Core.Element.PadController do end end - @spec handle_input_pad_added(Pad.ref(), State.t()) :: State.t() - def handle_input_pad_added(pad_ref, state) do - with %{pads_data: %{^pad_ref => %{flow_control: :auto, direction: :input} = pad_data}} <- - state do - EffectiveFlowController.handle_sender_effective_flow_control( - pad_ref, - pad_data.other_effective_flow_control, - state - ) - end - end - defp resolve_demand_units(output_info, input_info) do output_demand_unit = if output_info[:flow_control] == :push, @@ -299,9 +299,9 @@ defmodule Membrane.Core.Element.PadController do }) :ok = - DemandCounter.set_sender_mode( + DemandCounter.set_sender_status( data.demand_counter, - EffectiveFlowController.get_pad_effective_flow_control(data.ref, state) + {:resolved, EffectiveFlowController.get_pad_effective_flow_control(data.ref, state)} ) data = data |> Map.merge(init_pad_direction_data(data, endpoint.pad_props, metadata, state)) diff --git a/test/membrane/core/element/demand_counter_test.exs b/test/membrane/core/element/demand_counter_test.exs index f4c308ea7..df48fc227 100644 --- a/test/membrane/core/element/demand_counter_test.exs +++ b/test/membrane/core/element/demand_counter_test.exs @@ -44,17 +44,25 @@ defmodule Membrane.Core.Element.DemandCounterTest do test "if setting receiver and sender modes works properly" do demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :output) - :ok = DemandCounter.set_receiver_mode(demand_counter, :push) - assert DemandCounter.DistributedFlowMode.get(demand_counter.receiver_mode) == :push + :ok = DemandCounter.set_receiver_status(demand_counter, {:resolved, :push}) - :ok = DemandCounter.set_receiver_mode(demand_counter, :pull) - assert DemandCounter.DistributedFlowMode.get(demand_counter.receiver_mode) == :pull + assert DemandCounter.DistributedFlowStatus.get(demand_counter.receiver_status) == + {:resolved, :push} - :ok = DemandCounter.set_sender_mode(demand_counter, :push) - assert DemandCounter.DistributedFlowMode.get(demand_counter.sender_mode) == :push + :ok = DemandCounter.set_receiver_status(demand_counter, {:resolved, :pull}) - :ok = DemandCounter.set_sender_mode(demand_counter, :pull) - assert DemandCounter.DistributedFlowMode.get(demand_counter.sender_mode) == :pull + assert DemandCounter.DistributedFlowStatus.get(demand_counter.receiver_status) == + {:resolved, :pull} + + :ok = DemandCounter.set_sender_status(demand_counter, {:resolved, :push}) + + assert DemandCounter.DistributedFlowStatus.get(demand_counter.sender_status) == + {:resolved, :push} + + :ok = DemandCounter.set_sender_status(demand_counter, {:resolved, :pull}) + + assert DemandCounter.DistributedFlowStatus.get(demand_counter.sender_status) == + {:resolved, :pull} end test "if toilet overflows, only and only when it should" do @@ -64,21 +72,21 @@ defmodule Membrane.Core.Element.DemandCounterTest do demand_counter = DemandCounter.new(:pull, sleeping_process, :buffers, self(), :output) - :ok = DemandCounter.set_sender_mode(demand_counter, :push) + :ok = DemandCounter.set_sender_status(demand_counter, {:resolved, :push}) demand_counter = DemandCounter.decrease(demand_counter, 100) refute_receive {:DOWN, ^monitor_ref, :process, _pid, _reason} - possible_modes = [:push, :pull, :to_be_resolved] + possible_statuses = [{:resolved, :push}, {:resolved, :pull}, :to_be_resolved] demand_counter = - for mode_1 <- possible_modes, mode_2 <- possible_modes do - {mode_1, mode_2} + for status_1 <- possible_statuses, status_2 <- possible_statuses do + {status_1, status_2} end - |> List.delete({:push, :pull}) - |> Enum.reduce(demand_counter, fn {sender_mode, receiver_mode}, demand_counter -> - :ok = DemandCounter.set_sender_mode(demand_counter, sender_mode) - :ok = DemandCounter.set_receiver_mode(demand_counter, receiver_mode) + |> List.delete({{:resolved, :push}, {:resolved, :pull}}) + |> Enum.reduce(demand_counter, fn {sender_status, receiver_status}, demand_counter -> + :ok = DemandCounter.set_sender_status(demand_counter, sender_status) + :ok = DemandCounter.set_receiver_status(demand_counter, receiver_status) demand_counter = DemandCounter.decrease(demand_counter, 1000) refute_receive {:DOWN, ^monitor_ref, :process, _pid, _reason} @@ -86,8 +94,8 @@ defmodule Membrane.Core.Element.DemandCounterTest do demand_counter end) - :ok = DemandCounter.set_sender_mode(demand_counter, :push) - :ok = DemandCounter.set_receiver_mode(demand_counter, :pull) + :ok = DemandCounter.set_sender_status(demand_counter, {:resolved, :push}) + :ok = DemandCounter.set_receiver_status(demand_counter, {:resolved, :pull}) _demand_counter = DemandCounter.decrease(demand_counter, 1000) assert_receive {:DOWN, ^monitor_ref, :process, _pid, _reason} From e713e528e1e19d26061694045f170d60315b7ed6 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 18 May 2023 14:29:04 +0200 Subject: [PATCH 54/64] Refactor getting demand unit in demand controller --- lib/membrane/core/element/demand_controller.ex | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index cbfafc5eb..0dfc0502d 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -90,7 +90,12 @@ defmodule Membrane.Core.Element.DemandController do def decrease_demand_by_outgoing_buffers(pad_ref, buffers, state) do pad_data = PadModel.get_data!(state, pad_ref) - demand_unit = pad_data.demand_unit || pad_data.other_demand_unit || :buffers + demand_unit = + case pad_data.flow_control do + :push -> pad_data.other_demand_unit || :buffers + _pull_or_auto -> pad_data.demand_unit + end + buffers_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) demand_snapshot = pad_data.demand_snapshot - buffers_size From 1a1957a6294fe0393f35067921fbb2e78c13437d Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Thu, 18 May 2023 15:04:36 +0200 Subject: [PATCH 55/64] Rename lacking_buffers_size to demand --- .../core/element/buffer_controller.ex | 5 +- .../demand_controller/auto_flow_utils.ex | 6 +-- lib/membrane/core/element/input_queue.ex | 16 +++--- lib/membrane/core/element/pad_controller.ex | 1 - lib/membrane/element/pad_data.ex | 4 +- .../core/element/input_queue_test.exs | 52 +++++++++---------- 6 files changed, 41 insertions(+), 43 deletions(-) diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index 15f32561e..96446c72a 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -57,11 +57,10 @@ defmodule Membrane.Core.Element.BufferController do @spec do_handle_buffer(Pad.ref(), PadModel.pad_data(), [Buffer.t()] | Buffer.t(), State.t()) :: State.t() defp do_handle_buffer(pad_ref, %{flow_control: :auto} = data, buffers, state) do - %{lacking_buffer_size: lacking_buffer_size, demand_unit: demand_unit} = data + %{demand: demand, demand_unit: demand_unit} = data buf_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) - state = - PadModel.set_data!(state, pad_ref, :lacking_buffer_size, lacking_buffer_size - buf_size) + state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) state = AutoFlowUtils.auto_adjust_demand_counter(pad_ref, state) exec_buffer_callback(pad_ref, buffers, state) diff --git a/lib/membrane/core/element/demand_controller/auto_flow_utils.ex b/lib/membrane/core/element/demand_controller/auto_flow_utils.ex index c41183b7c..21d4ce90b 100644 --- a/lib/membrane/core/element/demand_controller/auto_flow_utils.ex +++ b/lib/membrane/core/element/demand_controller/auto_flow_utils.ex @@ -26,13 +26,13 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do defp do_auto_adjust_demand_counter(pad_data, state) when is_input_auto_pad_data(pad_data) do if increase_demand_counter?(pad_data, state) do - diff = pad_data.auto_demand_size - pad_data.lacking_buffer_size + diff = pad_data.auto_demand_size - pad_data.demand :ok = DemandCounter.increase(pad_data.demand_counter, diff) PadModel.set_data!( state, pad_data.ref, - :lacking_buffer_size, + :demand, pad_data.auto_demand_size ) else @@ -46,7 +46,7 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do defp increase_demand_counter?(pad_data, state) do state.effective_flow_control == :pull and - pad_data.lacking_buffer_size < pad_data.auto_demand_size / 2 and + pad_data.demand < pad_data.auto_demand_size / 2 and Enum.all?(pad_data.associated_pads, &demand_counter_positive?(&1, state)) end diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index 334bbcae1..5d3e8e37e 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -32,7 +32,7 @@ defmodule Membrane.Core.Element.InputQueue do log_tag: String.t(), target_size: pos_integer(), size: non_neg_integer(), - lacking_buffer_size: non_neg_integer(), + demand: non_neg_integer(), inbound_metric: module(), outbound_metric: module(), linked_output_ref: Pad.ref(), @@ -49,7 +49,7 @@ defmodule Membrane.Core.Element.InputQueue do :linked_output_ref ] - defstruct @enforce_keys ++ [size: 0, lacking_buffer_size: 0] + defstruct @enforce_keys ++ [size: 0, demand: 0] @default_target_size_factor 40 @@ -130,7 +130,7 @@ defmodule Membrane.Core.Element.InputQueue do %__MODULE__{ q: q, size: size, - lacking_buffer_size: lacking_buffer_size, + demand: demand, inbound_metric: inbound_metric, outbound_metric: outbound_metric } = input_queue, @@ -147,7 +147,7 @@ defmodule Membrane.Core.Element.InputQueue do input_queue | q: q |> @qe.push({:buffers, v, inbound_metric_buffer_size, outbound_metric_buffer_size}), size: size + inbound_metric_buffer_size, - lacking_buffer_size: lacking_buffer_size - inbound_metric_buffer_size + demand: demand - inbound_metric_buffer_size } end @@ -279,11 +279,11 @@ defmodule Membrane.Core.Element.InputQueue do size: size, target_size: target_size, demand_counter: demand_counter, - lacking_buffer_size: lacking_buffer_size + demand: demand } = input_queue ) - when target_size > size + lacking_buffer_size do - diff = max(target_size - size - lacking_buffer_size, div(target_size, 2)) + when target_size > size + demand do + diff = max(target_size - size - demand, div(target_size, 2)) """ Increasing DemandCounter linked to #{inspect(input_queue.linked_output_ref)} by #{inspect(diff)} @@ -292,7 +292,7 @@ defmodule Membrane.Core.Element.InputQueue do |> Membrane.Logger.debug_verbose() :ok = DemandCounter.increase(demand_counter, diff) - %{input_queue | lacking_buffer_size: lacking_buffer_size + diff} + %{input_queue | demand: demand + diff} end defp maybe_increase_demand_counter(%__MODULE__{} = input_queue), do: input_queue diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 3ec94a7b2..f9c702b66 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -4,7 +4,6 @@ defmodule Membrane.Core.Element.PadController do # Module handling linking and unlinking pads. use Bunch - alias Membrane.Core.Element.PadController alias Membrane.{LinkError, Pad} alias Membrane.Core.{CallbackHandler, Child, Events, Message, Observability} alias Membrane.Core.Child.PadModel diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 4f82e80c3..19452e449 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -56,7 +56,7 @@ defmodule Membrane.Element.PadData do # Field used in DemandController.AutoFlowUtils and InputQueue, to caluclate, how much DemandCounter should be increased. # Contains amount of data (:buffers/:bytes), that has been demanded from the element on the other side of link, but # hasn't arrived yet. Unused for output pads. - lacking_buffer_size: private_field, + demand: private_field, manual_demand_size: private_field, associated_pads: private_field, sticky_events: private_field, @@ -86,7 +86,7 @@ defmodule Membrane.Element.PadData do auto_demand_size: nil, sticky_messages: [], demand_counter: nil, - lacking_buffer_size: 0, + demand: 0, manual_demand_size: 0, associated_pads: [], sticky_events: [], diff --git a/test/membrane/core/element/input_queue_test.exs b/test/membrane/core/element/input_queue_test.exs index e250775da..e0480860d 100644 --- a/test/membrane/core/element/input_queue_test.exs +++ b/test/membrane/core/element/input_queue_test.exs @@ -41,7 +41,7 @@ defmodule Membrane.Core.Element.InputQueueTest do outbound_metric: context.expected_metric, linked_output_ref: context.linked_output_ref, size: 0, - lacking_buffer_size: context.target_queue_size + demand: context.target_queue_size } assert context.target_queue_size == DemandCounter.get(context.demand_counter) @@ -87,14 +87,14 @@ defmodule Membrane.Core.Element.InputQueueTest do setup do {:ok, %{ - lacking_buffer_size: 30, + demand: 30, size: 10, q: Qex.new() |> Qex.push({:buffers, [], 3, 3}), payload: <<1, 2, 3>> }} end - test "updated properly `size` and `lacking_buffer_size` when `:metric` is `Buffer.Metric.Count`", + test "updated properly `size` and `demand` when `:metric` is `Buffer.Metric.Count`", context do input_queue = struct(InputQueue, @@ -102,17 +102,17 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_metric: Buffer.Metric.Count, outbound_metric: Buffer.Metric.Count, q: context.q, - lacking_buffer_size: context.lacking_buffer_size + demand: context.demand ) v = [%Buffer{payload: context.payload}] updated_input_queue = InputQueue.store(input_queue, :buffers, v) assert updated_input_queue.size == context.size + 1 - assert updated_input_queue.lacking_buffer_size == context.lacking_buffer_size - 1 + assert updated_input_queue.demand == context.demand - 1 end - test "updated properly `size` and `lacking_buffer_size` when `:metric` is `Buffer.Metric.ByteSize`", + test "updated properly `size` and `demand` when `:metric` is `Buffer.Metric.ByteSize`", context do input_queue = struct(InputQueue, @@ -120,7 +120,7 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_metric: Buffer.Metric.ByteSize, outbound_metric: Buffer.Metric.ByteSize, q: context.q, - lacking_buffer_size: context.lacking_buffer_size + demand: context.demand ) v = [%Buffer{payload: context.payload}] @@ -128,8 +128,8 @@ defmodule Membrane.Core.Element.InputQueueTest do assert updated_input_queue.size == context.size + byte_size(context.payload) - assert updated_input_queue.lacking_buffer_size == - context.lacking_buffer_size - byte_size(context.payload) + assert updated_input_queue.demand == + context.demand - byte_size(context.payload) end test "append buffer to the queue", context do @@ -139,7 +139,7 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_metric: Buffer.Metric.ByteSize, outbound_metric: Buffer.Metric.ByteSize, q: context.q, - lacking_buffer_size: context.lacking_buffer_size + demand: context.demand ) v = [%Buffer{payload: context.payload}] @@ -156,7 +156,7 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_metric: Buffer.Metric.ByteSize, outbound_metric: Buffer.Metric.ByteSize, q: context.q, - lacking_buffer_size: context.lacking_buffer_size + demand: context.demand ) v = %Event{} @@ -173,7 +173,7 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_metric: Buffer.Metric.ByteSize, outbound_metric: Buffer.Metric.ByteSize, q: context.q, - lacking_buffer_size: context.lacking_buffer_size + demand: context.demand ) v = %Event{} @@ -204,9 +204,9 @@ defmodule Membrane.Core.Element.InputQueueTest do test "return {:empty, []} when the queue is empty", %{input_queue: input_queue} do old_counter_value = DemandCounter.get(input_queue.demand_counter) - old_lacking_buffer_size = input_queue.lacking_buffer_size + old_demand = input_queue.demand - assert {{:empty, []}, %InputQueue{size: 0, lacking_buffer_size: ^old_lacking_buffer_size}} = + assert {{:empty, []}, %InputQueue{size: 0, demand: ^old_demand}} = InputQueue.take(input_queue, 1) assert old_counter_value == DemandCounter.get(input_queue.demand_counter) @@ -219,7 +219,7 @@ defmodule Membrane.Core.Element.InputQueueTest do |> InputQueue.take(1) assert new_input_queue.size == 9 - assert new_input_queue.lacking_buffer_size >= 31 + assert new_input_queue.demand >= 31 assert DemandCounter.get(new_input_queue.demand_counter) >= 41 end end @@ -239,7 +239,7 @@ defmodule Membrane.Core.Element.InputQueueTest do input_queue = struct(InputQueue, size: size, - lacking_buffer_size: 94, + demand: 94, target_size: 100, inbound_metric: Buffer.Metric.Count, outbound_metric: Buffer.Metric.Count, @@ -265,12 +265,12 @@ defmodule Membrane.Core.Element.InputQueueTest do test "increase DemandCounter hen there are not enough buffers", context do old_counter_value = DemandCounter.get(context.input_queue.demand_counter) - old_lacking_buffer_size = context.input_queue.lacking_buffer_size + old_demand = context.input_queue.demand {_output, input_queue} = InputQueue.take(context.input_queue, 10) assert old_counter_value < DemandCounter.get(input_queue.demand_counter) - assert old_lacking_buffer_size < input_queue.lacking_buffer_size + assert old_demand < input_queue.demand end test "return `to_take` buffers from the queue when there are enough buffers and buffers don't have to be split", @@ -314,7 +314,7 @@ defmodule Membrane.Core.Element.InputQueueTest do }) assert_receive Message.new(:demand_counter_increased, :output_pad_ref) - assert queue.lacking_buffer_size == 10 + assert queue.demand == 10 queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) assert queue.size == 4 queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) @@ -322,11 +322,11 @@ defmodule Membrane.Core.Element.InputQueueTest do queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) queue = Map.update!(queue, :demand_counter, &DemandCounter.decrease(&1, 16)) assert queue.size == 16 - assert queue.lacking_buffer_size == -6 + assert queue.demand == -6 {out, queue} = InputQueue.take(queue, 2) assert bufs_size(out, :buffers) == 2 assert queue.size == 4 - assert queue.lacking_buffer_size >= 6 + assert queue.demand >= 6 assert_receive Message.new(:demand_counter_increased, :output_pad_ref) queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) @@ -334,7 +334,7 @@ defmodule Membrane.Core.Element.InputQueueTest do {out, queue} = InputQueue.take(queue, 1) assert bufs_size(out, :buffers) == 1 assert queue.size == 8 - assert queue.lacking_buffer_size >= 2 + assert queue.demand >= 2 end test "if the queue works properly for :buffers input metric and :bytes output metric" do @@ -351,7 +351,7 @@ defmodule Membrane.Core.Element.InputQueueTest do }) assert_receive Message.new(:demand_counter_increased, :output_pad_ref) - assert queue.lacking_buffer_size == 3 + assert queue.demand == 3 queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) assert queue.size == 1 queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) @@ -359,16 +359,16 @@ defmodule Membrane.Core.Element.InputQueueTest do queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) queue = Map.update!(queue, :demand_counter, &DemandCounter.decrease(&1, 4)) assert queue.size == 4 - assert queue.lacking_buffer_size == -1 + assert queue.demand == -1 {out, queue} = InputQueue.take(queue, 2) assert bufs_size(out, :bytes) == 2 assert queue.size == 4 - assert queue.lacking_buffer_size == -1 + assert queue.demand == -1 refute_receive Message.new(:demand_counter_increased, :output_pad_ref) {out, queue} = InputQueue.take(queue, 11) assert bufs_size(out, :bytes) == 11 assert queue.size == 2 - assert queue.lacking_buffer_size == 1 + assert queue.demand == 1 assert_receive Message.new(:demand_counter_increased, :output_pad_ref) end From 71ca3e7fe312a6452b1d364d7ec37e43ca8c302b Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 19 May 2023 14:47:02 +0200 Subject: [PATCH 56/64] Implement suggestion from CR --- .../core/element/demand_controller.ex | 9 +------- .../core/element/effective_flow_controller.ex | 21 ++++++------------- lib/membrane/core/element/pad_controller.ex | 11 ++-------- 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 0dfc0502d..3d196c569 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -89,14 +89,7 @@ defmodule Membrane.Core.Element.DemandController do @spec decrease_demand_by_outgoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: State.t() def decrease_demand_by_outgoing_buffers(pad_ref, buffers, state) do pad_data = PadModel.get_data!(state, pad_ref) - - demand_unit = - case pad_data.flow_control do - :push -> pad_data.other_demand_unit || :buffers - _pull_or_auto -> pad_data.demand_unit - end - - buffers_size = Buffer.Metric.from_unit(demand_unit).buffers_size(buffers) + buffers_size = Buffer.Metric.from_unit(pad_data.demand_unit).buffers_size(buffers) demand_snapshot = pad_data.demand_snapshot - buffers_size demand_counter = DemandCounter.decrease(pad_data.demand_counter, buffers_size) diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 925f85d13..4117f7a5a 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -72,7 +72,7 @@ defmodule Membrane.Core.Element.EffectiveFlowController do end @spec resolve_effective_flow_control(Pad.ref(), State.t()) :: State.t() - def resolve_effective_flow_control(last_changed_pad \\ nil, state) do + def resolve_effective_flow_control(triggering_pad \\ nil, state) do senders_flow_modes = Map.values(state.pads_data) |> Enum.filter(&(&1.direction == :input && &1.flow_control == :auto)) @@ -85,17 +85,17 @@ defmodule Membrane.Core.Element.EffectiveFlowController do true -> state.effective_flow_control end - set_effective_flow_control(new_effective_flow_control, last_changed_pad, state) + set_effective_flow_control(new_effective_flow_control, triggering_pad, state) end defp set_effective_flow_control( effective_flow_control, - _last_changed_pad, + _triggering_pad, %{effective_flow_control: effective_flow_control} = state ), do: state - defp set_effective_flow_control(new_effective_flow_control, last_changed_pad, state) do + defp set_effective_flow_control(new_effective_flow_control, triggering_pad, state) do Membrane.Logger.debug( "Transiting `flow_control: :auto` pads to #{inspect(new_effective_flow_control)} effective flow control" ) @@ -122,8 +122,8 @@ defmodule Membrane.Core.Element.EffectiveFlowController do state - {pad_ref, %{direction: :input} = pad_data}, state when last_changed_pad != nil -> - if pad_ref == last_changed_pad or + {pad_ref, %{direction: :input} = pad_data}, state -> + if triggering_pad in [pad_ref, nil] or DemandCounter.get_receiver_status(pad_data.demand_counter) != :to_be_resolved do :ok = DemandCounter.set_receiver_status( @@ -133,15 +133,6 @@ defmodule Membrane.Core.Element.EffectiveFlowController do end AutoFlowUtils.auto_adjust_demand_counter(pad_ref, state) - - {pad_ref, %{direction: :input} = pad_data}, state -> - :ok = - DemandCounter.set_receiver_status( - pad_data.demand_counter, - {:resolved, new_effective_flow_control} - ) - - AutoFlowUtils.auto_adjust_demand_counter(pad_ref, state) end) end end diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index f9c702b66..affdb4778 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -256,15 +256,8 @@ defmodule Membrane.Core.Element.PadController do end defp resolve_demand_units(output_info, input_info) do - output_demand_unit = - if output_info[:flow_control] == :push, - do: nil, - else: output_info[:demand_unit] || input_info[:demand_unit] || :buffers - - input_demand_unit = - if input_info[:flow_control] == :push, - do: nil, - else: input_info[:demand_unit] || output_info[:demand_unit] || :buffers + output_demand_unit = output_info[:demand_unit] || input_info[:demand_unit] || :buffers + input_demand_unit = input_info[:demand_unit] || output_info[:demand_unit] || :buffers {output_demand_unit, input_demand_unit} end From 7959a6ad279e3432acee4b7374e939ea180e44ee Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 19 May 2023 15:46:39 +0200 Subject: [PATCH 57/64] Fix Error log --- lib/membrane/core/element/demand_handler.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/membrane/core/element/demand_handler.ex b/lib/membrane/core/element/demand_handler.ex index bd5dd2c54..6f8e85b48 100644 --- a/lib/membrane/core/element/demand_handler.ex +++ b/lib/membrane/core/element/demand_handler.ex @@ -107,7 +107,7 @@ defmodule Membrane.Core.Element.DemandHandler do if new_manual_demand_size < 0 do raise Membrane.ElementError, - "Demand altering function requested negative demand on pad #{inspect(pad_ref)} in #{state.module}" + "Demand altering function requested negative demand on pad #{inspect(pad_ref)} in #{inspect(state.module)}" end PadModel.set_data!(state, pad_ref, :manual_demand_size, new_manual_demand_size) From 812cf9c5ad7f50cc08ad9ebc606fba05e02aa44b Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 19 May 2023 16:03:19 +0200 Subject: [PATCH 58/64] Rename DemandCounter to AtomicDemand --- lib/membrane/core/element.ex | 4 +- lib/membrane/core/element/action_handler.ex | 2 +- .../{demand_counter.ex => atomic_demand.ex} | 104 ++++++++-------- .../atomic_flow_status.ex} | 4 +- .../distributed_atomic.ex | 4 +- .../distributed_atomic/worker.ex | 2 +- .../core/element/buffer_controller.ex | 2 +- .../core/element/demand_controller.ex | 46 +++---- .../demand_controller/auto_flow_utils.ex | 36 +++--- .../core/element/effective_flow_controller.ex | 20 ++-- lib/membrane/core/element/input_queue.ex | 28 ++--- lib/membrane/core/element/pad_controller.ex | 24 ++-- lib/membrane/element/pad_data.ex | 12 +- .../core/element/action_handler_test.exs | 14 +-- .../core/element/demand_counter_test.exs | 112 +++++++++--------- .../core/element/event_controller_test.exs | 10 +- .../core/element/input_queue_test.exs | 62 +++++----- .../element/lifecycle_controller_test.exs | 8 +- .../element/stream_format_controller_test.exs | 8 +- test/membrane/core/element_test.exs | 24 ++-- 20 files changed, 262 insertions(+), 264 deletions(-) rename lib/membrane/core/element/{demand_counter.ex => atomic_demand.ex} (58%) rename lib/membrane/core/element/{demand_counter/distributed_flow_status.ex => atomic_demand/atomic_flow_status.ex} (88%) rename lib/membrane/core/element/{demand_counter => atomic_demand}/distributed_atomic.ex (95%) rename lib/membrane/core/element/{demand_counter => atomic_demand}/distributed_atomic/worker.ex (94%) diff --git a/lib/membrane/core/element.ex b/lib/membrane/core/element.ex index e4b67a4c9..b2c24ca0e 100644 --- a/lib/membrane/core/element.ex +++ b/lib/membrane/core/element.ex @@ -173,8 +173,8 @@ defmodule Membrane.Core.Element do @compile {:inline, do_handle_info: 2} - defp do_handle_info(Message.new(:demand_counter_increased, pad_ref), state) do - state = DemandController.snapshot_demand_counter(pad_ref, state) + defp do_handle_info(Message.new(:atomic_demand_increased, pad_ref), state) do + state = DemandController.snapshot_atomic_demand(pad_ref, state) {:noreply, state} end diff --git a/lib/membrane/core/element/action_handler.ex b/lib/membrane/core/element/action_handler.ex index 6f5e06847..318418196 100644 --- a/lib/membrane/core/element/action_handler.ex +++ b/lib/membrane/core/element/action_handler.ex @@ -319,7 +319,7 @@ defmodule Membrane.Core.Element.ActionHandler do |> PadModel.set_data!(pad_ref, :start_of_stream?, true) Message.send(pid, :buffer, buffers, for_pad: other_ref) - DemandController.snapshot_demand_counter(pad_ref, state) + DemandController.snapshot_atomic_demand(pad_ref, state) else %{direction: :input} -> raise PadDirectionError, action: :buffer, direction: :input, pad: pad_ref diff --git a/lib/membrane/core/element/demand_counter.ex b/lib/membrane/core/element/atomic_demand.ex similarity index 58% rename from lib/membrane/core/element/demand_counter.ex rename to lib/membrane/core/element/atomic_demand.ex index adb6c930c..a82212926 100644 --- a/lib/membrane/core/element/demand_counter.ex +++ b/lib/membrane/core/element/atomic_demand.ex @@ -1,11 +1,11 @@ -defmodule Membrane.Core.Element.DemandCounter do +defmodule Membrane.Core.Element.AtomicDemand do @moduledoc false - alias Membrane.Core.Element.DemandCounter.DistributedFlowStatus + alias Membrane.Core.Element.AtomicDemand.AtomicFlowStatus alias Membrane.Core.Element.EffectiveFlowController alias __MODULE__.{ DistributedAtomic, - DistributedFlowStatus + AtomicFlowStatus } require Membrane.Core.Message, as: Message @@ -18,9 +18,9 @@ defmodule Membrane.Core.Element.DemandCounter do @opaque t :: %__MODULE__{ counter: DistributedAtomic.t(), - receiver_status: DistributedFlowStatus.t(), + receiver_status: AtomicFlowStatus.t(), receiver_process: Process.dest(), - sender_status: DistributedFlowStatus.t(), + sender_status: AtomicFlowStatus.t(), sender_process: Process.dest(), sender_pad_ref: Pad.ref(), toilet_capacity: neg_integer(), @@ -30,7 +30,7 @@ defmodule Membrane.Core.Element.DemandCounter do receiver_demand_unit: Membrane.Buffer.Metric.unit() } - @type flow_mode :: DistributedFlowStatus.value() + @type flow_mode :: AtomicFlowStatus.value() @enforce_keys [ :counter, @@ -73,13 +73,13 @@ defmodule Membrane.Core.Element.DemandCounter do true -> @distributed_default_throttling_factor end - receiver_status = DistributedFlowStatus.new({:resolved, receiver_effective_flow_control}) + receiver_status = AtomicFlowStatus.new({:resolved, receiver_effective_flow_control}) %__MODULE__{ counter: counter, receiver_status: receiver_status, receiver_process: receiver_process, - sender_status: DistributedFlowStatus.new(:to_be_resolved), + sender_status: AtomicFlowStatus.new(:to_be_resolved), sender_process: sender_process, sender_pad_ref: sender_pad_ref, toilet_capacity: toilet_capacity || default_toilet_capacity(receiver_demand_unit), @@ -88,42 +88,42 @@ defmodule Membrane.Core.Element.DemandCounter do } end - @spec set_sender_status(t, DistributedFlowStatus.value()) :: :ok - def set_sender_status(%__MODULE__{} = demand_counter, mode) do - DistributedFlowStatus.put( - demand_counter.sender_status, + @spec set_sender_status(t, AtomicFlowStatus.value()) :: :ok + def set_sender_status(%__MODULE__{} = atomic_demand, mode) do + AtomicFlowStatus.put( + atomic_demand.sender_status, mode ) end - @spec get_sender_status(t) :: DistributedFlowStatus.value() - def get_sender_status(%__MODULE__{} = demand_counter) do - DistributedFlowStatus.get(demand_counter.sender_status) + @spec get_sender_status(t) :: AtomicFlowStatus.value() + def get_sender_status(%__MODULE__{} = atomic_demand) do + AtomicFlowStatus.get(atomic_demand.sender_status) end - @spec set_receiver_status(t, DistributedFlowStatus.value()) :: :ok - def set_receiver_status(%__MODULE__{} = demand_counter, mode) do - DistributedFlowStatus.put( - demand_counter.receiver_status, + @spec set_receiver_status(t, AtomicFlowStatus.value()) :: :ok + def set_receiver_status(%__MODULE__{} = atomic_demand, mode) do + AtomicFlowStatus.put( + atomic_demand.receiver_status, mode ) end - @spec get_receiver_status(t) :: DistributedFlowStatus.value() - def get_receiver_status(%__MODULE__{} = demand_counter) do - DistributedFlowStatus.get(demand_counter.receiver_status) + @spec get_receiver_status(t) :: AtomicFlowStatus.value() + def get_receiver_status(%__MODULE__{} = atomic_demand) do + AtomicFlowStatus.get(atomic_demand.receiver_status) end @spec increase(t, non_neg_integer()) :: :ok - def increase(%__MODULE__{} = demand_counter, value) do - new_counter_value = DistributedAtomic.add_get(demand_counter.counter, value) - old_counter_value = new_counter_value - value + def increase(%__MODULE__{} = atomic_demand, value) do + new_atomic_demand_value = DistributedAtomic.add_get(atomic_demand.counter, value) + old_atomic_demand_value = new_atomic_demand_value - value - if old_counter_value <= 0 do + if old_atomic_demand_value <= 0 do Message.send( - demand_counter.sender_process, - :demand_counter_increased, - demand_counter.sender_pad_ref + atomic_demand.sender_process, + :atomic_demand_increased, + atomic_demand.sender_pad_ref ) end @@ -131,41 +131,41 @@ defmodule Membrane.Core.Element.DemandCounter do end @spec decrease(t, non_neg_integer()) :: t - def decrease(%__MODULE__{} = demand_counter, value) do - demand_counter = Map.update!(demand_counter, :buffered_decrementation, &(&1 + value)) + def decrease(%__MODULE__{} = atomic_demand, value) do + atomic_demand = Map.update!(atomic_demand, :buffered_decrementation, &(&1 + value)) - if demand_counter.buffered_decrementation >= demand_counter.throttling_factor do - flush_buffered_decrementation(demand_counter) + if atomic_demand.buffered_decrementation >= atomic_demand.throttling_factor do + flush_buffered_decrementation(atomic_demand) else - demand_counter + atomic_demand end end @spec get(t) :: integer() - def get(%__MODULE__{} = demand_counter) do - DistributedAtomic.get(demand_counter.counter) + def get(%__MODULE__{} = atomic_demand) do + DistributedAtomic.get(atomic_demand.counter) end - defp flush_buffered_decrementation(demand_counter) do - counter_value = + defp flush_buffered_decrementation(atomic_demand) do + atomic_demand_value = DistributedAtomic.sub_get( - demand_counter.counter, - demand_counter.buffered_decrementation + atomic_demand.counter, + atomic_demand.buffered_decrementation ) - demand_counter = %{demand_counter | buffered_decrementation: 0} + atomic_demand = %{atomic_demand | buffered_decrementation: 0} - if not demand_counter.toilet_overflowed? and - get_receiver_status(demand_counter) == {:resolved, :pull} and - get_sender_status(demand_counter) == {:resolved, :push} and - -1 * counter_value > demand_counter.toilet_capacity do - overflow(demand_counter, counter_value) + if not atomic_demand.toilet_overflowed? and + get_receiver_status(atomic_demand) == {:resolved, :pull} and + get_sender_status(atomic_demand) == {:resolved, :push} and + -1 * atomic_demand_value > atomic_demand.toilet_capacity do + overflow(atomic_demand, atomic_demand_value) else - demand_counter + atomic_demand end end - defp overflow(demand_counter, counter_value) do + defp overflow(atomic_demand, atomic_demand_value) do Membrane.Logger.debug_verbose(~S""" Toilet overflow @@ -195,8 +195,8 @@ defmodule Membrane.Core.Element.DemandCounter do Membrane.Logger.error(""" Toilet overflow. - Demand counter reached the size of #{inspect(counter_value)}, which means that there are #{inspect(-1 * counter_value)} - #{demand_counter.receiver_demand_unit} sent without demanding it, which is above toilet capacity (#{inspect(demand_counter.toilet_capacity)}) + Atomic demand reached the size of #{inspect(atomic_demand_value)}, which means that there are #{inspect(-1 * atomic_demand_value)} + #{atomic_demand.receiver_demand_unit} sent without demanding it, which is above toilet capacity (#{inspect(atomic_demand.toilet_capacity)}) when storing data from output working in push mode. It means that some element in the pipeline processes the stream too slow or doesn't process it at all. To have control over amount of buffers being produced, consider using output in :auto or :manual @@ -204,9 +204,9 @@ defmodule Membrane.Core.Element.DemandCounter do You can also try changing the `toilet_capacity` in `Membrane.ChildrenSpec.via_in/3`. """) - Process.exit(demand_counter.receiver_process, :kill) + Process.exit(atomic_demand.receiver_process, :kill) - %{demand_counter | toilet_overflowed?: true} + %{atomic_demand | toilet_overflowed?: true} end defp default_toilet_capacity(demand_unit) do diff --git a/lib/membrane/core/element/demand_counter/distributed_flow_status.ex b/lib/membrane/core/element/atomic_demand/atomic_flow_status.ex similarity index 88% rename from lib/membrane/core/element/demand_counter/distributed_flow_status.ex rename to lib/membrane/core/element/atomic_demand/atomic_flow_status.ex index adcc38597..ae672a3da 100644 --- a/lib/membrane/core/element/demand_counter/distributed_flow_status.ex +++ b/lib/membrane/core/element/atomic_demand/atomic_flow_status.ex @@ -1,7 +1,7 @@ -defmodule Membrane.Core.Element.DemandCounter.DistributedFlowStatus do +defmodule Membrane.Core.Element.AtomicDemand.AtomicFlowStatus do @moduledoc false - alias Membrane.Core.Element.DemandCounter.DistributedAtomic + alias Membrane.Core.Element.AtomicDemand.DistributedAtomic alias Membrane.Core.Element.EffectiveFlowController @type t :: DistributedAtomic.t() diff --git a/lib/membrane/core/element/demand_counter/distributed_atomic.ex b/lib/membrane/core/element/atomic_demand/distributed_atomic.ex similarity index 95% rename from lib/membrane/core/element/demand_counter/distributed_atomic.ex rename to lib/membrane/core/element/atomic_demand/distributed_atomic.ex index 8f9cf81fc..86e730a24 100644 --- a/lib/membrane/core/element/demand_counter/distributed_atomic.ex +++ b/lib/membrane/core/element/atomic_demand/distributed_atomic.ex @@ -1,7 +1,7 @@ -defmodule Membrane.Core.Element.DemandCounter.DistributedAtomic do +defmodule Membrane.Core.Element.AtomicDemand.DistributedAtomic do @moduledoc false - # A module providing a common interface to access and modify a counter used in the DemandCounter implementation. + # A module providing a common interface to access and modify a counter used in the AtomicDemand implementation. # The counter uses :atomics module under the hood. # The module allows to create and modify the value of a counter in the same manner both when the counter is about to be accessed # from the same node, and from different nodes. diff --git a/lib/membrane/core/element/demand_counter/distributed_atomic/worker.ex b/lib/membrane/core/element/atomic_demand/distributed_atomic/worker.ex similarity index 94% rename from lib/membrane/core/element/demand_counter/distributed_atomic/worker.ex rename to lib/membrane/core/element/atomic_demand/distributed_atomic/worker.ex index e50b1d2ca..296e6e6d0 100644 --- a/lib/membrane/core/element/demand_counter/distributed_atomic/worker.ex +++ b/lib/membrane/core/element/atomic_demand/distributed_atomic/worker.ex @@ -1,4 +1,4 @@ -defmodule Membrane.Core.Element.DemandCounter.DistributedAtomic.Worker do +defmodule Membrane.Core.Element.AtomicDemand.DistributedAtomic.Worker do @moduledoc false # This is a GenServer created when the counter is about to be accessed from different nodes - it's running on the same node, diff --git a/lib/membrane/core/element/buffer_controller.ex b/lib/membrane/core/element/buffer_controller.ex index 96446c72a..ef3ec8410 100644 --- a/lib/membrane/core/element/buffer_controller.ex +++ b/lib/membrane/core/element/buffer_controller.ex @@ -62,7 +62,7 @@ defmodule Membrane.Core.Element.BufferController do state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) - state = AutoFlowUtils.auto_adjust_demand_counter(pad_ref, state) + state = AutoFlowUtils.auto_adjust_atomic_demand(pad_ref, state) exec_buffer_callback(pad_ref, buffers, state) end diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 3d196c569..654cf42ad 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -1,7 +1,7 @@ defmodule Membrane.Core.Element.DemandController do @moduledoc false - # Module handling changes in values of output pads demand counters + # Module handling changes in values of output pads demand atomics use Bunch @@ -11,7 +11,7 @@ defmodule Membrane.Core.Element.DemandController do alias Membrane.Core.Child.PadModel alias Membrane.Core.Element.{ - DemandCounter, + AtomicDemand, DemandHandler, PlaybackQueue, State @@ -22,54 +22,54 @@ defmodule Membrane.Core.Element.DemandController do require Membrane.Core.Child.PadModel require Membrane.Logger - @spec snapshot_demand_counter(Pad.ref(), State.t()) :: State.t() - def snapshot_demand_counter(pad_ref, state) do + @spec snapshot_atomic_demand(Pad.ref(), State.t()) :: State.t() + def snapshot_atomic_demand(pad_ref, state) do with {:ok, pad_data} <- PadModel.get_data(state, pad_ref), %State{playback: :playing} <- state do if pad_data.direction == :input, - do: raise("cannot snapshot demand counter in input pad") + do: raise("cannot snapshot atomic counter in input pad") - do_snapshot_demand_counter(pad_data, state) + do_snapshot_atomic_demand(pad_data, state) else {:error, :unknown_pad} -> - # We've got a :demand_counter_increased message on already unlinked pad + # We've got a :atomic_demand_increased message on already unlinked pad state %State{playback: :stopped} -> - PlaybackQueue.store(&snapshot_demand_counter(pad_ref, &1), state) + PlaybackQueue.store(&snapshot_atomic_demand(pad_ref, &1), state) end end - defp do_snapshot_demand_counter( + defp do_snapshot_atomic_demand( %{flow_control: :auto} = pad_data, %{effective_flow_control: :pull} = state ) do %{ - demand_counter: demand_counter, + atomic_demand: atomic_demand, associated_pads: associated_pads } = pad_data - if DemandCounter.get(demand_counter) > 0 do - AutoFlowUtils.auto_adjust_demand_counter(associated_pads, state) + if AtomicDemand.get(atomic_demand) > 0 do + AutoFlowUtils.auto_adjust_atomic_demand(associated_pads, state) else state end end - defp do_snapshot_demand_counter(%{flow_control: :manual} = pad_data, state) do - with %{demand_snapshot: demand_snapshot, demand_counter: demand_counter} + defp do_snapshot_atomic_demand(%{flow_control: :manual} = pad_data, state) do + with %{demand_snapshot: demand_snapshot, atomic_demand: atomic_demand} when demand_snapshot <= 0 <- pad_data, - demand_counter_value - when demand_counter_value > 0 and demand_counter_value > demand_snapshot <- - DemandCounter.get(demand_counter) do + atomic_demand_value + when atomic_demand_value > 0 and atomic_demand_value > demand_snapshot <- + AtomicDemand.get(atomic_demand) do state = PadModel.update_data!( state, pad_data.ref, &%{ &1 - | demand_snapshot: demand_counter_value, - incoming_demand: demand_counter_value - &1.demand_snapshot + | demand_snapshot: atomic_demand_value, + incoming_demand: atomic_demand_value - &1.demand_snapshot } ) @@ -79,12 +79,12 @@ defmodule Membrane.Core.Element.DemandController do end end - defp do_snapshot_demand_counter(_pad_data, state) do + defp do_snapshot_atomic_demand(_pad_data, state) do state end @doc """ - Decreases demand snapshot and demand counter on the output by the size of outgoing buffers. + Decreases demand snapshot and atomic demand on the output by the size of outgoing buffers. """ @spec decrease_demand_by_outgoing_buffers(Pad.ref(), [Buffer.t()], State.t()) :: State.t() def decrease_demand_by_outgoing_buffers(pad_ref, buffers, state) do @@ -92,12 +92,12 @@ defmodule Membrane.Core.Element.DemandController do buffers_size = Buffer.Metric.from_unit(pad_data.demand_unit).buffers_size(buffers) demand_snapshot = pad_data.demand_snapshot - buffers_size - demand_counter = DemandCounter.decrease(pad_data.demand_counter, buffers_size) + atomic_demand = AtomicDemand.decrease(pad_data.atomic_demand, buffers_size) PadModel.set_data!(state, pad_ref, %{ pad_data | demand_snapshot: demand_snapshot, - demand_counter: demand_counter + atomic_demand: atomic_demand }) end end diff --git a/lib/membrane/core/element/demand_controller/auto_flow_utils.ex b/lib/membrane/core/element/demand_controller/auto_flow_utils.ex index 21d4ce90b..52e89aebd 100644 --- a/lib/membrane/core/element/demand_controller/auto_flow_utils.ex +++ b/lib/membrane/core/element/demand_controller/auto_flow_utils.ex @@ -2,7 +2,7 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do @moduledoc false alias Membrane.Core.Element.{ - DemandCounter, + AtomicDemand, State } @@ -14,20 +14,20 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do pad_data.flow_control == :auto and is_map_key(pad_data, :direction) and pad_data.direction == :input - @spec auto_adjust_demand_counter(Pad.ref() | [Pad.ref()], State.t()) :: State.t() - def auto_adjust_demand_counter(pad_ref_list, state) when is_list(pad_ref_list) do - Enum.reduce(pad_ref_list, state, &auto_adjust_demand_counter/2) + @spec auto_adjust_atomic_demand(Pad.ref() | [Pad.ref()], State.t()) :: State.t() + def auto_adjust_atomic_demand(pad_ref_list, state) when is_list(pad_ref_list) do + Enum.reduce(pad_ref_list, state, &auto_adjust_atomic_demand/2) end - def auto_adjust_demand_counter(pad_ref, state) when Pad.is_pad_ref(pad_ref) do + def auto_adjust_atomic_demand(pad_ref, state) when Pad.is_pad_ref(pad_ref) do PadModel.get_data!(state, pad_ref) - |> do_auto_adjust_demand_counter(state) + |> do_auto_adjust_atomic_demand(state) end - defp do_auto_adjust_demand_counter(pad_data, state) when is_input_auto_pad_data(pad_data) do - if increase_demand_counter?(pad_data, state) do + defp do_auto_adjust_atomic_demand(pad_data, state) when is_input_auto_pad_data(pad_data) do + if increase_atomic_demand?(pad_data, state) do diff = pad_data.auto_demand_size - pad_data.demand - :ok = DemandCounter.increase(pad_data.demand_counter, diff) + :ok = AtomicDemand.increase(pad_data.atomic_demand, diff) PadModel.set_data!( state, @@ -40,21 +40,21 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do end end - defp do_auto_adjust_demand_counter(%{ref: ref}, _state) do - raise "#{__MODULE__}.auto_adjust_demand_counter/2 can be called only for auto input pads, while #{inspect(ref)} is not such a pad." + defp do_auto_adjust_atomic_demand(%{ref: ref}, _state) do + raise "#{__MODULE__}.auto_adjust_atomic_demand/2 can be called only for auto input pads, while #{inspect(ref)} is not such a pad." end - defp increase_demand_counter?(pad_data, state) do + defp increase_atomic_demand?(pad_data, state) do state.effective_flow_control == :pull and pad_data.demand < pad_data.auto_demand_size / 2 and - Enum.all?(pad_data.associated_pads, &demand_counter_positive?(&1, state)) + Enum.all?(pad_data.associated_pads, &atomic_demand_positive?(&1, state)) end - defp demand_counter_positive?(pad_ref, state) do - demand_counter_value = - PadModel.get_data!(state, pad_ref, :demand_counter) - |> DemandCounter.get() + defp atomic_demand_positive?(pad_ref, state) do + atomic_demand_value = + PadModel.get_data!(state, pad_ref, :atomic_demand) + |> AtomicDemand.get() - demand_counter_value > 0 + atomic_demand_value > 0 end end diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 4117f7a5a..9d74a81c7 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -19,7 +19,7 @@ defmodule Membrane.Core.Element.EffectiveFlowController do # Effective flow control of a single element can switch between :push and :pull many times during the element's lifetime. alias Membrane.Core.Element.DemandController.AutoFlowUtils - alias Membrane.Core.Element.{DemandCounter, State} + alias Membrane.Core.Element.{AtomicDemand, State} require Membrane.Core.Child.PadModel, as: PadModel require Membrane.Core.Message, as: Message @@ -58,8 +58,8 @@ defmodule Membrane.Core.Element.EffectiveFlowController do other_effective_flow_control == state.effective_flow_control -> :ok = - PadModel.get_data!(state, input_pad_ref, :demand_counter) - |> DemandCounter.set_receiver_status({:resolved, state.effective_flow_control}) + PadModel.get_data!(state, input_pad_ref, :atomic_demand) + |> AtomicDemand.set_receiver_status({:resolved, state.effective_flow_control}) state @@ -107,12 +107,12 @@ defmodule Membrane.Core.Element.EffectiveFlowController do |> Enum.reduce(state, fn {_ref, %{direction: :output} = pad_data}, state -> :ok = - DemandCounter.set_sender_status( - pad_data.demand_counter, + AtomicDemand.set_sender_status( + pad_data.atomic_demand, {:resolved, new_effective_flow_control} ) - :ok = DemandCounter.set_receiver_status(pad_data.demand_counter, :to_be_resolved) + :ok = AtomicDemand.set_receiver_status(pad_data.atomic_demand, :to_be_resolved) Message.send( pad_data.pid, @@ -124,15 +124,15 @@ defmodule Membrane.Core.Element.EffectiveFlowController do {pad_ref, %{direction: :input} = pad_data}, state -> if triggering_pad in [pad_ref, nil] or - DemandCounter.get_receiver_status(pad_data.demand_counter) != :to_be_resolved do + AtomicDemand.get_receiver_status(pad_data.atomic_demand) != :to_be_resolved do :ok = - DemandCounter.set_receiver_status( - pad_data.demand_counter, + AtomicDemand.set_receiver_status( + pad_data.atomic_demand, {:resolved, new_effective_flow_control} ) end - AutoFlowUtils.auto_adjust_demand_counter(pad_ref, state) + AutoFlowUtils.auto_adjust_atomic_demand(pad_ref, state) end) end end diff --git a/lib/membrane/core/element/input_queue.ex b/lib/membrane/core/element/input_queue.ex index 5d3e8e37e..41e5926fc 100644 --- a/lib/membrane/core/element/input_queue.ex +++ b/lib/membrane/core/element/input_queue.ex @@ -9,7 +9,7 @@ defmodule Membrane.Core.Element.InputQueue do use Bunch alias Membrane.Buffer - alias Membrane.Core.Element.DemandCounter + alias Membrane.Core.Element.AtomicDemand alias Membrane.Event alias Membrane.Pad alias Membrane.StreamFormat @@ -36,14 +36,14 @@ defmodule Membrane.Core.Element.InputQueue do inbound_metric: module(), outbound_metric: module(), linked_output_ref: Pad.ref(), - demand_counter: DemandCounter.t() + atomic_demand: AtomicDemand.t() } @enforce_keys [ :q, :log_tag, :target_size, - :demand_counter, + :atomic_demand, :inbound_metric, :outbound_metric, :linked_output_ref @@ -59,7 +59,7 @@ defmodule Membrane.Core.Element.InputQueue do @spec init(%{ inbound_demand_unit: Buffer.Metric.unit(), outbound_demand_unit: Buffer.Metric.unit(), - demand_counter: DemandCounter.t(), + atomic_demand: AtomicDemand.t(), linked_output_ref: Pad.ref(), log_tag: String.t(), target_size: pos_integer() | nil @@ -68,7 +68,7 @@ defmodule Membrane.Core.Element.InputQueue do %{ inbound_demand_unit: inbound_demand_unit, outbound_demand_unit: outbound_demand_unit, - demand_counter: demand_counter, + atomic_demand: atomic_demand, linked_output_ref: linked_output_ref, log_tag: log_tag, target_size: target_size @@ -87,10 +87,10 @@ defmodule Membrane.Core.Element.InputQueue do target_size: target_size, inbound_metric: inbound_metric, outbound_metric: outbound_metric, - demand_counter: demand_counter, + atomic_demand: atomic_demand, linked_output_ref: linked_output_ref } - |> maybe_increase_demand_counter() + |> maybe_increase_atomic_demand() end @spec store(t(), atom(), queue_item() | [queue_item()]) :: t() @@ -158,7 +158,7 @@ defmodule Membrane.Core.Element.InputQueue do |> Membrane.Logger.debug_verbose() {out, input_queue} = do_take(input_queue, count) - input_queue = maybe_increase_demand_counter(input_queue) + input_queue = maybe_increase_atomic_demand(input_queue) Telemetry.report_metric(:take, input_queue.size, input_queue.log_tag) @@ -273,12 +273,12 @@ defmodule Membrane.Core.Element.InputQueue do end end - @spec maybe_increase_demand_counter(t()) :: t() - defp maybe_increase_demand_counter( + @spec maybe_increase_atomic_demand(t()) :: t() + defp maybe_increase_atomic_demand( %__MODULE__{ size: size, target_size: target_size, - demand_counter: demand_counter, + atomic_demand: atomic_demand, demand: demand } = input_queue ) @@ -286,16 +286,16 @@ defmodule Membrane.Core.Element.InputQueue do diff = max(target_size - size - demand, div(target_size, 2)) """ - Increasing DemandCounter linked to #{inspect(input_queue.linked_output_ref)} by #{inspect(diff)} + Increasing AtomicDemand linked to #{inspect(input_queue.linked_output_ref)} by #{inspect(diff)} """ |> mk_log(input_queue) |> Membrane.Logger.debug_verbose() - :ok = DemandCounter.increase(demand_counter, diff) + :ok = AtomicDemand.increase(atomic_demand, diff) %{input_queue | demand: demand + diff} end - defp maybe_increase_demand_counter(%__MODULE__{} = input_queue), do: input_queue + defp maybe_increase_atomic_demand(%__MODULE__{} = input_queue), do: input_queue # This function may be unused if particular logs are pruned @dialyzer {:no_unused, mk_log: 2} diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index affdb4778..388b58106 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -11,7 +11,7 @@ defmodule Membrane.Core.Element.PadController do alias Membrane.Core.Element.{ ActionHandler, CallbackContext, - DemandCounter, + AtomicDemand, EffectiveFlowController, EventController, InputQueue, @@ -42,7 +42,7 @@ defmodule Membrane.Core.Element.PadController do } @type link_call_reply_props :: - {Endpoint.t(), PadModel.pad_info(), %{demand_counter: DemandCounter.t()}} + {Endpoint.t(), PadModel.pad_info(), %{atomic_demand: AtomicDemand.t()}} @type link_call_reply :: :ok @@ -159,8 +159,8 @@ defmodule Membrane.Core.Element.PadController do pad_effective_flow_control = EffectiveFlowController.get_pad_effective_flow_control(endpoint.pad_ref, state) - demand_counter = - DemandCounter.new( + atomic_demand = + AtomicDemand.new( pad_effective_flow_control, self(), input_demand_unit || :buffers, @@ -173,7 +173,7 @@ defmodule Membrane.Core.Element.PadController do # The sibiling was an initiator, we don't need to use the pid of a task spawned for observability _metadata = Observability.setup_link(endpoint.pad_ref, link_metadata.observability_metadata) - link_metadata = Map.put(link_metadata, :demand_counter, demand_counter) + link_metadata = Map.put(link_metadata, :atomic_demand, atomic_demand) :ok = Child.PadController.validate_pad_mode!( @@ -287,12 +287,12 @@ defmodule Membrane.Core.Element.PadController do start_of_stream?: false, end_of_stream?: false, associated_pads: [], - demand_counter: metadata.demand_counter + atomic_demand: metadata.atomic_demand }) :ok = - DemandCounter.set_sender_status( - data.demand_counter, + AtomicDemand.set_sender_status( + data.atomic_demand, {:resolved, EffectiveFlowController.get_pad_effective_flow_control(data.ref, state)} ) @@ -314,7 +314,7 @@ defmodule Membrane.Core.Element.PadController do end) if data.direction == :input, - do: AutoFlowUtils.auto_adjust_demand_counter(endpoint.pad_ref, state), + do: AutoFlowUtils.auto_adjust_atomic_demand(endpoint.pad_ref, state), else: state else state @@ -342,14 +342,14 @@ defmodule Membrane.Core.Element.PadController do ref: ref, other_ref: other_ref, demand_unit: this_demand_unit, - demand_counter: demand_counter + atomic_demand: atomic_demand } = data input_queue = InputQueue.init(%{ inbound_demand_unit: other_info[:demand_unit] || this_demand_unit, outbound_demand_unit: this_demand_unit, - demand_counter: demand_counter, + atomic_demand: atomic_demand, linked_output_ref: other_ref, log_tag: inspect(ref), target_size: props.target_queue_size @@ -433,7 +433,7 @@ defmodule Membrane.Core.Element.PadController do |> PadModel.set_data!(pad_ref, :associated_pads, []) if pad_data.direction == :output, - do: AutoFlowUtils.auto_adjust_demand_counter(pad_data.associated_pads, state), + do: AutoFlowUtils.auto_adjust_atomic_demand(pad_data.associated_pads, state), else: state _pad_data -> diff --git a/lib/membrane/element/pad_data.ex b/lib/membrane/element/pad_data.ex index 19452e449..e8c02c6c1 100644 --- a/lib/membrane/element/pad_data.ex +++ b/lib/membrane/element/pad_data.ex @@ -44,16 +44,16 @@ defmodule Membrane.Element.PadData do auto_demand_size: private_field, sticky_messages: private_field, - # Used only for output pads with :pull or :auto flow control. Holds the last captured value of DemandCounter, + # Used only for output pads with :pull or :auto flow control. Holds the last captured value of AtomicDemand, # decreased by the size of buffers sent via specific pad since the last capture, expressed in the appropriate metric. - # Moment, when demand_snapshot value drops to 0 or less, triggers another capture of DemandCounter value. + # Moment, when demand_snapshot value drops to 0 or less, triggers another capture of AtomicDemand value. demand_snapshot: integer() | nil, - # Instance of DemandCounter shared by both sides of link. Holds amount of data, that has been demanded by the element + # Instance of AtomicDemand shared by both sides of link. Holds amount of data, that has been demanded by the element # with input pad, but hasn't been sent yet by the element with output pad. Detects toilet overflow as well. - demand_counter: private_field, + atomic_demand: private_field, - # Field used in DemandController.AutoFlowUtils and InputQueue, to caluclate, how much DemandCounter should be increased. + # Field used in DemandController.AutoFlowUtils and InputQueue, to caluclate, how much AtomicDemand should be increased. # Contains amount of data (:buffers/:bytes), that has been demanded from the element on the other side of link, but # hasn't arrived yet. Unused for output pads. demand: private_field, @@ -85,7 +85,7 @@ defmodule Membrane.Element.PadData do end_of_stream?: false, auto_demand_size: nil, sticky_messages: [], - demand_counter: nil, + atomic_demand: nil, demand: 0, manual_demand_size: 0, associated_pads: [], diff --git a/test/membrane/core/element/action_handler_test.exs b/test/membrane/core/element/action_handler_test.exs index ae7d4e057..b83dc9b84 100644 --- a/test/membrane/core/element/action_handler_test.exs +++ b/test/membrane/core/element/action_handler_test.exs @@ -2,7 +2,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do use ExUnit.Case, async: true alias Membrane.{ActionError, Buffer, ElementError, PadDirectionError} - alias Membrane.Core.Element.{DemandCounter, State} + alias Membrane.Core.Element.{AtomicDemand, State} alias Membrane.Support.DemandsTest.Filter alias Membrane.Support.Element.{TrivialFilter, TrivialSource} @@ -72,11 +72,9 @@ defmodule Membrane.Core.Element.ActionHandlerTest do end defp trivial_filter_state(_context) do - input_demand_counter = - DemandCounter.new(:push, self(), :buffers, spawn(fn -> :ok end), :output) + input_atomic_demand = AtomicDemand.new(:push, self(), :buffers, spawn(fn -> :ok end), :output) - output_demand_counter = - DemandCounter.new(:push, spawn(fn -> :ok end), :bytes, self(), :output) + output_atomic_demand = AtomicDemand.new(:push, spawn(fn -> :ok end), :bytes, self(), :output) state = struct(State, @@ -97,7 +95,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do start_of_stream?: true, end_of_stream?: false, flow_control: :push, - demand_counter: output_demand_counter, + atomic_demand: output_atomic_demand, demand_snapshot: 0 }, input: %{ @@ -108,7 +106,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do start_of_stream?: true, end_of_stream?: false, flow_control: :push, - demand_counter: input_demand_counter, + atomic_demand: input_atomic_demand, demand_snapshot: 0 } }, @@ -163,7 +161,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do ) assert result.pads_data.output.demand_snapshot < 0 - assert DemandCounter.get(result.pads_data.output.demand_counter) < 0 + assert AtomicDemand.get(result.pads_data.output.atomic_demand) < 0 assert put_in(result, [:pads_data, :output, :demand_snapshot], 0) == state assert_received Message.new(:buffer, [@mock_buffer], for_pad: :other_ref) end diff --git a/test/membrane/core/element/demand_counter_test.exs b/test/membrane/core/element/demand_counter_test.exs index df48fc227..0628f8a69 100644 --- a/test/membrane/core/element/demand_counter_test.exs +++ b/test/membrane/core/element/demand_counter_test.exs @@ -1,67 +1,67 @@ -defmodule Membrane.Core.Element.DemandCounterTest do +defmodule Membrane.Core.Element.AtomicDemandTest do use ExUnit.Case - alias Membrane.Core.Element.DemandCounter + alias Membrane.Core.Element.AtomicDemand - test "if DemandCounter is implemented as :atomics for elements put on the same node" do - demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :output) - :ok = DemandCounter.increase(demand_counter, 10) + test "if AtomicDemand is implemented as :atomics for elements put on the same node" do + atomic_demand = AtomicDemand.new(:pull, self(), :buffers, self(), :output) + :ok = AtomicDemand.increase(atomic_demand, 10) - assert get_atomic_value(demand_counter) == 10 + assert get_atomic_value(atomic_demand) == 10 - demand_counter = DemandCounter.decrease(demand_counter, 15) + atomic_demand = AtomicDemand.decrease(atomic_demand, 15) - assert demand_counter.buffered_decrementation == 0 - assert get_atomic_value(demand_counter) == -5 - assert DemandCounter.get(demand_counter) == -5 + assert atomic_demand.buffered_decrementation == 0 + assert get_atomic_value(atomic_demand) == -5 + assert AtomicDemand.get(atomic_demand) == -5 end - test "if DemandCounter.DistributedAtomic.Worker works properly " do - demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :output) - :ok = DemandCounter.increase(demand_counter, 10) + test "if AtomicDemand.DistributedAtomic.Worker works properly " do + atomic_demand = AtomicDemand.new(:pull, self(), :buffers, self(), :output) + :ok = AtomicDemand.increase(atomic_demand, 10) assert GenServer.call( - demand_counter.counter.worker, - {:get, demand_counter.counter.atomic_ref} + atomic_demand.counter.worker, + {:get, atomic_demand.counter.atomic_ref} ) == 10 assert GenServer.call( - demand_counter.counter.worker, - {:sub_get, demand_counter.counter.atomic_ref, 15} + atomic_demand.counter.worker, + {:sub_get, atomic_demand.counter.atomic_ref, 15} ) == -5 - assert get_atomic_value(demand_counter) == -5 + assert get_atomic_value(atomic_demand) == -5 assert GenServer.call( - demand_counter.counter.worker, - {:add_get, demand_counter.counter.atomic_ref, 55} + atomic_demand.counter.worker, + {:add_get, atomic_demand.counter.atomic_ref, 55} ) == 50 - assert get_atomic_value(demand_counter) == 50 - assert DemandCounter.get(demand_counter) == 50 + assert get_atomic_value(atomic_demand) == 50 + assert AtomicDemand.get(atomic_demand) == 50 end test "if setting receiver and sender modes works properly" do - demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :output) + atomic_demand = AtomicDemand.new(:pull, self(), :buffers, self(), :output) - :ok = DemandCounter.set_receiver_status(demand_counter, {:resolved, :push}) + :ok = AtomicDemand.set_receiver_status(atomic_demand, {:resolved, :push}) - assert DemandCounter.DistributedFlowStatus.get(demand_counter.receiver_status) == + assert AtomicDemand.AtomicFlowStatus.get(atomic_demand.receiver_status) == {:resolved, :push} - :ok = DemandCounter.set_receiver_status(demand_counter, {:resolved, :pull}) + :ok = AtomicDemand.set_receiver_status(atomic_demand, {:resolved, :pull}) - assert DemandCounter.DistributedFlowStatus.get(demand_counter.receiver_status) == + assert AtomicDemand.AtomicFlowStatus.get(atomic_demand.receiver_status) == {:resolved, :pull} - :ok = DemandCounter.set_sender_status(demand_counter, {:resolved, :push}) + :ok = AtomicDemand.set_sender_status(atomic_demand, {:resolved, :push}) - assert DemandCounter.DistributedFlowStatus.get(demand_counter.sender_status) == + assert AtomicDemand.AtomicFlowStatus.get(atomic_demand.sender_status) == {:resolved, :push} - :ok = DemandCounter.set_sender_status(demand_counter, {:resolved, :pull}) + :ok = AtomicDemand.set_sender_status(atomic_demand, {:resolved, :pull}) - assert DemandCounter.DistributedFlowStatus.get(demand_counter.sender_status) == + assert AtomicDemand.AtomicFlowStatus.get(atomic_demand.sender_status) == {:resolved, :pull} end @@ -70,33 +70,33 @@ defmodule Membrane.Core.Element.DemandCounterTest do sleeping_process = spawn(fn -> Process.sleep(hour_in_millis) end) monitor_ref = Process.monitor(sleeping_process) - demand_counter = DemandCounter.new(:pull, sleeping_process, :buffers, self(), :output) + atomic_demand = AtomicDemand.new(:pull, sleeping_process, :buffers, self(), :output) - :ok = DemandCounter.set_sender_status(demand_counter, {:resolved, :push}) - demand_counter = DemandCounter.decrease(demand_counter, 100) + :ok = AtomicDemand.set_sender_status(atomic_demand, {:resolved, :push}) + atomic_demand = AtomicDemand.decrease(atomic_demand, 100) refute_receive {:DOWN, ^monitor_ref, :process, _pid, _reason} possible_statuses = [{:resolved, :push}, {:resolved, :pull}, :to_be_resolved] - demand_counter = + atomic_demand = for status_1 <- possible_statuses, status_2 <- possible_statuses do {status_1, status_2} end |> List.delete({{:resolved, :push}, {:resolved, :pull}}) - |> Enum.reduce(demand_counter, fn {sender_status, receiver_status}, demand_counter -> - :ok = DemandCounter.set_sender_status(demand_counter, sender_status) - :ok = DemandCounter.set_receiver_status(demand_counter, receiver_status) - demand_counter = DemandCounter.decrease(demand_counter, 1000) + |> Enum.reduce(atomic_demand, fn {sender_status, receiver_status}, atomic_demand -> + :ok = AtomicDemand.set_sender_status(atomic_demand, sender_status) + :ok = AtomicDemand.set_receiver_status(atomic_demand, receiver_status) + atomic_demand = AtomicDemand.decrease(atomic_demand, 1000) refute_receive {:DOWN, ^monitor_ref, :process, _pid, _reason} - demand_counter + atomic_demand end) - :ok = DemandCounter.set_sender_status(demand_counter, {:resolved, :push}) - :ok = DemandCounter.set_receiver_status(demand_counter, {:resolved, :pull}) - _demand_counter = DemandCounter.decrease(demand_counter, 1000) + :ok = AtomicDemand.set_sender_status(atomic_demand, {:resolved, :push}) + :ok = AtomicDemand.set_receiver_status(atomic_demand, {:resolved, :pull}) + _atomic_demand = AtomicDemand.decrease(atomic_demand, 1000) assert_receive {:DOWN, ^monitor_ref, :process, _pid, _reason} end @@ -104,24 +104,24 @@ defmodule Membrane.Core.Element.DemandCounterTest do test "if buffering decrementation works properly with distribution" do another_node = setup_another_node() pid_on_another_node = Node.spawn(another_node, fn -> :ok end) - demand_counter = DemandCounter.new(:push, self(), :buffers, pid_on_another_node, :output) + atomic_demand = AtomicDemand.new(:push, self(), :buffers, pid_on_another_node, :output) - assert %DemandCounter{throttling_factor: 150} = demand_counter + assert %AtomicDemand{throttling_factor: 150} = atomic_demand - demand_counter = DemandCounter.decrease(demand_counter, 100) + atomic_demand = AtomicDemand.decrease(atomic_demand, 100) - assert %DemandCounter{buffered_decrementation: 100} = demand_counter - assert get_atomic_value(demand_counter) == 0 + assert %AtomicDemand{buffered_decrementation: 100} = atomic_demand + assert get_atomic_value(atomic_demand) == 0 - demand_counter = DemandCounter.decrease(demand_counter, 49) + atomic_demand = AtomicDemand.decrease(atomic_demand, 49) - assert %DemandCounter{buffered_decrementation: 149} = demand_counter - assert get_atomic_value(demand_counter) == 0 + assert %AtomicDemand{buffered_decrementation: 149} = atomic_demand + assert get_atomic_value(atomic_demand) == 0 - demand_counter = DemandCounter.decrease(demand_counter, 51) + atomic_demand = AtomicDemand.decrease(atomic_demand, 51) - assert %DemandCounter{buffered_decrementation: 0} = demand_counter - assert get_atomic_value(demand_counter) == -200 + assert %AtomicDemand{buffered_decrementation: 0} = atomic_demand + assert get_atomic_value(atomic_demand) == -200 end defp setup_another_node() do @@ -135,8 +135,8 @@ defmodule Membrane.Core.Element.DemandCounterTest do another_node end - defp get_atomic_value(demand_counter) do - demand_counter.counter.atomic_ref + defp get_atomic_value(atomic_demand) do + atomic_demand.counter.atomic_ref |> :atomics.get(1) end end diff --git a/test/membrane/core/element/event_controller_test.exs b/test/membrane/core/element/event_controller_test.exs index c3b74474b..4c4b92bcd 100644 --- a/test/membrane/core/element/event_controller_test.exs +++ b/test/membrane/core/element/event_controller_test.exs @@ -1,7 +1,7 @@ defmodule Membrane.Core.Element.EventControllerTest do use ExUnit.Case - alias Membrane.Core.Element.{DemandCounter, EventController, InputQueue, State} + alias Membrane.Core.Element.{AtomicDemand, EventController, InputQueue, State} alias Membrane.Core.Events alias Membrane.Event @@ -19,8 +19,8 @@ defmodule Membrane.Core.Element.EventControllerTest do end setup do - demand_counter = - DemandCounter.new( + atomic_demand = + AtomicDemand.new( :pull, spawn(fn -> :ok end), :buffers, @@ -35,7 +35,7 @@ defmodule Membrane.Core.Element.EventControllerTest do outbound_demand_unit: :buffers, linked_output_ref: :some_pad, log_tag: "test", - demand_counter: demand_counter, + atomic_demand: atomic_demand, target_size: nil, min_demand_factor: nil }) @@ -63,7 +63,7 @@ defmodule Membrane.Core.Element.EventControllerTest do } ) - assert DemandCounter.get(demand_counter) > 0 + assert AtomicDemand.get(atomic_demand) > 0 [state: state] end diff --git a/test/membrane/core/element/input_queue_test.exs b/test/membrane/core/element/input_queue_test.exs index e0480860d..bb014f53d 100644 --- a/test/membrane/core/element/input_queue_test.exs +++ b/test/membrane/core/element/input_queue_test.exs @@ -2,7 +2,7 @@ defmodule Membrane.Core.Element.InputQueueTest do use ExUnit.Case, async: true alias Membrane.Buffer - alias Membrane.Core.Element.{DemandCounter, InputQueue} + alias Membrane.Core.Element.{AtomicDemand, InputQueue} alias Membrane.Core.Message alias Membrane.Testing.Event @@ -10,7 +10,7 @@ defmodule Membrane.Core.Element.InputQueueTest do describe ".init/6 should" do setup do - demand_counter = DemandCounter.new(:pull, self(), :bytes, self(), :output_pad_ref) + atomic_demand = AtomicDemand.new(:pull, self(), :bytes, self(), :output_pad_ref) {:ok, %{ @@ -19,7 +19,7 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_demand_unit: :bytes, outbound_demand_unit: :bytes, linked_output_ref: :output_pad_ref, - demand_counter: demand_counter, + atomic_demand: atomic_demand, expected_metric: Buffer.Metric.from_unit(:bytes) }} end @@ -30,13 +30,13 @@ defmodule Membrane.Core.Element.InputQueueTest do outbound_demand_unit: context.outbound_demand_unit, linked_output_ref: context.linked_output_ref, log_tag: context.log_tag, - demand_counter: context.demand_counter, + atomic_demand: context.atomic_demand, target_size: context.target_queue_size }) == %InputQueue{ q: Qex.new(), log_tag: context.log_tag, target_size: context.target_queue_size, - demand_counter: context.demand_counter, + atomic_demand: context.atomic_demand, inbound_metric: context.expected_metric, outbound_metric: context.expected_metric, linked_output_ref: context.linked_output_ref, @@ -44,9 +44,9 @@ defmodule Membrane.Core.Element.InputQueueTest do demand: context.target_queue_size } - assert context.target_queue_size == DemandCounter.get(context.demand_counter) + assert context.target_queue_size == AtomicDemand.get(context.atomic_demand) - expected_message = Message.new(:demand_counter_increased, context.linked_output_ref) + expected_message = Message.new(:atomic_demand_increased, context.linked_output_ref) assert_received ^expected_message end end @@ -185,7 +185,7 @@ defmodule Membrane.Core.Element.InputQueueTest do describe ".take/2 should" do setup do output_pad = :pad - demand_counter = DemandCounter.new(:pull, self(), :bytes, self(), output_pad) + atomic_demand = AtomicDemand.new(:pull, self(), :bytes, self(), output_pad) input_queue = InputQueue.init(%{ @@ -193,23 +193,23 @@ defmodule Membrane.Core.Element.InputQueueTest do outbound_demand_unit: :buffers, linked_output_ref: output_pad, log_tag: "test", - demand_counter: demand_counter, + atomic_demand: atomic_demand, target_size: 40 }) - assert_received Message.new(:demand_counter_increased, ^output_pad) + assert_received Message.new(:atomic_demand_increased, ^output_pad) [input_queue: input_queue] end test "return {:empty, []} when the queue is empty", %{input_queue: input_queue} do - old_counter_value = DemandCounter.get(input_queue.demand_counter) + old_atomic_demand_value = AtomicDemand.get(input_queue.atomic_demand) old_demand = input_queue.demand assert {{:empty, []}, %InputQueue{size: 0, demand: ^old_demand}} = InputQueue.take(input_queue, 1) - assert old_counter_value == DemandCounter.get(input_queue.demand_counter) + assert old_atomic_demand_value == AtomicDemand.get(input_queue.atomic_demand) end test "send demands to the pid and updates demand", %{input_queue: input_queue} do @@ -220,7 +220,7 @@ defmodule Membrane.Core.Element.InputQueueTest do assert new_input_queue.size == 9 assert new_input_queue.demand >= 31 - assert DemandCounter.get(new_input_queue.demand_counter) >= 41 + assert AtomicDemand.get(new_input_queue.atomic_demand) >= 41 end end @@ -231,10 +231,10 @@ defmodule Membrane.Core.Element.InputQueueTest do buffers2 = {:buffers, [:b4, :b5, :b6], 3, 3} q = Qex.new() |> Qex.push(buffers1) |> Qex.push(buffers2) output_pad = :pad - demand_counter = DemandCounter.new(:pull, self(), :bytes, self(), output_pad) + atomic_demand = AtomicDemand.new(:pull, self(), :bytes, self(), output_pad) - :ok = DemandCounter.increase(demand_counter, 94) - assert_received Message.new(:demand_counter_increased, ^output_pad) + :ok = AtomicDemand.increase(atomic_demand, 94) + assert_received Message.new(:atomic_demand_increased, ^output_pad) input_queue = struct(InputQueue, @@ -245,7 +245,7 @@ defmodule Membrane.Core.Element.InputQueueTest do outbound_metric: Buffer.Metric.Count, q: q, linked_output_ref: output_pad, - demand_counter: demand_counter + atomic_demand: atomic_demand ) {:ok, %{input_queue: input_queue, q: q, size: size, buffers1: buffers1, buffers2: buffers2}} @@ -263,13 +263,13 @@ defmodule Membrane.Core.Element.InputQueueTest do assert new_size == 0 end - test "increase DemandCounter hen there are not enough buffers", context do - old_counter_value = DemandCounter.get(context.input_queue.demand_counter) + test "increase AtomicDemand hen there are not enough buffers", context do + old_atomic_demand_value = AtomicDemand.get(context.input_queue.atomic_demand) old_demand = context.input_queue.demand {_output, input_queue} = InputQueue.take(context.input_queue, 10) - assert old_counter_value < DemandCounter.get(input_queue.demand_counter) + assert old_atomic_demand_value < AtomicDemand.get(input_queue.atomic_demand) assert old_demand < input_queue.demand end @@ -301,33 +301,33 @@ defmodule Membrane.Core.Element.InputQueueTest do end test "if the queue works properly for :bytes input metric and :buffers output metric" do - demand_counter = DemandCounter.new(:pull, self(), :bytes, self(), :output_pad_ref) + atomic_demand = AtomicDemand.new(:pull, self(), :bytes, self(), :output_pad_ref) queue = InputQueue.init(%{ inbound_demand_unit: :bytes, outbound_demand_unit: :buffers, - demand_counter: demand_counter, + atomic_demand: atomic_demand, linked_output_ref: :output_pad_ref, log_tag: nil, target_size: 10 }) - assert_receive Message.new(:demand_counter_increased, :output_pad_ref) + assert_receive Message.new(:atomic_demand_increased, :output_pad_ref) assert queue.demand == 10 queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) assert queue.size == 4 queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) - queue = Map.update!(queue, :demand_counter, &DemandCounter.decrease(&1, 16)) + queue = Map.update!(queue, :atomic_demand, &AtomicDemand.decrease(&1, 16)) assert queue.size == 16 assert queue.demand == -6 {out, queue} = InputQueue.take(queue, 2) assert bufs_size(out, :buffers) == 2 assert queue.size == 4 assert queue.demand >= 6 - assert_receive Message.new(:demand_counter_increased, :output_pad_ref) + assert_receive Message.new(:atomic_demand_increased, :output_pad_ref) queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) @@ -338,38 +338,38 @@ defmodule Membrane.Core.Element.InputQueueTest do end test "if the queue works properly for :buffers input metric and :bytes output metric" do - demand_counter = DemandCounter.new(:pull, self(), :bytes, self(), :output_pad_ref) + atomic_demand = AtomicDemand.new(:pull, self(), :bytes, self(), :output_pad_ref) queue = InputQueue.init(%{ inbound_demand_unit: :buffers, outbound_demand_unit: :bytes, - demand_counter: demand_counter, + atomic_demand: atomic_demand, linked_output_ref: :output_pad_ref, log_tag: nil, target_size: 3 }) - assert_receive Message.new(:demand_counter_increased, :output_pad_ref) + assert_receive Message.new(:atomic_demand_increased, :output_pad_ref) assert queue.demand == 3 queue = InputQueue.store(queue, [%Buffer{payload: "1234"}]) assert queue.size == 1 queue = InputQueue.store(queue, [%Buffer{payload: "12345678"}]) queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) queue = InputQueue.store(queue, [%Buffer{payload: "12"}]) - queue = Map.update!(queue, :demand_counter, &DemandCounter.decrease(&1, 4)) + queue = Map.update!(queue, :atomic_demand, &AtomicDemand.decrease(&1, 4)) assert queue.size == 4 assert queue.demand == -1 {out, queue} = InputQueue.take(queue, 2) assert bufs_size(out, :bytes) == 2 assert queue.size == 4 assert queue.demand == -1 - refute_receive Message.new(:demand_counter_increased, :output_pad_ref) + refute_receive Message.new(:atomic_demand_increased, :output_pad_ref) {out, queue} = InputQueue.take(queue, 11) assert bufs_size(out, :bytes) == 11 assert queue.size == 2 assert queue.demand == 1 - assert_receive Message.new(:demand_counter_increased, :output_pad_ref) + assert_receive Message.new(:atomic_demand_increased, :output_pad_ref) end defp bufs_size(output, unit) do diff --git a/test/membrane/core/element/lifecycle_controller_test.exs b/test/membrane/core/element/lifecycle_controller_test.exs index fd64a66d4..2ee7d6a44 100644 --- a/test/membrane/core/element/lifecycle_controller_test.exs +++ b/test/membrane/core/element/lifecycle_controller_test.exs @@ -1,7 +1,7 @@ defmodule Membrane.Core.Element.LifecycleControllerTest do use ExUnit.Case - alias Membrane.Core.Element.{DemandCounter, InputQueue, LifecycleController, State} + alias Membrane.Core.Element.{AtomicDemand, InputQueue, LifecycleController, State} alias Membrane.Core.Message require Membrane.Core.Message @@ -17,13 +17,13 @@ defmodule Membrane.Core.Element.LifecycleControllerTest do end setup do - demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :some_pad) + atomic_demand = AtomicDemand.new(:pull, self(), :buffers, self(), :some_pad) input_queue = InputQueue.init(%{ inbound_demand_unit: :buffers, outbound_demand_unit: :buffers, - demand_counter: demand_counter, + atomic_demand: atomic_demand, linked_output_ref: :some_pad, log_tag: "test", target_size: nil @@ -52,7 +52,7 @@ defmodule Membrane.Core.Element.LifecycleControllerTest do } ) - assert_received Message.new(:demand_counter_increased, :some_pad) + assert_received Message.new(:atomic_demand_increased, :some_pad) [state: state] end diff --git a/test/membrane/core/element/stream_format_controller_test.exs b/test/membrane/core/element/stream_format_controller_test.exs index 6a61c0638..7379d16b8 100644 --- a/test/membrane/core/element/stream_format_controller_test.exs +++ b/test/membrane/core/element/stream_format_controller_test.exs @@ -3,7 +3,7 @@ defmodule Membrane.Core.Element.StreamFormatControllerTest do alias Membrane.Buffer alias Membrane.Core.Message - alias Membrane.Core.Element.{DemandCounter, InputQueue, State} + alias Membrane.Core.Element.{AtomicDemand, InputQueue, State} alias Membrane.StreamFormat.Mock, as: MockStreamFormat alias Membrane.Support.DemandsTest.Filter @@ -13,13 +13,13 @@ defmodule Membrane.Core.Element.StreamFormatControllerTest do @module Membrane.Core.Element.StreamFormatController setup do - demand_counter = DemandCounter.new(:pull, self(), :buffers, self(), :some_pad) + atomic_demand = AtomicDemand.new(:pull, self(), :buffers, self(), :some_pad) input_queue = InputQueue.init(%{ inbound_demand_unit: :buffers, outbound_demand_unit: :buffers, - demand_counter: demand_counter, + atomic_demand: atomic_demand, linked_output_ref: :some_pad, log_tag: "test", target_size: nil @@ -46,7 +46,7 @@ defmodule Membrane.Core.Element.StreamFormatControllerTest do } ) - assert_received Message.new(:demand_counter_increased, :some_pad) + assert_received Message.new(:atomic_demand_increased, :some_pad) [state: state] end diff --git a/test/membrane/core/element_test.exs b/test/membrane/core/element_test.exs index 37ae6da9a..8eee4580f 100644 --- a/test/membrane/core/element_test.exs +++ b/test/membrane/core/element_test.exs @@ -111,11 +111,11 @@ defmodule Membrane.Core.ElementTest do other_info = %{direction: :input, flow_control: :manual, demand_unit: :buffers} - output_demand_counter = - Element.DemandCounter.new(:pull, helper_server, :buffers, self(), :output) + output_atomic_demand = + Element.AtomicDemand.new(:pull, helper_server, :buffers, self(), :output) reply_link_metadata = %{ - demand_counter: output_demand_counter, + atomic_demand: output_atomic_demand, observability_metadata: %{}, input_demand_unit: :buffers, output_demand_unit: :buffers @@ -133,7 +133,7 @@ defmodule Membrane.Core.ElementTest do output_other_endpoint, %{ other_info: other_info, - link_metadata: %{demand_counter: output_demand_counter, observability_metadata: %{}}, + link_metadata: %{atomic_demand: output_atomic_demand, observability_metadata: %{}}, stream_format_validation_params: [], other_effective_flow_control: :pull } @@ -202,10 +202,10 @@ defmodule Membrane.Core.ElementTest do test "should store demand/buffer/event/stream format when not playing" do initial_state = linked_state() - :ok = increase_output_demand_counter(initial_state, 10) + :ok = increase_output_atomic_demand(initial_state, 10) [ - Message.new(:demand_counter_increased, :output), + Message.new(:atomic_demand_increased, :output), Message.new(:buffer, %Membrane.Buffer{payload: <<>>}, for_pad: :dynamic_input), Message.new(:stream_format, %StreamFormat{}, for_pad: :dynamic_input), Message.new(:event, %Membrane.Testing.Event{}, for_pad: :dynamic_input), @@ -221,9 +221,9 @@ defmodule Membrane.Core.ElementTest do test "should update demand" do state = playing_state() - :ok = increase_output_demand_counter(state, 10) + :ok = increase_output_atomic_demand(state, 10) - msg = Message.new(:demand_counter_increased, :output) + msg = Message.new(:atomic_demand_increased, :output) assert {:noreply, state} = Element.handle_info(msg, state) assert state.pads_data.output.demand_snapshot == 10 @@ -301,7 +301,7 @@ defmodule Membrane.Core.ElementTest do options: nil }, %{ - demand_counter: %Element.DemandCounter{}, + atomic_demand: %Element.AtomicDemand{}, output_demand_unit: :buffers, input_demand_unit: :buffers }} = reply @@ -414,9 +414,9 @@ defmodule Membrane.Core.ElementTest do } end - defp increase_output_demand_counter(state, value) do + defp increase_output_atomic_demand(state, value) do :ok = - state.pads_data.output.demand_counter - |> Element.DemandCounter.increase(value) + state.pads_data.output.atomic_demand + |> Element.AtomicDemand.increase(value) end end From 0821fb519c159556b8ad9ae1adbd5f025e13d594 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 19 May 2023 16:36:58 +0200 Subject: [PATCH 59/64] Start AtomicDemand Worker under supervisor --- lib/membrane/core/element/atomic_demand.ex | 51 +++++++++++-------- .../atomic_demand/atomic_flow_status.ex | 6 +-- .../atomic_demand/distributed_atomic.ex | 7 +-- .../distributed_atomic/worker.ex | 14 ++--- .../core/element/effective_flow_controller.ex | 2 +- lib/membrane/core/element/pad_controller.ex | 21 ++++---- 6 files changed, 53 insertions(+), 48 deletions(-) diff --git a/lib/membrane/core/element/atomic_demand.ex b/lib/membrane/core/element/atomic_demand.ex index a82212926..4e555c237 100644 --- a/lib/membrane/core/element/atomic_demand.ex +++ b/lib/membrane/core/element/atomic_demand.ex @@ -1,6 +1,6 @@ defmodule Membrane.Core.Element.AtomicDemand do @moduledoc false - alias Membrane.Core.Element.AtomicDemand.AtomicFlowStatus + alias Membrane.Core.Element.EffectiveFlowController alias __MODULE__.{ @@ -46,40 +46,49 @@ defmodule Membrane.Core.Element.AtomicDemand do defstruct @enforce_keys ++ [buffered_decrementation: 0, toilet_overflowed?: false] - @spec new( - receiver_effective_flow_control :: EffectiveFlowController.effective_flow_control(), - receiver_process :: Process.dest(), - receiver_demand_unit :: Membrane.Buffer.Metric.unit(), - sender_process :: Process.dest(), - sender_pad_ref :: Pad.ref(), - toilet_capacity :: non_neg_integer() | nil, - throttling_factor :: pos_integer() | nil - ) :: t + @spec new(%{ + :receiver_effective_flow_control => EffectiveFlowController.effective_flow_control(), + :receiver_process => Process.dest(), + :receiver_demand_unit => Membrane.Buffer.Metric.unit(), + :sender_process => Process.dest(), + :sender_pad_ref => Pad.ref(), + :supervisor => pid(), + optional(:toilet_capacity) => non_neg_integer() | nil, + optional(:throttling_factor) => pos_integer() | nil + }) :: t def new( - receiver_effective_flow_control, - receiver_process, - receiver_demand_unit, - sender_process, - sender_pad_ref, - toilet_capacity \\ nil, - throttling_factor \\ nil + %{ + receiver_effective_flow_control: receiver_effective_flow_control, + receiver_process: receiver_process, + receiver_demand_unit: receiver_demand_unit, + sender_process: sender_process, + sender_pad_ref: sender_pad_ref, + supervisor: supervisor + } = options ) do - %DistributedAtomic{worker: worker} = counter = DistributedAtomic.new() + toilet_capacity = options[:toilet_capacity] + throttling_factor = options[:throttling_factor] + + counter = DistributedAtomic.new(supervisor: supervisor) throttling_factor = cond do throttling_factor != nil -> throttling_factor - node(sender_process) == node(worker) -> @default_throttling_factor + node(sender_process) == node(counter.worker) -> @default_throttling_factor true -> @distributed_default_throttling_factor end - receiver_status = AtomicFlowStatus.new({:resolved, receiver_effective_flow_control}) + receiver_status = + AtomicFlowStatus.new( + {:resolved, receiver_effective_flow_control}, + supervisor: supervisor + ) %__MODULE__{ counter: counter, receiver_status: receiver_status, receiver_process: receiver_process, - sender_status: AtomicFlowStatus.new(:to_be_resolved), + sender_status: AtomicFlowStatus.new(:to_be_resolved, supervisor: supervisor), sender_process: sender_process, sender_pad_ref: sender_pad_ref, toilet_capacity: toilet_capacity || default_toilet_capacity(receiver_demand_unit), diff --git a/lib/membrane/core/element/atomic_demand/atomic_flow_status.ex b/lib/membrane/core/element/atomic_demand/atomic_flow_status.ex index ae672a3da..c9ca3b5a3 100644 --- a/lib/membrane/core/element/atomic_demand/atomic_flow_status.ex +++ b/lib/membrane/core/element/atomic_demand/atomic_flow_status.ex @@ -7,11 +7,11 @@ defmodule Membrane.Core.Element.AtomicDemand.AtomicFlowStatus do @type t :: DistributedAtomic.t() @type value :: {:resolved, EffectiveFlowController.effective_flow_control()} | :to_be_resolved - @spec new(value) :: t - def new(initial_value) do + @spec new(value, supervisor: pid()) :: t + def new(initial_value, supervisor: supervisor) do initial_value |> flow_status_to_int() - |> DistributedAtomic.new() + |> DistributedAtomic.new(supervisor: supervisor) end @spec get(t) :: value() diff --git a/lib/membrane/core/element/atomic_demand/distributed_atomic.ex b/lib/membrane/core/element/atomic_demand/distributed_atomic.ex index 86e730a24..7a7fddf1e 100644 --- a/lib/membrane/core/element/atomic_demand/distributed_atomic.ex +++ b/lib/membrane/core/element/atomic_demand/distributed_atomic.ex @@ -6,6 +6,7 @@ defmodule Membrane.Core.Element.AtomicDemand.DistributedAtomic do # The module allows to create and modify the value of a counter in the same manner both when the counter is about to be accessed # from the same node, and from different nodes. + alias Membrane.Core.SubprocessSupervisor alias __MODULE__.Worker @enforce_keys [:worker, :atomic_ref] @@ -16,10 +17,10 @@ defmodule Membrane.Core.Element.AtomicDemand.DistributedAtomic do defguardp on_the_same_node_as_self(distributed_atomic) when distributed_atomic.worker |> node() == self() |> node() - @spec new(integer() | nil) :: t - def new(initial_value \\ nil) do + @spec new(integer() | nil, supervisor: pid()) :: t + def new(initial_value \\ nil, supervisor: supervisor) do atomic_ref = :atomics.new(1, []) - {:ok, worker} = Worker.start_link(self()) + {:ok, worker} = SubprocessSupervisor.start_utility(supervisor, Worker) distributed_atomic = %__MODULE__{ atomic_ref: atomic_ref, diff --git a/lib/membrane/core/element/atomic_demand/distributed_atomic/worker.ex b/lib/membrane/core/element/atomic_demand/distributed_atomic/worker.ex index 296e6e6d0..5f60450d7 100644 --- a/lib/membrane/core/element/atomic_demand/distributed_atomic/worker.ex +++ b/lib/membrane/core/element/atomic_demand/distributed_atomic/worker.ex @@ -8,13 +8,12 @@ defmodule Membrane.Core.Element.AtomicDemand.DistributedAtomic.Worker do @type t :: pid() - @spec start_link(pid()) :: {:ok, t} - def start_link(owner_pid), do: GenServer.start_link(__MODULE__, owner_pid) + @spec start_link(any()) :: {:ok, t} + def start_link(opts), do: GenServer.start_link(__MODULE__, opts) @impl true - def init(owner_pid) do - ref = Process.monitor(owner_pid) - {:ok, %{ref: ref}, :hibernate} + def init(_opts) do + {:ok, nil, :hibernate} end @impl true @@ -40,9 +39,4 @@ defmodule Membrane.Core.Element.AtomicDemand.DistributedAtomic.Worker do :atomics.put(atomic_ref, 1, value) {:noreply, nil} end - - @impl true - def handle_info({:DOWN, ref, _process, _pid, _reason}, %{ref: ref} = state) do - {:stop, :normal, state} - end end diff --git a/lib/membrane/core/element/effective_flow_controller.ex b/lib/membrane/core/element/effective_flow_controller.ex index 9d74a81c7..8db26fb79 100644 --- a/lib/membrane/core/element/effective_flow_controller.ex +++ b/lib/membrane/core/element/effective_flow_controller.ex @@ -8,7 +8,7 @@ defmodule Membrane.Core.Element.EffectiveFlowController do # If element A is linked via its input auto pads only to the :push output pads of other elements, then effective flow # control of element A will be set to :push. Otherwise, if element A is linked via its input auto pads to at least one - # :pull output pad, element A will set itss effective flow control to :pull and will forward this information + # :pull output pad, element A will set its effective flow control to :pull and will forward this information # via its output auto pads. # Resolving effective flow control is performed on diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index 388b58106..d7729af4e 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -10,8 +10,8 @@ defmodule Membrane.Core.Element.PadController do alias Membrane.Core.Element.{ ActionHandler, - CallbackContext, AtomicDemand, + CallbackContext, EffectiveFlowController, EventController, InputQueue, @@ -160,15 +160,16 @@ defmodule Membrane.Core.Element.PadController do EffectiveFlowController.get_pad_effective_flow_control(endpoint.pad_ref, state) atomic_demand = - AtomicDemand.new( - pad_effective_flow_control, - self(), - input_demand_unit || :buffers, - other_endpoint.pid, - other_endpoint.pad_ref, - endpoint.pad_props[:toilet_capacity], - endpoint.pad_props[:throttling_factor] - ) + AtomicDemand.new(%{ + receiver_effective_flow_control: pad_effective_flow_control, + receiver_process: self(), + receiver_demand_unit: input_demand_unit || :buffers, + sender_process: other_endpoint.pid, + sender_pad_ref: other_endpoint.pad_ref, + supervisor: state.subprocess_supervisor, + toilet_capacity: endpoint.pad_props[:toilet_capacity], + throttling_factor: endpoint.pad_props[:throttling_factor] + }) # The sibiling was an initiator, we don't need to use the pid of a task spawned for observability _metadata = Observability.setup_link(endpoint.pad_ref, link_metadata.observability_metadata) From 765adad00ea815288213900c5d692433d7828eca Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 19 May 2023 16:45:17 +0200 Subject: [PATCH 60/64] Fix AtomicDemand tests wip --- ...ounter_test.exs => atomic_demand_test.exs} | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) rename test/membrane/core/element/{demand_counter_test.exs => atomic_demand_test.exs} (86%) diff --git a/test/membrane/core/element/demand_counter_test.exs b/test/membrane/core/element/atomic_demand_test.exs similarity index 86% rename from test/membrane/core/element/demand_counter_test.exs rename to test/membrane/core/element/atomic_demand_test.exs index 0628f8a69..b6490b219 100644 --- a/test/membrane/core/element/demand_counter_test.exs +++ b/test/membrane/core/element/atomic_demand_test.exs @@ -2,9 +2,10 @@ defmodule Membrane.Core.Element.AtomicDemandTest do use ExUnit.Case alias Membrane.Core.Element.AtomicDemand + alias Membrane.Core.SubprocessSupervisor test "if AtomicDemand is implemented as :atomics for elements put on the same node" do - atomic_demand = AtomicDemand.new(:pull, self(), :buffers, self(), :output) + atomic_demand = new_atomic_demand(:pull, self(), self()) :ok = AtomicDemand.increase(atomic_demand, 10) assert get_atomic_value(atomic_demand) == 10 @@ -17,7 +18,7 @@ defmodule Membrane.Core.Element.AtomicDemandTest do end test "if AtomicDemand.DistributedAtomic.Worker works properly " do - atomic_demand = AtomicDemand.new(:pull, self(), :buffers, self(), :output) + atomic_demand = new_atomic_demand(:pull, self(), self()) :ok = AtomicDemand.increase(atomic_demand, 10) assert GenServer.call( @@ -42,7 +43,7 @@ defmodule Membrane.Core.Element.AtomicDemandTest do end test "if setting receiver and sender modes works properly" do - atomic_demand = AtomicDemand.new(:pull, self(), :buffers, self(), :output) + atomic_demand = new_atomic_demand(:pull, self(), self()) :ok = AtomicDemand.set_receiver_status(atomic_demand, {:resolved, :push}) @@ -70,7 +71,7 @@ defmodule Membrane.Core.Element.AtomicDemandTest do sleeping_process = spawn(fn -> Process.sleep(hour_in_millis) end) monitor_ref = Process.monitor(sleeping_process) - atomic_demand = AtomicDemand.new(:pull, sleeping_process, :buffers, self(), :output) + atomic_demand = new_atomic_demand(:pull, sleeping_process, self()) :ok = AtomicDemand.set_sender_status(atomic_demand, {:resolved, :push}) atomic_demand = AtomicDemand.decrease(atomic_demand, 100) @@ -104,7 +105,7 @@ defmodule Membrane.Core.Element.AtomicDemandTest do test "if buffering decrementation works properly with distribution" do another_node = setup_another_node() pid_on_another_node = Node.spawn(another_node, fn -> :ok end) - atomic_demand = AtomicDemand.new(:push, self(), :buffers, pid_on_another_node, :output) + atomic_demand = new_atomic_demand(:push, self(), pid_on_another_node) assert %AtomicDemand{throttling_factor: 150} = atomic_demand @@ -139,4 +140,15 @@ defmodule Membrane.Core.Element.AtomicDemandTest do atomic_demand.counter.atomic_ref |> :atomics.get(1) end + + defp new_atomic_demand(receiver_effective_flow_control, receiver_pid, sender_pid) do + AtomicDemand.new(%{ + receiver_effective_flow_control: receiver_effective_flow_control, + receiver_process: receiver_pid, + receiver_demand_unit: :buffers, + sender_process: sender_pid, + sender_pad_ref: :output, + supervisor: SubprocessSupervisor.start_link!() + }) + end end From 70840cb383c8591b0b4ed0564d11334f8888a7e9 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Fri, 19 May 2023 16:47:30 +0200 Subject: [PATCH 61/64] Sort aliases --- lib/membrane/core/element/atomic_demand/distributed_atomic.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/membrane/core/element/atomic_demand/distributed_atomic.ex b/lib/membrane/core/element/atomic_demand/distributed_atomic.ex index 7a7fddf1e..e5e4ff869 100644 --- a/lib/membrane/core/element/atomic_demand/distributed_atomic.ex +++ b/lib/membrane/core/element/atomic_demand/distributed_atomic.ex @@ -6,8 +6,8 @@ defmodule Membrane.Core.Element.AtomicDemand.DistributedAtomic do # The module allows to create and modify the value of a counter in the same manner both when the counter is about to be accessed # from the same node, and from different nodes. - alias Membrane.Core.SubprocessSupervisor alias __MODULE__.Worker + alias Membrane.Core.SubprocessSupervisor @enforce_keys [:worker, :atomic_ref] defstruct @enforce_keys From efc9edb9ba8a1e1951a6375a1df08426666f799b Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 22 May 2023 13:33:17 +0200 Subject: [PATCH 62/64] Fix tests failing because of changes in AtomicDemand --- lib/membrane/core/element/pad_controller.ex | 1 - .../core/element/action_handler_test.exs | 25 +++++++++++-- .../core/element/event_controller_test.exs | 18 +++++----- .../core/element/input_queue_test.exs | 36 +++++++++++-------- .../element/lifecycle_controller_test.exs | 16 +++++++-- .../core/element/pad_controller_test.exs | 4 ++- .../element/stream_format_controller_test.exs | 11 +++++- test/membrane/core/element_test.exs | 18 ++++++++-- 8 files changed, 95 insertions(+), 34 deletions(-) diff --git a/lib/membrane/core/element/pad_controller.ex b/lib/membrane/core/element/pad_controller.ex index d7729af4e..173c33c38 100644 --- a/lib/membrane/core/element/pad_controller.ex +++ b/lib/membrane/core/element/pad_controller.ex @@ -20,7 +20,6 @@ defmodule Membrane.Core.Element.PadController do } alias Membrane.Core.Element.DemandController.AutoFlowUtils - alias Membrane.Core.Parent.Link.Endpoint require Membrane.Core.Child.PadModel diff --git a/test/membrane/core/element/action_handler_test.exs b/test/membrane/core/element/action_handler_test.exs index b83dc9b84..584bda1b3 100644 --- a/test/membrane/core/element/action_handler_test.exs +++ b/test/membrane/core/element/action_handler_test.exs @@ -3,6 +3,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do alias Membrane.{ActionError, Buffer, ElementError, PadDirectionError} alias Membrane.Core.Element.{AtomicDemand, State} + alias Membrane.Core.SubprocessSupervisor alias Membrane.Support.DemandsTest.Filter alias Membrane.Support.Element.{TrivialFilter, TrivialSource} @@ -72,9 +73,27 @@ defmodule Membrane.Core.Element.ActionHandlerTest do end defp trivial_filter_state(_context) do - input_atomic_demand = AtomicDemand.new(:push, self(), :buffers, spawn(fn -> :ok end), :output) - - output_atomic_demand = AtomicDemand.new(:push, spawn(fn -> :ok end), :bytes, self(), :output) + supervisor = SubprocessSupervisor.start_link!() + + input_atomic_demand = + AtomicDemand.new(%{ + receiver_effective_flow_control: :push, + receiver_process: self(), + receiver_demand_unit: :buffers, + sender_process: spawn(fn -> :ok end), + sender_pad_ref: :output, + supervisor: supervisor + }) + + output_atomic_demand = + AtomicDemand.new(%{ + receiver_effective_flow_control: :push, + receiver_process: spawn(fn -> :ok end), + receiver_demand_unit: :bytes, + sender_process: self(), + sender_pad_ref: :output, + supervisor: supervisor + }) state = struct(State, diff --git a/test/membrane/core/element/event_controller_test.exs b/test/membrane/core/element/event_controller_test.exs index 4c4b92bcd..55f6f777b 100644 --- a/test/membrane/core/element/event_controller_test.exs +++ b/test/membrane/core/element/event_controller_test.exs @@ -3,6 +3,7 @@ defmodule Membrane.Core.Element.EventControllerTest do alias Membrane.Core.Element.{AtomicDemand, EventController, InputQueue, State} alias Membrane.Core.Events + alias Membrane.Core.SubprocessSupervisor alias Membrane.Event require Membrane.Core.Message @@ -20,14 +21,15 @@ defmodule Membrane.Core.Element.EventControllerTest do setup do atomic_demand = - AtomicDemand.new( - :pull, - spawn(fn -> :ok end), - :buffers, - spawn(fn -> :ok end), - :output, - -300 - ) + AtomicDemand.new(%{ + receiver_effective_flow_control: :pull, + receiver_process: spawn(fn -> :ok end), + receiver_demand_unit: :buffers, + sender_process: spawn(fn -> :ok end), + sender_pad_ref: :output, + supervisor: SubprocessSupervisor.start_link!(), + toilet_capacity: 300 + }) input_queue = InputQueue.init(%{ diff --git a/test/membrane/core/element/input_queue_test.exs b/test/membrane/core/element/input_queue_test.exs index bb014f53d..269917fab 100644 --- a/test/membrane/core/element/input_queue_test.exs +++ b/test/membrane/core/element/input_queue_test.exs @@ -4,14 +4,13 @@ defmodule Membrane.Core.Element.InputQueueTest do alias Membrane.Buffer alias Membrane.Core.Element.{AtomicDemand, InputQueue} alias Membrane.Core.Message + alias Membrane.Core.SubprocessSupervisor alias Membrane.Testing.Event require Message describe ".init/6 should" do setup do - atomic_demand = AtomicDemand.new(:pull, self(), :bytes, self(), :output_pad_ref) - {:ok, %{ log_tag: "test", @@ -19,7 +18,7 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_demand_unit: :bytes, outbound_demand_unit: :bytes, linked_output_ref: :output_pad_ref, - atomic_demand: atomic_demand, + atomic_demand: new_atomic_demand(), expected_metric: Buffer.Metric.from_unit(:bytes) }} end @@ -184,20 +183,17 @@ defmodule Membrane.Core.Element.InputQueueTest do describe ".take/2 should" do setup do - output_pad = :pad - atomic_demand = AtomicDemand.new(:pull, self(), :bytes, self(), output_pad) - input_queue = InputQueue.init(%{ inbound_demand_unit: :buffers, outbound_demand_unit: :buffers, - linked_output_ref: output_pad, + linked_output_ref: :output_pad_ref, log_tag: "test", - atomic_demand: atomic_demand, + atomic_demand: new_atomic_demand(), target_size: 40 }) - assert_received Message.new(:atomic_demand_increased, ^output_pad) + assert_received Message.new(:atomic_demand_increased, :output_pad_ref) [input_queue: input_queue] end @@ -230,11 +226,10 @@ defmodule Membrane.Core.Element.InputQueueTest do buffers1 = {:buffers, [:b1, :b2, :b3], 3, 3} buffers2 = {:buffers, [:b4, :b5, :b6], 3, 3} q = Qex.new() |> Qex.push(buffers1) |> Qex.push(buffers2) - output_pad = :pad - atomic_demand = AtomicDemand.new(:pull, self(), :bytes, self(), output_pad) + atomic_demand = new_atomic_demand() :ok = AtomicDemand.increase(atomic_demand, 94) - assert_received Message.new(:atomic_demand_increased, ^output_pad) + assert_received Message.new(:atomic_demand_increased, :output_pad_ref) input_queue = struct(InputQueue, @@ -244,7 +239,7 @@ defmodule Membrane.Core.Element.InputQueueTest do inbound_metric: Buffer.Metric.Count, outbound_metric: Buffer.Metric.Count, q: q, - linked_output_ref: output_pad, + linked_output_ref: :output_pad_ref, atomic_demand: atomic_demand ) @@ -301,7 +296,7 @@ defmodule Membrane.Core.Element.InputQueueTest do end test "if the queue works properly for :bytes input metric and :buffers output metric" do - atomic_demand = AtomicDemand.new(:pull, self(), :bytes, self(), :output_pad_ref) + atomic_demand = new_atomic_demand() queue = InputQueue.init(%{ @@ -338,7 +333,7 @@ defmodule Membrane.Core.Element.InputQueueTest do end test "if the queue works properly for :buffers input metric and :bytes output metric" do - atomic_demand = AtomicDemand.new(:pull, self(), :bytes, self(), :output_pad_ref) + atomic_demand = new_atomic_demand() queue = InputQueue.init(%{ @@ -372,6 +367,17 @@ defmodule Membrane.Core.Element.InputQueueTest do assert_receive Message.new(:atomic_demand_increased, :output_pad_ref) end + defp new_atomic_demand(), + do: + AtomicDemand.new(%{ + receiver_effective_flow_control: :pull, + receiver_process: self(), + receiver_demand_unit: :bytes, + sender_process: self(), + sender_pad_ref: :output_pad_ref, + supervisor: SubprocessSupervisor.start_link!() + }) + defp bufs_size(output, unit) do {_state, bufs} = output diff --git a/test/membrane/core/element/lifecycle_controller_test.exs b/test/membrane/core/element/lifecycle_controller_test.exs index 2ee7d6a44..6b5688d65 100644 --- a/test/membrane/core/element/lifecycle_controller_test.exs +++ b/test/membrane/core/element/lifecycle_controller_test.exs @@ -2,7 +2,11 @@ defmodule Membrane.Core.Element.LifecycleControllerTest do use ExUnit.Case alias Membrane.Core.Element.{AtomicDemand, InputQueue, LifecycleController, State} - alias Membrane.Core.Message + + alias Membrane.Core.{ + Message, + SubprocessSupervisor + } require Membrane.Core.Message @@ -17,7 +21,15 @@ defmodule Membrane.Core.Element.LifecycleControllerTest do end setup do - atomic_demand = AtomicDemand.new(:pull, self(), :buffers, self(), :some_pad) + atomic_demand = + AtomicDemand.new(%{ + receiver_effective_flow_control: :pull, + receiver_process: self(), + receiver_demand_unit: :buffers, + sender_process: self(), + sender_pad_ref: :some_pad, + supervisor: SubprocessSupervisor.start_link!() + }) input_queue = InputQueue.init(%{ diff --git a/test/membrane/core/element/pad_controller_test.exs b/test/membrane/core/element/pad_controller_test.exs index 94c827752..4a5b32c2a 100644 --- a/test/membrane/core/element/pad_controller_test.exs +++ b/test/membrane/core/element/pad_controller_test.exs @@ -4,6 +4,7 @@ defmodule Membrane.Core.Element.PadControllerTest do alias Membrane.Core.Child.{PadModel, PadSpecHandler} alias Membrane.Core.Element.State alias Membrane.Core.Message + alias Membrane.Core.SubprocessSupervisor alias Membrane.LinkError alias Membrane.Pad alias Membrane.Support.Element.{DynamicFilter, TrivialFilter} @@ -19,7 +20,8 @@ defmodule Membrane.Core.Element.PadControllerTest do module: elem_module, parent_pid: self(), internal_state: %{}, - synchronization: %{clock: nil, parent_clock: nil} + synchronization: %{clock: nil, parent_clock: nil}, + subprocess_supervisor: SubprocessSupervisor.start_link!() ) |> PadSpecHandler.init_pads() end diff --git a/test/membrane/core/element/stream_format_controller_test.exs b/test/membrane/core/element/stream_format_controller_test.exs index 7379d16b8..3b159e4ae 100644 --- a/test/membrane/core/element/stream_format_controller_test.exs +++ b/test/membrane/core/element/stream_format_controller_test.exs @@ -4,6 +4,7 @@ defmodule Membrane.Core.Element.StreamFormatControllerTest do alias Membrane.Buffer alias Membrane.Core.Message alias Membrane.Core.Element.{AtomicDemand, InputQueue, State} + alias Membrane.Core.SubprocessSupervisor alias Membrane.StreamFormat.Mock, as: MockStreamFormat alias Membrane.Support.DemandsTest.Filter @@ -13,7 +14,15 @@ defmodule Membrane.Core.Element.StreamFormatControllerTest do @module Membrane.Core.Element.StreamFormatController setup do - atomic_demand = AtomicDemand.new(:pull, self(), :buffers, self(), :some_pad) + atomic_demand = + AtomicDemand.new(%{ + receiver_effective_flow_control: :pull, + receiver_process: self(), + receiver_demand_unit: :buffers, + sender_process: self(), + sender_pad_ref: :some_pad, + supervisor: SubprocessSupervisor.start_link!() + }) input_queue = InputQueue.init(%{ diff --git a/test/membrane/core/element_test.exs b/test/membrane/core/element_test.exs index 8eee4580f..5860bf70a 100644 --- a/test/membrane/core/element_test.exs +++ b/test/membrane/core/element_test.exs @@ -2,8 +2,13 @@ defmodule Membrane.Core.ElementTest do use ExUnit.Case, async: true alias __MODULE__.SomeElement - alias Membrane.Core.Element - alias Membrane.Core.Message + + alias Membrane.Core.{ + Element, + Message, + SubprocessSupervisor + } + alias Membrane.Core.Parent.Link.Endpoint require Membrane.Core.Message @@ -112,7 +117,14 @@ defmodule Membrane.Core.ElementTest do other_info = %{direction: :input, flow_control: :manual, demand_unit: :buffers} output_atomic_demand = - Element.AtomicDemand.new(:pull, helper_server, :buffers, self(), :output) + Element.AtomicDemand.new(%{ + receiver_effective_flow_control: :pull, + receiver_process: helper_server, + receiver_demand_unit: :buffers, + sender_process: self(), + sender_pad_ref: :output, + supervisor: SubprocessSupervisor.start_link!() + }) reply_link_metadata = %{ atomic_demand: output_atomic_demand, From f3ef7988a9460ca5ff790a27429549e121362c70 Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 22 May 2023 13:46:08 +0200 Subject: [PATCH 63/64] Implememnt suggestions from CR --- lib/membrane/core/element/atomic_demand.ex | 4 ++-- .../core/element/atomic_demand/atomic_flow_status.ex | 6 +++--- .../core/element/atomic_demand/distributed_atomic.ex | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/membrane/core/element/atomic_demand.ex b/lib/membrane/core/element/atomic_demand.ex index 4e555c237..1b387254e 100644 --- a/lib/membrane/core/element/atomic_demand.ex +++ b/lib/membrane/core/element/atomic_demand.ex @@ -99,7 +99,7 @@ defmodule Membrane.Core.Element.AtomicDemand do @spec set_sender_status(t, AtomicFlowStatus.value()) :: :ok def set_sender_status(%__MODULE__{} = atomic_demand, mode) do - AtomicFlowStatus.put( + AtomicFlowStatus.set( atomic_demand.sender_status, mode ) @@ -112,7 +112,7 @@ defmodule Membrane.Core.Element.AtomicDemand do @spec set_receiver_status(t, AtomicFlowStatus.value()) :: :ok def set_receiver_status(%__MODULE__{} = atomic_demand, mode) do - AtomicFlowStatus.put( + AtomicFlowStatus.set( atomic_demand.receiver_status, mode ) diff --git a/lib/membrane/core/element/atomic_demand/atomic_flow_status.ex b/lib/membrane/core/element/atomic_demand/atomic_flow_status.ex index c9ca3b5a3..e4605bec2 100644 --- a/lib/membrane/core/element/atomic_demand/atomic_flow_status.ex +++ b/lib/membrane/core/element/atomic_demand/atomic_flow_status.ex @@ -21,10 +21,10 @@ defmodule Membrane.Core.Element.AtomicDemand.AtomicFlowStatus do |> int_to_flow_status() end - @spec put(t, value()) :: :ok - def put(distributed_atomic, value) do + @spec set(t, value()) :: :ok + def set(distributed_atomic, value) do value = flow_status_to_int(value) - DistributedAtomic.put(distributed_atomic, value) + DistributedAtomic.set(distributed_atomic, value) end defp int_to_flow_status(0), do: :to_be_resolved diff --git a/lib/membrane/core/element/atomic_demand/distributed_atomic.ex b/lib/membrane/core/element/atomic_demand/distributed_atomic.ex index e5e4ff869..e18f7cfbb 100644 --- a/lib/membrane/core/element/atomic_demand/distributed_atomic.ex +++ b/lib/membrane/core/element/atomic_demand/distributed_atomic.ex @@ -28,7 +28,7 @@ defmodule Membrane.Core.Element.AtomicDemand.DistributedAtomic do } if initial_value != nil do - :ok = put(distributed_atomic, initial_value) + :ok = set(distributed_atomic, initial_value) end distributed_atomic @@ -54,13 +54,13 @@ defmodule Membrane.Core.Element.AtomicDemand.DistributedAtomic do GenServer.cast(distributed_atomic.worker, {:sub_get, distributed_atomic.atomic_ref, value}) end - @spec put(t, integer()) :: :ok - def put(%__MODULE__{} = distributed_atomic, value) + @spec set(t, integer()) :: :ok + def set(%__MODULE__{} = distributed_atomic, value) when on_the_same_node_as_self(distributed_atomic) do :atomics.put(distributed_atomic.atomic_ref, 1, value) end - def put(%__MODULE__{} = distributed_atomic, value) do + def set(%__MODULE__{} = distributed_atomic, value) do GenServer.cast(distributed_atomic.worker, {:put, distributed_atomic.atomic_ref, value}) end From 3d8f81c9806bd156da6397d8be902c8a5d0b68bc Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Mon, 22 May 2023 14:38:55 +0200 Subject: [PATCH 64/64] Fix typo in comment --- lib/membrane/core/element/demand_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/membrane/core/element/demand_controller.ex b/lib/membrane/core/element/demand_controller.ex index 654cf42ad..cb5d99618 100644 --- a/lib/membrane/core/element/demand_controller.ex +++ b/lib/membrane/core/element/demand_controller.ex @@ -1,7 +1,7 @@ defmodule Membrane.Core.Element.DemandController do @moduledoc false - # Module handling changes in values of output pads demand atomics + # Module handling changes in values of output pads atomic demand use Bunch