aboutsummaryrefslogtreecommitdiffstats
path: root/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_locker.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_locker.erl')
-rw-r--r--lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_locker.erl1022
1 files changed, 0 insertions, 1022 deletions
diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_locker.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_locker.erl
deleted file mode 100644
index 8fe08414d0..0000000000
--- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_locker.erl
+++ /dev/null
@@ -1,1022 +0,0 @@
-%% ``The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved via the world wide web at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
-%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
-%% AB. All Rights Reserved.''
-%%
-%% $Id: mnesia_locker.erl,v 1.2 2009/07/01 15:45:40 kostis Exp $
-%%
--module(mnesia_locker).
-
--export([
- get_held_locks/0,
- get_lock_queue/0,
- global_lock/5,
- ixrlock/5,
- init/1,
- mnesia_down/2,
- release_tid/1,
- async_release_tid/2,
- send_release_tid/2,
- receive_release_tid_acc/2,
- rlock/3,
- rlock_table/3,
- rwlock/3,
- sticky_rwlock/3,
- start/0,
- sticky_wlock/3,
- sticky_wlock_table/3,
- wlock/3,
- wlock_no_exist/4,
- wlock_table/3
- ]).
-
-%% sys callback functions
--export([system_continue/3,
- system_terminate/4,
- system_code_change/4
- ]).
-
--include("mnesia.hrl").
--import(mnesia_lib, [dbg_out/2, error/2, verbose/2]).
-
--define(dbg(S,V), ok).
-%-define(dbg(S,V), dbg_out("~p:~p: " ++ S, [?MODULE, ?LINE] ++ V)).
-
--define(ALL, '______WHOLETABLE_____').
--define(STICK, '______STICK_____').
--define(GLOBAL, '______GLOBAL_____').
-
--record(state, {supervisor}).
-
--record(queue, {oid, tid, op, pid, lucky}).
-
-%% mnesia_held_locks: contain {Oid, Op, Tid} entries (bag)
--define(match_oid_held_locks(Oid), {Oid, '_', '_'}).
-%% mnesia_tid_locks: contain {Tid, Oid, Op} entries (bag)
--define(match_oid_tid_locks(Tid), {Tid, '_', '_'}).
-%% mnesia_sticky_locks: contain {Oid, Node} entries and {Tab, Node} entries (set)
--define(match_oid_sticky_locks(Oid),{Oid, '_'}).
-%% mnesia_lock_queue: contain {queue, Oid, Tid, Op, ReplyTo, WaitForTid} entries (ordered_set)
--define(match_oid_lock_queue(Oid), #queue{oid=Oid, tid='_', op = '_', pid = '_', lucky = '_'}).
-%% mnesia_lock_counter: {{write, Tab}, Number} &&
-%% {{read, Tab}, Number} entries (set)
-
-start() ->
- mnesia_monitor:start_proc(?MODULE, ?MODULE, init, [self()]).
-
-init(Parent) ->
- register(?MODULE, self()),
- process_flag(trap_exit, true),
- proc_lib:init_ack(Parent, {ok, self()}),
- loop(#state{supervisor = Parent}).
-
-val(Var) ->
- case ?catch_val(Var) of
- {'EXIT', _ReASoN_} -> mnesia_lib:other_val(Var, _ReASoN_);
- _VaLuE_ -> _VaLuE_
- end.
-
-reply(From, R) ->
- From ! {?MODULE, node(), R}.
-
-l_request(Node, X, Store) ->
- {?MODULE, Node} ! {self(), X},
- l_req_rec(Node, Store).
-
-l_req_rec(Node, Store) ->
- ?ets_insert(Store, {nodes, Node}),
- receive
- {?MODULE, Node, {switch, Node2, Req}} ->
- ?ets_insert(Store, {nodes, Node2}),
- {?MODULE, Node2} ! Req,
- {switch, Node2, Req};
- {?MODULE, Node, Reply} ->
- Reply;
- {mnesia_down, Node} ->
- {not_granted, {node_not_running, Node}}
- end.
-
-release_tid(Tid) ->
- ?MODULE ! {release_tid, Tid}.
-
-async_release_tid(Nodes, Tid) ->
- rpc:abcast(Nodes, ?MODULE, {release_tid, Tid}).
-
-send_release_tid(Nodes, Tid) ->
- rpc:abcast(Nodes, ?MODULE, {self(), {sync_release_tid, Tid}}).
-
-receive_release_tid_acc([Node | Nodes], Tid) ->
- receive
- {?MODULE, Node, {tid_released, Tid}} ->
- receive_release_tid_acc(Nodes, Tid);
- {mnesia_down, Node} ->
- receive_release_tid_acc(Nodes, Tid)
- end;
-receive_release_tid_acc([], _Tid) ->
- ok.
-
-loop(State) ->
- receive
- {From, {write, Tid, Oid}} ->
- try_sticky_lock(Tid, write, From, Oid),
- loop(State);
-
- %% If Key == ?ALL it's a request to lock the entire table
- %%
-
- {From, {read, Tid, Oid}} ->
- try_sticky_lock(Tid, read, From, Oid),
- loop(State);
-
- %% Really do a read, but get hold of a write lock
- %% used by mnesia:wread(Oid).
-
- {From, {read_write, Tid, Oid}} ->
- try_sticky_lock(Tid, read_write, From, Oid),
- loop(State);
-
- %% Tid has somehow terminated, clear up everything
- %% and pass locks on to queued processes.
- %% This is the purpose of the mnesia_tid_locks table
-
- {release_tid, Tid} ->
- do_release_tid(Tid),
- loop(State);
-
- %% stick lock, first tries this to the where_to_read Node
- {From, {test_set_sticky, Tid, {Tab, _} = Oid, Lock}} ->
- case ?ets_lookup(mnesia_sticky_locks, Tab) of
- [] ->
- reply(From, not_stuck),
- loop(State);
- [{_,Node}] when Node == node() ->
- %% Lock is stuck here, see now if we can just set
- %% a regular write lock
- try_lock(Tid, Lock, From, Oid),
- loop(State);
- [{_,Node}] ->
- reply(From, {stuck_elsewhere, Node}),
- loop(State)
- end;
-
- %% If test_set_sticky fails, we send this to all nodes
- %% after aquiring a real write lock on Oid
-
- {stick, {Tab, _}, N} ->
- ?ets_insert(mnesia_sticky_locks, {Tab, N}),
- loop(State);
-
- %% The caller which sends this message, must have first
- %% aquired a write lock on the entire table
- {unstick, Tab} ->
- ?ets_delete(mnesia_sticky_locks, Tab),
- loop(State);
-
- {From, {ix_read, Tid, Tab, IxKey, Pos}} ->
- case catch mnesia_index:get_index_table(Tab, Pos) of
- {'EXIT', _} ->
- reply(From, {not_granted, {no_exists, Tab, {index, [Pos]}}}),
- loop(State);
- Index ->
- Rk = mnesia_lib:elems(2,mnesia_index:db_get(Index, IxKey)),
- %% list of real keys
- case ?ets_lookup(mnesia_sticky_locks, Tab) of
- [] ->
- set_read_lock_on_all_keys(Tid, From,Tab,Rk,Rk,
- []),
- loop(State);
- [{_,N}] when N == node() ->
- set_read_lock_on_all_keys(Tid, From,Tab,Rk,Rk,
- []),
- loop(State);
- [{_,N}] ->
- Req = {From, {ix_read, Tid, Tab, IxKey, Pos}},
- From ! {?MODULE, node(), {switch, N, Req}},
- loop(State)
- end
- end;
-
- {From, {sync_release_tid, Tid}} ->
- do_release_tid(Tid),
- reply(From, {tid_released, Tid}),
- loop(State);
-
- {release_remote_non_pending, Node, Pending} ->
- release_remote_non_pending(Node, Pending),
- mnesia_monitor:mnesia_down(?MODULE, Node),
- loop(State);
-
- {'EXIT', Pid, _} when Pid == State#state.supervisor ->
- do_stop();
-
- {system, From, Msg} ->
- verbose("~p got {system, ~p, ~p}~n", [?MODULE, From, Msg]),
- Parent = State#state.supervisor,
- sys:handle_system_msg(Msg, From, Parent, ?MODULE, [], State);
-
- Msg ->
- error("~p got unexpected message: ~p~n", [?MODULE, Msg]),
- loop(State)
- end.
-
-set_lock(Tid, Oid, Op) ->
- ?dbg("Granted ~p ~p ~p~n", [Tid,Oid,Op]),
- ?ets_insert(mnesia_held_locks, {Oid, Op, Tid}),
- ?ets_insert(mnesia_tid_locks, {Tid, Oid, Op}).
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Acquire locks
-
-try_sticky_lock(Tid, Op, Pid, {Tab, _} = Oid) ->
- case ?ets_lookup(mnesia_sticky_locks, Tab) of
- [] ->
- try_lock(Tid, Op, Pid, Oid);
- [{_,N}] when N == node() ->
- try_lock(Tid, Op, Pid, Oid);
- [{_,N}] ->
- Req = {Pid, {Op, Tid, Oid}},
- Pid ! {?MODULE, node(), {switch, N, Req}}
- end.
-
-try_lock(Tid, read_write, Pid, Oid) ->
- try_lock(Tid, read_write, read, write, Pid, Oid);
-try_lock(Tid, Op, Pid, Oid) ->
- try_lock(Tid, Op, Op, Op, Pid, Oid).
-
-try_lock(Tid, Op, SimpleOp, Lock, Pid, Oid) ->
- case can_lock(Tid, Lock, Oid, {no, bad_luck}) of
- yes ->
- Reply = grant_lock(Tid, SimpleOp, Lock, Oid),
- reply(Pid, Reply);
- {no, Lucky} ->
- C = #cyclic{op = SimpleOp, lock = Lock, oid = Oid, lucky = Lucky},
- ?dbg("Rejected ~p ~p ~p ~p ~n", [Tid, Oid, Lock, Lucky]),
- reply(Pid, {not_granted, C});
- {queue, Lucky} ->
- ?dbg("Queued ~p ~p ~p ~p ~n", [Tid, Oid, Lock, Lucky]),
- %% Append to queue: Nice place for trace output
- ?ets_insert(mnesia_lock_queue,
- #queue{oid = Oid, tid = Tid, op = Op,
- pid = Pid, lucky = Lucky}),
- ?ets_insert(mnesia_tid_locks, {Tid, Oid, {queued, Op}})
- end.
-
-grant_lock(Tid, read, Lock, {Tab, Key})
- when Key /= ?ALL, Tab /= ?GLOBAL ->
- case node(Tid#tid.pid) == node() of
- true ->
- set_lock(Tid, {Tab, Key}, Lock),
- {granted, lookup_in_client};
- false ->
- case catch mnesia_lib:db_get(Tab, Key) of %% lookup as well
- {'EXIT', _Reason} ->
- %% Table has been deleted from this node,
- %% restart the transaction.
- C = #cyclic{op = read, lock = Lock, oid = {Tab, Key},
- lucky = nowhere},
- {not_granted, C};
- Val ->
- set_lock(Tid, {Tab, Key}, Lock),
- {granted, Val}
- end
- end;
-grant_lock(Tid, read, Lock, Oid) ->
- set_lock(Tid, Oid, Lock),
- {granted, ok};
-grant_lock(Tid, write, Lock, Oid) ->
- set_lock(Tid, Oid, Lock),
- granted.
-
-%% 1) Impose an ordering on all transactions favour old (low tid) transactions
-%% newer (higher tid) transactions may never wait on older ones,
-%% 2) When releasing the tids from the queue always begin with youngest (high tid)
-%% because of 1) it will avoid the deadlocks.
-%% 3) TabLocks is the problem :-) They should not starve and not deadlock
-%% handle tablocks in queue as they had locks on unlocked records.
-
-can_lock(Tid, read, {Tab, Key}, AlreadyQ) when Key /= ?ALL ->
- %% The key is bound, no need for the other BIF
- Oid = {Tab, Key},
- ObjLocks = ?ets_match_object(mnesia_held_locks, {Oid, write, '_'}),
- TabLocks = ?ets_match_object(mnesia_held_locks, {{Tab, ?ALL}, write, '_'}),
- check_lock(Tid, Oid, ObjLocks, TabLocks, yes, AlreadyQ, read);
-
-can_lock(Tid, read, Oid, AlreadyQ) -> % Whole tab
- Tab = element(1, Oid),
- ObjLocks = ?ets_match_object(mnesia_held_locks, {{Tab, '_'}, write, '_'}),
- check_lock(Tid, Oid, ObjLocks, [], yes, AlreadyQ, read);
-
-can_lock(Tid, write, {Tab, Key}, AlreadyQ) when Key /= ?ALL ->
- Oid = {Tab, Key},
- ObjLocks = ?ets_lookup(mnesia_held_locks, Oid),
- TabLocks = ?ets_lookup(mnesia_held_locks, {Tab, ?ALL}),
- check_lock(Tid, Oid, ObjLocks, TabLocks, yes, AlreadyQ, write);
-
-can_lock(Tid, write, Oid, AlreadyQ) -> % Whole tab
- Tab = element(1, Oid),
- ObjLocks = ?ets_match_object(mnesia_held_locks, ?match_oid_held_locks({Tab, '_'})),
- check_lock(Tid, Oid, ObjLocks, [], yes, AlreadyQ, write).
-
-%% Check held locks for conflicting locks
-check_lock(Tid, Oid, [Lock | Locks], TabLocks, X, AlreadyQ, Type) ->
- case element(3, Lock) of
- Tid ->
- check_lock(Tid, Oid, Locks, TabLocks, X, AlreadyQ, Type);
- WaitForTid when WaitForTid > Tid -> % Important order
- check_lock(Tid, Oid, Locks, TabLocks, {queue, WaitForTid}, AlreadyQ, Type);
- WaitForTid when Tid#tid.pid == WaitForTid#tid.pid ->
- dbg_out("Spurious lock conflict ~w ~w: ~w -> ~w~n",
- [Oid, Lock, Tid, WaitForTid]),
-%% check_lock(Tid, Oid, Locks, TabLocks, {queue, WaitForTid}, AlreadyQ);
- %% BUGBUG Fix this if possible
- {no, WaitForTid};
- WaitForTid ->
- {no, WaitForTid}
- end;
-
-check_lock(_, _, [], [], X, {queue, bad_luck}, _) ->
- X; %% The queue should be correct already no need to check it again
-
-check_lock(_, _, [], [], X = {queue, _Tid}, _AlreadyQ, _) ->
- X;
-
-check_lock(Tid, Oid, [], [], X, AlreadyQ, Type) ->
- {Tab, Key} = Oid,
- if
- Type == write ->
- check_queue(Tid, Tab, X, AlreadyQ);
- Key == ?ALL ->
- %% hmm should be solvable by a clever select expr but not today...
- check_queue(Tid, Tab, X, AlreadyQ);
- true ->
- %% If there is a queue on that object, read_lock shouldn't be granted
- ObjLocks = ets:lookup(mnesia_lock_queue, Oid),
- Greatest = max(ObjLocks),
- case Greatest of
- empty ->
- check_queue(Tid, Tab, X, AlreadyQ);
- ObjL when Tid > ObjL ->
- {no, ObjL}; %% Starvation Preemption (write waits for read)
- ObjL ->
- check_queue(Tid, Tab, {queue, ObjL}, AlreadyQ)
- end
- end;
-
-check_lock(Tid, Oid, [], TabLocks, X, AlreadyQ, Type) ->
- check_lock(Tid, Oid, TabLocks, [], X, AlreadyQ, Type).
-
-%% Check queue for conflicting locks
-%% Assume that all queued locks belongs to other tid's
-
-check_queue(Tid, Tab, X, AlreadyQ) ->
- TabLocks = ets:lookup(mnesia_lock_queue, {Tab,?ALL}),
- Greatest = max(TabLocks),
- case Greatest of
- empty ->
- X;
- Tid ->
- X;
- WaitForTid when WaitForTid#queue.tid > Tid -> % Important order
- {queue, WaitForTid};
- WaitForTid ->
- case AlreadyQ of
- {no, bad_luck} -> {no, WaitForTid};
- _ ->
- erlang:error({mnesia_locker, assert, AlreadyQ})
- end
- end.
-
-max([]) ->
- empty;
-max([H|R]) ->
- max(R, H#queue.tid).
-
-max([H|R], Tid) when H#queue.tid > Tid ->
- max(R, H#queue.tid);
-max([_|R], Tid) ->
- max(R, Tid);
-max([], Tid) ->
- Tid.
-
-%% We can't queue the ixlock requests since it
-%% becomes to complivated for little me :-)
-%% If we encounter an object with a wlock we reject the
-%% entire lock request
-%%
-%% BUGBUG: this is actually a bug since we may starve
-
-set_read_lock_on_all_keys(Tid, From, Tab, [RealKey | Tail], Orig, Ack) ->
- Oid = {Tab, RealKey},
- case can_lock(Tid, read, Oid, {no, bad_luck}) of
- yes ->
- {granted, Val} = grant_lock(Tid, read, read, Oid),
- case opt_lookup_in_client(Val, Oid, read) of % Ought to be invoked
- C when record(C, cyclic) -> % in the client
- reply(From, {not_granted, C});
- Val2 ->
- Ack2 = lists:append(Val2, Ack),
- set_read_lock_on_all_keys(Tid, From, Tab, Tail, Orig, Ack2)
- end;
- {no, Lucky} ->
- C = #cyclic{op = read, lock = read, oid = Oid, lucky = Lucky},
- reply(From, {not_granted, C});
- {queue, Lucky} ->
- C = #cyclic{op = read, lock = read, oid = Oid, lucky = Lucky},
- reply(From, {not_granted, C})
- end;
-set_read_lock_on_all_keys(_Tid, From, _Tab, [], Orig, Ack) ->
- reply(From, {granted, Ack, Orig}).
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Release of locks
-
-%% Release remote non-pending nodes
-release_remote_non_pending(Node, Pending) ->
- %% Clear the mnesia_sticky_locks table first, to avoid
- %% unnecessary requests to the failing node
- ?ets_match_delete(mnesia_sticky_locks, {'_' , Node}),
-
- %% Then we have to release all locks held by processes
- %% running at the failed node and also simply remove all
- %% queue'd requests back to the failed node
-
- AllTids = ?ets_match(mnesia_tid_locks, {'$1', '_', '_'}),
- Tids = [T || [T] <- AllTids, Node == node(T#tid.pid), not lists:member(T, Pending)],
- do_release_tids(Tids).
-
-do_release_tids([Tid | Tids]) ->
- do_release_tid(Tid),
- do_release_tids(Tids);
-do_release_tids([]) ->
- ok.
-
-do_release_tid(Tid) ->
- Locks = ?ets_lookup(mnesia_tid_locks, Tid),
- ?dbg("Release ~p ~p ~n", [Tid, Locks]),
- ?ets_delete(mnesia_tid_locks, Tid),
- release_locks(Locks),
- %% Removed queued locks which has had locks
- UniqueLocks = keyunique(lists:sort(Locks),[]),
- rearrange_queue(UniqueLocks).
-
-keyunique([{_Tid, Oid, _Op}|R], Acc = [{_, Oid, _}|_]) ->
- keyunique(R, Acc);
-keyunique([H|R], Acc) ->
- keyunique(R, [H|Acc]);
-keyunique([], Acc) ->
- Acc.
-
-release_locks([Lock | Locks]) ->
- release_lock(Lock),
- release_locks(Locks);
-release_locks([]) ->
- ok.
-
-release_lock({Tid, Oid, {queued, _}}) ->
- ?ets_match_delete(mnesia_lock_queue,
- #queue{oid=Oid, tid = Tid, op = '_',
- pid = '_', lucky = '_'});
-release_lock({Tid, Oid, Op}) ->
- if
- Op == write ->
- ?ets_delete(mnesia_held_locks, Oid);
- Op == read ->
- ?ets_match_delete(mnesia_held_locks, {Oid, Op, Tid})
- end.
-
-rearrange_queue([{_Tid, {Tab, Key}, _} | Locks]) ->
- if
- Key /= ?ALL->
- Queue =
- ets:lookup(mnesia_lock_queue, {Tab, ?ALL}) ++
- ets:lookup(mnesia_lock_queue, {Tab, Key}),
- case Queue of
- [] ->
- ok;
- _ ->
- Sorted = lists:reverse(lists:keysort(#queue.tid, Queue)),
- try_waiters_obj(Sorted)
- end;
- true ->
- Pat = ?match_oid_lock_queue({Tab, '_'}),
- Queue = ?ets_match_object(mnesia_lock_queue, Pat),
- Sorted = lists:reverse(lists:keysort(#queue.tid, Queue)),
- try_waiters_tab(Sorted)
- end,
- ?dbg("RearrQ ~p~n", [Queue]),
- rearrange_queue(Locks);
-rearrange_queue([]) ->
- ok.
-
-try_waiters_obj([W | Waiters]) ->
- case try_waiter(W) of
- queued ->
- no;
- _ ->
- try_waiters_obj(Waiters)
- end;
-try_waiters_obj([]) ->
- ok.
-
-try_waiters_tab([W | Waiters]) ->
- case W#queue.oid of
- {_Tab, ?ALL} ->
- case try_waiter(W) of
- queued ->
- no;
- _ ->
- try_waiters_tab(Waiters)
- end;
- Oid ->
- case try_waiter(W) of
- queued ->
- Rest = key_delete_all(Oid, #queue.oid, Waiters),
- try_waiters_tab(Rest);
- _ ->
- try_waiters_tab(Waiters)
- end
- end;
-try_waiters_tab([]) ->
- ok.
-
-try_waiter({queue, Oid, Tid, read_write, ReplyTo, _}) ->
- try_waiter(Oid, read_write, read, write, ReplyTo, Tid);
-try_waiter({queue, Oid, Tid, Op, ReplyTo, _}) ->
- try_waiter(Oid, Op, Op, Op, ReplyTo, Tid).
-
-try_waiter(Oid, Op, SimpleOp, Lock, ReplyTo, Tid) ->
- case can_lock(Tid, Lock, Oid, {queue, bad_luck}) of
- yes ->
- %% Delete from queue: Nice place for trace output
- ?ets_match_delete(mnesia_lock_queue,
- #queue{oid=Oid, tid = Tid, op = Op,
- pid = ReplyTo, lucky = '_'}),
- Reply = grant_lock(Tid, SimpleOp, Lock, Oid),
- ReplyTo ! {?MODULE, node(), Reply},
- locked;
- {queue, _Why} ->
- ?dbg("Keep ~p ~p ~p ~p~n", [Tid, Oid, Lock, _Why]),
- queued; % Keep waiter in queue
- {no, Lucky} ->
- C = #cyclic{op = SimpleOp, lock = Lock, oid = Oid, lucky = Lucky},
- verbose("** WARNING ** Restarted transaction, possible deadlock in lock queue ~w: cyclic = ~w~n",
- [Tid, C]),
- ?ets_match_delete(mnesia_lock_queue,
- #queue{oid=Oid, tid = Tid, op = Op,
- pid = ReplyTo, lucky = '_'}),
- Reply = {not_granted, C},
- ReplyTo ! {?MODULE, node(), Reply},
- removed
- end.
-
-key_delete_all(Key, Pos, TupleList) ->
- key_delete_all(Key, Pos, TupleList, []).
-key_delete_all(Key, Pos, [H|T], Ack) when element(Pos, H) == Key ->
- key_delete_all(Key, Pos, T, Ack);
-key_delete_all(Key, Pos, [H|T], Ack) ->
- key_delete_all(Key, Pos, T, [H|Ack]);
-key_delete_all(_, _, [], Ack) ->
- lists:reverse(Ack).
-
-
-%% ********************* end server code ********************
-%% The following code executes at the client side of a transactions
-
-mnesia_down(N, Pending) ->
- case whereis(?MODULE) of
- undefined ->
- %% Takes care of mnesia_down's in early startup
- mnesia_monitor:mnesia_down(?MODULE, N);
- Pid ->
- %% Syncronously call needed in order to avoid
- %% race with mnesia_tm's coordinator processes
- %% that may restart and acquire new locks.
- %% mnesia_monitor ensures the sync.
- Pid ! {release_remote_non_pending, N, Pending}
- end.
-
-%% Aquire a write lock, but do a read, used by
-%% mnesia:wread/1
-
-rwlock(Tid, Store, Oid) ->
- {Tab, Key} = Oid,
- case val({Tab, where_to_read}) of
- nowhere ->
- mnesia:abort({no_exists, Tab});
- Node ->
- Lock = write,
- case need_lock(Store, Tab, Key, Lock) of
- yes ->
- Ns = w_nodes(Tab),
- Res = get_rwlocks_on_nodes(Ns, Ns, Node, Store, Tid, Oid),
- ?ets_insert(Store, {{locks, Tab, Key}, Lock}),
- Res;
- no ->
- if
- Key == ?ALL ->
- w_nodes(Tab);
- Tab == ?GLOBAL ->
- w_nodes(Tab);
- true ->
- dirty_rpc(Node, Tab, Key, Lock)
- end
- end
- end.
-
-get_rwlocks_on_nodes([Node | Tail], Orig, Node, Store, Tid, Oid) ->
- Op = {self(), {read_write, Tid, Oid}},
- {?MODULE, Node} ! Op,
- ?ets_insert(Store, {nodes, Node}),
- add_debug(Node),
- get_rwlocks_on_nodes(Tail, Orig, Node, Store, Tid, Oid);
-get_rwlocks_on_nodes([Node | Tail], Orig, OtherNode, Store, Tid, Oid) ->
- Op = {self(), {write, Tid, Oid}},
- {?MODULE, Node} ! Op,
- add_debug(Node),
- ?ets_insert(Store, {nodes, Node}),
- get_rwlocks_on_nodes(Tail, Orig, OtherNode, Store, Tid, Oid);
-get_rwlocks_on_nodes([], Orig, _Node, Store, _Tid, Oid) ->
- receive_wlocks(Orig, read_write_lock, Store, Oid).
-
-%% Return a list of nodes or abort transaction
-%% WE also insert any additional where_to_write nodes
-%% in the local store under the key == nodes
-
-w_nodes(Tab) ->
- Nodes = ?catch_val({Tab, where_to_write}),
- case Nodes of
- [_ | _] -> Nodes;
- _ -> mnesia:abort({no_exists, Tab})
- end.
-
-%% aquire a sticky wlock, a sticky lock is a lock
-%% which remains at this node after the termination of the
-%% transaction.
-
-sticky_wlock(Tid, Store, Oid) ->
- sticky_lock(Tid, Store, Oid, write).
-
-sticky_rwlock(Tid, Store, Oid) ->
- sticky_lock(Tid, Store, Oid, read_write).
-
-sticky_lock(Tid, Store, {Tab, Key} = Oid, Lock) ->
- N = val({Tab, where_to_read}),
- if
- node() == N ->
- case need_lock(Store, Tab, Key, write) of
- yes ->
- do_sticky_lock(Tid, Store, Oid, Lock);
- no ->
- dirty_sticky_lock(Tab, Key, [N], Lock)
- end;
- true ->
- mnesia:abort({not_local, Tab})
- end.
-
-do_sticky_lock(Tid, Store, {Tab, Key} = Oid, Lock) ->
- ?MODULE ! {self(), {test_set_sticky, Tid, Oid, Lock}},
- receive
- {?MODULE, _N, granted} ->
- ?ets_insert(Store, {{locks, Tab, Key}, write}),
- granted;
- {?MODULE, _N, {granted, Val}} -> %% for rwlocks
- case opt_lookup_in_client(Val, Oid, write) of
- C when record(C, cyclic) ->
- exit({aborted, C});
- Val2 ->
- ?ets_insert(Store, {{locks, Tab, Key}, write}),
- Val2
- end;
- {?MODULE, _N, {not_granted, Reason}} ->
- exit({aborted, Reason});
- {?MODULE, N, not_stuck} ->
- not_stuck(Tid, Store, Tab, Key, Oid, Lock, N),
- dirty_sticky_lock(Tab, Key, [N], Lock);
- {mnesia_down, N} ->
- exit({aborted, {node_not_running, N}});
- {?MODULE, N, {stuck_elsewhere, _N2}} ->
- stuck_elsewhere(Tid, Store, Tab, Key, Oid, Lock),
- dirty_sticky_lock(Tab, Key, [N], Lock)
- end.
-
-not_stuck(Tid, Store, Tab, _Key, Oid, _Lock, N) ->
- rlock(Tid, Store, {Tab, ?ALL}), %% needed?
- wlock(Tid, Store, Oid), %% perfect sync
- wlock(Tid, Store, {Tab, ?STICK}), %% max one sticker/table
- Ns = val({Tab, where_to_write}),
- rpc:abcast(Ns, ?MODULE, {stick, Oid, N}).
-
-stuck_elsewhere(Tid, Store, Tab, _Key, Oid, _Lock) ->
- rlock(Tid, Store, {Tab, ?ALL}), %% needed?
- wlock(Tid, Store, Oid), %% perfect sync
- wlock(Tid, Store, {Tab, ?STICK}), %% max one sticker/table
- Ns = val({Tab, where_to_write}),
- rpc:abcast(Ns, ?MODULE, {unstick, Tab}).
-
-dirty_sticky_lock(Tab, Key, Nodes, Lock) ->
- if
- Lock == read_write ->
- mnesia_lib:db_get(Tab, Key);
- Key == ?ALL ->
- Nodes;
- Tab == ?GLOBAL ->
- Nodes;
- true ->
- ok
- end.
-
-sticky_wlock_table(Tid, Store, Tab) ->
- sticky_lock(Tid, Store, {Tab, ?ALL}, write).
-
-%% aquire a wlock on Oid
-%% We store a {Tabname, write, Tid} in all locktables
-%% on all nodes containing a copy of Tabname
-%% We also store an item {{locks, Tab, Key}, write} in the
-%% local store when we have aquired the lock.
-%%
-wlock(Tid, Store, Oid) ->
- {Tab, Key} = Oid,
- case need_lock(Store, Tab, Key, write) of
- yes ->
- Ns = w_nodes(Tab),
- Op = {self(), {write, Tid, Oid}},
- ?ets_insert(Store, {{locks, Tab, Key}, write}),
- get_wlocks_on_nodes(Ns, Ns, Store, Op, Oid);
- no when Key /= ?ALL, Tab /= ?GLOBAL ->
- [];
- no ->
- w_nodes(Tab)
- end.
-
-wlock_table(Tid, Store, Tab) ->
- wlock(Tid, Store, {Tab, ?ALL}).
-
-%% Write lock even if the table does not exist
-
-wlock_no_exist(Tid, Store, Tab, Ns) ->
- Oid = {Tab, ?ALL},
- Op = {self(), {write, Tid, Oid}},
- get_wlocks_on_nodes(Ns, Ns, Store, Op, Oid).
-
-need_lock(Store, Tab, Key, LockPattern) ->
- TabL = ?ets_match_object(Store, {{locks, Tab, ?ALL}, LockPattern}),
- if
- TabL == [] ->
- KeyL = ?ets_match_object(Store, {{locks, Tab, Key}, LockPattern}),
- if
- KeyL == [] ->
- yes;
- true ->
- no
- end;
- true ->
- no
- end.
-
-add_debug(Node) -> % Use process dictionary for debug info
- case get(mnesia_wlock_nodes) of
- undefined ->
- put(mnesia_wlock_nodes, [Node]);
- NodeList ->
- put(mnesia_wlock_nodes, [Node|NodeList])
- end.
-
-del_debug(Node) ->
- case get(mnesia_wlock_nodes) of
- undefined -> % Shouldn't happen
- ignore;
- [Node] ->
- erase(mnesia_wlock_nodes);
- List ->
- put(mnesia_wlock_nodes, lists:delete(Node, List))
- end.
-
-%% We first send lock requests to the lockmanagers on all
-%% nodes holding a copy of the table
-
-get_wlocks_on_nodes([Node | Tail], Orig, Store, Request, Oid) ->
- {?MODULE, Node} ! Request,
- ?ets_insert(Store, {nodes, Node}),
- add_debug(Node),
- get_wlocks_on_nodes(Tail, Orig, Store, Request, Oid);
-get_wlocks_on_nodes([], Orig, Store, _Request, Oid) ->
- receive_wlocks(Orig, Orig, Store, Oid).
-
-receive_wlocks([Node | Tail], Res, Store, Oid) ->
- receive
- {?MODULE, Node, granted} ->
- del_debug(Node),
- receive_wlocks(Tail, Res, Store, Oid);
- {?MODULE, Node, {granted, Val}} -> %% for rwlocks
- del_debug(Node),
- case opt_lookup_in_client(Val, Oid, write) of
- C when record(C, cyclic) ->
- flush_remaining(Tail, Node, {aborted, C});
- Val2 ->
- receive_wlocks(Tail, Val2, Store, Oid)
- end;
- {?MODULE, Node, {not_granted, Reason}} ->
- del_debug(Node),
- Reason1 = {aborted, Reason},
- flush_remaining(Tail, Node, Reason1);
- {mnesia_down, Node} ->
- del_debug(Node),
- Reason1 = {aborted, {node_not_running, Node}},
- flush_remaining(Tail, Node, Reason1);
- {?MODULE, Node, {switch, Node2, Req}} -> %% for rwlocks
- del_debug(Node),
- add_debug(Node2),
- ?ets_insert(Store, {nodes, Node2}),
- {?MODULE, Node2} ! Req,
- receive_wlocks([Node2 | Tail], Res, Store, Oid)
- end;
-
-receive_wlocks([], Res, _Store, _Oid) ->
- Res.
-
-flush_remaining([], _SkipNode, Res) ->
- exit(Res);
-flush_remaining([SkipNode | Tail ], SkipNode, Res) ->
- del_debug(SkipNode),
- flush_remaining(Tail, SkipNode, Res);
-flush_remaining([Node | Tail], SkipNode, Res) ->
- receive
- {?MODULE, Node, _} ->
- del_debug(Node),
- flush_remaining(Tail, SkipNode, Res);
- {mnesia_down, Node} ->
- del_debug(Node),
- flush_remaining(Tail, SkipNode, {aborted, {node_not_running, Node}})
- end.
-
-opt_lookup_in_client(lookup_in_client, Oid, Lock) ->
- {Tab, Key} = Oid,
- case catch mnesia_lib:db_get(Tab, Key) of
- {'EXIT', _} ->
- %% Table has been deleted from this node,
- %% restart the transaction.
- #cyclic{op = read, lock = Lock, oid = Oid, lucky = nowhere};
- Val ->
- Val
- end;
-opt_lookup_in_client(Val, _Oid, _Lock) ->
- Val.
-
-return_granted_or_nodes({_, ?ALL} , Nodes) -> Nodes;
-return_granted_or_nodes({?GLOBAL, _}, Nodes) -> Nodes;
-return_granted_or_nodes(_ , _Nodes) -> granted.
-
-%% We store a {Tab, read, From} item in the
-%% locks table on the node where we actually do pick up the object
-%% and we also store an item {lock, Oid, read} in our local store
-%% so that we can release any locks we hold when we commit.
-%% This function not only aquires a read lock, but also reads the object
-
-%% Oid's are always {Tab, Key} tuples
-rlock(Tid, Store, Oid) ->
- {Tab, Key} = Oid,
- case val({Tab, where_to_read}) of
- nowhere ->
- mnesia:abort({no_exists, Tab});
- Node ->
- case need_lock(Store, Tab, Key, '_') of
- yes ->
- R = l_request(Node, {read, Tid, Oid}, Store),
- rlock_get_reply(Node, Store, Oid, R);
- no ->
- if
- Key == ?ALL ->
- [Node];
- Tab == ?GLOBAL ->
- [Node];
- true ->
- dirty_rpc(Node, Tab, Key, read)
- end
- end
- end.
-
-dirty_rpc(nowhere, Tab, Key, _Lock) ->
- mnesia:abort({no_exists, {Tab, Key}});
-dirty_rpc(Node, _Tab, ?ALL, _Lock) ->
- [Node];
-dirty_rpc(Node, ?GLOBAL, _Key, _Lock) ->
- [Node];
-dirty_rpc(Node, Tab, Key, Lock) ->
- Args = [Tab, Key],
- case rpc:call(Node, mnesia_lib, db_get, Args) of
- {badrpc, Reason} ->
- case val({Tab, where_to_read}) of
- Node ->
- ErrorTag = mnesia_lib:dirty_rpc_error_tag(Reason),
- mnesia:abort({ErrorTag, Args});
- _NewNode ->
- %% Table has been deleted from the node,
- %% restart the transaction.
- C = #cyclic{op = read, lock = Lock, oid = {Tab, Key}, lucky = nowhere},
- exit({aborted, C})
- end;
- Other ->
- Other
- end.
-
-rlock_get_reply(Node, Store, Oid, {granted, V}) ->
- {Tab, Key} = Oid,
- ?ets_insert(Store, {{locks, Tab, Key}, read}),
- ?ets_insert(Store, {nodes, Node}),
- case opt_lookup_in_client(V, Oid, read) of
- C when record(C, cyclic) ->
- mnesia:abort(C);
- Val ->
- Val
- end;
-rlock_get_reply(Node, Store, Oid, granted) ->
- {Tab, Key} = Oid,
- ?ets_insert(Store, {{locks, Tab, Key}, read}),
- ?ets_insert(Store, {nodes, Node}),
- return_granted_or_nodes(Oid, [Node]);
-rlock_get_reply(Node, Store, Tab, {granted, V, RealKeys}) ->
- L = fun(K) -> ?ets_insert(Store, {{locks, Tab, K}, read}) end,
- lists:foreach(L, RealKeys),
- ?ets_insert(Store, {nodes, Node}),
- V;
-rlock_get_reply(_Node, _Store, _Oid, {not_granted , Reason}) ->
- exit({aborted, Reason});
-
-rlock_get_reply(_Node, Store, Oid, {switch, N2, Req}) ->
- ?ets_insert(Store, {nodes, N2}),
- {?MODULE, N2} ! Req,
- rlock_get_reply(N2, Store, Oid, l_req_rec(N2, Store)).
-
-
-rlock_table(Tid, Store, Tab) ->
- rlock(Tid, Store, {Tab, ?ALL}).
-
-ixrlock(Tid, Store, Tab, IxKey, Pos) ->
- case val({Tab, where_to_read}) of
- nowhere ->
- mnesia:abort({no_exists, Tab});
- Node ->
- R = l_request(Node, {ix_read, Tid, Tab, IxKey, Pos}, Store),
- rlock_get_reply(Node, Store, Tab, R)
- end.
-
-%% Grabs the locks or exits
-global_lock(Tid, Store, Item, write, Ns) ->
- Oid = {?GLOBAL, Item},
- Op = {self(), {write, Tid, Oid}},
- get_wlocks_on_nodes(Ns, Ns, Store, Op, Oid);
-global_lock(Tid, Store, Item, read, Ns) ->
- Oid = {?GLOBAL, Item},
- send_requests(Ns, {read, Tid, Oid}),
- rec_requests(Ns, Oid, Store),
- Ns.
-
-send_requests([Node | Nodes], X) ->
- {?MODULE, Node} ! {self(), X},
- send_requests(Nodes, X);
-send_requests([], _X) ->
- ok.
-
-rec_requests([Node | Nodes], Oid, Store) ->
- Res = l_req_rec(Node, Store),
- case catch rlock_get_reply(Node, Store, Oid, Res) of
- {'EXIT', Reason} ->
- flush_remaining(Nodes, Node, Reason);
- _ ->
- rec_requests(Nodes, Oid, Store)
- end;
-rec_requests([], _Oid, _Store) ->
- ok.
-
-get_held_locks() ->
- ?ets_match_object(mnesia_held_locks, '_').
-
-get_lock_queue() ->
- Q = ?ets_match_object(mnesia_lock_queue, '_'),
- [{Oid, Op, Pid, Tid, WFT} || {queue, Oid, Tid, Op, Pid, WFT} <- Q].
-
-do_stop() ->
- exit(shutdown).
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% System upgrade
-
-system_continue(_Parent, _Debug, State) ->
- loop(State).
-
-system_terminate(_Reason, _Parent, _Debug, _State) ->
- do_stop().
-
-system_code_change(State, _Module, _OldVsn, _Extra) ->
- {ok, State}.
-