monitor parser limits and handles demand

This commit is contained in:
mmacia committed 2026-05-01 23:41:56 +00:00
1 parent 976fb3fa8c
commit d591e7479e
1 file changed
+44 -1
+44 -1
View File
@@ -78,10 +78,14 @@ defmodule FeedReader.Parser do
defp start_parser(input, format, opts, chunk_size) do defp start_parser(input, format, opts, chunk_size) do
parent = self() parent = self()
reference = make_ref() reference = make_ref()
max_queue_bytes = queue_limit(opts)
{pid, monitor} = {pid, monitor} =
spawn_monitor(fn -> spawn_monitor(fn ->
sink = fn event -> send(parent, {reference, {:event, event}}) end owner_monitor = Process.monitor(parent)
sink = event_sink(parent, reference, owner_monitor, max_queue_bytes)
state = format.new_parser_state(sink, opts) state = format.new_parser_state(sink, opts)
result = result =
@@ -100,6 +104,8 @@ defmodule FeedReader.Parser do
defp next_event(%{done?: true} = state), do: {:halt, state} defp next_event(%{done?: true} = state), do: {:halt, state}
defp next_event(state) do defp next_event(state) do
send(state.pid, {state.reference, :demand})
receive do receive do
{reference, {:event, event}} when reference == state.reference -> {reference, {:event, event}} when reference == state.reference ->
state = if match?({:error, _reason}, event), do: %{state | done?: true}, else: state state = if match?({:error, _reason}, event), do: %{state | done?: true}, else: state
@@ -122,4 +128,41 @@ defmodule FeedReader.Parser do
Process.demonitor(monitor, [:flush]) Process.demonitor(monitor, [:flush])
:ok :ok
end end
defp event_sink(parent, reference, owner_monitor, max_queue_bytes) do
fn event ->
await_demand(parent, reference, owner_monitor)
if byte_size(:erlang.term_to_binary(event)) > max_queue_bytes do
send(
parent,
{reference, {:event, {:error, {:resource_limit, :max_queue_bytes, max_queue_bytes}}}}
)
exit(:normal)
else
send(parent, {reference, {:event, event}})
end
end
end
defp await_demand(parent, reference, owner_monitor) do
receive do
{^reference, :demand} -> :ok
{:DOWN, ^owner_monitor, :process, ^parent, _reason} -> exit(:normal)
end
end
defp queue_limit(opts) do
default =
case Keyword.get(opts, :max_item_bytes, 4_194_304) do
value when is_integer(value) and value >= 0 -> value
_invalid -> 4_194_304
end
case Keyword.get(opts, :max_queue_bytes, default) do
value when is_integer(value) and value >= 0 -> value
_invalid -> default
end
end
end end