| defmodule Plausible.InstallationSupport.Check do |
| @moduledoc """ |
| Behaviour to be implemented by a specific installation support check. |
| |
| `report_progress_as()` doesn't necessarily reflect the actual check |
| description, it serves as a user-facing message grouping mechanism, |
| to prevent frequent message flashing when checks rotate often. |
| |
| Each check operates on `%Plausible.InstallationSupport.State{}` and is |
| expected to return it, optionally modified, by all means. `perform_safe/1` |
| is used to guarantee no exceptions are thrown by faulty implementations, |
| not to interrupt LiveView. |
| """ |
| @type state() :: Plausible.InstallationSupport.State.t() |
| @callback report_progress_as() :: String.t() |
| @callback perform(state(), Keyword.t()) :: state() |
|
|
| defmacro __using__(_) do |
| quote do |
| import Plausible.InstallationSupport.State |
| alias Plausible.InstallationSupport.State |
|
|
| require Logger |
|
|
| @behaviour Plausible.InstallationSupport.Check |
|
|
| def perform_safe(state, opts) do |
| timeout = Keyword.get(opts, :timeout, 10_000) |
|
|
| task = |
| Task.async(fn -> |
| try do |
| perform(state, opts) |
| catch |
| _, e -> |
| Logger.error( |
| "Error running check #{inspect(__MODULE__)} on #{state.url}: #{inspect(e)}" |
| ) |
|
|
| put_diagnostics(state, service_error: %{code: :internal_check_error, extra: e}) |
| end |
| end) |
|
|
| try do |
| Task.await(task, timeout) |
| catch |
| :exit, {:timeout, _} -> |
| Task.shutdown(task, :brutal_kill) |
| check_name = __MODULE__ |> Atom.to_string() |> String.split(".") |> List.last() |
|
|
| put_diagnostics(state, |
| service_error: %{ |
| code: :internal_check_timeout, |
| extra: "#{check_name} timed out after #{timeout}ms" |
| } |
| ) |
| end |
| end |
| end |
| end |
| end |
|
|