Skip to content

Instantly share code, notes, and snippets.

@sshine
Created October 2, 2014 16:37
Show Gist options
  • Save sshine/62c8bab5ab89211a7ef3 to your computer and use it in GitHub Desktop.
Save sshine/62c8bab5ab89211a7ef3 to your computer and use it in GitHub Desktop.
-module(transact).
-export([ start_server/1, start_transaction/1,
modify_state/3, commit_transaction/2 ]).
%%%%%%%
rpc_send(Pid, Msg) ->
Pid ! {self(), Msg},
receive
{reply, Reply} ->
Reply
end.
rpc_reply(Pid, Reply) ->
Pid ! {reply, Reply},
ok.
%%%%%%%
start_server(InitState) ->
Pid = spawn(fun () -> server(InitState, []) end),
{ok, Pid}.
start_transaction(ServerPid) ->
case rpc_send(ServerPid, start_transaction) of
TransId -> {ok, TransId}
end.
commit_transaction(ServerPid, TransId) ->
case rpc_send(ServerPid, {commit_transaction, TransId}) of
Reply -> Reply
end.
modify_state(ServerPid, TransId, F) ->
ServerPid ! {modify_state, TransId, F},
ok.
server(State, Transactions) ->
receive
{From, start_transaction} ->
TransId = make_ref(),
Transaction = {TransId, State},
NewTransactions = [Transaction | Transactions],
io:format("~w has ~w~n", [self(), NewTransactions]),
rpc_reply(From, TransId),
server(State, NewTransactions);
{modify_state, TransId, F} ->
case lists:keyfind(TransId, 1, Transactions) of
{_, TransState} ->
% try NewTransState = F(TransState)
% catch
% _ -> % abandon transaction with id = TransId
NewTransaction = {TransId, F(TransState)},
NewTransactions =
lists:keyreplace(TransId, 1, Transactions, NewTransaction),
server(State, NewTransactions);
false -> server(State, Transactions)
end;
{From, {commit_transaction, TransId}} ->
case lists:keyfind(TransId, 1, Transactions) of
{_, TransState} ->
rpc_reply(From, {commit_success, TransState}),
server(TransState, []);
false ->
rpc_reply(From, commit_failure),
server(State, Transactions)
end
end.
-module(transact_test).
-export([test/0]).
annoying(X) ->
timer:sleep(1000),
X + 1.
test1() ->
{ok, Server} = transact:start_server(42),
{ok, T1} = transact:start_transaction(Server),
transact:modify_state(Server, T1, fun annoying/1),
transact:modify_state(Server, T1, fun annoying/1),
{ok, T2} = transact:start_transaction(Server),
true.
% Transaction modifications for a transaction are sequential.
% Assumes that transact:modify_state is non-blocking.
test2() ->
{ok, Server} = transact:start_server(42),
{ok, T1} = transact:start_transaction(Server),
transact:modify_state(Server, T1, fun annoying/1),
transact:modify_state(Server, T1, fun (X) -> X+1 end),
case transact:commit_transaction(Server, T1) of
{commit_success, 44} -> true;
_ -> false
end.
% Check that ...
test3() ->
{ok, Server} = transact:start_server(42),
{ok, T1} = transact:start_transaction(Server),
{ok, T2} = transact:start_transaction(Server),
transact:modify_state(Server, T1, fun (X) -> X+1 end),
transact:modify_state(Server, T1, fun (X) -> X+1 end),
transact:modify_state(Server, T2, fun (X) -> X+1 end),
{commit_success, 44} = transact:commit_transaction(Server, T1),
case transact:commit_transaction(Server, T2) of
commit_failure -> true;
_ -> false
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment