-module(log_server). -behaviour(gen_server). -export([start_link/2, start_link/3, append/2, tip/1, entries/1, replay/3, segments/1, stop/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2]). %% Step 3c.b — gen_server in front of `log` that owns a single %% per-actor disk-backed log state and serialises concurrent %% appenders through `gen_server:call`. %% %% Architecture: the pure `log` module from Step 3c.a remains the %% canonical substrate (open_disk, append, tip, replay, entries, %% segments). This wrapper owns one log state per process; every %% public op (append/tip/entries/replay/segments) routes through %% gen_server:call so that the on-disk segment writer sees one %% append at a time, regardless of how many writer processes are %% pushing concurrently. %% %% Port notes carried from Step 5b's registry_server: %% * `gen_server:start_link/2` returns the raw Pid, not `{ok,Pid}`. %% * Spawned processes don't survive across separate %% `erlang-eval-ast` invocations — every concurrency test has %% to start the server, spin writers, join them, and assert all %% within one eval expression. %% %% API takes the server Pid (not a registered name) so multiple %% per-actor servers can coexist without colliding on the registry. %% --- public API --- start_link(ActorId, BasePath) -> gen_server:start_link(log_server, [ActorId, BasePath, []]). start_link(ActorId, BasePath, Opts) -> gen_server:start_link(log_server, [ActorId, BasePath, Opts]). append(Pid, Activity) -> gen_server:call(Pid, {append, Activity}). tip(Pid) -> gen_server:call(Pid, tip). entries(Pid) -> gen_server:call(Pid, entries). replay(Pid, InitAcc, Fun) -> %% The fold runs server-side so the state stays consistent %% with concurrent writers; the caller's Fun is closed over %% the message and shipped opaque through gen_server:call. gen_server:call(Pid, {replay, InitAcc, Fun}). segments(Pid) -> gen_server:call(Pid, segments). stop(Pid) -> gen_server:call(Pid, '$gen_stop'). %% --- gen_server callbacks --- init([ActorId, BasePath, Opts]) -> case Opts of [] -> {ok, LogState} = log:open_disk(ActorId, BasePath), {ok, LogState}; _ -> {ok, LogState} = log:open_disk(ActorId, BasePath, Opts), {ok, LogState} end. handle_call({append, Activity}, _From, State) -> {ok, NewState, Seq} = log:append(State, Activity), {reply, {ok, Seq}, NewState}; handle_call(tip, _From, State) -> {reply, log:tip(State), State}; handle_call(entries, _From, State) -> {reply, log:entries(State), State}; handle_call({replay, InitAcc, Fun}, _From, State) -> {reply, log:replay(State, InitAcc, Fun), State}; handle_call(segments, _From, State) -> {reply, log:segments(State), State}. handle_cast(_, S) -> {noreply, S}. handle_info(_, S) -> {noreply, S}.