Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/khepri_machine.erl
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,16 @@
-type tx_ret() :: khepri:ok(khepri_tx:tx_fun_result()) |
khepri_tx:tx_abort() |
no_return().
%% Return value of a transaction.
%%
%% A successful transaction returns `{ok, Result}'. A failure is reported as a
%% bare `{error, Reason}' tuple, never wrapped in `{ok, _}'. This covers both
%% an aborted transaction (see {@link khepri_tx:abort/1}) and an infrastructure
%% error surfaced while dispatching the transaction (for example `{error,
%% noproc}' or `{error, timeout}' when the store is not running). This matches
%% how {@link khepri:handle_async_ret/2} propagates the same errors for
%% asynchronous transactions. To return an error-shaped value as a successful
%% result, wrap it explicitly, e.g. `{ok, {error, Reason}}'.

-type async_ret() :: ok.

Expand Down Expand Up @@ -541,6 +551,8 @@ readonly_transaction(StoreId, Fun, Args, Options)
case process_query(StoreId, Query, Options) of
{exception, _, _, _} = Exception ->
handle_tx_exception(Exception);
{error, _} = Error ->
Error;
Ret ->
{ok, Ret}
end;
Expand All @@ -559,6 +571,8 @@ readonly_transaction(StoreId, PathPattern, Args, Options)
case process_query(StoreId, Query, Options) of
{exception, _, _, _} = Exception ->
handle_tx_exception(Exception);
{error, _} = Error ->
Error;
Ret ->
{ok, Ret}
end.
Expand Down Expand Up @@ -591,6 +605,8 @@ readwrite_transaction1(StoreId, StandaloneFunOrPath, Args, Options) ->
case process_command(StoreId, Command, Options1) of
{exception, _, _, _} = Exception ->
handle_tx_exception(Exception);
{error, _} = Error ->
Error;
ok = Ret ->
CommandType = select_command_type(Options),
case CommandType of
Expand Down
28 changes: 28 additions & 0 deletions test/simple_tx_misc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -696,3 +696,31 @@ tx_using_erl_eval_test_() ->
fun local_fun_using_erl_eval/0,
rw)
end)]}.

readwrite_transaction_propagates_infra_error_test() ->
%% When the store is not running, `process_command/3' returns an
%% infrastructure error such as `{error, noproc}'.
%% `readwrite_transaction1/3' must propagate that error tuple directly
%% instead of wrapping it as `{ok, {error, noproc}}'; a caller matching
%% on `{ok, Value}' would otherwise mistake the error for a successful
%% transaction result and crash downstream.
%%
%% A path-pattern transaction is used so the command reaches
%% `process_command/3' without going through stand-alone function
%% extraction. `?FUNCTION_NAME' is a store ID that is never started, so
%% the command cannot reach a Ra server.
?assertEqual(
{error, noproc},
khepri:transaction(?FUNCTION_NAME, [foo], [], rw)).

readonly_transaction_propagates_infra_error_test() ->
%% Same expectation as the read-write case, but for a read-only
%% transaction going through `process_query/3'. The infrastructure error
%% must be propagated verbatim rather than wrapped as
%% `{ok, {error, noproc}}'.
%%
%% `?FUNCTION_NAME' is a store ID that is never started, so the query
%% cannot reach a Ra server.
?assertEqual(
{error, noproc},
khepri:transaction(?FUNCTION_NAME, [foo], [], ro)).