Ctrl K
ComunidadGuías…

lib/audio_proxy/application.ex

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
defmodule AudioProxy.Application do
  @moduledoc """
  OTP application entry point.

  Reads and validates the `AP_*` environment (see `AudioProxy.Config`) before
  starting anything, so an invalid configuration aborts the boot rather than
  surfacing as a request-time failure.

  The log is set up in the same breath, and before the first line is emitted:
  `AP_LOG_LEVEL` is applied to the primary logger, and
  `AudioProxy.LogHandler` attaches to the telemetry the router and the render
  path produce. Neither is a process, so neither is in the supervision tree —
  a telemetry handler runs in whichever process emits the event.
  """

  use Application

  require Logger

  @impl true
  def start(_type, _args) do
    config = AudioProxy.Config.load!()

    Logger.configure(level: config.log_level)
    AudioProxy.LogHandler.attach()

    # Staging left behind by a write that was killed outright would otherwise
    # accumulate forever. Before the tree starts, nothing can be staging.
    case config.variant_store do
      {:file, root} -> AudioProxy.VariantStore.Local.sweep_staging(root)
      _other -> :ok
    end

    # S3's HTTP client gets a `:httpc` profile of its own, sized against
    # AP_MAX_CONCURRENCY — the default profile is shared with the rest of the
    # VM and allows two sessions per host, which would throttle concurrent
    # renders on the connection pool instead of on the semaphore meant to
    # govern them. Started unconditionally: it costs one idle process, and
    # making it conditional would mean a deployment that gains S3 config
    # without a restart finds no profile.
    AudioProxy.S3.HttpClient.setup!(config.max_concurrency)

    if config.allow_insecure do
      Logger.warning(
        "AP_ALLOW_INSECURE is enabled — /insecure/ URLs bypass signature verification; never enable in production"
      )
    end

    # Renders before the listener: a request that arrives on the first accepted
    # connection must find somewhere to start a subprocess. The coalescing
    # registry and its supervisor sit between them, because a coordinator
    # spawns a render in `init/1` and must therefore stop before the thing it
    # spawns into — which reverse-order shutdown gives for free. The tee
    # supervisor sits first for the same reason: coordinators spawn tees too,
    # and a coordinator stopping is what tells its tee to abort.
    #
    # The semaphore is ahead of both, for that reason read the other way round:
    # a coordinator releases its slot from `terminate/2`, so the thing it
    # releases into has to still be there when the last coordinator stops.
    # The probe pool is the same shape one layer over: `AudioProxy.ProbeLimiter`
    # ahead of the coordinators that take slots from it, and its registry and
    # supervisor after, so reverse-order shutdown stops the coordinators before
    # the counter they release into.
    children =
      [
        AudioProxy.Semaphore,
        AudioProxy.ProbeLimiter,
        # Behind the semaphore, because a scrape samples it. Ahead of the
        # listener for the reason `AudioProxy.Readiness` is: once `/metrics` is
        # served, the first accepted connection may be the scraper's.
        AudioProxy.Metrics,
        # Reads the semaphore's depth, so it starts behind it — and ahead of
        # the listener, because `/ready` must have somewhere to ask on the
        # first accepted connection.
        AudioProxy.Readiness,
        AudioProxy.VariantStore.Tee.supervisor(),
        AudioProxy.Ffmpeg.RenderSupervisor
      ] ++
        AudioProxy.ProbeCoordinator.children() ++
        AudioProxy.RenderCoordinator.children() ++ listener(config)

    Logger.info("audio_proxy #{vsn()} starting (serve_mode: #{config.serve_mode})")

    Supervisor.start_link(children, strategy: :one_for_one, name: AudioProxy.Supervisor)
  end

  # The test suite drives the router through Plug.Test, so it binds no socket.
  #
  # Two listeners, because `/metrics` is unsigned and its access control is the
  # bind address (API doc §2, and `AudioProxy.Metrics.Router` for why a bind
  # rather than a peer check). The metrics one is not optional and has no
  # enable flag: it defaults to loopback, where it is reachable by a sidecar
  # scraper and by nothing off-host.
  defp listener(config) do
    if Application.get_env(:audio_proxy, :start_listener, true) do
      Logger.info("listening on http://0.0.0.0:#{config.port}")
      Logger.info("metrics on http://#{bind(config.metrics_bind)}:#{config.metrics_port}")

      [
        {Bandit, plug: AudioProxy.Router, scheme: :http, port: config.port},
        {Bandit,
         plug: AudioProxy.Metrics.Router,
         scheme: :http,
         ip: config.metrics_bind,
         port: config.metrics_port}
      ]
    else
      []
    end
  end

  defp bind(address), do: address |> :inet.ntoa() |> to_string()

  defp vsn, do: :audio_proxy |> Application.spec(:vsn) |> to_string()
end