Current section

Files

Jump to
agent_session_manager lib asm extensions persistence pipeline_plug.ex
Raw

lib/asm/extensions/persistence/pipeline_plug.ex

defmodule ASM.Extensions.Persistence.PipelinePlug do
@moduledoc """
Non-blocking pipeline plug that forwards events to an async persistence writer.
Options:
- `:writer` (required): writer pid started via `ASM.Extensions.Persistence.start_writer/1`
- `:on_failure` (optional): `:drop` (default) or `:halt`
"""
@behaviour ASM.Pipeline.Plug
alias ASM.{Error, Event, Telemetry}
alias ASM.Extensions.Persistence.Writer
@type on_failure :: :drop | :halt
@impl true
@spec call(Event.t(), map(), keyword()) ::
{:ok, Event.t(), map()} | {:error, Error.t(), map()}
def call(%Event{} = event, ctx, opts) when is_map(ctx) and is_list(opts) do
on_failure = Keyword.get(opts, :on_failure, :drop)
case Keyword.fetch(opts, :writer) do
{:ok, writer} when is_pid(writer) ->
if Process.alive?(writer) do
:ok = Writer.enqueue(writer, event)
{:ok, event, ctx}
else
handle_unavailable_writer(event, ctx, on_failure)
end
_missing_or_down ->
handle_unavailable_writer(event, ctx, on_failure)
end
end
defp handle_unavailable_writer(%Event{} = event, ctx, :halt) do
error =
Error.new(
:unknown,
:runtime,
"persistence pipeline writer is unavailable"
)
emit_drop_telemetry(event, error)
{:error, error, ctx}
end
defp handle_unavailable_writer(%Event{} = event, ctx, _drop) do
error = Error.new(:unknown, :runtime, "persistence pipeline writer is unavailable")
emit_drop_telemetry(event, error)
{:ok, event, ctx}
end
defp emit_drop_telemetry(%Event{} = event, %Error{} = error) do
Telemetry.execute([:asm, :ext, :persistence, :pipeline, :drop], %{}, %{
session_id: event.session_id,
run_id: event.run_id,
event_id: event.id,
event_kind: event.kind,
error_kind: error.kind,
error_domain: error.domain
})
end
end