From 28c47ab435220d1b81870093aaf50225d7b8f05c Mon Sep 17 00:00:00 2001 From: Steffen Beyer Date: Fri, 20 Mar 2026 04:10:06 +0100 Subject: [PATCH] test/build: Stability, compatibility --- justfile | 11 +-- .../storage/adapters/memory/store.ex | 76 ++++++++++++++++--- lib/parrhesia/storage/supervisor.ex | 10 ++- .../storage/adapters/memory/adapter_test.exs | 6 ++ 4 files changed, 86 insertions(+), 17 deletions(-) diff --git a/justfile b/justfile index 7f06cd9..08f3dcc 100644 --- a/justfile +++ b/justfile @@ -1,5 +1,4 @@ set shell := ["bash", "-euo", "pipefail", "-c"] -set script-interpreter := ["bash", "-euo", "pipefail"] repo_root := justfile_directory() @@ -12,8 +11,9 @@ help topic="": @cd "{{repo_root}}" && ./scripts/just_help.sh "{{topic}}" # Raw e2e harness commands. -[script] -e2e subcommand="help" *args: +e2e subcommand *args: + #!/usr/bin/env bash + set -euo pipefail cd "{{repo_root}}" subcommand="{{subcommand}}" @@ -40,8 +40,9 @@ e2e subcommand="help" *args: fi # Benchmark flows (local/cloud/history + direct relay targets). -[script] -bench subcommand="help" *args: +bench subcommand *args: + #!/usr/bin/env bash + set -euo pipefail cd "{{repo_root}}" subcommand="{{subcommand}}" diff --git a/lib/parrhesia/storage/adapters/memory/store.ex b/lib/parrhesia/storage/adapters/memory/store.ex index 1a74d90..625a15b 100644 --- a/lib/parrhesia/storage/adapters/memory/store.ex +++ b/lib/parrhesia/storage/adapters/memory/store.ex @@ -22,7 +22,17 @@ defmodule Parrhesia.Storage.Adapters.Memory.Store do audit_logs: [] } - def ensure_started, do: start_store() + def start_link(opts \\ []) do + name = Keyword.get(opts, :name, @name) + Agent.start_link(&init_state/0, name: name) + end + + def ensure_started do + case Process.whereis(@name) do + pid when is_pid(pid) -> :ok + nil -> start_store() + end + end def put_event(event_id, event) when is_binary(event_id) and is_map(event) do :ok = ensure_started() @@ -159,28 +169,72 @@ defmodule Parrhesia.Storage.Adapters.Memory.Store do defp normalize_reduce_result(next_acc), do: next_acc def get(fun) do - :ok = ensure_started() - Agent.get(@name, fun) + with_store(fn pid -> Agent.get(pid, fun) end) end def update(fun) do - :ok = ensure_started() - Agent.update(@name, fun) + with_store(fn pid -> Agent.update(pid, fun) end) end def get_and_update(fun) do - :ok = ensure_started() - Agent.get_and_update(@name, fun) + with_store(fn pid -> Agent.get_and_update(pid, fun) end) end defp start_store do - case Agent.start_link(&init_state/0, name: @name) do - {:ok, _pid} -> :ok - {:error, {:already_started, _pid}} -> :ok - {:error, reason} -> {:error, reason} + case start_link() do + {:ok, _pid} -> + :ok + + {:error, {:already_started, pid}} -> + if Process.alive?(pid) do + :ok + else + wait_for_store_exit(pid) + end + + {:error, reason} -> + {:error, reason} end end + defp with_store(fun, attempts \\ 2) + + defp with_store(fun, attempts) when attempts > 0 do + :ok = ensure_started() + + case Process.whereis(@name) do + pid when is_pid(pid) -> + try do + fun.(pid) + catch + :exit, reason -> + if noproc_exit?(reason) and attempts > 1 do + with_store(fun, attempts - 1) + else + exit(reason) + end + end + + nil -> + with_store(fun, attempts - 1) + end + end + + defp with_store(_fun, 0), do: exit(:noproc) + + defp wait_for_store_exit(pid) do + ref = Process.monitor(pid) + + receive do + {:DOWN, ^ref, :process, ^pid, _reason} -> start_store() + after + 100 -> start_store() + end + end + + defp noproc_exit?({:noproc, _details}), do: true + defp noproc_exit?(_reason), do: false + defp init_state do ensure_tables_started() diff --git a/lib/parrhesia/storage/supervisor.ex b/lib/parrhesia/storage/supervisor.ex index 3e73332..adf4423 100644 --- a/lib/parrhesia/storage/supervisor.ex +++ b/lib/parrhesia/storage/supervisor.ex @@ -13,11 +13,19 @@ defmodule Parrhesia.Storage.Supervisor do @impl true def init(_init_arg) do - children = moderation_cache_children() ++ PostgresRepos.started_repos() + children = + memory_store_children() ++ moderation_cache_children() ++ PostgresRepos.started_repos() Supervisor.init(children, strategy: :one_for_one) end + defp memory_store_children do + case Application.get_env(:parrhesia, :storage, [])[:backend] do + :memory -> [Parrhesia.Storage.Adapters.Memory.Store] + _other -> [] + end + end + defp moderation_cache_children do if PostgresRepos.postgres_enabled?() and Application.get_env(:parrhesia, :moderation_cache_enabled, true) do diff --git a/test/parrhesia/storage/adapters/memory/adapter_test.exs b/test/parrhesia/storage/adapters/memory/adapter_test.exs index 27bbce7..e238ed8 100644 --- a/test/parrhesia/storage/adapters/memory/adapter_test.exs +++ b/test/parrhesia/storage/adapters/memory/adapter_test.exs @@ -6,6 +6,12 @@ defmodule Parrhesia.Storage.Adapters.Memory.AdapterTest do alias Parrhesia.Storage.Adapters.Memory.Events alias Parrhesia.Storage.Adapters.Memory.Groups alias Parrhesia.Storage.Adapters.Memory.Moderation + alias Parrhesia.Storage.Adapters.Memory.Store + + setup do + start_supervised!(Store) + :ok + end test "memory adapter supports basic behavior contract operations" do event_id = String.duplicate("a", 64)