%% This Source Code Form is subject to the terms of the Mozilla Public
%% License, v. 2.0. If a copy of the MPL was not distributed with this
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
%%
%% Copyright (c) 2017-2025 Broadcom. All Rights Reserved. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries.
%%
%% @hidden
-module(ra_log_sync).
-behaviour(gen_batch_server).

-export([start_link/1,
         pool_size/0,
         worker_name/2,
         sync/2]).

-export([init/1,
         handle_batch/2,
         terminate/2,
         format_status/1]).

-type sync_fun() :: fun(() -> ok | {error, term()}).
-type pool_ref() :: {pool, BaseName :: atom(), PoolSize :: pos_integer()}.

-export_type([pool_ref/0]).

-record(state, {}).

%%%===================================================================
%%% API
%%%===================================================================

-spec pool_size() -> pos_integer().
pool_size() ->
    max(1, erlang:system_info(schedulers) div 4).

-spec worker_name(atom(), non_neg_integer()) -> atom().
worker_name(BaseName, Idx) ->
    % elp:ignore W0023 (atoms_exhaustion)
    list_to_atom(atom_to_list(BaseName) ++ "_" ++ integer_to_list(Idx)).

-spec start_link(#{name := atom()}) ->
    {ok, pid()} | {error, term()}.
start_link(#{name := Name} = _Config) ->
    gen_batch_server:start_link({local, Name}, ?MODULE, {},
                                [{reversed_batch, true}]).

-spec sync(pool_ref(), sync_fun()) ->
    ok | {error, term()}.
sync({pool, BaseName, PoolSize}, SyncFun) when is_function(SyncFun, 0) ->
    Idx = rand:uniform(PoolSize) - 1,
    Worker = worker_name(BaseName, Idx),
    gen_batch_server:call(Worker, {sync, SyncFun}, infinity).

%%%===================================================================
%%% gen_batch_server callbacks
%%%===================================================================

init({}) ->
    {ok, #state{}}.

handle_batch(Ops, State) ->
    %% Ops arrive in reverse arrival order (most recent first) because
    %% reversed_batch = true. On journaling filesystems (ext4, XFS),
    %% syncing the most recently written file first flushes the journal,
    %% which tends to make subsequent syncs near-instant since the earlier
    %% files are already durable.
    lists:foreach(
      fun({call, From, {sync, SyncFun}}) ->
              Result = try SyncFun()
                       catch _:Err -> {error, Err}
                       end,
              gen:reply(From, Result);
         (_) ->
              ok
      end, Ops),
    {ok, [], State}.

terminate(_Reason, _State) ->
    ok.

format_status(State) ->
    State.
