Raxol.Gateway.Session (RaxolGateway v0.2.0)

Copy Markdown View Source

One process per chat.

A session is created by Raxol.Gateway.SessionRouter for a Route and runs a Raxol.Gateway.Handler. Each inbound event is dispatched to the handler; a {:reply, rendered, state} result is delivered back through the session's :deliver function (the router builds it from the adapter and connection). A session stops itself after :idle_timeout ms of inactivity.

A session has a conversation_id (defaulting to its route key) that is stable across a platform handoff: the router can start a session on a new route with an existing conversation_id, so a configured :log resumes the same history.

Handler startup

start_link/1 returns as soon as the process exists; the handler's init/2 runs in a handle_continue after it. This keeps an expensive handler (one starting a per-chat TEA app, say) from blocking the router that started it, which starts sessions synchronously inside its own handle_call.

Two consequences for callers. A handler that fails to initialize stops the session after start_link/1 has already returned {:ok, pid}, so the failure arrives as a DOWN rather than a return value -- SessionRouter emits [:raxol_gateway, :session, :down] for it. And "the handler has started" is not implied by start_link/1 returning: any GenServer.call/3 to the session is serialized behind the continue and can be used as the barrier.

Because startup is no longer reported through a return value, it is reported through telemetry instead: [:raxol_gateway, :session, :ready] once the handler has initialized, and [..., :init_timeout] when it never does. A :started from the router with no :ready behind it is a broken handler.

Handler init is bounded

A handler stuck in init/2 parks the session inside its own continue, where it can read no message -- not a queued event, and not the idle timer, which is armed only once init succeeds. Nothing would ever reap it, while the router goes on routing that chat to it. So init/1 spawns a watchdog that kills the session if the handler has not returned within :handler_init_timeout. The kill has to be brutal (a wedged process cannot honour a graceful exit), which is why the diagnosis is emitted as telemetry before it lands.

This matters most for a handler that starts a per-chat TEA app: start_link on deployment-authored init/1 code has no timeout of its own.

Options

  • :route (required) -- the Raxol.Gateway.Route this session serves
  • :handler (required) -- {module, opts} implementing Gateway.Handler
  • :deliver -- (Route.t(), rendered -> any()), default a no-op. {:error, reason}, a raised exception, or an exit reports [:raxol_gateway, :session, :delivery_failed]; the session remains alive.
  • :idle_timeout -- ms before the session stops (default 10 minutes)
  • :handler_init_timeout -- ms the handler's init/2 may take before the session is killed (default 30 seconds), or :infinity to wait forever
  • :conversation_id -- a stable id for this chat (default Route.key/1)
  • :log -- {module, server} whose append(server, conversation_id, items) records each inbound event and each successfully delivered outbound reply (e.g. {Raxol.Agent.Conversation.Log, log_server}); default none

Summary

Functions

Returns a specification to start this module under a supervisor.

The session's stable conversation id.

Dispatch an inbound event to the session.

The route this session serves.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

conversation_id(server, timeout \\ 5000)

@spec conversation_id(GenServer.server(), timeout()) :: String.t()

The session's stable conversation id.

dispatch(server, event)

@spec dispatch(GenServer.server(), term()) :: :ok

Dispatch an inbound event to the session.

route(server)

The route this session serves.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()