diff options
author | Stavros Aronis <[email protected]> | 2011-04-02 18:57:42 +0300 |
---|---|---|
committer | Henrik Nord <[email protected]> | 2011-05-04 15:06:15 +0200 |
commit | ca4633fd683527097451ca1398c90c87bb5c14fc (patch) | |
tree | 3d8e18c9becd4feb7d3ceb1eed24bdce2ef69dd6 /lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia | |
parent | bc619f0cbf9555df6dfc44a499f0cd9cee8bd1be (diff) | |
download | otp-ca4633fd683527097451ca1398c90c87bb5c14fc.tar.gz otp-ca4633fd683527097451ca1398c90c87bb5c14fc.tar.bz2 otp-ca4633fd683527097451ca1398c90c87bb5c14fc.zip |
Rename suite data directories
Diffstat (limited to 'lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia')
33 files changed, 0 insertions, 23134 deletions
diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/Makefile b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/Makefile deleted file mode 100644 index 461dc82155..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/Makefile +++ /dev/null @@ -1,137 +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: Makefile,v 1.1 2008/12/17 09:53:37 mikpe Exp $ -# -include $(ERL_TOP)/make/target.mk - -ifeq ($(TYPE),debug) -ERL_COMPILE_FLAGS += -Ddebug -W -endif - -include $(ERL_TOP)/make/$(TARGET)/otp.mk - -# ---------------------------------------------------- -# Application version -# ---------------------------------------------------- -include ../vsn.mk -VSN=$(MNESIA_VSN) - -# ---------------------------------------------------- -# Release directory specification -# ---------------------------------------------------- -RELSYSDIR = $(RELEASE_PATH)/lib/mnesia-$(VSN) - -# ---------------------------------------------------- -# Target Specs -# ---------------------------------------------------- -MODULES= \ - mnesia \ - mnesia_backup \ - mnesia_bup \ - mnesia_checkpoint \ - mnesia_checkpoint_sup \ - mnesia_controller \ - mnesia_dumper\ - mnesia_event \ - mnesia_frag \ - mnesia_frag_hash \ - mnesia_frag_old_hash \ - mnesia_index \ - mnesia_kernel_sup \ - mnesia_late_loader \ - mnesia_lib\ - mnesia_loader \ - mnesia_locker \ - mnesia_log \ - mnesia_monitor \ - mnesia_recover \ - mnesia_registry \ - mnesia_schema\ - mnesia_snmp_hook \ - mnesia_snmp_sup \ - mnesia_subscr \ - mnesia_sup \ - mnesia_sp \ - mnesia_text \ - mnesia_tm - -HRL_FILES= mnesia.hrl - -ERL_FILES= $(MODULES:%=%.erl) - -TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) - -APP_FILE= mnesia.app - -APP_SRC= $(APP_FILE).src -APP_TARGET= $(EBIN)/$(APP_FILE) - -APPUP_FILE= mnesia.appup - -APPUP_SRC= $(APPUP_FILE).src -APPUP_TARGET= $(EBIN)/$(APPUP_FILE) - - - -# ---------------------------------------------------- -# FLAGS -# ---------------------------------------------------- -ERL_FLAGS += -ERL_COMPILE_FLAGS += \ - +warn_unused_vars \ - +'{parse_transform,sys_pre_attributes}' \ - +'{attribute,insert,vsn,"mnesia_$(MNESIA_VSN)"}' \ - -W - -# ---------------------------------------------------- -# Targets -# ---------------------------------------------------- - -opt: $(TARGET_FILES) - -debug: - @${MAKE} TYPE=debug - -clean: - rm -f $(TARGET_FILES) - rm -f core - -docs: - -# ---------------------------------------------------- -# Special Build Targets -# ---------------------------------------------------- - -$(APP_TARGET): $(APP_SRC) ../vsn.mk - sed -e 's;%VSN%;$(VSN);' $< > $@ - -$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk - sed -e 's;%VSN%;$(VSN);' $< > $@ - - -# ---------------------------------------------------- -# Release Target -# ---------------------------------------------------- -include $(ERL_TOP)/make/otp_release_targets.mk - -release_spec: opt - $(INSTALL_DIR) $(RELSYSDIR)/src - $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src - $(INSTALL_DIR) $(RELSYSDIR)/ebin - $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin - -release_docs_spec: - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia.app.src b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia.app.src deleted file mode 100644 index 3715488ec2..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia.app.src +++ /dev/null @@ -1,52 +0,0 @@ -{application, mnesia, - [{description, "MNESIA CXC 138 12"}, - {vsn, "%VSN%"}, - {modules, [ - mnesia, - mnesia_backup, - mnesia_bup, - mnesia_checkpoint, - mnesia_checkpoint_sup, - mnesia_controller, - mnesia_dumper, - mnesia_event, - mnesia_frag, - mnesia_frag_hash, - mnesia_frag_old_hash, - mnesia_index, - mnesia_kernel_sup, - mnesia_late_loader, - mnesia_lib, - mnesia_loader, - mnesia_locker, - mnesia_log, - mnesia_monitor, - mnesia_recover, - mnesia_registry, - mnesia_schema, - mnesia_snmp_hook, - mnesia_snmp_sup, - mnesia_subscr, - mnesia_sup, - mnesia_sp, - mnesia_text, - mnesia_tm - ]}, - {registered, [ - mnesia_dumper_load_regulator, - mnesia_event, - mnesia_fallback, - mnesia_controller, - mnesia_kernel_sup, - mnesia_late_loader, - mnesia_locker, - mnesia_monitor, - mnesia_recover, - mnesia_substr, - mnesia_sup, - mnesia_tm - ]}, - {applications, [kernel, stdlib]}, - {mod, {mnesia_sup, []}}]}. - - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia.appup.src b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia.appup.src deleted file mode 100644 index 502ddb02fc..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia.appup.src +++ /dev/null @@ -1,6 +0,0 @@ -{"%VSN%", - [ - ], - [ - ] -}. diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia.erl deleted file mode 100644 index 956f4f5395..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia.erl +++ /dev/null @@ -1,2191 +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.erl,v 1.2 2010/03/04 13:54:19 maria Exp $ -%% -%% This module exports the public interface of the Mnesia DBMS engine - --module(mnesia). -%-behaviour(mnesia_access). - --export([ - %% Start, stop and debugging - start/0, start/1, stop/0, % Not for public use - set_debug_level/1, lkill/0, kill/0, % Not for public use - ms/0, nc/0, nc/1, ni/0, ni/1, % Not for public use - change_config/2, - - %% Activity mgt - abort/1, transaction/1, transaction/2, transaction/3, - sync_transaction/1, sync_transaction/2, sync_transaction/3, - async_dirty/1, async_dirty/2, sync_dirty/1, sync_dirty/2, ets/1, ets/2, - activity/2, activity/3, activity/4, % Not for public use - - %% Access within an activity - Lock acquisition - lock/2, lock/4, - read_lock_table/1, - write_lock_table/1, - - %% Access within an activity - Updates - write/1, s_write/1, write/3, write/5, - delete/1, s_delete/1, delete/3, delete/5, - delete_object/1, s_delete_object/1, delete_object/3, delete_object/5, - - %% Access within an activity - Reads - read/1, wread/1, read/3, read/5, - match_object/1, match_object/3, match_object/5, - select/2, select/3, select/5, - all_keys/1, all_keys/4, - index_match_object/2, index_match_object/4, index_match_object/6, - index_read/3, index_read/6, - - %% Iterators within an activity - foldl/3, foldl/4, foldr/3, foldr/4, - - %% Dirty access regardless of activities - Updates - dirty_write/1, dirty_write/2, - dirty_delete/1, dirty_delete/2, - dirty_delete_object/1, dirty_delete_object/2, - dirty_update_counter/2, dirty_update_counter/3, - - %% Dirty access regardless of activities - Read - dirty_read/1, dirty_read/2, - dirty_select/2, - dirty_match_object/1, dirty_match_object/2, dirty_all_keys/1, - dirty_index_match_object/2, dirty_index_match_object/3, - dirty_index_read/3, dirty_slot/2, - dirty_first/1, dirty_next/2, dirty_last/1, dirty_prev/2, - - %% Info - table_info/2, table_info/4, schema/0, schema/1, - error_description/1, info/0, system_info/1, - system_info/0, % Not for public use - - %% Database mgt - create_schema/1, delete_schema/1, - backup/1, backup/2, traverse_backup/4, traverse_backup/6, - install_fallback/1, install_fallback/2, - uninstall_fallback/0, uninstall_fallback/1, - activate_checkpoint/1, deactivate_checkpoint/1, - backup_checkpoint/2, backup_checkpoint/3, restore/2, - - %% Table mgt - create_table/1, create_table/2, delete_table/1, - add_table_copy/3, del_table_copy/2, move_table_copy/3, - add_table_index/2, del_table_index/2, - transform_table/3, transform_table/4, - change_table_copy_type/3, - read_table_property/2, write_table_property/2, delete_table_property/2, - change_table_frag/2, - clear_table/1, - - %% Table load - dump_tables/1, wait_for_tables/2, force_load_table/1, - change_table_access_mode/2, change_table_load_order/2, - set_master_nodes/1, set_master_nodes/2, - - %% Misc admin - dump_log/0, subscribe/1, unsubscribe/1, report_event/1, - - %% Snmp - snmp_open_table/2, snmp_close_table/1, - snmp_get_row/2, snmp_get_next_index/2, snmp_get_mnesia_key/2, - - %% Textfile access - load_textfile/1, dump_to_textfile/1, - - %% Mnemosyne exclusive - get_activity_id/0, put_activity_id/1, % Not for public use - - %% Mnesia internal functions - dirty_rpc/4, % Not for public use - has_var/1, fun_select/7, - foldl/6, foldr/6, - - %% Module internal callback functions - remote_dirty_match_object/2, % Not for public use - remote_dirty_select/2 % Not for public use - ]). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - --include("mnesia.hrl"). --import(mnesia_lib, [verbose/2]). - --define(DEFAULT_ACCESS, ?MODULE). - -%% Select --define(PATTERN_TO_OBJECT_MATCH_SPEC(Pat), [{Pat,[],['$_']}]). --define(PATTERN_TO_BINDINGS_MATCH_SPEC(Pat), [{Pat,[],['$$']}]). - -%% Local function in order to avoid external function call -val(Var) -> - case ?catch_val(Var) of - {'EXIT', Reason} -> mnesia_lib:other_val(Var, Reason); - Value -> Value - end. - -is_dollar_digits(Var) -> - case atom_to_list(Var) of - [$$ | Digs] -> - is_digits(Digs); - _ -> - false - end. - -is_digits([Dig | Tail]) -> - if - $0 =< Dig, Dig =< $9 -> - is_digits(Tail); - true -> - false - end; -is_digits([]) -> - true. - -has_var(X) when atom(X) -> - if - X == '_' -> - true; - atom(X) -> - is_dollar_digits(X); - true -> - false - end; -has_var(X) when tuple(X) -> - e_has_var(X, size(X)); -has_var([H|T]) -> - case has_var(H) of - false -> has_var(T); - Other -> Other - end; -has_var(_) -> false. - -e_has_var(_, 0) -> false; -e_has_var(X, Pos) -> - case has_var(element(Pos, X))of - false -> e_has_var(X, Pos-1); - Other -> Other - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Start and stop - -start() -> - {Time , Res} = timer:tc(application, start, [?APPLICATION, temporary]), - - Secs = Time div 1000000, - case Res of - ok -> - verbose("Mnesia started, ~p seconds~n",[ Secs]), - ok; - {error, {already_started, mnesia}} -> - verbose("Mnesia already started, ~p seconds~n",[ Secs]), - ok; - {error, R} -> - verbose("Mnesia failed to start, ~p seconds: ~p~n",[ Secs, R]), - {error, R} - end. - -start(ExtraEnv) when list(ExtraEnv) -> - case mnesia_lib:ensure_loaded(?APPLICATION) of - ok -> - patched_start(ExtraEnv); - Error -> - Error - end; -start(ExtraEnv) -> - {error, {badarg, ExtraEnv}}. - -patched_start([{Env, Val} | Tail]) when atom(Env) -> - case mnesia_monitor:patch_env(Env, Val) of - {error, Reason} -> - {error, Reason}; - _NewVal -> - patched_start(Tail) - end; -patched_start([Head | _]) -> - {error, {bad_type, Head}}; -patched_start([]) -> - start(). - -stop() -> - case application:stop(?APPLICATION) of - ok -> stopped; - {error, {not_started, ?APPLICATION}} -> stopped; - Other -> Other - end. - -change_config(extra_db_nodes, Ns) when list(Ns) -> - mnesia_controller:connect_nodes(Ns); -change_config(BadKey, _BadVal) -> - {error, {badarg, BadKey}}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Debugging - -set_debug_level(Level) -> - mnesia_subscr:set_debug_level(Level). - -lkill() -> - mnesia_sup:kill(). - -kill() -> - rpc:multicall(mnesia_sup, kill, []). - -ms() -> - [ - mnesia, - mnesia_backup, - mnesia_bup, - mnesia_checkpoint, - mnesia_checkpoint_sup, - mnesia_controller, - mnesia_dumper, - mnesia_loader, - mnesia_frag, - mnesia_frag_hash, - mnesia_frag_old_hash, - mnesia_index, - mnesia_kernel_sup, - mnesia_late_loader, - mnesia_lib, - mnesia_log, - mnesia_registry, - mnesia_schema, - mnesia_snmp_hook, - mnesia_snmp_sup, - mnesia_subscr, - mnesia_sup, - mnesia_text, - mnesia_tm, - mnesia_recover, - mnesia_locker, - - %% Keep these last in the list, so - %% mnesia_sup kills these last - mnesia_monitor, - mnesia_event - ]. - -nc() -> - Mods = ms(), - nc(Mods). - -nc(Mods) when list(Mods)-> - [Mod || Mod <- Mods, ok /= load(Mod, compile)]. - -ni() -> - Mods = ms(), - ni(Mods). - -ni(Mods) when list(Mods) -> - [Mod || Mod <- Mods, ok /= load(Mod, interpret)]. - -load(Mod, How) when atom(Mod) -> - case try_load(Mod, How) of - ok -> - ok; - _ -> - mnesia_lib:show( "~n RETRY ~p FROM: ", [Mod]), - Abs = mod2abs(Mod), - load(Abs, How) - end; -load(Abs, How) -> - case try_load(Abs, How) of - ok -> - ok; - {error, Reason} -> - mnesia_lib:show( " *** ERROR *** ~p~n", [Reason]), - {error, Reason} - end. - -try_load(Mod, How) -> - mnesia_lib:show( " ~p ", [Mod]), - Flags = [{d, debug}], - case How of - compile -> - case catch c:nc(Mod, Flags) of - {ok, _} -> ok; - Other -> {error, Other} - end; - interpret -> - case catch int:ni(Mod, Flags) of - {module, _} -> ok; - Other -> {error, Other} - end - end. - -mod2abs(Mod) -> - ModString = atom_to_list(Mod), - SubDir = - case lists:suffix("test", ModString) of - true -> test; - false -> src - end, - filename:join([code:lib_dir(?APPLICATION), SubDir, ModString]). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Activity mgt - -abort(Reason) -> - exit({aborted, Reason}). - -transaction(Fun) -> - transaction(get(mnesia_activity_state), Fun, [], infinity, ?DEFAULT_ACCESS, async). -transaction(Fun, Retries) when integer(Retries), Retries >= 0 -> - transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, async); -transaction(Fun, Retries) when Retries == infinity -> - transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, async); -transaction(Fun, Args) -> - transaction(get(mnesia_activity_state), Fun, Args, infinity, ?DEFAULT_ACCESS, async). -transaction(Fun, Args, Retries) -> - transaction(get(mnesia_activity_state), Fun, Args, Retries, ?DEFAULT_ACCESS, async). - -sync_transaction(Fun) -> - transaction(get(mnesia_activity_state), Fun, [], infinity, ?DEFAULT_ACCESS, sync). -sync_transaction(Fun, Retries) when integer(Retries), Retries >= 0 -> - transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, sync); -sync_transaction(Fun, Retries) when Retries == infinity -> - transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, sync); -sync_transaction(Fun, Args) -> - transaction(get(mnesia_activity_state), Fun, Args, infinity, ?DEFAULT_ACCESS, sync). -sync_transaction(Fun, Args, Retries) -> - transaction(get(mnesia_activity_state), Fun, Args, Retries, ?DEFAULT_ACCESS, sync). - - -transaction(State, Fun, Args, Retries, Mod, Kind) - when function(Fun), list(Args), Retries == infinity, atom(Mod) -> - mnesia_tm:transaction(State, Fun, Args, Retries, Mod, Kind); -transaction(State, Fun, Args, Retries, Mod, Kind) - when function(Fun), list(Args), integer(Retries), Retries >= 0, atom(Mod) -> - mnesia_tm:transaction(State, Fun, Args, Retries, Mod, Kind); -transaction(_State, Fun, Args, Retries, Mod, _Kind) -> - {aborted, {badarg, Fun, Args, Retries, Mod}}. - -non_transaction(State, Fun, Args, ActivityKind, Mod) - when function(Fun), list(Args), atom(Mod) -> - mnesia_tm:non_transaction(State, Fun, Args, ActivityKind, Mod); -non_transaction(_State, Fun, Args, _ActivityKind, _Mod) -> - {aborted, {badarg, Fun, Args}}. - -async_dirty(Fun) -> - async_dirty(Fun, []). -async_dirty(Fun, Args) -> - non_transaction(get(mnesia_activity_state), Fun, Args, async_dirty, ?DEFAULT_ACCESS). - -sync_dirty(Fun) -> - sync_dirty(Fun, []). -sync_dirty(Fun, Args) -> - non_transaction(get(mnesia_activity_state), Fun, Args, sync_dirty, ?DEFAULT_ACCESS). - -ets(Fun) -> - ets(Fun, []). -ets(Fun, Args) -> - non_transaction(get(mnesia_activity_state), Fun, Args, ets, ?DEFAULT_ACCESS). - -activity(Kind, Fun) -> - activity(Kind, Fun, []). -activity(Kind, Fun, Args) when list(Args) -> - activity(Kind, Fun, Args, mnesia_monitor:get_env(access_module)); -activity(Kind, Fun, Mod) -> - activity(Kind, Fun, [], Mod). - -activity(Kind, Fun, Args, Mod) -> - State = get(mnesia_activity_state), - case Kind of - ets -> non_transaction(State, Fun, Args, Kind, Mod); - async_dirty -> non_transaction(State, Fun, Args, Kind, Mod); - sync_dirty -> non_transaction(State, Fun, Args, Kind, Mod); - transaction -> wrap_trans(State, Fun, Args, infinity, Mod, async); - {transaction, Retries} -> wrap_trans(State, Fun, Args, Retries, Mod, async); - sync_transaction -> wrap_trans(State, Fun, Args, infinity, Mod, sync); - {sync_transaction, Retries} -> wrap_trans(State, Fun, Args, Retries, Mod, sync); - _ -> {aborted, {bad_type, Kind}} - end. - -wrap_trans(State, Fun, Args, Retries, Mod, Kind) -> - case transaction(State, Fun, Args, Retries, Mod, Kind) of - {'atomic', GoodRes} -> GoodRes; - BadRes -> exit(BadRes) - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Access within an activity - lock acquisition - -%% Grab a lock on an item in the global lock table -%% Item may be any term. Lock may be write or read. -%% write lock is set on all the given nodes -%% read lock is only set on the first node -%% Nodes may either be a list of nodes or one node as an atom -%% Mnesia on all Nodes must be connected to each other, but -%% it is not neccessary that they are up and running. - -lock(LockItem, LockKind) -> - case get(mnesia_activity_state) of - {?DEFAULT_ACCESS, Tid, Ts} -> - lock(Tid, Ts, LockItem, LockKind); - {Mod, Tid, Ts} -> - Mod:lock(Tid, Ts, LockItem, LockKind); - _ -> - abort(no_transaction) - end. - -lock(Tid, Ts, LockItem, LockKind) -> - case element(1, Tid) of - tid -> - case LockItem of - {record, Tab, Key} -> - lock_record(Tid, Ts, Tab, Key, LockKind); - {table, Tab} -> - lock_table(Tid, Ts, Tab, LockKind); - {global, GlobalKey, Nodes} -> - global_lock(Tid, Ts, GlobalKey, LockKind, Nodes); - _ -> - abort({bad_type, LockItem}) - end; - _Protocol -> - [] - end. - -%% Grab a read lock on a whole table -read_lock_table(Tab) -> - lock({table, Tab}, read), - ok. - -%% Grab a write lock on a whole table -write_lock_table(Tab) -> - lock({table, Tab}, write), - ok. - -lock_record(Tid, Ts, Tab, Key, LockKind) when atom(Tab) -> - Store = Ts#tidstore.store, - Oid = {Tab, Key}, - case LockKind of - read -> - mnesia_locker:rlock(Tid, Store, Oid); - write -> - mnesia_locker:wlock(Tid, Store, Oid); - sticky_write -> - mnesia_locker:sticky_wlock(Tid, Store, Oid); - none -> - []; - _ -> - abort({bad_type, Tab, LockKind}) - end; -lock_record(_Tid, _Ts, Tab, _Key, _LockKind) -> - abort({bad_type, Tab}). - -lock_table(Tid, Ts, Tab, LockKind) when atom(Tab) -> - Store = Ts#tidstore.store, - case LockKind of - read -> - mnesia_locker:rlock_table(Tid, Store, Tab); - write -> - mnesia_locker:wlock_table(Tid, Store, Tab); - sticky_write -> - mnesia_locker:sticky_wlock_table(Tid, Store, Tab); - none -> - []; - _ -> - abort({bad_type, Tab, LockKind}) - end; -lock_table(_Tid, _Ts, Tab, _LockKind) -> - abort({bad_type, Tab}). - -global_lock(Tid, Ts, Item, Kind, Nodes) when list(Nodes) -> - case element(1, Tid) of - tid -> - Store = Ts#tidstore.store, - GoodNs = good_global_nodes(Nodes), - if - Kind /= read, Kind /= write -> - abort({bad_type, Kind}); - true -> - mnesia_locker:global_lock(Tid, Store, Item, Kind, GoodNs) - end; - _Protocol -> - [] - end; -global_lock(_Tid, _Ts, _Item, _Kind, Nodes) -> - abort({bad_type, Nodes}). - -good_global_nodes(Nodes) -> - Recover = [node() | val(recover_nodes)], - mnesia_lib:intersect(Nodes, Recover). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Access within an activity - updates - -write(Val) when tuple(Val), size(Val) > 2 -> - Tab = element(1, Val), - write(Tab, Val, write); -write(Val) -> - abort({bad_type, Val}). - -s_write(Val) when tuple(Val), size(Val) > 2 -> - Tab = element(1, Val), - write(Tab, Val, sticky_write). - -write(Tab, Val, LockKind) -> - case get(mnesia_activity_state) of - {?DEFAULT_ACCESS, Tid, Ts} -> - write(Tid, Ts, Tab, Val, LockKind); - {Mod, Tid, Ts} -> - Mod:write(Tid, Ts, Tab, Val, LockKind); - _ -> - abort(no_transaction) - end. - -write(Tid, Ts, Tab, Val, LockKind) - when atom(Tab), Tab /= schema, tuple(Val), size(Val) > 2 -> - case element(1, Tid) of - ets -> - ?ets_insert(Tab, Val), - ok; - tid -> - Store = Ts#tidstore.store, - Oid = {Tab, element(2, Val)}, - case LockKind of - write -> - mnesia_locker:wlock(Tid, Store, Oid); - sticky_write -> - mnesia_locker:sticky_wlock(Tid, Store, Oid); - _ -> - abort({bad_type, Tab, LockKind}) - end, - write_to_store(Tab, Store, Oid, Val); - Protocol -> - do_dirty_write(Protocol, Tab, Val) - end; -write(_Tid, _Ts, Tab, Val, LockKind) -> - abort({bad_type, Tab, Val, LockKind}). - -write_to_store(Tab, Store, Oid, Val) -> - case ?catch_val({Tab, record_validation}) of - {RecName, Arity, Type} - when size(Val) == Arity, RecName == element(1, Val) -> - case Type of - bag -> - ?ets_insert(Store, {Oid, Val, write}); - _ -> - ?ets_delete(Store, Oid), - ?ets_insert(Store, {Oid, Val, write}) - end, - ok; - {'EXIT', _} -> - abort({no_exists, Tab}); - _ -> - abort({bad_type, Val}) - end. - -delete({Tab, Key}) -> - delete(Tab, Key, write); -delete(Oid) -> - abort({bad_type, Oid}). - -s_delete({Tab, Key}) -> - delete(Tab, Key, sticky_write); -s_delete(Oid) -> - abort({bad_type, Oid}). - -delete(Tab, Key, LockKind) -> - case get(mnesia_activity_state) of - {?DEFAULT_ACCESS, Tid, Ts} -> - delete(Tid, Ts, Tab, Key, LockKind); - {Mod, Tid, Ts} -> - Mod:delete(Tid, Ts, Tab, Key, LockKind); - _ -> - abort(no_transaction) - end. - -delete(Tid, Ts, Tab, Key, LockKind) - when atom(Tab), Tab /= schema -> - case element(1, Tid) of - ets -> - ?ets_delete(Tab, Key), - ok; - tid -> - Store = Ts#tidstore.store, - Oid = {Tab, Key}, - case LockKind of - write -> - mnesia_locker:wlock(Tid, Store, Oid); - sticky_write -> - mnesia_locker:sticky_wlock(Tid, Store, Oid); - _ -> - abort({bad_type, Tab, LockKind}) - end, - ?ets_delete(Store, Oid), - ?ets_insert(Store, {Oid, Oid, delete}), - ok; - Protocol -> - do_dirty_delete(Protocol, Tab, Key) - end; -delete(_Tid, _Ts, Tab, _Key, _LockKind) -> - abort({bad_type, Tab}). - -delete_object(Val) when tuple(Val), size(Val) > 2 -> - Tab = element(1, Val), - delete_object(Tab, Val, write); -delete_object(Val) -> - abort({bad_type, Val}). - -s_delete_object(Val) when tuple(Val), size(Val) > 2 -> - Tab = element(1, Val), - delete_object(Tab, Val, sticky_write); -s_delete_object(Val) -> - abort({bad_type, Val}). - -delete_object(Tab, Val, LockKind) -> - case get(mnesia_activity_state) of - {?DEFAULT_ACCESS, Tid, Ts} -> - delete_object(Tid, Ts, Tab, Val, LockKind); - {Mod, Tid, Ts} -> - Mod:delete_object(Tid, Ts, Tab, Val, LockKind); - _ -> - abort(no_transaction) - end. - -delete_object(Tid, Ts, Tab, Val, LockKind) - when atom(Tab), Tab /= schema, tuple(Val), size(Val) > 2 -> - case element(1, Tid) of - ets -> - ?ets_match_delete(Tab, Val), - ok; - tid -> - Store = Ts#tidstore.store, - Oid = {Tab, element(2, Val)}, - case LockKind of - write -> - mnesia_locker:wlock(Tid, Store, Oid); - sticky_write -> - mnesia_locker:sticky_wlock(Tid, Store, Oid); - _ -> - abort({bad_type, Tab, LockKind}) - end, - case val({Tab, setorbag}) of - bag -> - ?ets_match_delete(Store, {Oid, Val, '_'}), - ?ets_insert(Store, {Oid, Val, delete_object}); - _ -> - case ?ets_match_object(Store, {Oid, '_', write}) of - [] -> - ?ets_match_delete(Store, {Oid, Val, '_'}), - ?ets_insert(Store, {Oid, Val, delete_object}); - _ -> - ?ets_delete(Store, Oid), - ?ets_insert(Store, {Oid, Oid, delete}) - end - end, - ok; - Protocol -> - do_dirty_delete_object(Protocol, Tab, Val) - end; -delete_object(_Tid, _Ts, Tab, _Key, _LockKind) -> - abort({bad_type, Tab}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Access within an activity - read - -read({Tab, Key}) -> - read(Tab, Key, read); -read(Oid) -> - abort({bad_type, Oid}). - -wread({Tab, Key}) -> - read(Tab, Key, write); -wread(Oid) -> - abort({bad_type, Oid}). - -read(Tab, Key, LockKind) -> - case get(mnesia_activity_state) of - {?DEFAULT_ACCESS, Tid, Ts} -> - read(Tid, Ts, Tab, Key, LockKind); - {Mod, Tid, Ts} -> - Mod:read(Tid, Ts, Tab, Key, LockKind); - _ -> - abort(no_transaction) - end. - -read(Tid, Ts, Tab, Key, LockKind) - when atom(Tab), Tab /= schema -> - case element(1, Tid) of - ets -> - ?ets_lookup(Tab, Key); - tid -> - Store = Ts#tidstore.store, - Oid = {Tab, Key}, - Objs = - case LockKind of - read -> - mnesia_locker:rlock(Tid, Store, Oid); - write -> - mnesia_locker:rwlock(Tid, Store, Oid); - sticky_write -> - mnesia_locker:sticky_rwlock(Tid, Store, Oid); - _ -> - abort({bad_type, Tab, LockKind}) - end, - add_written(?ets_lookup(Store, Oid), Tab, Objs); - _Protocol -> - dirty_read(Tab, Key) - end; -read(_Tid, _Ts, Tab, _Key, _LockKind) -> - abort({bad_type, Tab}). - -%%%%%%%%%%%%%%%%%%%%% -%% Iterators - -foldl(Fun, Acc, Tab) -> - foldl(Fun, Acc, Tab, read). - -foldl(Fun, Acc, Tab, LockKind) when function(Fun) -> - case get(mnesia_activity_state) of - {?DEFAULT_ACCESS, Tid, Ts} -> - foldl(Tid, Ts, Fun, Acc, Tab, LockKind); - {Mod, Tid, Ts} -> - Mod:foldl(Tid, Ts, Fun, Acc, Tab, LockKind); - _ -> - abort(no_transaction) - end. - -foldl(ActivityId, Opaque, Fun, Acc, Tab, LockKind) -> - {Type, Prev} = init_iteration(ActivityId, Opaque, Tab, LockKind), - Res = (catch do_foldl(ActivityId, Opaque, Tab, dirty_first(Tab), Fun, Acc, Type, Prev)), - close_iteration(Res, Tab). - -do_foldl(A, O, Tab, '$end_of_table', Fun, RAcc, _Type, Stored) -> - lists:foldl(fun(Key, Acc) -> - lists:foldl(Fun, Acc, read(A, O, Tab, Key, read)) - end, RAcc, Stored); -do_foldl(A, O, Tab, Key, Fun, Acc, ordered_set, [H | Stored]) when H == Key -> - NewAcc = lists:foldl(Fun, Acc, read(A, O, Tab, Key, read)), - do_foldl(A, O, Tab, dirty_next(Tab, Key), Fun, NewAcc, ordered_set, Stored); -do_foldl(A, O, Tab, Key, Fun, Acc, ordered_set, [H | Stored]) when H < Key -> - NewAcc = lists:foldl(Fun, Acc, read(A, O, Tab, H, read)), - do_foldl(A, O, Tab, Key, Fun, NewAcc, ordered_set, Stored); -do_foldl(A, O, Tab, Key, Fun, Acc, ordered_set, [H | Stored]) when H > Key -> - NewAcc = lists:foldl(Fun, Acc, read(A, O, Tab, Key, read)), - do_foldl(A, O, Tab, dirty_next(Tab, Key), Fun, NewAcc, ordered_set, [H |Stored]); -do_foldl(A, O, Tab, Key, Fun, Acc, Type, Stored) -> %% Type is set or bag - NewAcc = lists:foldl(Fun, Acc, read(A, O, Tab, Key, read)), - NewStored = ordsets:del_element(Key, Stored), - do_foldl(A, O, Tab, dirty_next(Tab, Key), Fun, NewAcc, Type, NewStored). - -foldr(Fun, Acc, Tab) -> - foldr(Fun, Acc, Tab, read). -foldr(Fun, Acc, Tab, LockKind) when function(Fun) -> - case get(mnesia_activity_state) of - {?DEFAULT_ACCESS, Tid, Ts} -> - foldr(Tid, Ts, Fun, Acc, Tab, LockKind); - {Mod, Tid, Ts} -> - Mod:foldr(Tid, Ts, Fun, Acc, Tab, LockKind); - _ -> - abort(no_transaction) - end. - -foldr(ActivityId, Opaque, Fun, Acc, Tab, LockKind) -> - {Type, TempPrev} = init_iteration(ActivityId, Opaque, Tab, LockKind), - Prev = - if - Type == ordered_set -> - lists:reverse(TempPrev); - true -> %% Order doesn't matter for set and bag - TempPrev %% Keep the order so we can use ordsets:del_element - end, - Res = (catch do_foldr(ActivityId, Opaque, Tab, dirty_last(Tab), Fun, Acc, Type, Prev)), - close_iteration(Res, Tab). - -do_foldr(A, O, Tab, '$end_of_table', Fun, RAcc, _Type, Stored) -> - lists:foldl(fun(Key, Acc) -> - lists:foldl(Fun, Acc, read(A, O, Tab, Key, read)) - end, RAcc, Stored); -do_foldr(A, O, Tab, Key, Fun, Acc, ordered_set, [H | Stored]) when H == Key -> - NewAcc = lists:foldl(Fun, Acc, read(A, O, Tab, Key, read)), - do_foldr(A, O, Tab, dirty_prev(Tab, Key), Fun, NewAcc, ordered_set, Stored); -do_foldr(A, O, Tab, Key, Fun, Acc, ordered_set, [H | Stored]) when H > Key -> - NewAcc = lists:foldl(Fun, Acc, read(A, O, Tab, H, read)), - do_foldr(A, O, Tab, Key, Fun, NewAcc, ordered_set, Stored); -do_foldr(A, O, Tab, Key, Fun, Acc, ordered_set, [H | Stored]) when H < Key -> - NewAcc = lists:foldl(Fun, Acc, read(A, O, Tab, Key, read)), - do_foldr(A, O, Tab, dirty_prev(Tab, Key), Fun, NewAcc, ordered_set, [H |Stored]); -do_foldr(A, O, Tab, Key, Fun, Acc, Type, Stored) -> %% Type is set or bag - NewAcc = lists:foldl(Fun, Acc, read(A, O, Tab, Key, read)), - NewStored = ordsets:del_element(Key, Stored), - do_foldr(A, O, Tab, dirty_prev(Tab, Key), Fun, NewAcc, Type, NewStored). - -init_iteration(ActivityId, Opaque, Tab, LockKind) -> - lock(ActivityId, Opaque, {table, Tab}, LockKind), - Type = val({Tab, setorbag}), - Previous = add_previous(ActivityId, Opaque, Type, Tab), - St = val({Tab, storage_type}), - if - St == unknown -> - ignore; - true -> - mnesia_lib:db_fixtable(St, Tab, true) - end, - {Type, Previous}. - -close_iteration(Res, Tab) -> - case val({Tab, storage_type}) of - unknown -> - ignore; - St -> - mnesia_lib:db_fixtable(St, Tab, false) - end, - case Res of - {'EXIT', {aborted, What}} -> - abort(What); - {'EXIT', What} -> - abort(What); - _ -> - Res - end. - -add_previous(_ActivityId, non_transaction, _Type, _Tab) -> - []; -add_previous(_Tid, Ts, _Type, Tab) -> - Previous = ?ets_match(Ts#tidstore.store, {{Tab, '$1'}, '_', write}), - lists:sort(lists:concat(Previous)). - -%% This routine fixes up the return value from read/1 so that -%% it is correct with respect to what this particular transaction -%% has already written, deleted .... etc - -add_written([], _Tab, Objs) -> - Objs; % standard normal fast case -add_written(Written, Tab, Objs) -> - case val({Tab, setorbag}) of - bag -> - add_written_to_bag(Written, Objs, []); - _ -> - add_written_to_set(Written) - end. - -add_written_to_set(Ws) -> - case lists:last(Ws) of - {_, _, delete} -> []; - {_, Val, write} -> [Val]; - {_, _, delete_object} -> [] - end. - -add_written_to_bag([{_, Val, write} | Tail], Objs, Ack) -> - add_written_to_bag(Tail, lists:delete(Val, Objs), [Val | Ack]); -add_written_to_bag([], Objs, Ack) -> - Objs ++ lists:reverse(Ack); %% Oldest write first as in ets -add_written_to_bag([{_, _ , delete} | Tail], _Objs, _Ack) -> - %% This transaction just deleted all objects - %% with this key - add_written_to_bag(Tail, [], []); -add_written_to_bag([{_, Val, delete_object} | Tail], Objs, Ack) -> - add_written_to_bag(Tail, lists:delete(Val, Objs), lists:delete(Val, Ack)). - -match_object(Pat) when tuple(Pat), size(Pat) > 2 -> - Tab = element(1, Pat), - match_object(Tab, Pat, read); -match_object(Pat) -> - abort({bad_type, Pat}). - -match_object(Tab, Pat, LockKind) -> - case get(mnesia_activity_state) of - {?DEFAULT_ACCESS, Tid, Ts} -> - match_object(Tid, Ts, Tab, Pat, LockKind); - {Mod, Tid, Ts} -> - Mod:match_object(Tid, Ts, Tab, Pat, LockKind); - _ -> - abort(no_transaction) - end. - -match_object(Tid, Ts, Tab, Pat, LockKind) - when atom(Tab), Tab /= schema, tuple(Pat), size(Pat) > 2 -> - case element(1, Tid) of - ets -> - mnesia_lib:db_match_object(ram_copies, Tab, Pat); - tid -> - Key = element(2, Pat), - case has_var(Key) of - false -> lock_record(Tid, Ts, Tab, Key, LockKind); - true -> lock_table(Tid, Ts, Tab, LockKind) - end, - Objs = dirty_match_object(Tab, Pat), - add_written_match(Ts#tidstore.store, Pat, Tab, Objs); - _Protocol -> - dirty_match_object(Tab, Pat) - end; -match_object(_Tid, _Ts, Tab, Pat, _LockKind) -> - abort({bad_type, Tab, Pat}). - -add_written_match(S, Pat, Tab, Objs) -> - Ops = find_ops(S, Tab, Pat), - add_match(Ops, Objs, val({Tab, setorbag})). - -find_ops(S, Tab, Pat) -> - GetWritten = [{{{Tab, '_'}, Pat, write}, [], ['$_']}, - {{{Tab, '_'}, '_', delete}, [], ['$_']}, - {{{Tab, '_'}, Pat, delete_object}, [], ['$_']}], - ets:select(S, GetWritten). - -add_match([], Objs, _Type) -> - Objs; -add_match(Written, Objs, ordered_set) -> - %% Must use keysort which is stable - add_ordered_match(lists:keysort(1,Written), Objs, []); -add_match([{Oid, _, delete}|R], Objs, Type) -> - add_match(R, deloid(Oid, Objs), Type); -add_match([{_Oid, Val, delete_object}|R], Objs, Type) -> - add_match(R, lists:delete(Val, Objs), Type); -add_match([{_Oid, Val, write}|R], Objs, bag) -> - add_match(R, [Val | lists:delete(Val, Objs)], bag); -add_match([{Oid, Val, write}|R], Objs, set) -> - add_match(R, [Val | deloid(Oid,Objs)],set). - -%% For ordered_set only !! -add_ordered_match(Written = [{{_, Key}, _, _}|_], [Obj|Objs], Acc) - when Key > element(2, Obj) -> - add_ordered_match(Written, Objs, [Obj|Acc]); -add_ordered_match([{{_, Key}, Val, write}|Rest], Objs =[Obj|_], Acc) - when Key < element(2, Obj) -> - add_ordered_match(Rest, [Val|Objs],Acc); -add_ordered_match([{{_, Key}, _, _DelOP}|Rest], Objs =[Obj|_], Acc) - when Key < element(2, Obj) -> - add_ordered_match(Rest,Objs,Acc); -%% Greater than last object -add_ordered_match([{_, Val, write}|Rest], [], Acc) -> - add_ordered_match(Rest, [Val], Acc); -add_ordered_match([_|Rest], [], Acc) -> - add_ordered_match(Rest, [], Acc); -%% Keys are equal from here -add_ordered_match([{_, Val, write}|Rest], [_Obj|Objs], Acc) -> - add_ordered_match(Rest, [Val|Objs], Acc); -add_ordered_match([{_, _Val, delete}|Rest], [_Obj|Objs], Acc) -> - add_ordered_match(Rest, Objs, Acc); -add_ordered_match([{_, Val, delete_object}|Rest], [Val|Objs], Acc) -> - add_ordered_match(Rest, Objs, Acc); -add_ordered_match([{_, _, delete_object}|Rest], Objs, Acc) -> - add_ordered_match(Rest, Objs, Acc); -add_ordered_match([], Objs, Acc) -> - lists:reverse(Acc, Objs). - - -%%%%%%%%%%%%%%%%%% -% select - -select(Tab, Pat) -> - select(Tab, Pat, read). -select(Tab, Pat, LockKind) - when atom(Tab), Tab /= schema, list(Pat) -> - case get(mnesia_activity_state) of - {?DEFAULT_ACCESS, Tid, Ts} -> - select(Tid, Ts, Tab, Pat, LockKind); - {Mod, Tid, Ts} -> - Mod:select(Tid, Ts, Tab, Pat, LockKind); - _ -> - abort(no_transaction) - end; -select(Tab, Pat, _Lock) -> - abort({badarg, Tab, Pat}). - -select(Tid, Ts, Tab, Spec, LockKind) -> - SelectFun = fun(FixedSpec) -> dirty_select(Tab, FixedSpec) end, - fun_select(Tid, Ts, Tab, Spec, LockKind, Tab, SelectFun). - -fun_select(Tid, Ts, Tab, Spec, LockKind, TabPat, SelectFun) -> - case element(1, Tid) of - ets -> - mnesia_lib:db_select(ram_copies, Tab, Spec); - tid -> - Store = Ts#tidstore.store, - Written = ?ets_match_object(Store, {{TabPat, '_'}, '_', '_'}), - %% Avoid table lock if possible - case Spec of - [{HeadPat,_, _}] when tuple(HeadPat), size(HeadPat) > 2 -> - Key = element(2, HeadPat), - case has_var(Key) of - false -> lock_record(Tid, Ts, Tab, Key, LockKind); - true -> lock_table(Tid, Ts, Tab, LockKind) - end; - _ -> - lock_table(Tid, Ts, Tab, LockKind) - end, - case Written of - [] -> - %% Nothing changed in the table during this transaction, - %% Simple case get results from [d]ets - SelectFun(Spec); - _ -> - %% Hard (slow case) records added or deleted earlier - %% in the transaction, have to cope with that. - Type = val({Tab, setorbag}), - FixedSpec = get_record_pattern(Spec), - TabRecs = SelectFun(FixedSpec), - FixedRes = add_match(Written, TabRecs, Type), - CMS = ets:match_spec_compile(Spec), -% case Type of -% ordered_set -> -% ets:match_spec_run(lists:sort(FixedRes), CMS); -% _ -> -% ets:match_spec_run(FixedRes, CMS) -% end - ets:match_spec_run(FixedRes, CMS) - end; - _Protocol -> - SelectFun(Spec) - end. - -get_record_pattern([]) -> - []; -get_record_pattern([{M,C,_B}|R]) -> - [{M,C,['$_']} | get_record_pattern(R)]. - -deloid(_Oid, []) -> - []; -deloid({Tab, Key}, [H | T]) when element(2, H) == Key -> - deloid({Tab, Key}, T); -deloid(Oid, [H | T]) -> - [H | deloid(Oid, T)]. - -all_keys(Tab) -> - case get(mnesia_activity_state) of - {?DEFAULT_ACCESS, Tid, Ts} -> - all_keys(Tid, Ts, Tab, read); - {Mod, Tid, Ts} -> - Mod:all_keys(Tid, Ts, Tab, read); - _ -> - abort(no_transaction) - end. - -all_keys(Tid, Ts, Tab, LockKind) - when atom(Tab), Tab /= schema -> - Pat0 = val({Tab, wild_pattern}), - Pat = setelement(2, Pat0, '$1'), - Keys = select(Tid, Ts, Tab, [{Pat, [], ['$1']}], LockKind), - case val({Tab, setorbag}) of - bag -> - mnesia_lib:uniq(Keys); - _ -> - Keys - end; -all_keys(_Tid, _Ts, Tab, _LockKind) -> - abort({bad_type, Tab}). - -index_match_object(Pat, Attr) when tuple(Pat), size(Pat) > 2 -> - Tab = element(1, Pat), - index_match_object(Tab, Pat, Attr, read); -index_match_object(Pat, _Attr) -> - abort({bad_type, Pat}). - -index_match_object(Tab, Pat, Attr, LockKind) -> - case get(mnesia_activity_state) of - {?DEFAULT_ACCESS, Tid, Ts} -> - index_match_object(Tid, Ts, Tab, Pat, Attr, LockKind); - {Mod, Tid, Ts} -> - Mod:index_match_object(Tid, Ts, Tab, Pat, Attr, LockKind); - _ -> - abort(no_transaction) - end. - -index_match_object(Tid, Ts, Tab, Pat, Attr, LockKind) - when atom(Tab), Tab /= schema, tuple(Pat), size(Pat) > 2 -> - case element(1, Tid) of - ets -> - dirty_index_match_object(Tab, Pat, Attr); % Should be optimized? - tid -> - case mnesia_schema:attr_tab_to_pos(Tab, Attr) of - Pos when Pos =< size(Pat) -> - case LockKind of - read -> - Store = Ts#tidstore.store, - mnesia_locker:rlock_table(Tid, Store, Tab), - Objs = dirty_index_match_object(Tab, Pat, Attr), - add_written_match(Store, Pat, Tab, Objs); - _ -> - abort({bad_type, Tab, LockKind}) - end; - BadPos -> - abort({bad_type, Tab, BadPos}) - end; - _Protocol -> - dirty_index_match_object(Tab, Pat, Attr) - end; -index_match_object(_Tid, _Ts, Tab, Pat, _Attr, _LockKind) -> - abort({bad_type, Tab, Pat}). - -index_read(Tab, Key, Attr) -> - case get(mnesia_activity_state) of - {?DEFAULT_ACCESS, Tid, Ts} -> - index_read(Tid, Ts, Tab, Key, Attr, read); - {Mod, Tid, Ts} -> - Mod:index_read(Tid, Ts, Tab, Key, Attr, read); - _ -> - abort(no_transaction) - end. - -index_read(Tid, Ts, Tab, Key, Attr, LockKind) - when atom(Tab), Tab /= schema -> - case element(1, Tid) of - ets -> - dirty_index_read(Tab, Key, Attr); % Should be optimized? - tid -> - Pos = mnesia_schema:attr_tab_to_pos(Tab, Attr), - case LockKind of - read -> - case has_var(Key) of - false -> - Store = Ts#tidstore.store, - Objs = mnesia_index:read(Tid, Store, Tab, Key, Pos), - Pat = setelement(Pos, val({Tab, wild_pattern}), Key), - add_written_match(Store, Pat, Tab, Objs); - true -> - abort({bad_type, Tab, Attr, Key}) - end; - _ -> - abort({bad_type, Tab, LockKind}) - end; - _Protocol -> - dirty_index_read(Tab, Key, Attr) - end; -index_read(_Tid, _Ts, Tab, _Key, _Attr, _LockKind) -> - abort({bad_type, Tab}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Dirty access regardless of activities - updates - -dirty_write(Val) when tuple(Val), size(Val) > 2 -> - Tab = element(1, Val), - dirty_write(Tab, Val); -dirty_write(Val) -> - abort({bad_type, Val}). - -dirty_write(Tab, Val) -> - do_dirty_write(async_dirty, Tab, Val). - -do_dirty_write(SyncMode, Tab, Val) - when atom(Tab), Tab /= schema, tuple(Val), size(Val) > 2 -> - case ?catch_val({Tab, record_validation}) of - {RecName, Arity, _Type} - when size(Val) == Arity, RecName == element(1, Val) -> - Oid = {Tab, element(2, Val)}, - mnesia_tm:dirty(SyncMode, {Oid, Val, write}); - {'EXIT', _} -> - abort({no_exists, Tab}); - _ -> - abort({bad_type, Val}) - end; -do_dirty_write(_SyncMode, Tab, Val) -> - abort({bad_type, Tab, Val}). - -dirty_delete({Tab, Key}) -> - dirty_delete(Tab, Key); -dirty_delete(Oid) -> - abort({bad_type, Oid}). - -dirty_delete(Tab, Key) -> - do_dirty_delete(async_dirty, Tab, Key). - -do_dirty_delete(SyncMode, Tab, Key) when atom(Tab), Tab /= schema -> - Oid = {Tab, Key}, - mnesia_tm:dirty(SyncMode, {Oid, Oid, delete}); -do_dirty_delete(_SyncMode, Tab, _Key) -> - abort({bad_type, Tab}). - -dirty_delete_object(Val) when tuple(Val), size(Val) > 2 -> - Tab = element(1, Val), - dirty_delete_object(Tab, Val); -dirty_delete_object(Val) -> - abort({bad_type, Val}). - -dirty_delete_object(Tab, Val) -> - do_dirty_delete_object(async_dirty, Tab, Val). - -do_dirty_delete_object(SyncMode, Tab, Val) - when atom(Tab), Tab /= schema, tuple(Val), size(Val) > 2 -> - Oid = {Tab, element(2, Val)}, - mnesia_tm:dirty(SyncMode, {Oid, Val, delete_object}); -do_dirty_delete_object(_SyncMode, Tab, Val) -> - abort({bad_type, Tab, Val}). - -%% A Counter is an Oid being {CounterTab, CounterName} - -dirty_update_counter({Tab, Key}, Incr) -> - dirty_update_counter(Tab, Key, Incr); -dirty_update_counter(Counter, _Incr) -> - abort({bad_type, Counter}). - -dirty_update_counter(Tab, Key, Incr) -> - do_dirty_update_counter(async_dirty, Tab, Key, Incr). - -do_dirty_update_counter(SyncMode, Tab, Key, Incr) - when atom(Tab), Tab /= schema, integer(Incr) -> - case ?catch_val({Tab, record_validation}) of - {RecName, 3, set} -> - Oid = {Tab, Key}, - mnesia_tm:dirty(SyncMode, {Oid, {RecName, Incr}, update_counter}); - _ -> - abort({combine_error, Tab, update_counter}) - end; -do_dirty_update_counter(_SyncMode, Tab, _Key, Incr) -> - abort({bad_type, Tab, Incr}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Dirty access regardless of activities - read - -dirty_read({Tab, Key}) -> - dirty_read(Tab, Key); -dirty_read(Oid) -> - abort({bad_type, Oid}). - -dirty_read(Tab, Key) - when atom(Tab), Tab /= schema -> -%% case catch ?ets_lookup(Tab, Key) of -%% {'EXIT', _} -> - %% Bad luck, we have to perform a real lookup - dirty_rpc(Tab, mnesia_lib, db_get, [Tab, Key]); -%% Val -> -%% Val -%% end; -dirty_read(Tab, _Key) -> - abort({bad_type, Tab}). - -dirty_match_object(Pat) when tuple(Pat), size(Pat) > 2 -> - Tab = element(1, Pat), - dirty_match_object(Tab, Pat); -dirty_match_object(Pat) -> - abort({bad_type, Pat}). - -dirty_match_object(Tab, Pat) - when atom(Tab), Tab /= schema, tuple(Pat), size(Pat) > 2 -> - dirty_rpc(Tab, ?MODULE, remote_dirty_match_object, [Tab, Pat]); -dirty_match_object(Tab, Pat) -> - abort({bad_type, Tab, Pat}). - -remote_dirty_match_object(Tab, Pat) -> - Key = element(2, Pat), - case has_var(Key) of - false -> - mnesia_lib:db_match_object(Tab, Pat); - true -> - PosList = val({Tab, index}), - remote_dirty_match_object(Tab, Pat, PosList) - end. - -remote_dirty_match_object(Tab, Pat, [Pos | Tail]) when Pos =< size(Pat) -> - IxKey = element(Pos, Pat), - case has_var(IxKey) of - false -> - mnesia_index:dirty_match_object(Tab, Pat, Pos); - true -> - remote_dirty_match_object(Tab, Pat, Tail) - end; -remote_dirty_match_object(Tab, Pat, []) -> - mnesia_lib:db_match_object(Tab, Pat); -remote_dirty_match_object(Tab, Pat, _PosList) -> - abort({bad_type, Tab, Pat}). - -dirty_select(Tab, Spec) when atom(Tab), Tab /= schema, list(Spec) -> - dirty_rpc(Tab, ?MODULE, remote_dirty_select, [Tab, Spec]); -dirty_select(Tab, Spec) -> - abort({bad_type, Tab, Spec}). - -remote_dirty_select(Tab, Spec) -> - case Spec of - [{HeadPat, _, _}] when tuple(HeadPat), size(HeadPat) > 2 -> - Key = element(2, HeadPat), - case has_var(Key) of - false -> - mnesia_lib:db_select(Tab, Spec); - true -> - PosList = val({Tab, index}), - remote_dirty_select(Tab, Spec, PosList) - end; - _ -> - mnesia_lib:db_select(Tab, Spec) - end. - -remote_dirty_select(Tab, [{HeadPat,_, _}] = Spec, [Pos | Tail]) - when tuple(HeadPat), size(HeadPat) > 2, Pos =< size(Spec) -> - Key = element(Pos, HeadPat), - case has_var(Key) of - false -> - Recs = mnesia_index:dirty_select(Tab, Spec, Pos), - %% Returns the records without applying the match spec - %% The actual filtering is handled by the caller - CMS = ets:match_spec_compile(Spec), - case val({Tab, setorbag}) of - ordered_set -> - ets:match_spec_run(lists:sort(Recs), CMS); - _ -> - ets:match_spec_run(Recs, CMS) - end; - true -> - remote_dirty_select(Tab, Spec, Tail) - end; -remote_dirty_select(Tab, Spec, _) -> - mnesia_lib:db_select(Tab, Spec). - -dirty_all_keys(Tab) when atom(Tab), Tab /= schema -> - case ?catch_val({Tab, wild_pattern}) of - {'EXIT', _} -> - abort({no_exists, Tab}); - Pat0 -> - Pat = setelement(2, Pat0, '$1'), - Keys = dirty_select(Tab, [{Pat, [], ['$1']}]), - case val({Tab, setorbag}) of - bag -> mnesia_lib:uniq(Keys); - _ -> Keys - end - end; -dirty_all_keys(Tab) -> - abort({bad_type, Tab}). - -dirty_index_match_object(Pat, Attr) when tuple(Pat), size(Pat) > 2 -> - Tab = element(1, Pat), - dirty_index_match_object(Tab, Pat, Attr); -dirty_index_match_object(Pat, _Attr) -> - abort({bad_type, Pat}). - -dirty_index_match_object(Tab, Pat, Attr) - when atom(Tab), Tab /= schema, tuple(Pat), size(Pat) > 2 -> - case mnesia_schema:attr_tab_to_pos(Tab, Attr) of - Pos when Pos =< size(Pat) -> - case has_var(element(2, Pat)) of - false -> - dirty_match_object(Tab, Pat); - true -> - Elem = element(Pos, Pat), - case has_var(Elem) of - false -> - dirty_rpc(Tab, mnesia_index, dirty_match_object, - [Tab, Pat, Pos]); - true -> - abort({bad_type, Tab, Attr, Elem}) - end - end; - BadPos -> - abort({bad_type, Tab, BadPos}) - end; -dirty_index_match_object(Tab, Pat, _Attr) -> - abort({bad_type, Tab, Pat}). - -dirty_index_read(Tab, Key, Attr) when atom(Tab), Tab /= schema -> - Pos = mnesia_schema:attr_tab_to_pos(Tab, Attr), - case has_var(Key) of - false -> - mnesia_index:dirty_read(Tab, Key, Pos); - true -> - abort({bad_type, Tab, Attr, Key}) - end; -dirty_index_read(Tab, _Key, _Attr) -> - abort({bad_type, Tab}). - -dirty_slot(Tab, Slot) when atom(Tab), Tab /= schema, integer(Slot) -> - dirty_rpc(Tab, mnesia_lib, db_slot, [Tab, Slot]); -dirty_slot(Tab, Slot) -> - abort({bad_type, Tab, Slot}). - -dirty_first(Tab) when atom(Tab), Tab /= schema -> - dirty_rpc(Tab, mnesia_lib, db_first, [Tab]); -dirty_first(Tab) -> - abort({bad_type, Tab}). - -dirty_last(Tab) when atom(Tab), Tab /= schema -> - dirty_rpc(Tab, mnesia_lib, db_last, [Tab]); -dirty_last(Tab) -> - abort({bad_type, Tab}). - -dirty_next(Tab, Key) when atom(Tab), Tab /= schema -> - dirty_rpc(Tab, mnesia_lib, db_next_key, [Tab, Key]); -dirty_next(Tab, _Key) -> - abort({bad_type, Tab}). - -dirty_prev(Tab, Key) when atom(Tab), Tab /= schema -> - dirty_rpc(Tab, mnesia_lib, db_prev_key, [Tab, Key]); -dirty_prev(Tab, _Key) -> - abort({bad_type, Tab}). - - -dirty_rpc(Tab, M, F, Args) -> - Node = val({Tab, where_to_read}), - do_dirty_rpc(Tab, Node, M, F, Args). - -do_dirty_rpc(_Tab, nowhere, _, _, Args) -> - mnesia:abort({no_exists, Args}); -do_dirty_rpc(Tab, Node, M, F, Args) -> - case rpc:call(Node, M, F, Args) of - {badrpc,{'EXIT', {undef, [{ M, F, _} | _]}}} - when M == ?MODULE, F == remote_dirty_select -> - %% Oops, the other node has not been upgraded - %% to 4.0.3 yet. Lets do it the old way. - %% Remove this in next release. - do_dirty_rpc(Tab, Node, mnesia_lib, db_select, Args); - {badrpc, Reason} -> - erlang:yield(), %% Do not be too eager - case mnesia_controller:call({check_w2r, Node, Tab}) of % Sync - NewNode when NewNode == Node -> - ErrorTag = mnesia_lib:dirty_rpc_error_tag(Reason), - mnesia:abort({ErrorTag, Args}); - NewNode -> - case get(mnesia_activity_state) of - {_Mod, Tid, _Ts} when record(Tid, tid) -> - %% In order to perform a consistent - %% retry of a transaction we need - %% to acquire the lock on the NewNode. - %% In this context we do neither know - %% the kind or granularity of the lock. - %% --> Abort the transaction - mnesia:abort({node_not_running, Node}); - _ -> - %% Splendid! A dirty retry is safe - %% 'Node' probably went down now - %% Let mnesia_controller get broken link message first - do_dirty_rpc(Tab, NewNode, M, F, Args) - end - end; - Other -> - Other - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Info - -%% Info about one table -table_info(Tab, Item) -> - case get(mnesia_activity_state) of - undefined -> - any_table_info(Tab, Item); - {?DEFAULT_ACCESS, _Tid, _Ts} -> - any_table_info(Tab, Item); - {Mod, Tid, Ts} -> - Mod:table_info(Tid, Ts, Tab, Item); - _ -> - abort(no_transaction) - end. - -table_info(_Tid, _Ts, Tab, Item) -> - any_table_info(Tab, Item). - - -any_table_info(Tab, Item) when atom(Tab) -> - case Item of - master_nodes -> - mnesia_recover:get_master_nodes(Tab); -% checkpoints -> -% case ?catch_val({Tab, commit_work}) of -% [{checkpoints, List} | _] -> List; -% No_chk when list(No_chk) -> []; -% Else -> info_reply(Else, Tab, Item) -% end; - size -> - raw_table_info(Tab, Item); - memory -> - raw_table_info(Tab, Item); - type -> - case ?catch_val({Tab, setorbag}) of - {'EXIT', _} -> - bad_info_reply(Tab, Item); - Val -> - Val - end; - all -> - case mnesia_schema:get_table_properties(Tab) of - [] -> - abort({no_exists, Tab, Item}); - Props -> - lists:map(fun({setorbag, Type}) -> {type, Type}; - (Prop) -> Prop end, - Props) - end; - _ -> - case ?catch_val({Tab, Item}) of - {'EXIT', _} -> - bad_info_reply(Tab, Item); - Val -> - Val - end - end; -any_table_info(Tab, _Item) -> - abort({bad_type, Tab}). - -raw_table_info(Tab, Item) -> - case ?catch_val({Tab, storage_type}) of - ram_copies -> - info_reply(catch ?ets_info(Tab, Item), Tab, Item); - disc_copies -> - info_reply(catch ?ets_info(Tab, Item), Tab, Item); - disc_only_copies -> - info_reply(catch dets:info(Tab, Item), Tab, Item); - unknown -> - bad_info_reply(Tab, Item); - {'EXIT', _} -> - bad_info_reply(Tab, Item) - end. - -info_reply({'EXIT', _Reason}, Tab, Item) -> - bad_info_reply(Tab, Item); -info_reply({error, _Reason}, Tab, Item) -> - bad_info_reply(Tab, Item); -info_reply(Val, _Tab, _Item) -> - Val. - -bad_info_reply(_Tab, size) -> 0; -bad_info_reply(_Tab, memory) -> 0; -bad_info_reply(Tab, Item) -> abort({no_exists, Tab, Item}). - -%% Raw info about all tables -schema() -> - mnesia_schema:info(). - -%% Raw info about one tables -schema(Tab) -> - mnesia_schema:info(Tab). - -error_description(Err) -> - mnesia_lib:error_desc(Err). - -info() -> - case mnesia_lib:is_running() of - yes -> - TmInfo = mnesia_tm:get_info(10000), - Held = system_info(held_locks), - Queued = system_info(lock_queue), - - io:format("---> Processes holding locks <--- ~n", []), - lists:foreach(fun(L) -> io:format("Lock: ~p~n", [L]) end, - Held), - - io:format( "---> Processes waiting for locks <--- ~n", []), - lists:foreach(fun({Oid, Op, _Pid, Tid, OwnerTid}) -> - io:format("Tid ~p waits for ~p lock " - "on oid ~p owned by ~p ~n", - [Tid, Op, Oid, OwnerTid]) - end, Queued), - mnesia_tm:display_info(group_leader(), TmInfo), - - Pat = {'_', unclear, '_'}, - Uncertain = ets:match_object(mnesia_decision, Pat), - - io:format( "---> Uncertain transactions <--- ~n", []), - lists:foreach(fun({Tid, _, Nodes}) -> - io:format("Tid ~w waits for decision " - "from ~w~n", - [Tid, Nodes]) - end, Uncertain), - - mnesia_controller:info(), - display_system_info(Held, Queued, TmInfo, Uncertain); - _ -> - mini_info() - end, - ok. - -mini_info() -> - io:format("===> System info in version ~p, debug level = ~p <===~n", - [system_info(version), system_info(debug)]), - Not = - case system_info(use_dir) of - true -> ""; - false -> "NOT " - end, - - io:format("~w. Directory ~p is ~sused.~n", - [system_info(schema_location), system_info(directory), Not]), - io:format("use fallback at restart = ~w~n", - [system_info(fallback_activated)]), - Running = system_info(running_db_nodes), - io:format("running db nodes = ~w~n", [Running]), - All = mnesia_lib:all_nodes(), - io:format("stopped db nodes = ~w ~n", [All -- Running]). - -display_system_info(Held, Queued, TmInfo, Uncertain) -> - mini_info(), - display_tab_info(), - S = fun(Items) -> [system_info(I) || I <- Items] end, - - io:format("~w transactions committed, ~w aborted, " - "~w restarted, ~w logged to disc~n", - S([transaction_commits, transaction_failures, - transaction_restarts, transaction_log_writes])), - - {Active, Pending} = - case TmInfo of - {timeout, _} -> {infinity, infinity}; - {info, P, A} -> {length(A), length(P)} - end, - io:format("~w held locks, ~w in queue; " - "~w local transactions, ~w remote~n", - [length(Held), length(Queued), Active, Pending]), - - Ufold = fun({_, _, Ns}, {C, Old}) -> - New = [N || N <- Ns, not lists:member(N, Old)], - {C + 1, New ++ Old} - end, - {Ucount, Unodes} = lists:foldl(Ufold, {0, []}, Uncertain), - io:format("~w transactions waits for other nodes: ~p~n", - [Ucount, Unodes]). - -display_tab_info() -> - MasterTabs = mnesia_recover:get_master_node_tables(), - io:format("master node tables = ~p~n", [lists:sort(MasterTabs)]), - - Tabs = system_info(tables), - - {Unknown, Ram, Disc, DiscOnly} = - lists:foldl(fun storage_count/2, {[], [], [], []}, Tabs), - - io:format("remote = ~p~n", [lists:sort(Unknown)]), - io:format("ram_copies = ~p~n", [lists:sort(Ram)]), - io:format("disc_copies = ~p~n", [lists:sort(Disc)]), - io:format("disc_only_copies = ~p~n", [lists:sort(DiscOnly)]), - - Rfoldl = fun(T, Acc) -> - Rpat = - case val({T, access_mode}) of - read_only -> - lists:sort([{A, read_only} || A <- val({T, active_replicas})]); - read_write -> - table_info(T, where_to_commit) - end, - case lists:keysearch(Rpat, 1, Acc) of - {value, {_Rpat, Rtabs}} -> - lists:keyreplace(Rpat, 1, Acc, {Rpat, [T | Rtabs]}); - false -> - [{Rpat, [T]} | Acc] - end - end, - Repl = lists:foldl(Rfoldl, [], Tabs), - Rdisp = fun({Rpat, Rtabs}) -> io:format("~p = ~p~n", [Rpat, Rtabs]) end, - lists:foreach(Rdisp, lists:sort(Repl)). - -storage_count(T, {U, R, D, DO}) -> - case table_info(T, storage_type) of - unknown -> {[T | U], R, D, DO}; - ram_copies -> {U, [T | R], D, DO}; - disc_copies -> {U, R, [T | D], DO}; - disc_only_copies -> {U, R, D, [T | DO]} - end. - -system_info(Item) -> - case catch system_info2(Item) of - {'EXIT',Error} -> abort(Error); - Other -> Other - end. - -system_info2(all) -> - Items = system_info_items(mnesia_lib:is_running()), - [{I, system_info(I)} || I <- Items]; - -system_info2(db_nodes) -> - DiscNs = ?catch_val({schema, disc_copies}), - RamNs = ?catch_val({schema, ram_copies}), - if - list(DiscNs), list(RamNs) -> - DiscNs ++ RamNs; - true -> - case mnesia_schema:read_nodes() of - {ok, Nodes} -> Nodes; - {error,Reason} -> exit(Reason) - end - end; -system_info2(running_db_nodes) -> - case ?catch_val({current, db_nodes}) of - {'EXIT',_} -> - %% Ensure that we access the intended Mnesia - %% directory. This function may not be called - %% during startup since it will cause the - %% application_controller to get into deadlock - load_mnesia_or_abort(), - mnesia_lib:running_nodes(); - Other -> - Other - end; - -system_info2(extra_db_nodes) -> - case ?catch_val(extra_db_nodes) of - {'EXIT',_} -> - %% Ensure that we access the intended Mnesia - %% directory. This function may not be called - %% during startup since it will cause the - %% application_controller to get into deadlock - load_mnesia_or_abort(), - mnesia_monitor:get_env(extra_db_nodes); - Other -> - Other - end; - -system_info2(directory) -> - case ?catch_val(directory) of - {'EXIT',_} -> - %% Ensure that we access the intended Mnesia - %% directory. This function may not be called - %% during startup since it will cause the - %% application_controller to get into deadlock - load_mnesia_or_abort(), - mnesia_monitor:get_env(dir); - Other -> - Other - end; - -system_info2(use_dir) -> - case ?catch_val(use_dir) of - {'EXIT',_} -> - %% Ensure that we access the intended Mnesia - %% directory. This function may not be called - %% during startup since it will cause the - %% application_controller to get into deadlock - load_mnesia_or_abort(), - mnesia_monitor:use_dir(); - Other -> - Other - end; - -system_info2(schema_location) -> - case ?catch_val(schema_location) of - {'EXIT',_} -> - %% Ensure that we access the intended Mnesia - %% directory. This function may not be called - %% during startup since it will cause the - %% application_controller to get into deadlock - load_mnesia_or_abort(), - mnesia_monitor:get_env(schema_location); - Other -> - Other - end; - -system_info2(fallback_activated) -> - case ?catch_val(fallback_activated) of - {'EXIT',_} -> - %% Ensure that we access the intended Mnesia - %% directory. This function may not be called - %% during startup since it will cause the - %% application_controller to get into deadlock - load_mnesia_or_abort(), - mnesia_bup:fallback_exists(); - Other -> - Other - end; - -system_info2(version) -> - case ?catch_val(version) of - {'EXIT', _} -> - Apps = application:loaded_applications(), - case lists:keysearch(?APPLICATION, 1, Apps) of - {value, {_Name, _Desc, Version}} -> - Version; - false -> - %% Ensure that it does not match - {mnesia_not_loaded, node(), now()} - end; - Version -> - Version - end; - -system_info2(access_module) -> mnesia_monitor:get_env(access_module); -system_info2(auto_repair) -> mnesia_monitor:get_env(auto_repair); -system_info2(is_running) -> mnesia_lib:is_running(); -system_info2(backup_module) -> mnesia_monitor:get_env(backup_module); -system_info2(event_module) -> mnesia_monitor:get_env(event_module); -system_info2(debug) -> mnesia_monitor:get_env(debug); -system_info2(dump_log_load_regulation) -> mnesia_monitor:get_env(dump_log_load_regulation); -system_info2(dump_log_write_threshold) -> mnesia_monitor:get_env(dump_log_write_threshold); -system_info2(dump_log_time_threshold) -> mnesia_monitor:get_env(dump_log_time_threshold); -system_info2(dump_log_update_in_place) -> - mnesia_monitor:get_env(dump_log_update_in_place); -system_info2(dump_log_update_in_place) -> - mnesia_monitor:get_env(dump_log_update_in_place); -system_info2(max_wait_for_decision) -> mnesia_monitor:get_env(max_wait_for_decision); -system_info2(embedded_mnemosyne) -> mnesia_monitor:get_env(embedded_mnemosyne); -system_info2(ignore_fallback_at_startup) -> mnesia_monitor:get_env(ignore_fallback_at_startup); -system_info2(fallback_error_function) -> mnesia_monitor:get_env(fallback_error_function); -system_info2(log_version) -> mnesia_log:version(); -system_info2(protocol_version) -> mnesia_monitor:protocol_version(); -system_info2(schema_version) -> mnesia_schema:version(); %backward compatibility -system_info2(tables) -> val({schema, tables}); -system_info2(local_tables) -> val({schema, local_tables}); -system_info2(master_node_tables) -> mnesia_recover:get_master_node_tables(); -system_info2(subscribers) -> mnesia_subscr:subscribers(); -system_info2(checkpoints) -> mnesia_checkpoint:checkpoints(); -system_info2(held_locks) -> mnesia_locker:get_held_locks(); -system_info2(lock_queue) -> mnesia_locker:get_lock_queue(); -system_info2(transactions) -> mnesia_tm:get_transactions(); -system_info2(transaction_failures) -> mnesia_lib:read_counter(trans_failures); -system_info2(transaction_commits) -> mnesia_lib:read_counter(trans_commits); -system_info2(transaction_restarts) -> mnesia_lib:read_counter(trans_restarts); -system_info2(transaction_log_writes) -> mnesia_dumper:get_log_writes(); - -system_info2(Item) -> exit({badarg, Item}). - -system_info_items(yes) -> - [ - access_module, - auto_repair, - backup_module, - checkpoints, - db_nodes, - debug, - directory, - dump_log_load_regulation, - dump_log_time_threshold, - dump_log_update_in_place, - dump_log_write_threshold, - embedded_mnemosyne, - event_module, - extra_db_nodes, - fallback_activated, - held_locks, - ignore_fallback_at_startup, - fallback_error_function, - is_running, - local_tables, - lock_queue, - log_version, - master_node_tables, - max_wait_for_decision, - protocol_version, - running_db_nodes, - schema_location, - schema_version, - subscribers, - tables, - transaction_commits, - transaction_failures, - transaction_log_writes, - transaction_restarts, - transactions, - use_dir, - version - ]; -system_info_items(no) -> - [ - auto_repair, - backup_module, - db_nodes, - debug, - directory, - dump_log_load_regulation, - dump_log_time_threshold, - dump_log_update_in_place, - dump_log_write_threshold, - event_module, - extra_db_nodes, - ignore_fallback_at_startup, - fallback_error_function, - is_running, - log_version, - max_wait_for_decision, - protocol_version, - running_db_nodes, - schema_location, - schema_version, - use_dir, - version - ]. - -system_info() -> - IsRunning = mnesia_lib:is_running(), - case IsRunning of - yes -> - TmInfo = mnesia_tm:get_info(10000), - Held = system_info(held_locks), - Queued = system_info(lock_queue), - Pat = {'_', unclear, '_'}, - Uncertain = ets:match_object(mnesia_decision, Pat), - display_system_info(Held, Queued, TmInfo, Uncertain); - _ -> - mini_info() - end, - IsRunning. - -load_mnesia_or_abort() -> - case mnesia_lib:ensure_loaded(?APPLICATION) of - ok -> - ok; - {error, Reason} -> - abort(Reason) - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Database mgt - -create_schema(Ns) -> - mnesia_bup:create_schema(Ns). - -delete_schema(Ns) -> - mnesia_schema:delete_schema(Ns). - -backup(Opaque) -> - mnesia_log:backup(Opaque). - -backup(Opaque, Mod) -> - mnesia_log:backup(Opaque, Mod). - -traverse_backup(S, T, Fun, Acc) -> - mnesia_bup:traverse_backup(S, T, Fun, Acc). - -traverse_backup(S, SM, T, TM, F, A) -> - mnesia_bup:traverse_backup(S, SM, T, TM, F, A). - -install_fallback(Opaque) -> - mnesia_bup:install_fallback(Opaque). - -install_fallback(Opaque, Mod) -> - mnesia_bup:install_fallback(Opaque, Mod). - -uninstall_fallback() -> - mnesia_bup:uninstall_fallback(). - -uninstall_fallback(Args) -> - mnesia_bup:uninstall_fallback(Args). - -activate_checkpoint(Args) -> - mnesia_checkpoint:activate(Args). - -deactivate_checkpoint(Name) -> - mnesia_checkpoint:deactivate(Name). - -backup_checkpoint(Name, Opaque) -> - mnesia_log:backup_checkpoint(Name, Opaque). - -backup_checkpoint(Name, Opaque, Mod) -> - mnesia_log:backup_checkpoint(Name, Opaque, Mod). - -restore(Opaque, Args) -> - mnesia_schema:restore(Opaque, Args). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Table mgt - -create_table(Arg) -> - mnesia_schema:create_table(Arg). -create_table(Name, Arg) when list(Arg) -> - mnesia_schema:create_table([{name, Name}| Arg]); -create_table(Name, Arg) -> - {aborted, badarg, Name, Arg}. - -delete_table(Tab) -> - mnesia_schema:delete_table(Tab). - -add_table_copy(Tab, N, S) -> - mnesia_schema:add_table_copy(Tab, N, S). -del_table_copy(Tab, N) -> - mnesia_schema:del_table_copy(Tab, N). - -move_table_copy(Tab, From, To) -> - mnesia_schema:move_table(Tab, From, To). - -add_table_index(Tab, Ix) -> - mnesia_schema:add_table_index(Tab, Ix). -del_table_index(Tab, Ix) -> - mnesia_schema:del_table_index(Tab, Ix). - -transform_table(Tab, Fun, NewA) -> - case catch val({Tab, record_name}) of - {'EXIT', Reason} -> - mnesia:abort(Reason); - OldRN -> - mnesia_schema:transform_table(Tab, Fun, NewA, OldRN) - end. - -transform_table(Tab, Fun, NewA, NewRN) -> - mnesia_schema:transform_table(Tab, Fun, NewA, NewRN). - -change_table_copy_type(T, N, S) -> - mnesia_schema:change_table_copy_type(T, N, S). - -clear_table(Tab) -> - mnesia_schema:clear_table(Tab). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Table mgt - user properties - -read_table_property(Tab, PropKey) -> - val({Tab, user_property, PropKey}). - -write_table_property(Tab, Prop) -> - mnesia_schema:write_table_property(Tab, Prop). - -delete_table_property(Tab, PropKey) -> - mnesia_schema:delete_table_property(Tab, PropKey). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Table mgt - user properties - -change_table_frag(Tab, FragProp) -> - mnesia_schema:change_table_frag(Tab, FragProp). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Table mgt - table load - -%% Dump a ram table to disc -dump_tables(Tabs) -> - mnesia_schema:dump_tables(Tabs). - -%% allow the user to wait for some tables to be loaded -wait_for_tables(Tabs, Timeout) -> - mnesia_controller:wait_for_tables(Tabs, Timeout). - -force_load_table(Tab) -> - case mnesia_controller:force_load_table(Tab) of - ok -> yes; % Backwards compatibility - Other -> Other - end. - -change_table_access_mode(T, Access) -> - mnesia_schema:change_table_access_mode(T, Access). - -change_table_load_order(T, O) -> - mnesia_schema:change_table_load_order(T, O). - -set_master_nodes(Nodes) when list(Nodes) -> - UseDir = system_info(use_dir), - IsRunning = system_info(is_running), - case IsRunning of - yes -> - CsPat = {{'_', cstruct}, '_'}, - Cstructs0 = ?ets_match_object(mnesia_gvar, CsPat), - Cstructs = [Cs || {_, Cs} <- Cstructs0], - log_valid_master_nodes(Cstructs, Nodes, UseDir, IsRunning); - _NotRunning -> - case UseDir of - true -> - mnesia_lib:lock_table(schema), - Res = - case mnesia_schema:read_cstructs_from_disc() of - {ok, Cstructs} -> - log_valid_master_nodes(Cstructs, Nodes, UseDir, IsRunning); - {error, Reason} -> - {error, Reason} - end, - mnesia_lib:unlock_table(schema), - Res; - false -> - ok - end - end; -set_master_nodes(Nodes) -> - {error, {bad_type, Nodes}}. - -log_valid_master_nodes(Cstructs, Nodes, UseDir, IsRunning) -> - Fun = fun(Cs) -> - Copies = mnesia_lib:copy_holders(Cs), - Valid = mnesia_lib:intersect(Nodes, Copies), - {Cs#cstruct.name, Valid} - end, - Args = lists:map(Fun, Cstructs), - mnesia_recover:log_master_nodes(Args, UseDir, IsRunning). - -set_master_nodes(Tab, Nodes) when list(Nodes) -> - UseDir = system_info(use_dir), - IsRunning = system_info(is_running), - case IsRunning of - yes -> - case ?catch_val({Tab, cstruct}) of - {'EXIT', _} -> - {error, {no_exists, Tab}}; - Cs -> - case Nodes -- mnesia_lib:copy_holders(Cs) of - [] -> - Args = [{Tab , Nodes}], - mnesia_recover:log_master_nodes(Args, UseDir, IsRunning); - BadNodes -> - {error, {no_exists, Tab, BadNodes}} - end - end; - _NotRunning -> - case UseDir of - true -> - mnesia_lib:lock_table(schema), - Res = - case mnesia_schema:read_cstructs_from_disc() of - {ok, Cstructs} -> - case lists:keysearch(Tab, 2, Cstructs) of - {value, Cs} -> - case Nodes -- mnesia_lib:copy_holders(Cs) of - [] -> - Args = [{Tab , Nodes}], - mnesia_recover:log_master_nodes(Args, UseDir, IsRunning); - BadNodes -> - {error, {no_exists, Tab, BadNodes}} - end; - false -> - {error, {no_exists, Tab}} - end; - {error, Reason} -> - {error, Reason} - end, - mnesia_lib:unlock_table(schema), - Res; - false -> - ok - end - end; -set_master_nodes(Tab, Nodes) -> - {error, {bad_type, Tab, Nodes}}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Misc admin - -dump_log() -> - mnesia_controller:sync_dump_log(user). - -subscribe(What) -> - mnesia_subscr:subscribe(self(), What). - -unsubscribe(What) -> - mnesia_subscr:unsubscribe(self(), What). - -report_event(Event) -> - mnesia_lib:report_system_event({mnesia_user, Event}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Snmp - -snmp_open_table(Tab, Us) -> - mnesia_schema:add_snmp(Tab, Us). - -snmp_close_table(Tab) -> - mnesia_schema:del_snmp(Tab). - -snmp_get_row(Tab, RowIndex) when atom(Tab), Tab /= schema -> - dirty_rpc(Tab, mnesia_snmp_hook, get_row, [Tab, RowIndex]); -snmp_get_row(Tab, _RowIndex) -> - abort({bad_type, Tab}). - -snmp_get_next_index(Tab, RowIndex) when atom(Tab), Tab /= schema -> - dirty_rpc(Tab, mnesia_snmp_hook, get_next_index, [Tab, RowIndex]); -snmp_get_next_index(Tab, _RowIndex) -> - abort({bad_type, Tab}). - -snmp_get_mnesia_key(Tab, RowIndex) when atom(Tab), Tab /= schema -> - dirty_rpc(Tab, mnesia_snmp_hook, get_mnesia_key, [Tab, RowIndex]); -snmp_get_mnesia_key(Tab, _RowIndex) -> - abort({bad_type, Tab}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Textfile access - -load_textfile(F) -> - mnesia_text:load_textfile(F). -dump_to_textfile(F) -> - mnesia_text:dump_to_textfile(F). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Mnemosyne exclusive - -get_activity_id() -> - get(mnesia_activity_state). - -put_activity_id(Activity) -> - mnesia_tm:put_activity_id(Activity). diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia.hrl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia.hrl deleted file mode 100644 index b9715ad927..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia.hrl +++ /dev/null @@ -1,118 +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.hrl,v 1.1 2008/12/17 09:53:37 mikpe Exp $ -%% - --define(APPLICATION, mnesia). - --define(ets_lookup(Tab, Key), ets:lookup(Tab, Key)). --define(ets_lookup_element(Tab, Key, Pos), ets:lookup_element(Tab, Key, Pos)). --define(ets_insert(Tab, Rec), ets:insert(Tab, Rec)). --define(ets_delete(Tab, Key), ets:delete(Tab, Key)). --define(ets_match_delete(Tab, Pat), ets:match_delete(Tab, Pat)). --define(ets_match_object(Tab, Pat), ets:match_object(Tab, Pat)). --define(ets_match(Tab, Pat), ets:match(Tab, Pat)). --define(ets_info(Tab, Item), ets:info(Tab, Item)). --define(ets_update_counter(Tab, Key, Incr), ets:update_counter(Tab, Key, Incr)). --define(ets_first(Tab), ets:first(Tab)). --define(ets_next(Tab, Key), ets:next(Tab, Key)). --define(ets_last(Tab), ets:last(Tab)). --define(ets_prev(Tab, Key), ets:prev(Tab, Key)). --define(ets_slot(Tab, Pos), ets:slot(Tab, Pos)). --define(ets_new_table(Tab, Props), ets:new(Tab, Props)). --define(ets_delete_table(Tab), ets:delete(Tab)). --define(ets_fixtable(Tab, Bool), ets:fixtable(Tab, Bool)). - --define(catch_val(Var), (catch ?ets_lookup_element(mnesia_gvar, Var, 2))). - -%% It's important that counter is first, since we compare tid's - --record(tid, - {counter, %% serial no for tid - pid}). %% owner of tid - - --record(tidstore, - {store, %% current ets table for tid - up_stores = [], %% list of upper layer stores for nested trans - level = 1}). %% transaction level - --define(unique_cookie, {erlang:now(), node()}). - --record(cstruct, {name, % Atom - type = set, % set | bag - ram_copies = [], % [Node] - disc_copies = [], % [Node] - disc_only_copies = [], % [Node] - load_order = 0, % Integer - access_mode = read_write, % read_write | read_only - index = [], % [Integer] - snmp = [], % Snmp Ustruct - local_content = false, % true | false - record_name = {bad_record_name}, % Atom (Default = Name) - attributes = [key, val], % [Atom] - user_properties = [], % [Record] - frag_properties = [], % [{Key, Val] - cookie = ?unique_cookie, % Term - version = {{2, 0}, []}}). % {{Integer, Integer}, [Node]} - -%% Record for the head structure in Mnesia's log files -%% -%% The definition of this record may *NEVER* be changed -%% since it may be written to very old backup files. -%% By holding this record definition stable we can be -%% able to comprahend backups from timepoint 0. It also -%% allows us to use the backup format as an interchange -%% format between Mnesia releases. - --record(log_header,{log_kind, - log_version, - mnesia_version, - node, - now}). - -%% Commit records stored in the transaction log --record(commit, {node, - decision, % presume_commit | Decision - ram_copies = [], - disc_copies = [], - disc_only_copies = [], - snmp = [], - schema_ops = [] - }). - --record(decision, {tid, - outcome, % presume_abort | committed - disc_nodes, - ram_nodes}). - -%% Maybe cyclic wait --record(cyclic, {node = node(), - oid, % {Tab, Key} - op, % read | write - lock, % read | write - lucky - }). - -%% Managing conditional debug functions - --ifdef(debug). - -define(eval_debug_fun(I, C), - mnesia_lib:eval_debug_fun(I, C, ?FILE, ?LINE)). --else. - -define(eval_debug_fun(I, C), ok). --endif. - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_backup.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_backup.erl deleted file mode 100644 index a1fbb21d94..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_backup.erl +++ /dev/null @@ -1,195 +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_backup.erl,v 1.1 2008/12/17 09:53:37 mikpe Exp $ -%% -%0 - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% -%% This module contains one implementation of callback functions -%% used by Mnesia at backup and restore. The user may however -%% write an own module the same interface as mnesia_backup and -%% configure Mnesia so the alternate module performs the actual -%% accesses to the backup media. This means that the user may put -%% the backup on medias that Mnesia does not know about, possibly -%% on hosts where Erlang is not running. -%% -%% The OpaqueData argument is never interpreted by other parts of -%% Mnesia. It is the property of this module. Alternate implementations -%% of this module may have different interpretations of OpaqueData. -%% The OpaqueData argument given to open_write/1 and open_read/1 -%% are forwarded directly from the user. -%% -%% All functions must return {ok, NewOpaqueData} or {error, Reason}. -%% -%% The NewOpaqueData arguments returned by backup callback functions will -%% be given as input when the next backup callback function is invoked. -%% If any return value does not match {ok, _} the backup will be aborted. -%% -%% The NewOpaqueData arguments returned by restore callback functions will -%% be given as input when the next restore callback function is invoked -%% If any return value does not match {ok, _} the restore will be aborted. -%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - --module(mnesia_backup). --behaviour(mnesia_backup). - --include_lib("kernel/include/file.hrl"). - --export([ - %% Write access - open_write/1, - write/2, - commit_write/1, - abort_write/1, - - %% Read access - open_read/1, - read/1, - close_read/1 - ]). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Backup callback interface --record(backup, {tmp_file, file, file_desc}). - -%% Opens backup media for write -%% -%% Returns {ok, OpaqueData} or {error, Reason} -open_write(OpaqueData) -> - File = OpaqueData, - Tmp = lists:concat([File,".BUPTMP"]), - file:delete(Tmp), - file:delete(File), - case disk_log:open([{name, make_ref()}, - {file, Tmp}, - {repair, false}, - {linkto, self()}]) of - {ok, Fd} -> - {ok, #backup{tmp_file = Tmp, file = File, file_desc = Fd}}; - {error, Reason} -> - {error, Reason} - end. - -%% Writes BackupItems to the backup media -%% -%% Returns {ok, OpaqueData} or {error, Reason} -write(OpaqueData, BackupItems) -> - B = OpaqueData, - case disk_log:log_terms(B#backup.file_desc, BackupItems) of - ok -> - {ok, B}; - {error, Reason} -> - abort_write(B), - {error, Reason} - end. - -%% Closes the backup media after a successful backup -%% -%% Returns {ok, ReturnValueToUser} or {error, Reason} -commit_write(OpaqueData) -> - B = OpaqueData, - case disk_log:sync(B#backup.file_desc) of - ok -> - case disk_log:close(B#backup.file_desc) of - ok -> - case file:rename(B#backup.tmp_file, B#backup.file) of - ok -> - {ok, B#backup.file}; - {error, Reason} -> - {error, Reason} - end; - {error, Reason} -> - {error, Reason} - end; - {error, Reason} -> - {error, Reason} - end. - -%% Closes the backup media after an interrupted backup -%% -%% Returns {ok, ReturnValueToUser} or {error, Reason} -abort_write(BackupRef) -> - Res = disk_log:close(BackupRef#backup.file_desc), - file:delete(BackupRef#backup.tmp_file), - case Res of - ok -> - {ok, BackupRef#backup.file}; - {error, Reason} -> - {error, Reason} - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Restore callback interface - --record(restore, {file, file_desc, cont}). - -%% Opens backup media for read -%% -%% Returns {ok, OpaqueData} or {error, Reason} -open_read(OpaqueData) -> - File = OpaqueData, - case file:read_file_info(File) of - {error, Reason} -> - {error, Reason}; - _FileInfo -> %% file exists - case disk_log:open([{file, File}, - {name, make_ref()}, - {repair, false}, - {mode, read_only}, - {linkto, self()}]) of - {ok, Fd} -> - {ok, #restore{file = File, file_desc = Fd, cont = start}}; - {repaired, Fd, _, {badbytes, 0}} -> - {ok, #restore{file = File, file_desc = Fd, cont = start}}; - {repaired, Fd, _, _} -> - {ok, #restore{file = File, file_desc = Fd, cont = start}}; - {error, Reason} -> - {error, Reason} - end - end. - -%% Reads BackupItems from the backup media -%% -%% Returns {ok, OpaqueData, BackupItems} or {error, Reason} -%% -%% BackupItems == [] is interpreted as eof -read(OpaqueData) -> - R = OpaqueData, - Fd = R#restore.file_desc, - case disk_log:chunk(Fd, R#restore.cont) of - {error, Reason} -> - {error, {"Possibly truncated", Reason}}; - eof -> - {ok, R, []}; - {Cont, []} -> - read(R#restore{cont = Cont}); - {Cont, BackupItems} -> - {ok, R#restore{cont = Cont}, BackupItems} - end. - -%% Closes the backup media after restore -%% -%% Returns {ok, ReturnValueToUser} or {error, Reason} -close_read(OpaqueData) -> - R = OpaqueData, - case disk_log:close(R#restore.file_desc) of - ok -> {ok, R#restore.file}; - {error, Reason} -> {error, Reason} - end. -%0 - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_bup.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_bup.erl deleted file mode 100644 index f03dc029cc..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_bup.erl +++ /dev/null @@ -1,1169 +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_bup.erl,v 1.1 2008/12/17 09:53:37 mikpe Exp $ -%% --module(mnesia_bup). --export([ - %% Public interface - iterate/4, - read_schema/2, - fallback_bup/0, - fallback_exists/0, - tm_fallback_start/1, - create_schema/1, - install_fallback/1, - install_fallback/2, - uninstall_fallback/0, - uninstall_fallback/1, - traverse_backup/4, - traverse_backup/6, - make_initial_backup/3, - fallback_to_schema/0, - lookup_schema/2, - schema2bup/1, - refresh_cookie/2, - - %% Internal - fallback_receiver/2, - install_fallback_master/2, - uninstall_fallback_master/2, - local_uninstall_fallback/2, - do_traverse_backup/7, - trav_apply/4 - ]). - --include("mnesia.hrl"). --import(mnesia_lib, [verbose/2, dbg_out/2]). - --record(restore, {mode, bup_module, bup_data}). - --record(fallback_args, {opaque, - scope = global, - module = mnesia_monitor:get_env(backup_module), - use_default_dir = true, - mnesia_dir, - fallback_bup, - fallback_tmp, - skip_tables = [], - keep_tables = [], - default_op = keep_tables - }). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Backup iterator - -%% Reads schema section and iterates over all records in a backup. -%% -%% Fun(BunchOfRecords, Header, Schema, Acc) is applied when a suitable amount -%% of records has been collected. -%% -%% BunchOfRecords will be [] when the iteration is done. -iterate(Mod, Fun, Opaque, Acc) -> - R = #restore{bup_module = Mod, bup_data = Opaque}, - case catch read_schema_section(R) of - {error, Reason} -> - {error, Reason}; - {R2, {Header, Schema, Rest}} -> - case catch iter(R2, Header, Schema, Fun, Acc, Rest) of - {ok, R3, Res} -> - catch safe_apply(R3, close_read, [R3#restore.bup_data]), - {ok, Res}; - {error, Reason} -> - catch safe_apply(R2, close_read, [R2#restore.bup_data]), - {error, Reason}; - {'EXIT', Pid, Reason} -> - catch safe_apply(R2, close_read, [R2#restore.bup_data]), - {error, {'EXIT', Pid, Reason}}; - {'EXIT', Reason} -> - catch safe_apply(R2, close_read, [R2#restore.bup_data]), - {error, {'EXIT', Reason}} - end - end. - -iter(R, Header, Schema, Fun, Acc, []) -> - case safe_apply(R, read, [R#restore.bup_data]) of - {R2, []} -> - Res = Fun([], Header, Schema, Acc), - {ok, R2, Res}; - {R2, BupItems} -> - iter(R2, Header, Schema, Fun, Acc, BupItems) - end; -iter(R, Header, Schema, Fun, Acc, BupItems) -> - Acc2 = Fun(BupItems, Header, Schema, Acc), - iter(R, Header, Schema, Fun, Acc2, []). - -safe_apply(R, write, [_, Items]) when Items == [] -> - R; -safe_apply(R, What, Args) -> - Abort = fun(Re) -> abort_restore(R, What, Args, Re) end, - receive - {'EXIT', Pid, Re} -> Abort({'EXIT', Pid, Re}) - after 0 -> - Mod = R#restore.bup_module, - case catch apply(Mod, What, Args) of - {ok, Opaque, Items} when What == read -> - {R#restore{bup_data = Opaque}, Items}; - {ok, Opaque} when What /= read-> - R#restore{bup_data = Opaque}; - {error, Re} -> - Abort(Re); - Re -> - Abort(Re) - end - end. - -abort_restore(R, What, Args, Reason) -> - Mod = R#restore.bup_module, - Opaque = R#restore.bup_data, - dbg_out("Restore aborted. ~p:~p~p -> ~p~n", - [Mod, What, Args, Reason]), - catch apply(Mod, close_read, [Opaque]), - throw({error, Reason}). - -fallback_to_schema() -> - Fname = fallback_bup(), - fallback_to_schema(Fname). - -fallback_to_schema(Fname) -> - Mod = mnesia_backup, - case read_schema(Mod, Fname) of - {error, Reason} -> - {error, Reason}; - Schema -> - case catch lookup_schema(schema, Schema) of - {error, _} -> - {error, "No schema in fallback"}; - List -> - {ok, fallback, List} - end - end. - -%% Opens Opaque reads schema and then close -read_schema(Mod, Opaque) -> - R = #restore{bup_module = Mod, bup_data = Opaque}, - case catch read_schema_section(R) of - {error, Reason} -> - {error, Reason}; - {R2, {_Header, Schema, _}} -> - catch safe_apply(R2, close_read, [R2#restore.bup_data]), - Schema - end. - -%% Open backup media and extract schema -%% rewind backup media and leave it open -%% Returns {R, {Header, Schema}} -read_schema_section(R) -> - case catch do_read_schema_section(R) of - {'EXIT', Reason} -> - catch safe_apply(R, close_read, [R#restore.bup_data]), - {error, {'EXIT', Reason}}; - {error, Reason} -> - catch safe_apply(R, close_read, [R#restore.bup_data]), - {error, Reason}; - {R2, {H, Schema, Rest}} -> - Schema2 = convert_schema(H#log_header.log_version, Schema), - {R2, {H, Schema2, Rest}} - end. - -do_read_schema_section(R) -> - R2 = safe_apply(R, open_read, [R#restore.bup_data]), - {R3, RawSchema} = safe_apply(R2, read, [R2#restore.bup_data]), - do_read_schema_section(R3, verify_header(RawSchema), []). - -do_read_schema_section(R, {ok, B, C, []}, Acc) -> - case safe_apply(R, read, [R#restore.bup_data]) of - {R2, []} -> - {R2, {B, Acc, []}}; - {R2, RawSchema} -> - do_read_schema_section(R2, {ok, B, C, RawSchema}, Acc) - end; - -do_read_schema_section(R, {ok, B, C, [Head | Tail]}, Acc) - when element(1, Head) == schema -> - do_read_schema_section(R, {ok, B, C, Tail}, Acc ++ [Head]); - -do_read_schema_section(R, {ok, B, _C, Rest}, Acc) -> - {R, {B, Acc, Rest}}; - -do_read_schema_section(_R, {error, Reason}, _Acc) -> - {error, Reason}. - -verify_header([H | RawSchema]) when record(H, log_header) -> - Current = mnesia_log:backup_log_header(), - if - H#log_header.log_kind == Current#log_header.log_kind -> - Versions = ["0.1", "1.1", Current#log_header.log_version], - case lists:member(H#log_header.log_version, Versions) of - true -> - {ok, H, Current, RawSchema}; - false -> - {error, {"Bad header version. Cannot be used as backup.", H}} - end; - true -> - {error, {"Bad kind of header. Cannot be used as backup.", H}} - end; -verify_header(RawSchema) -> - {error, {"Missing header. Cannot be used as backup.", catch hd(RawSchema)}}. - -refresh_cookie(Schema, NewCookie) -> - case lists:keysearch(schema, 2, Schema) of - {value, {schema, schema, List}} -> - Cs = mnesia_schema:list2cs(List), - Cs2 = Cs#cstruct{cookie = NewCookie}, - Item = {schema, schema, mnesia_schema:cs2list(Cs2)}, - lists:keyreplace(schema, 2, Schema, Item); - - false -> - Reason = "No schema found. Cannot be used as backup.", - throw({error, {Reason, Schema}}) - end. - -%% Convert schema items from an external backup -%% If backup format is the latest, no conversion is needed -%% All supported backup formats should have their converters -%% here as separate function clauses. -convert_schema("0.1", Schema) -> - convert_0_1(Schema); -convert_schema("1.1", Schema) -> - %% The new backup format is a pure extension of the old one - Current = mnesia_log:backup_log_header(), - convert_schema(Current#log_header.log_version, Schema); -convert_schema(Latest, Schema) -> - H = mnesia_log:backup_log_header(), - if - H#log_header.log_version == Latest -> - Schema; - true -> - Reason = "Bad backup header version. Cannot convert schema.", - throw({error, {Reason, H}}) - end. - -%% Backward compatibility for 0.1 -convert_0_1(Schema) -> - case lists:keysearch(schema, 2, Schema) of - {value, {schema, schema, List}} -> - Schema2 = lists:keydelete(schema, 2, Schema), - Cs = mnesia_schema:list2cs(List), - convert_0_1(Schema2, [], Cs); - false -> - List = mnesia_schema:get_initial_schema(disc_copies, [node()]), - Cs = mnesia_schema:list2cs(List), - convert_0_1(Schema, [], Cs) - end. - -convert_0_1([{schema, cookie, Cookie} | Schema], Acc, Cs) -> - convert_0_1(Schema, Acc, Cs#cstruct{cookie = Cookie}); -convert_0_1([{schema, db_nodes, DbNodes} | Schema], Acc, Cs) -> - convert_0_1(Schema, Acc, Cs#cstruct{disc_copies = DbNodes}); -convert_0_1([{schema, version, Version} | Schema], Acc, Cs) -> - convert_0_1(Schema, Acc, Cs#cstruct{version = Version}); -convert_0_1([{schema, Tab, Def} | Schema], Acc, Cs) -> - Head = - case lists:keysearch(index, 1, Def) of - {value, {index, PosList}} -> - %% Remove the snmp "index" - P = PosList -- [snmp], - Def2 = lists:keyreplace(index, 1, Def, {index, P}), - {schema, Tab, Def2}; - false -> - {schema, Tab, Def} - end, - convert_0_1(Schema, [Head | Acc], Cs); -convert_0_1([Head | Schema], Acc, Cs) -> - convert_0_1(Schema, [Head | Acc], Cs); -convert_0_1([], Acc, Cs) -> - [schema2bup({schema, schema, Cs}) | Acc]. - -%% Returns Val or throw error -lookup_schema(Key, Schema) -> - case lists:keysearch(Key, 2, Schema) of - {value, {schema, Key, Val}} -> Val; - false -> throw({error, {"Cannot lookup", Key}}) - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Backup compatibility - -%% Convert internal schema items to backup dito -schema2bup({schema, Tab}) -> - {schema, Tab}; -schema2bup({schema, Tab, TableDef}) -> - {schema, Tab, mnesia_schema:cs2list(TableDef)}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Create schema on the given nodes -%% Requires that old schemas has been deleted -%% Returns ok | {error, Reason} -create_schema([]) -> - create_schema([node()]); -create_schema(Ns) when list(Ns) -> - case is_set(Ns) of - true -> - create_schema(Ns, mnesia_schema:ensure_no_schema(Ns)); - false -> - {error, {combine_error, Ns}} - end; -create_schema(Ns) -> - {error, {badarg, Ns}}. - -is_set(List) when list(List) -> - ordsets:is_set(lists:sort(List)); -is_set(_) -> - false. - -create_schema(Ns, ok) -> - %% Ensure that we access the intended Mnesia - %% directory. This function may not be called - %% during startup since it will cause the - %% application_controller to get into deadlock - case mnesia_lib:ensure_loaded(?APPLICATION) of - ok -> - case mnesia_monitor:get_env(schema_location) of - ram -> - {error, {has_no_disc, node()}}; - _ -> - case mnesia_schema:opt_create_dir(true, mnesia_lib:dir()) of - {error, What} -> - {error, What}; - ok -> - Mod = mnesia_backup, - Str = mk_str(), - File = mnesia_lib:dir(Str), - file:delete(File), - case catch make_initial_backup(Ns, File, Mod) of - {ok, _Res} -> - case do_install_fallback(File, Mod) of - ok -> - file:delete(File), - ok; - {error, Reason} -> - {error, Reason} - end; - {error, Reason} -> - {error, Reason} - end - end - end; - {error, Reason} -> - {error, Reason} - end; -create_schema(_Ns, {error, Reason}) -> - {error, Reason}; -create_schema(_Ns, Reason) -> - {error, Reason}. - -mk_str() -> - Now = [integer_to_list(I) || I <- tuple_to_list(now())], - lists:concat([node()] ++ Now ++ ".TMP"). - -make_initial_backup(Ns, Opaque, Mod) -> - Schema = [{schema, schema, mnesia_schema:get_initial_schema(disc_copies, Ns)}], - O2 = do_apply(Mod, open_write, [Opaque], Opaque), - O3 = do_apply(Mod, write, [O2, [mnesia_log:backup_log_header()]], O2), - O4 = do_apply(Mod, write, [O3, Schema], O3), - O5 = do_apply(Mod, commit_write, [O4], O4), - {ok, O5}. - -do_apply(_, write, [_, Items], Opaque) when Items == [] -> - Opaque; -do_apply(Mod, What, Args, _Opaque) -> - case catch apply(Mod, What, Args) of - {ok, Opaque2} -> Opaque2; - {error, Reason} -> throw({error, Reason}); - {'EXIT', Reason} -> throw({error, {'EXIT', Reason}}) - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Restore - -%% Restore schema and possibly other tables from a backup -%% and replicate them to the necessary nodes -%% Requires that old schemas has been deleted -%% Returns ok | {error, Reason} -install_fallback(Opaque) -> - install_fallback(Opaque, []). - -install_fallback(Opaque, Args) -> - %% Ensure that we access the intended Mnesia - %% directory. This function may not be called - %% during startup since it will cause the - %% application_controller to get into deadlock - case mnesia_lib:ensure_loaded(?APPLICATION) of - ok -> - do_install_fallback(Opaque, Args); - {error, Reason} -> - {error, Reason} - end. - -do_install_fallback(Opaque, Mod) when atom(Mod) -> - do_install_fallback(Opaque, [{module, Mod}]); -do_install_fallback(Opaque, Args) when list(Args) -> - case check_fallback_args(Args, #fallback_args{opaque = Opaque}) of - {ok, FA} -> - do_install_fallback(FA); - {error, Reason} -> - {error, Reason} - end; -do_install_fallback(_Opaque, Args) -> - {error, {badarg, Args}}. - -check_fallback_args([Arg | Tail], FA) -> - case catch check_fallback_arg_type(Arg, FA) of - {'EXIT', _Reason} -> - {error, {badarg, Arg}}; - FA2 -> - check_fallback_args(Tail, FA2) - end; -check_fallback_args([], FA) -> - {ok, FA}. - -check_fallback_arg_type(Arg, FA) -> - case Arg of - {scope, global} -> - FA#fallback_args{scope = global}; - {scope, local} -> - FA#fallback_args{scope = local}; - {module, Mod} -> - Mod2 = mnesia_monitor:do_check_type(backup_module, Mod), - FA#fallback_args{module = Mod2}; - {mnesia_dir, Dir} -> - FA#fallback_args{mnesia_dir = Dir, - use_default_dir = false}; - {keep_tables, Tabs} -> - atom_list(Tabs), - FA#fallback_args{keep_tables = Tabs}; - {skip_tables, Tabs} -> - atom_list(Tabs), - FA#fallback_args{skip_tables = Tabs}; - {default_op, keep_tables} -> - FA#fallback_args{default_op = keep_tables}; - {default_op, skip_tables} -> - FA#fallback_args{default_op = skip_tables} - end. - -atom_list([H | T]) when atom(H) -> - atom_list(T); -atom_list([]) -> - ok. - -do_install_fallback(FA) -> - Pid = spawn_link(?MODULE, install_fallback_master, [self(), FA]), - Res = - receive - {'EXIT', Pid, Reason} -> % if appl has trapped exit - {error, {'EXIT', Reason}}; - {Pid, Res2} -> - case Res2 of - {ok, _} -> - ok; - {error, Reason} -> - {error, {"Cannot install fallback", Reason}} - end - end, - Res. - -install_fallback_master(ClientPid, FA) -> - process_flag(trap_exit, true), - State = {start, FA}, - Opaque = FA#fallback_args.opaque, - Mod = FA#fallback_args.module, - Res = (catch iterate(Mod, fun restore_recs/4, Opaque, State)), - unlink(ClientPid), - ClientPid ! {self(), Res}, - exit(shutdown). - -restore_recs(_, _, _, stop) -> - throw({error, "restore_recs already stopped"}); - -restore_recs(Recs, Header, Schema, {start, FA}) -> - %% No records in backup - Schema2 = convert_schema(Header#log_header.log_version, Schema), - CreateList = lookup_schema(schema, Schema2), - case catch mnesia_schema:list2cs(CreateList) of - {'EXIT', Reason} -> - throw({error, {"Bad schema in restore_recs", Reason}}); - Cs -> - Ns = get_fallback_nodes(FA, Cs#cstruct.disc_copies), - global:set_lock({{mnesia_table_lock, schema}, self()}, Ns, infinity), - Args = [self(), FA], - Pids = [spawn_link(N, ?MODULE, fallback_receiver, Args) || N <- Ns], - send_fallback(Pids, {start, Header, Schema2}), - Res = restore_recs(Recs, Header, Schema2, Pids), - global:del_lock({{mnesia_table_lock, schema}, self()}, Ns), - Res - end; - -restore_recs([], _Header, _Schema, Pids) -> - send_fallback(Pids, swap), - send_fallback(Pids, stop), - stop; - -restore_recs(Recs, _, _, Pids) -> - send_fallback(Pids, {records, Recs}), - Pids. - -get_fallback_nodes(FA, Ns) -> - This = node(), - case lists:member(This, Ns) of - true -> - case FA#fallback_args.scope of - global -> Ns; - local -> [This] - end; - false -> - throw({error, {"No disc resident schema on local node", Ns}}) - end. - -send_fallback(Pids, Msg) when list(Pids), Pids /= [] -> - lists:foreach(fun(Pid) -> Pid ! {self(), Msg} end, Pids), - rec_answers(Pids, []). - -rec_answers([], Acc) -> - case {lists:keysearch(error, 1, Acc), mnesia_lib:uniq(Acc)} of - {{value, {error, Val}}, _} -> throw({error, Val}); - {_, [SameAnswer]} -> SameAnswer; - {_, Other} -> throw({error, {"Different answers", Other}}) - end; -rec_answers(Pids, Acc) -> - receive - {'EXIT', Pid, stopped} -> - Pids2 = lists:delete(Pid, Pids), - rec_answers(Pids2, [stopped|Acc]); - {'EXIT', Pid, Reason} -> - Pids2 = lists:delete(Pid, Pids), - rec_answers(Pids2, [{error, {'EXIT', Pid, Reason}}|Acc]); - {Pid, Reply} -> - Pids2 = lists:delete(Pid, Pids), - rec_answers(Pids2, [Reply|Acc]) - end. - -fallback_exists() -> - Fname = fallback_bup(), - fallback_exists(Fname). - -fallback_exists(Fname) -> - case mnesia_monitor:use_dir() of - true -> - mnesia_lib:exists(Fname); - false -> - case ?catch_val(active_fallback) of - {'EXIT', _} -> false; - Bool -> Bool - end - end. - -fallback_name() -> "FALLBACK.BUP". -fallback_bup() -> mnesia_lib:dir(fallback_name()). - -fallback_tmp_name() -> "FALLBACK.TMP". -%% fallback_full_tmp_name() -> mnesia_lib:dir(fallback_tmp_name()). - -fallback_receiver(Master, FA) -> - process_flag(trap_exit, true), - - case catch register(mnesia_fallback, self()) of - {'EXIT', _} -> - Reason = {already_exists, node()}, - local_fallback_error(Master, Reason); - true -> - FA2 = check_fallback_dir(Master, FA), - Bup = FA2#fallback_args.fallback_bup, - case mnesia_lib:exists(Bup) of - true -> - Reason2 = {already_exists, node()}, - local_fallback_error(Master, Reason2); - false -> - Mod = mnesia_backup, - Tmp = FA2#fallback_args.fallback_tmp, - R = #restore{mode = replace, - bup_module = Mod, - bup_data = Tmp}, - file:delete(Tmp), - case catch fallback_receiver_loop(Master, R, FA2, schema) of - {error, Reason} -> - local_fallback_error(Master, Reason); - Other -> - exit(Other) - end - end - end. - -local_fallback_error(Master, Reason) -> - Master ! {self(), {error, Reason}}, - unlink(Master), - exit(Reason). - -check_fallback_dir(Master, FA) -> - case mnesia:system_info(schema_location) of - ram -> - Reason = {has_no_disc, node()}, - local_fallback_error(Master, Reason); - _ -> - Dir = check_fallback_dir_arg(Master, FA), - Bup = filename:join([Dir, fallback_name()]), - Tmp = filename:join([Dir, fallback_tmp_name()]), - FA#fallback_args{fallback_bup = Bup, - fallback_tmp = Tmp, - mnesia_dir = Dir} - end. - -check_fallback_dir_arg(Master, FA) -> - case FA#fallback_args.use_default_dir of - true -> - mnesia_lib:dir(); - false when FA#fallback_args.scope == local -> - Dir = FA#fallback_args.mnesia_dir, - case catch mnesia_monitor:do_check_type(dir, Dir) of - {'EXIT', _R} -> - Reason = {badarg, {dir, Dir}, node()}, - local_fallback_error(Master, Reason); - AbsDir-> - AbsDir - end; - false when FA#fallback_args.scope == global -> - Reason = {combine_error, global, dir, node()}, - local_fallback_error(Master, Reason) - end. - -fallback_receiver_loop(Master, R, FA, State) -> - receive - {Master, {start, Header, Schema}} when State == schema -> - Dir = FA#fallback_args.mnesia_dir, - throw_bad_res(ok, mnesia_schema:opt_create_dir(true, Dir)), - R2 = safe_apply(R, open_write, [R#restore.bup_data]), - R3 = safe_apply(R2, write, [R2#restore.bup_data, [Header]]), - BupSchema = [schema2bup(S) || S <- Schema], - R4 = safe_apply(R3, write, [R3#restore.bup_data, BupSchema]), - Master ! {self(), ok}, - fallback_receiver_loop(Master, R4, FA, records); - - {Master, {records, Recs}} when State == records -> - R2 = safe_apply(R, write, [R#restore.bup_data, Recs]), - Master ! {self(), ok}, - fallback_receiver_loop(Master, R2, FA, records); - - {Master, swap} when State /= schema -> - ?eval_debug_fun({?MODULE, fallback_receiver_loop, pre_swap}, []), - safe_apply(R, commit_write, [R#restore.bup_data]), - Bup = FA#fallback_args.fallback_bup, - Tmp = FA#fallback_args.fallback_tmp, - throw_bad_res(ok, file:rename(Tmp, Bup)), - catch mnesia_lib:set(active_fallback, true), - ?eval_debug_fun({?MODULE, fallback_receiver_loop, post_swap}, []), - Master ! {self(), ok}, - fallback_receiver_loop(Master, R, FA, stop); - - {Master, stop} when State == stop -> - stopped; - - Msg -> - safe_apply(R, abort_write, [R#restore.bup_data]), - Tmp = FA#fallback_args.fallback_tmp, - file:delete(Tmp), - throw({error, "Unexpected msg fallback_receiver_loop", Msg}) - end. - -throw_bad_res(Expected, Expected) -> Expected; -throw_bad_res(_Expected, {error, Actual}) -> throw({error, Actual}); -throw_bad_res(_Expected, Actual) -> throw({error, Actual}). - --record(local_tab, {name, storage_type, dets_args, open, close, add, record_name}). - -tm_fallback_start(IgnoreFallback) -> - mnesia_schema:lock_schema(), - Res = do_fallback_start(fallback_exists(), IgnoreFallback), - mnesia_schema: unlock_schema(), - case Res of - ok -> ok; - {error, Reason} -> exit(Reason) - end. - -do_fallback_start(false, _IgnoreFallback) -> - ok; -do_fallback_start(true, true) -> - verbose("Ignoring fallback at startup, but leaving it active...~n", []), - mnesia_lib:set(active_fallback, true), - ok; -do_fallback_start(true, false) -> - verbose("Starting from fallback...~n", []), - - Fname = fallback_bup(), - Mod = mnesia_backup, - Ets = ?ets_new_table(mnesia_local_tables, [set, public, {keypos, 2}]), - case catch iterate(Mod, fun restore_tables/4, Fname, {start, Ets}) of - {ok, Res} -> - case Res of - {local, _, LT} -> %% Close the last file - (LT#local_tab.close)(LT); - _ -> - ignore - end, - List = ?ets_match_object(Ets, '_'), - Tabs = [L#local_tab.name || L <- List, L#local_tab.name /= schema], - ?ets_delete_table(Ets), - mnesia_lib:swap_tmp_files(Tabs), - catch dets:close(schema), - Tmp = mnesia_lib:tab2tmp(schema), - Dat = mnesia_lib:tab2dat(schema), - case file:rename(Tmp, Dat) of - ok -> - file:delete(Fname), - ok; - {error, Reason} -> - file:delete(Tmp), - {error, {"Cannot start from fallback. Rename error.", Reason}} - end; - {error, Reason} -> - {error, {"Cannot start from fallback", Reason}}; - {'EXIT', Reason} -> - {error, {"Cannot start from fallback", Reason}} - end. - -restore_tables(Recs, Header, Schema, {start, LocalTabs}) -> - Dir = mnesia_lib:dir(), - OldDir = filename:join([Dir, "OLD_DIR"]), - mnesia_schema:purge_dir(OldDir, []), - mnesia_schema:purge_dir(Dir, [fallback_name()]), - init_dat_files(Schema, LocalTabs), - State = {new, LocalTabs}, - restore_tables(Recs, Header, Schema, State); -restore_tables([Rec | Recs], Header, Schema, {new, LocalTabs}) -> - Tab = element(1, Rec), - case ?ets_lookup(LocalTabs, Tab) of - [] -> - State = {not_local, LocalTabs, Tab}, - restore_tables(Recs, Header, Schema, State); - [L] when record(L, local_tab) -> - (L#local_tab.open)(Tab, L), - State = {local, LocalTabs, L}, - restore_tables([Rec | Recs], Header, Schema, State) - end; -restore_tables([Rec | Recs], Header, Schema, S = {not_local, LocalTabs, PrevTab}) -> - Tab = element(1, Rec), - if - Tab == PrevTab -> - restore_tables(Recs, Header, Schema, S); - true -> - State = {new, LocalTabs}, - restore_tables([Rec | Recs], Header, Schema, State) - end; -restore_tables([Rec | Recs], Header, Schema, State = {local, LocalTabs, L}) -> - Tab = element(1, Rec), - if - Tab == L#local_tab.name -> - Key = element(2, Rec), - (L#local_tab.add)(Tab, Key, Rec, L), - restore_tables(Recs, Header, Schema, State); - true -> - (L#local_tab.close)(L), - NState = {new, LocalTabs}, - restore_tables([Rec | Recs], Header, Schema, NState) - end; -restore_tables([], _Header, _Schema, State) -> - State. - -%% Creates all neccessary dat files and inserts -%% the table definitions in the schema table -%% -%% Returns a list of local_tab tuples for all local tables -init_dat_files(Schema, LocalTabs) -> - Fname = mnesia_lib:tab2tmp(schema), - Args = [{file, Fname}, {keypos, 2}, {type, set}], - case dets:open_file(schema, Args) of % Assume schema lock - {ok, _} -> - create_dat_files(Schema, LocalTabs), - dets:close(schema), - LocalTab = #local_tab{name = schema, - storage_type = disc_copies, - dets_args = Args, - open = fun open_media/2, - close = fun close_media/1, - add = fun add_to_media/4, - record_name = schema}, - ?ets_insert(LocalTabs, LocalTab); - {error, Reason} -> - throw({error, {"Cannot open file", schema, Args, Reason}}) - end. - -create_dat_files([{schema, schema, TabDef} | Tail], LocalTabs) -> - ok = dets:insert(schema, {schema, schema, TabDef}), - create_dat_files(Tail, LocalTabs); -create_dat_files([{schema, Tab, TabDef} | Tail], LocalTabs) -> - Cs = mnesia_schema:list2cs(TabDef), - ok = dets:insert(schema, {schema, Tab, TabDef}), - RecName = Cs#cstruct.record_name, - case mnesia_lib:cs_to_storage_type(node(), Cs) of - unknown -> - cleanup_dat_file(Tab), - create_dat_files(Tail, LocalTabs); - disc_only_copies -> - Fname = mnesia_lib:tab2tmp(Tab), - Args = [{file, Fname}, {keypos, 2}, - {type, mnesia_lib:disk_type(Tab, Cs#cstruct.type)}], - case mnesia_lib:dets_sync_open(Tab, Args) of - {ok, _} -> - mnesia_lib:dets_sync_close(Tab), - LocalTab = #local_tab{name = Tab, - storage_type = disc_only_copies, - dets_args = Args, - open = fun open_media/2, - close = fun close_media/1, - add = fun add_to_media/4, - record_name = RecName}, - ?ets_insert(LocalTabs, LocalTab), - create_dat_files(Tail, LocalTabs); - {error, Reason} -> - throw({error, {"Cannot open file", Tab, Args, Reason}}) - end; - ram_copies -> - %% Create .DCD if needed in open_media in case any ram_copies - %% are backed up. - LocalTab = #local_tab{name = Tab, - storage_type = ram_copies, - dets_args = ignore, - open = fun open_media/2, - close = fun close_media/1, - add = fun add_to_media/4, - record_name = RecName}, - ?ets_insert(LocalTabs, LocalTab), - create_dat_files(Tail, LocalTabs); - Storage -> - %% Create DCD - Fname = mnesia_lib:tab2dcd(Tab), - file:delete(Fname), - Log = mnesia_log:open_log(fallback_tab, mnesia_log:dcd_log_header(), - Fname, false), - LocalTab = #local_tab{name = Tab, - storage_type = Storage, - dets_args = ignore, - open = fun open_media/2, - close = fun close_media/1, - add = fun add_to_media/4, - record_name = RecName}, - mnesia_log:close_log(Log), - ?ets_insert(LocalTabs, LocalTab), - create_dat_files(Tail, LocalTabs) - end; -create_dat_files([{schema, Tab} | Tail], LocalTabs) -> - cleanup_dat_file(Tab), - create_dat_files(Tail, LocalTabs); -create_dat_files([], _LocalTabs) -> - ok. - -cleanup_dat_file(Tab) -> - ok = dets:delete(schema, {schema, Tab}), - mnesia_lib:cleanup_tmp_files([Tab]). - -open_media(Tab, LT) -> - case LT#local_tab.storage_type of - disc_only_copies -> - Args = LT#local_tab.dets_args, - case mnesia_lib:dets_sync_open(Tab, Args) of - {ok, _} -> ok; - {error, Reason} -> - throw({error, {"Cannot open file", Tab, Args, Reason}}) - end; - ram_copies -> - %% Create .DCD as ram_copies backed up. - FnameDCD = mnesia_lib:tab2dcd(Tab), - file:delete(FnameDCD), - Log = mnesia_log:open_log(fallback_tab, - mnesia_log:dcd_log_header(), - FnameDCD, false), - mnesia_log:close_log(Log), - - %% Create .DCL - Fname = mnesia_lib:tab2dcl(Tab), - file:delete(Fname), - mnesia_log:open_log({?MODULE,Tab}, - mnesia_log:dcl_log_header(), - Fname, false, false, - read_write); - _ -> - Fname = mnesia_lib:tab2dcl(Tab), - file:delete(Fname), - mnesia_log:open_log({?MODULE,Tab}, - mnesia_log:dcl_log_header(), - Fname, false, false, - read_write) - end. -close_media(L) -> - Tab = L#local_tab.name, - case L#local_tab.storage_type of - disc_only_copies -> - mnesia_lib:dets_sync_close(Tab); - _ -> - mnesia_log:close_log({?MODULE,Tab}) - end. - -add_to_media(Tab, Key, Rec, L) -> - RecName = L#local_tab.record_name, - case L#local_tab.storage_type of - disc_only_copies -> - case Rec of - {Tab, Key} -> - ok = dets:delete(Tab, Key); - (Rec) when Tab == RecName -> - ok = dets:insert(Tab, Rec); - (Rec) -> - Rec2 = setelement(1, Rec, RecName), - ok = dets:insert(Tab, Rec2) - end; - _ -> - Log = {?MODULE, Tab}, - case Rec of - {Tab, Key} -> - mnesia_log:append(Log, {{Tab, Key}, {Tab, Key}, delete}); - (Rec) when Tab == RecName -> - mnesia_log:append(Log, {{Tab, Key}, Rec, write}); - (Rec) -> - Rec2 = setelement(1, Rec, RecName), - mnesia_log:append(Log, {{Tab, Key}, Rec2, write}) - end - end. - -uninstall_fallback() -> - uninstall_fallback([{scope, global}]). - -uninstall_fallback(Args) -> - case check_fallback_args(Args, #fallback_args{}) of - {ok, FA} -> - do_uninstall_fallback(FA); - {error, Reason} -> - {error, Reason} - end. - -do_uninstall_fallback(FA) -> - %% Ensure that we access the intended Mnesia - %% directory. This function may not be called - %% during startup since it will cause the - %% application_controller to get into deadlock - case mnesia_lib:ensure_loaded(?APPLICATION) of - ok -> - Pid = spawn_link(?MODULE, uninstall_fallback_master, [self(), FA]), - receive - {'EXIT', Pid, Reason} -> % if appl has trapped exit - {error, {'EXIT', Reason}}; - {Pid, Res} -> - Res - end; - {error, Reason} -> - {error, Reason} - end. - -uninstall_fallback_master(ClientPid, FA) -> - process_flag(trap_exit, true), - - FA2 = check_fallback_dir(ClientPid, FA), % May exit - Bup = FA2#fallback_args.fallback_bup, - case fallback_to_schema(Bup) of - {ok, fallback, List} -> - Cs = mnesia_schema:list2cs(List), - case catch get_fallback_nodes(FA, Cs#cstruct.disc_copies) of - Ns when list(Ns) -> - do_uninstall(ClientPid, Ns, FA); - {error, Reason} -> - local_fallback_error(ClientPid, Reason) - end; - {error, Reason} -> - local_fallback_error(ClientPid, Reason) - end. - -do_uninstall(ClientPid, Ns, FA) -> - Args = [self(), FA], - global:set_lock({{mnesia_table_lock, schema}, self()}, Ns, infinity), - Pids = [spawn_link(N, ?MODULE, local_uninstall_fallback, Args) || N <- Ns], - Res = do_uninstall(ClientPid, Pids, [], [], ok), - global:del_lock({{mnesia_table_lock, schema}, self()}, Ns), - ClientPid ! {self(), Res}, - unlink(ClientPid), - exit(shutdown). - -do_uninstall(ClientPid, [Pid | Pids], GoodPids, BadNodes, Res) -> - receive - %% {'EXIT', ClientPid, _} -> - %% client_exit; - {'EXIT', Pid, Reason} -> - BadNode = node(Pid), - BadRes = {error, {"Uninstall fallback", BadNode, Reason}}, - do_uninstall(ClientPid, Pids, GoodPids, [BadNode | BadNodes], BadRes); - {Pid, {error, Reason}} -> - BadNode = node(Pid), - BadRes = {error, {"Uninstall fallback", BadNode, Reason}}, - do_uninstall(ClientPid, Pids, GoodPids, [BadNode | BadNodes], BadRes); - {Pid, started} -> - do_uninstall(ClientPid, Pids, [Pid | GoodPids], BadNodes, Res) - end; -do_uninstall(ClientPid, [], GoodPids, [], ok) -> - lists:foreach(fun(Pid) -> Pid ! {self(), do_uninstall} end, GoodPids), - rec_uninstall(ClientPid, GoodPids, ok); -do_uninstall(_ClientPid, [], GoodPids, BadNodes, BadRes) -> - lists:foreach(fun(Pid) -> exit(Pid, shutdown) end, GoodPids), - {error, {node_not_running, BadNodes, BadRes}}. - -local_uninstall_fallback(Master, FA) -> - %% Don't trap exit - - register(mnesia_fallback, self()), % May exit - FA2 = check_fallback_dir(Master, FA), % May exit - Master ! {self(), started}, - - receive - {Master, do_uninstall} -> - ?eval_debug_fun({?MODULE, uninstall_fallback2, pre_delete}, []), - catch mnesia_lib:set(active_fallback, false), - Tmp = FA2#fallback_args.fallback_tmp, - Bup = FA2#fallback_args.fallback_bup, - file:delete(Tmp), - Res = - case fallback_exists(Bup) of - true -> file:delete(Bup); - false -> ok - end, - ?eval_debug_fun({?MODULE, uninstall_fallback2, post_delete}, []), - Master ! {self(), Res}, - unlink(Master), - exit(normal) - end. - -rec_uninstall(ClientPid, [Pid | Pids], AccRes) -> - receive - %% {'EXIT', ClientPid, _} -> - %% exit(shutdown); - {'EXIT', Pid, R} -> - Reason = {node_not_running, {node(Pid), R}}, - rec_uninstall(ClientPid, Pids, {error, Reason}); - {Pid, ok} -> - rec_uninstall(ClientPid, Pids, AccRes); - {Pid, BadRes} -> - rec_uninstall(ClientPid, Pids, BadRes) - end; -rec_uninstall(ClientPid, [], Res) -> - ClientPid ! {self(), Res}, - unlink(ClientPid), - exit(normal). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Backup traversal - -%% Iterate over a backup and produce a new backup. -%% Fun(BackupItem, Acc) is applied for each BackupItem. -%% -%% Valid BackupItems are: -%% -%% {schema, Tab} Table to be deleted -%% {schema, Tab, CreateList} Table to be created, CreateList may be empty -%% {schema, db_nodes, DbNodes}List of nodes, defaults to [node()] OLD -%% {schema, version, Version} Schema version OLD -%% {schema, cookie, Cookie} Unique schema cookie OLD -%% {Tab, Key} Oid for record to be deleted -%% Record Record to be inserted. -%% -%% The Fun must return a tuple {BackupItems, NewAcc} -%% where BackupItems is a list of valid BackupItems and -%% NewAcc is a new accumulator value. Once BackupItems -%% that not are schema related has been returned, no more schema -%% items may be returned. The schema related items must always be -%% first in the backup. -%% -%% If TargetMod == read_only, no new backup will be created. -%% -%% Opening of the source media will be performed by -%% to SourceMod:open_read(Source) -%% -%% Opening of the target media will be performed by -%% to TargetMod:open_write(Target) -traverse_backup(Source, Target, Fun, Acc) -> - Mod = mnesia_monitor:get_env(backup_module), - traverse_backup(Source, Mod, Target, Mod, Fun, Acc). - -traverse_backup(Source, SourceMod, Target, TargetMod, Fun, Acc) -> - Args = [self(), Source, SourceMod, Target, TargetMod, Fun, Acc], - Pid = spawn_link(?MODULE, do_traverse_backup, Args), - receive - {'EXIT', Pid, Reason} -> - {error, {"Backup traversal crashed", Reason}}; - {iter_done, Pid, Res} -> - Res - end. - -do_traverse_backup(ClientPid, Source, SourceMod, Target, TargetMod, Fun, Acc) -> - process_flag(trap_exit, true), - Iter = - if - TargetMod /= read_only -> - case catch do_apply(TargetMod, open_write, [Target], Target) of - {error, Error} -> - unlink(ClientPid), - ClientPid ! {iter_done, self(), {error, Error}}, - exit(Error); - Else -> Else - end; - true -> - ignore - end, - A = {start, Fun, Acc, TargetMod, Iter}, - Res = - case iterate(SourceMod, fun trav_apply/4, Source, A) of - {ok, {iter, _, Acc2, _, Iter2}} when TargetMod /= read_only -> - case catch do_apply(TargetMod, commit_write, [Iter2], Iter2) of - {error, Reason} -> - {error, Reason}; - _ -> - {ok, Acc2} - end; - {ok, {iter, _, Acc2, _, _}} -> - {ok, Acc2}; - {error, Reason} when TargetMod /= read_only-> - catch do_apply(TargetMod, abort_write, [Iter], Iter), - {error, {"Backup traversal failed", Reason}}; - {error, Reason} -> - {error, {"Backup traversal failed", Reason}} - end, - unlink(ClientPid), - ClientPid ! {iter_done, self(), Res}. - -trav_apply(Recs, _Header, _Schema, {iter, Fun, Acc, Mod, Iter}) -> - {NewRecs, Acc2} = filter_foldl(Fun, Acc, Recs), - if - Mod /= read_only, NewRecs /= [] -> - Iter2 = do_apply(Mod, write, [Iter, NewRecs], Iter), - {iter, Fun, Acc2, Mod, Iter2}; - true -> - {iter, Fun, Acc2, Mod, Iter} - end; -trav_apply(Recs, Header, Schema, {start, Fun, Acc, Mod, Iter}) -> - Iter2 = - if - Mod /= read_only -> - do_apply(Mod, write, [Iter, [Header]], Iter); - true -> - Iter - end, - TravAcc = trav_apply(Schema, Header, Schema, {iter, Fun, Acc, Mod, Iter2}), - trav_apply(Recs, Header, Schema, TravAcc). - -filter_foldl(Fun, Acc, [Head|Tail]) -> - case Fun(Head, Acc) of - {HeadItems, HeadAcc} when list(HeadItems) -> - {TailItems, TailAcc} = filter_foldl(Fun, HeadAcc, Tail), - {HeadItems ++ TailItems, TailAcc}; - Other -> - throw({error, {"Fun must return a list", Other}}) - end; -filter_foldl(_Fun, Acc, []) -> - {[], Acc}. - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_checkpoint.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_checkpoint.erl deleted file mode 100644 index aa2e99642b..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_checkpoint.erl +++ /dev/null @@ -1,1284 +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_checkpoint.erl,v 1.1 2008/12/17 09:53:38 mikpe Exp $ -%% --module(mnesia_checkpoint). - -%% TM callback interface --export([ - tm_add_copy/2, - tm_change_table_copy_type/3, - tm_del_copy/2, - tm_mnesia_down/1, - tm_prepare/1, - tm_retain/4, - tm_retain/5, - tm_enter_pending/1, - tm_enter_pending/3, - tm_exit_pending/1, - convert_cp_record/1 - ]). - -%% Public interface --export([ - activate/1, - checkpoints/0, - deactivate/1, - deactivate/2, - iterate/6, - most_local_node/2, - really_retain/2, - stop/0, - stop_iteration/1, - tables_and_cookie/1 - ]). - -%% Internal --export([ - call/2, - cast/2, - init/1, - remote_deactivate/1, - start/1 - ]). - -%% sys callback interface --export([ - system_code_change/4, - system_continue/3, - system_terminate/4 - ]). - --include("mnesia.hrl"). --import(mnesia_lib, [add/2, del/2, set/2, unset/1]). --import(mnesia_lib, [dbg_out/2]). - --record(tm, {log, pending, transactions, checkpoints}). - --record(checkpoint_args, {name = {now(), node()}, - allow_remote = true, - ram_overrides_dump = false, - nodes = [], - node = node(), - now = now(), - cookie = ?unique_cookie, - min = [], - max = [], - pending_tab, - wait_for_old, % Initially undefined then List - is_activated = false, - ignore_new = [], - retainers = [], - iterators = [], - supervisor, - pid - }). - -%% Old record definition --record(checkpoint, {name, - allow_remote, - ram_overrides_dump, - nodes, - node, - now, - min, - max, - pending_tab, - wait_for_old, - is_activated, - ignore_new, - retainers, - iterators, - supervisor, - pid - }). - --record(retainer, {cp_name, tab_name, store, writers = [], really_retain = true}). - --record(iter, {tab_name, oid_tab, main_tab, retainer_tab, source, val, pid}). - --record(pending, {tid, disc_nodes = [], ram_nodes = []}). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% TM callback functions - -stop() -> - lists:foreach(fun(Name) -> call(Name, stop) end, - checkpoints()), - ok. - -tm_prepare(Cp) when record(Cp, checkpoint_args) -> - Name = Cp#checkpoint_args.name, - case lists:member(Name, checkpoints()) of - false -> - start_retainer(Cp); - true -> - {error, {already_exists, Name, node()}} - end; -tm_prepare(Cp) when record(Cp, checkpoint) -> - %% Node with old protocol sent an old checkpoint record - %% and we have to convert it - case convert_cp_record(Cp) of - {ok, NewCp} -> - tm_prepare(NewCp); - {error, Reason} -> - {error, Reason} - end. - -tm_mnesia_down(Node) -> - lists:foreach(fun(Name) -> cast(Name, {mnesia_down, Node}) end, - checkpoints()). - -%% Returns pending -tm_enter_pending(Tid, DiscNs, RamNs) -> - Pending = #pending{tid = Tid, disc_nodes = DiscNs, ram_nodes = RamNs}, - tm_enter_pending(Pending). - -tm_enter_pending(Pending) -> - PendingTabs = val(pending_checkpoints), - tm_enter_pending(PendingTabs, Pending). - -tm_enter_pending([], Pending) -> - Pending; -tm_enter_pending([Tab | Tabs], Pending) -> - catch ?ets_insert(Tab, Pending), - tm_enter_pending(Tabs, Pending). - -tm_exit_pending(Tid) -> - Pids = val(pending_checkpoint_pids), - tm_exit_pending(Pids, Tid). - -tm_exit_pending([], Tid) -> - Tid; -tm_exit_pending([Pid | Pids], Tid) -> - Pid ! {self(), {exit_pending, Tid}}, - tm_exit_pending(Pids, Tid). - -enter_still_pending([Tid | Tids], Tab) -> - ?ets_insert(Tab, #pending{tid = Tid}), - enter_still_pending(Tids, Tab); -enter_still_pending([], _Tab) -> - ok. - - -%% Looks up checkpoints for functions in mnesia_tm. -tm_retain(Tid, Tab, Key, Op) -> - case val({Tab, commit_work}) of - [{checkpoints, Checkpoints} | _ ] -> - tm_retain(Tid, Tab, Key, Op, Checkpoints); - _ -> - undefined - end. - -tm_retain(Tid, Tab, Key, Op, Checkpoints) -> - case Op of - clear_table -> - OldRecs = mnesia_lib:db_match_object(Tab, '_'), - send_group_retain(OldRecs, Checkpoints, Tid, Tab, []), - OldRecs; - _ -> - OldRecs = mnesia_lib:db_get(Tab, Key), - send_retain(Checkpoints, {retain, Tid, Tab, Key, OldRecs}), - OldRecs - end. - -send_group_retain([Rec | Recs], Checkpoints, Tid, Tab, [PrevRec | PrevRecs]) - when element(2, Rec) /= element(2, PrevRec) -> - Key = element(2, PrevRec), - OldRecs = lists:reverse([PrevRec | PrevRecs]), - send_retain(Checkpoints, {retain, Tid, Tab, Key, OldRecs}), - send_group_retain(Recs, Checkpoints, Tid, Tab, [Rec]); -send_group_retain([Rec | Recs], Checkpoints, Tid, Tab, Acc) -> - send_group_retain(Recs, Checkpoints, Tid, Tab, [Rec | Acc]); -send_group_retain([], Checkpoints, Tid, Tab, [PrevRec | PrevRecs]) -> - Key = element(2, PrevRec), - OldRecs = lists:reverse([PrevRec | PrevRecs]), - send_retain(Checkpoints, {retain, Tid, Tab, Key, OldRecs}), - ok; -send_group_retain([], _Checkpoints, _Tid, _Tab, []) -> - ok. - -send_retain([Name | Names], Msg) -> - cast(Name, Msg), - send_retain(Names, Msg); -send_retain([], _Msg) -> - ok. - -tm_add_copy(Tab, Node) when Node /= node() -> - case val({Tab, commit_work}) of - [{checkpoints, Checkpoints} | _ ] -> - Fun = fun(Name) -> call(Name, {add_copy, Tab, Node}) end, - map_call(Fun, Checkpoints, ok); - _ -> - ok - end. - -tm_del_copy(Tab, Node) when Node == node() -> - mnesia_subscr:unsubscribe_table(Tab), - case val({Tab, commit_work}) of - [{checkpoints, Checkpoints} | _ ] -> - Fun = fun(Name) -> call(Name, {del_copy, Tab, Node}) end, - map_call(Fun, Checkpoints, ok); - _ -> - ok - end. - -tm_change_table_copy_type(Tab, From, To) -> - case val({Tab, commit_work}) of - [{checkpoints, Checkpoints} | _ ] -> - Fun = fun(Name) -> call(Name, {change_copy, Tab, From, To}) end, - map_call(Fun, Checkpoints, ok); - _ -> - ok - end. - -map_call(Fun, [Name | Names], Res) -> - case Fun(Name) of - ok -> - map_call(Fun, Names, Res); - {error, {no_exists, Name}} -> - map_call(Fun, Names, Res); - {error, Reason} -> - %% BUGBUG: We may end up with some checkpoint retainers - %% too much in the add_copy case. How do we remove them? - map_call(Fun, Names, {error, Reason}) - end; -map_call(_Fun, [], Res) -> - Res. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Public functions - -deactivate(Name) -> - case call(Name, get_checkpoint) of - {error, Reason} -> - {error, Reason}; - Cp -> - deactivate(Cp#checkpoint_args.nodes, Name) - end. - -deactivate(Nodes, Name) -> - rpc:multicall(Nodes, ?MODULE, remote_deactivate, [Name]), - ok. - -remote_deactivate(Name) -> - call(Name, deactivate). - -checkpoints() -> val(checkpoints). - -tables_and_cookie(Name) -> - case call(Name, get_checkpoint) of - {error, Reason} -> - {error, Reason}; - Cp -> - Tabs = Cp#checkpoint_args.min ++ Cp#checkpoint_args.max, - Cookie = Cp#checkpoint_args.cookie, - {ok, Tabs, Cookie} - end. - -most_local_node(Name, Tab) -> - case ?catch_val({Tab, {retainer, Name}}) of - {'EXIT', _} -> - {error, {"No retainer attached to table", [Tab, Name]}}; - R -> - Writers = R#retainer.writers, - LocalWriter = lists:member(node(), Writers), - if - LocalWriter == true -> - {ok, node()}; - Writers /= [] -> - {ok, hd(Writers)}; - true -> - {error, {"No retainer attached to table", [Tab, Name]}} - end - end. - -really_retain(Name, Tab) -> - R = val({Tab, {retainer, Name}}), - R#retainer.really_retain. - -%% Activate a checkpoint. -%% -%% A checkpoint is a transaction consistent state that may be used to -%% perform a distributed backup or to rollback the involved tables to -%% their old state. Backups may also be used to restore tables to -%% their old state. Args is a list of the following tuples: -%% -%% {name, Name} -%% Name of checkpoint. Each checkpoint must have a name which -%% is unique on the reachable nodes. The name may be reused when -%% the checkpoint has been deactivated. -%% By default a probably unique name is generated. -%% Multiple checkpoints may be set on the same table. -%% -%% {allow_remote, Bool} -%% false means that all retainers must be local. If the -%% table does not reside locally, the checkpoint fails. -%% true allows retainers on other nodes. -%% -%% {min, MinTabs} -%% Minimize redundancy and only keep checkpoint info together with -%% one replica, preferrably at the local node. If any node involved -%% the checkpoint goes down, the checkpoint is deactivated. -%% -%% {max, MaxTabs} -%% Maximize redundancy and keep checkpoint info together with all -%% replicas. The checkpoint becomes more fault tolerant if the -%% tables has several replicas. When new replicas are added, they -%% will also get a retainer attached to them. -%% -%% {ram_overrides_dump, Bool} -%% {ram_overrides_dump, Tabs} -%% Only applicable for ram_copies. Bool controls which versions of -%% the records that should be included in the checkpoint state. -%% true means that the latest comitted records in ram (i.e. the -%% records that the application accesses) should be included -%% in the checkpoint. false means that the records dumped to -%% dat-files (the records that will be loaded at startup) should -%% be included in the checkpoint. Tabs is a list of tables. -%% Default is false. -%% -%% {ignore_new, TidList} -%% Normally we wait for all pending transactions to complete -%% before we allow iteration over the checkpoint. But in order -%% to cope with checkpoint activation inside a transaction that -%% currently prepares commit (mnesia_init:get_net_work_copy) we -%% need to have the ability to ignore the enclosing transaction. -%% We do not wait for the transactions in TidList to end. The -%% transactions in TidList are regarded as newer than the checkpoint. - -activate(Args) -> - case args2cp(Args) of - {ok, Cp} -> - do_activate(Cp); - {error, Reason} -> - {error, Reason} - end. - -args2cp(Args) when list(Args)-> - case catch lists:foldl(fun check_arg/2, #checkpoint_args{}, Args) of - {'EXIT', Reason} -> - {error, Reason}; - Cp -> - case check_tables(Cp) of - {error, Reason} -> - {error, Reason}; - {ok, Overriders, AllTabs} -> - arrange_retainers(Cp, Overriders, AllTabs) - end - end; -args2cp(Args) -> - {error, {badarg, Args}}. - -check_arg({name, Name}, Cp) -> - case lists:member(Name, checkpoints()) of - true -> - exit({already_exists, Name}); - false -> - case catch tab2retainer({foo, Name}) of - List when list(List) -> - Cp#checkpoint_args{name = Name}; - _ -> - exit({badarg, Name}) - end - end; -check_arg({allow_remote, true}, Cp) -> - Cp#checkpoint_args{allow_remote = true}; -check_arg({allow_remote, false}, Cp) -> - Cp#checkpoint_args{allow_remote = false}; -check_arg({ram_overrides_dump, true}, Cp) -> - Cp#checkpoint_args{ram_overrides_dump = true}; -check_arg({ram_overrides_dump, false}, Cp) -> - Cp#checkpoint_args{ram_overrides_dump = false}; -check_arg({ram_overrides_dump, Tabs}, Cp) when list(Tabs) -> - Cp#checkpoint_args{ram_overrides_dump = Tabs}; -check_arg({min, Tabs}, Cp) when list(Tabs) -> - Cp#checkpoint_args{min = Tabs}; -check_arg({max, Tabs}, Cp) when list(Tabs) -> - Cp#checkpoint_args{max = Tabs}; -check_arg({ignore_new, Tids}, Cp) when list(Tids) -> - Cp#checkpoint_args{ignore_new = Tids}; -check_arg(Arg, _) -> - exit({badarg, Arg}). - -check_tables(Cp) -> - Min = Cp#checkpoint_args.min, - Max = Cp#checkpoint_args.max, - AllTabs = Min ++ Max, - DoubleTabs = [T || T <- Min, lists:member(T, Max)], - Overriders = Cp#checkpoint_args.ram_overrides_dump, - if - DoubleTabs /= [] -> - {error, {combine_error, Cp#checkpoint_args.name, - [{min, DoubleTabs}, {max, DoubleTabs}]}}; - Min == [], Max == [] -> - {error, {combine_error, Cp#checkpoint_args.name, - [{min, Min}, {max, Max}]}}; - Overriders == false -> - {ok, [], AllTabs}; - Overriders == true -> - {ok, AllTabs, AllTabs}; - list(Overriders) -> - case [T || T <- Overriders, not lists:member(T, Min)] of - [] -> - case [T || T <- Overriders, not lists:member(T, Max)] of - [] -> - {ok, Overriders, AllTabs}; - Outsiders -> - {error, {combine_error, Cp#checkpoint_args.name, - [{ram_overrides_dump, Outsiders}, - {max, Outsiders}]}} - end; - Outsiders -> - {error, {combine_error, Cp#checkpoint_args.name, - [{ram_overrides_dump, Outsiders}, - {min, Outsiders}]}} - end - end. - -arrange_retainers(Cp, Overriders, AllTabs) -> - R = #retainer{cp_name = Cp#checkpoint_args.name}, - case catch [R#retainer{tab_name = Tab, - writers = select_writers(Cp, Tab)} - || Tab <- AllTabs] of - {'EXIT', Reason} -> - {error, Reason}; - Retainers -> - {ok, Cp#checkpoint_args{ram_overrides_dump = Overriders, - retainers = Retainers, - nodes = writers(Retainers)}} - end. - -select_writers(Cp, Tab) -> - case filter_remote(Cp, val({Tab, active_replicas})) of - [] -> - exit({"Cannot prepare checkpoint (replica not available)", - [Tab, Cp#checkpoint_args.name]}); - Writers -> - This = node(), - case {lists:member(Tab, Cp#checkpoint_args.max), - lists:member(This, Writers)} of - {true, _} -> Writers; % Max - {false, true} -> [This]; - {false, false} -> [hd(Writers)] - end - end. - -filter_remote(Cp, Writers) when Cp#checkpoint_args.allow_remote == true -> - Writers; -filter_remote(_Cp, Writers) -> - This = node(), - case lists:member(This, Writers) of - true -> [This]; - false -> [] - end. - -writers(Retainers) -> - Fun = fun(R, Acc) -> R#retainer.writers ++ Acc end, - Writers = lists:foldl(Fun, [], Retainers), - mnesia_lib:uniq(Writers). - -do_activate(Cp) -> - Name = Cp#checkpoint_args.name, - Nodes = Cp#checkpoint_args.nodes, - case mnesia_tm:prepare_checkpoint(Nodes, Cp) of - {Replies, []} -> - check_prep(Replies, Name, Nodes, Cp#checkpoint_args.ignore_new); - {_, BadNodes} -> - {error, {"Cannot prepare checkpoint (bad nodes)", - [Name, BadNodes]}} - end. - -check_prep([{ok, Name, IgnoreNew, _Node} | Replies], Name, Nodes, IgnoreNew) -> - check_prep(Replies, Name, Nodes, IgnoreNew); -check_prep([{error, Reason} | _Replies], Name, _Nodes, _IgnoreNew) -> - {error, {"Cannot prepare checkpoint (bad reply)", - [Name, Reason]}}; -check_prep([{badrpc, Reason} | _Replies], Name, _Nodes, _IgnoreNew) -> - {error, {"Cannot prepare checkpoint (badrpc)", - [Name, Reason]}}; -check_prep([], Name, Nodes, IgnoreNew) -> - collect_pending(Name, Nodes, IgnoreNew). - -collect_pending(Name, Nodes, IgnoreNew) -> - case rpc:multicall(Nodes, ?MODULE, call, [Name, collect_pending]) of - {Replies, []} -> - case catch ?ets_new_table(mnesia_union, [bag]) of - {'EXIT', Reason} -> %% system limit - Msg = "Cannot create an ets table pending union", - {error, {system_limit, Msg, Reason}}; - UnionTab -> - compute_union(Replies, Nodes, Name, UnionTab, IgnoreNew) - end; - {_, BadNodes} -> - deactivate(Nodes, Name), - {error, {"Cannot collect from pending checkpoint", Name, BadNodes}} - end. - -compute_union([{ok, Pending} | Replies], Nodes, Name, UnionTab, IgnoreNew) -> - add_pending(Pending, UnionTab), - compute_union(Replies, Nodes, Name, UnionTab, IgnoreNew); -compute_union([{error, Reason} | _Replies], Nodes, Name, UnionTab, _IgnoreNew) -> - deactivate(Nodes, Name), - ?ets_delete_table(UnionTab), - {error, Reason}; -compute_union([{badrpc, Reason} | _Replies], Nodes, Name, UnionTab, _IgnoreNew) -> - deactivate(Nodes, Name), - ?ets_delete_table(UnionTab), - {error, {badrpc, Reason}}; -compute_union([], Nodes, Name, UnionTab, IgnoreNew) -> - send_activate(Nodes, Nodes, Name, UnionTab, IgnoreNew). - -add_pending([P | Pending], UnionTab) -> - add_pending_node(P#pending.disc_nodes, P#pending.tid, UnionTab), - add_pending_node(P#pending.ram_nodes, P#pending.tid, UnionTab), - add_pending(Pending, UnionTab); -add_pending([], _UnionTab) -> - ok. - -add_pending_node([Node | Nodes], Tid, UnionTab) -> - ?ets_insert(UnionTab, {Node, Tid}), - add_pending_node(Nodes, Tid, UnionTab); -add_pending_node([], _Tid, _UnionTab) -> - ok. - -send_activate([Node | Nodes], AllNodes, Name, UnionTab, IgnoreNew) -> - Pending = [Tid || {_, Tid} <- ?ets_lookup(UnionTab, Node), - not lists:member(Tid, IgnoreNew)], - case rpc:call(Node, ?MODULE, call, [Name, {activate, Pending}]) of - activated -> - send_activate(Nodes, AllNodes, Name, UnionTab, IgnoreNew); - {badrpc, Reason} -> - deactivate(Nodes, Name), - ?ets_delete_table(UnionTab), - {error, {"Activation failed (bad node)", Name, Node, Reason}}; - {error, Reason} -> - deactivate(Nodes, Name), - ?ets_delete_table(UnionTab), - {error, {"Activation failed", Name, Node, Reason}} - end; -send_activate([], AllNodes, Name, UnionTab, _IgnoreNew) -> - ?ets_delete_table(UnionTab), - {ok, Name, AllNodes}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Checkpoint server - -cast(Name, Msg) -> - case ?catch_val({checkpoint, Name}) of - {'EXIT', _} -> - {error, {no_exists, Name}}; - - Pid when pid(Pid) -> - Pid ! {self(), Msg}, - {ok, Pid} - end. - -call(Name, Msg) -> - case cast(Name, Msg) of - {ok, Pid} -> - catch link(Pid), % Always local - Self = self(), - receive - {'EXIT', Pid, Reason} -> - {error, {"Got exit", [Name, Reason]}}; - {Name, Self, Reply} -> - unlink(Pid), - Reply - end; - Error -> - Error - end. - -abcast(Nodes, Name, Msg) -> - rpc:eval_everywhere(Nodes, ?MODULE, cast, [Name, Msg]). - -reply(nopid, _Name, _Reply) -> - ignore; -reply(ReplyTo, Name, Reply) -> - ReplyTo ! {Name, ReplyTo, Reply}. - -%% Returns {ok, NewCp} or {error, Reason} -start_retainer(Cp) -> - % Will never be restarted - Name = Cp#checkpoint_args.name, - case supervisor:start_child(mnesia_checkpoint_sup, [Cp]) of - {ok, _Pid} -> - {ok, Name, Cp#checkpoint_args.ignore_new, node()}; - {error, Reason} -> - {error, {"Cannot create checkpoint retainer", - Name, node(), Reason}} - end. - -start(Cp) -> - Name = Cp#checkpoint_args.name, - Args = [Cp#checkpoint_args{supervisor = self()}], - mnesia_monitor:start_proc({?MODULE, Name}, ?MODULE, init, Args). - -init(Cp) -> - process_flag(trap_exit, true), - Name = Cp#checkpoint_args.name, - Props = [set, public, {keypos, 2}], - case catch ?ets_new_table(mnesia_pending_checkpoint, Props) of - {'EXIT', Reason} -> %% system limit - Msg = "Cannot create an ets table for pending transactions", - Error = {error, {system_limit, Name, Msg, Reason}}, - proc_lib:init_ack(Cp#checkpoint_args.supervisor, Error); - PendingTab -> - Rs = [prepare_tab(Cp, R) || R <- Cp#checkpoint_args.retainers], - Cp2 = Cp#checkpoint_args{retainers = Rs, - pid = self(), - pending_tab = PendingTab}, - add(pending_checkpoint_pids, self()), - add(pending_checkpoints, PendingTab), - set({checkpoint, Name}, self()), - add(checkpoints, Name), - dbg_out("Checkpoint ~p (~p) started~n", [Name, self()]), - proc_lib:init_ack(Cp2#checkpoint_args.supervisor, {ok, self()}), - retainer_loop(Cp2) - end. - -prepare_tab(Cp, R) -> - Tab = R#retainer.tab_name, - prepare_tab(Cp, R, val({Tab, storage_type})). - -prepare_tab(Cp, R, Storage) -> - Tab = R#retainer.tab_name, - Name = R#retainer.cp_name, - case lists:member(node(), R#retainer.writers) of - true -> - R2 = retainer_create(Cp, R, Tab, Name, Storage), - set({Tab, {retainer, Name}}, R2), - add({Tab, checkpoints}, Name), %% Keep checkpoint info for table_info & mnesia_session - add_chkp_info(Tab, Name), - R2; - false -> - set({Tab, {retainer, Name}}, R#retainer{store = undefined}), - R - end. - -add_chkp_info(Tab, Name) -> - case val({Tab, commit_work}) of - [{checkpoints, OldList} | CommitList] -> - case lists:member(Name, OldList) of - true -> - ok; - false -> - NewC = [{checkpoints, [Name | OldList]} | CommitList], - mnesia_lib:set({Tab, commit_work}, NewC) - end; - CommitList -> - Chkp = {checkpoints, [Name]}, - %% OBS checkpoints needs to be first in the list! - mnesia_lib:set({Tab, commit_work}, [Chkp | CommitList]) - end. - -tab2retainer({Tab, Name}) -> - FlatName = lists:flatten(io_lib:write(Name)), - mnesia_lib:dir(lists:concat([?MODULE, "_", Tab, "_", FlatName, ".RET"])). - -retainer_create(_Cp, R, Tab, Name, disc_only_copies) -> - Fname = tab2retainer({Tab, Name}), - file:delete(Fname), - Args = [{file, Fname}, {type, set}, {keypos, 2}, {repair, false}], - {ok, _} = mnesia_lib:dets_sync_open({Tab, Name}, Args), - dbg_out("Checkpoint retainer created ~p ~p~n", [Name, Tab]), - R#retainer{store = {dets, {Tab, Name}}, really_retain = true}; -retainer_create(Cp, R, Tab, Name, Storage) -> - T = ?ets_new_table(mnesia_retainer, [set, public, {keypos, 2}]), - Overriders = Cp#checkpoint_args.ram_overrides_dump, - ReallyR = R#retainer.really_retain, - ReallyCp = lists:member(Tab, Overriders), - ReallyR2 = prepare_ram_tab(Tab, T, Storage, ReallyR, ReallyCp), - dbg_out("Checkpoint retainer created ~p ~p~n", [Name, Tab]), - R#retainer{store = {ets, T}, really_retain = ReallyR2}. - -%% Copy the dumped table into retainer if needed -%% If the really_retain flag already has been set to false, -%% it should remain false even if we change storage type -%% while the checkpoint is activated. -prepare_ram_tab(Tab, T, ram_copies, true, false) -> - Fname = mnesia_lib:tab2dcd(Tab), - case mnesia_lib:exists(Fname) of - true -> - Log = mnesia_log:open_log(prepare_ram_tab, - mnesia_log:dcd_log_header(), - Fname, true, - mnesia_monitor:get_env(auto_repair), - read_only), - Add = fun(Rec) -> - Key = element(2, Rec), - Recs = - case ?ets_lookup(T, Key) of - [] -> []; - [{_, _, Old}] -> Old - end, - ?ets_insert(T, {Tab, Key, [Rec | Recs]}), - continue - end, - traverse_dcd(mnesia_log:chunk_log(Log, start), Log, Add), - mnesia_log:close_log(Log); - false -> - ok - end, - false; -prepare_ram_tab(_, _, _, ReallyRetain, _) -> - ReallyRetain. - -traverse_dcd({Cont, [LogH | Rest]}, Log, Fun) - when record(LogH, log_header), - LogH#log_header.log_kind == dcd_log, - LogH#log_header.log_version >= "1.0" -> - traverse_dcd({Cont, Rest}, Log, Fun); %% BUGBUG Error handling repaired files -traverse_dcd({Cont, Recs}, Log, Fun) -> %% trashed data?? - lists:foreach(Fun, Recs), - traverse_dcd(mnesia_log:chunk_log(Log, Cont), Log, Fun); -traverse_dcd(eof, _Log, _Fun) -> - ok. - -retainer_get({ets, Store}, Key) -> ?ets_lookup(Store, Key); -retainer_get({dets, Store}, Key) -> dets:lookup(Store, Key). - -retainer_put({ets, Store}, Val) -> ?ets_insert(Store, Val); -retainer_put({dets, Store}, Val) -> dets:insert(Store, Val). - -retainer_first({ets, Store}) -> ?ets_first(Store); -retainer_first({dets, Store}) -> dets:first(Store). - -retainer_next({ets, Store}, Key) -> ?ets_next(Store, Key); -retainer_next({dets, Store}, Key) -> dets:next(Store, Key). - -%% retainer_next_slot(Tab, Pos) -> -%% case retainer_slot(Tab, Pos) of -%% '$end_of_table' -> -%% '$end_of_table'; -%% [] -> -%% retainer_next_slot(Tab, Pos + 1); -%% Recs when list(Recs) -> -%% {Pos, Recs} -%% end. -%% -%% retainer_slot({ets, Store}, Pos) -> ?ets_next(Store, Pos); -%% retainer_slot({dets, Store}, Pos) -> dets:slot(Store, Pos). - -retainer_fixtable(Tab, Bool) when atom(Tab) -> - mnesia_lib:db_fixtable(val({Tab, storage_type}), Tab, Bool); -retainer_fixtable({ets, Tab}, Bool) -> - mnesia_lib:db_fixtable(ram_copies, Tab, Bool); -retainer_fixtable({dets, Tab}, Bool) -> - mnesia_lib:db_fixtable(disc_only_copies, Tab, Bool). - -retainer_delete({ets, Store}) -> - ?ets_delete_table(Store); -retainer_delete({dets, Store}) -> - mnesia_lib:dets_sync_close(Store), - Fname = tab2retainer(Store), - file:delete(Fname). - -retainer_loop(Cp) -> - Name = Cp#checkpoint_args.name, - receive - {_From, {retain, Tid, Tab, Key, OldRecs}} - when Cp#checkpoint_args.wait_for_old == [] -> - R = val({Tab, {retainer, Name}}), - case R#retainer.really_retain of - true -> - PendingTab = Cp#checkpoint_args.pending_tab, - case catch ?ets_lookup_element(PendingTab, Tid, 1) of - {'EXIT', _} -> - Store = R#retainer.store, - case retainer_get(Store, Key) of - [] -> - retainer_put(Store, {Tab, Key, OldRecs}); - _ -> - already_retained - end; - pending -> - ignore - end; - false -> - ignore - end, - retainer_loop(Cp); - - %% Adm - {From, deactivate} -> - do_stop(Cp), - reply(From, Name, deactivated), - unlink(From), - exit(shutdown); - - {'EXIT', Parent, _} when Parent == Cp#checkpoint_args.supervisor -> - %% do_stop(Cp), - %% assume that entire Mnesia is terminating - exit(shutdown); - - {_From, {mnesia_down, Node}} -> - Cp2 = do_del_retainers(Cp, Node), - retainer_loop(Cp2); - {From, get_checkpoint} -> - reply(From, Name, Cp), - retainer_loop(Cp); - {From, {add_copy, Tab, Node}} when Cp#checkpoint_args.wait_for_old == [] -> - {Res, Cp2} = do_add_copy(Cp, Tab, Node), - reply(From, Name, Res), - retainer_loop(Cp2); - {From, {del_copy, Tab, Node}} when Cp#checkpoint_args.wait_for_old == [] -> - Cp2 = do_del_copy(Cp, Tab, Node), - reply(From, Name, ok), - retainer_loop(Cp2); - {From, {change_copy, Tab, From, To}} when Cp#checkpoint_args.wait_for_old == [] -> - Cp2 = do_change_copy(Cp, Tab, From, To), - reply(From, Name, ok), - retainer_loop(Cp2); - {_From, {add_retainer, R, Node}} -> - Cp2 = do_add_retainer(Cp, R, Node), - retainer_loop(Cp2); - {_From, {del_retainer, R, Node}} when Cp#checkpoint_args.wait_for_old == [] -> - Cp2 = do_del_retainer(Cp, R, Node), - retainer_loop(Cp2); - - %% Iteration - {From, {iter_begin, Iter}} when Cp#checkpoint_args.wait_for_old == [] -> - Cp2 = iter_begin(Cp, From, Iter), - retainer_loop(Cp2); - - {From, {iter_end, Iter}} when Cp#checkpoint_args.wait_for_old == [] -> - retainer_fixtable(Iter#iter.oid_tab, false), - Iters = Cp#checkpoint_args.iterators -- [Iter], - reply(From, Name, ok), - retainer_loop(Cp#checkpoint_args{iterators = Iters}); - - {_From, {exit_pending, Tid}} - when list(Cp#checkpoint_args.wait_for_old) -> - StillPending = lists:delete(Tid, Cp#checkpoint_args.wait_for_old), - Cp2 = Cp#checkpoint_args{wait_for_old = StillPending}, - Cp3 = maybe_activate(Cp2), - retainer_loop(Cp3); - - {From, collect_pending} -> - PendingTab = Cp#checkpoint_args.pending_tab, - del(pending_checkpoints, PendingTab), - Pending = ?ets_match_object(PendingTab, '_'), - reply(From, Name, {ok, Pending}), - retainer_loop(Cp); - - {From, {activate, Pending}} -> - StillPending = mnesia_recover:still_pending(Pending), - enter_still_pending(StillPending, Cp#checkpoint_args.pending_tab), - Cp2 = maybe_activate(Cp#checkpoint_args{wait_for_old = StillPending}), - reply(From, Name, activated), - retainer_loop(Cp2); - - {'EXIT', From, _Reason} -> - Iters = [Iter || Iter <- Cp#checkpoint_args.iterators, - check_iter(From, Iter)], - retainer_loop(Cp#checkpoint_args{iterators = Iters}); - - {system, From, Msg} -> - dbg_out("~p got {system, ~p, ~p}~n", [?MODULE, From, Msg]), - sys:handle_system_msg(Msg, From, no_parent, ?MODULE, [], Cp) - end. - -maybe_activate(Cp) - when Cp#checkpoint_args.wait_for_old == [], - Cp#checkpoint_args.is_activated == false -> - Cp#checkpoint_args{pending_tab = undefined, is_activated = true}; -maybe_activate(Cp) -> - Cp. - -iter_begin(Cp, From, Iter) -> - Name = Cp#checkpoint_args.name, - R = val({Iter#iter.tab_name, {retainer, Name}}), - Iter2 = init_tabs(R, Iter), - Iter3 = Iter2#iter{pid = From}, - retainer_fixtable(Iter3#iter.oid_tab, true), - Iters = [Iter3 | Cp#checkpoint_args.iterators], - reply(From, Name, {ok, Iter3, self()}), - Cp#checkpoint_args{iterators = Iters}. - -do_stop(Cp) -> - Name = Cp#checkpoint_args.name, - del(pending_checkpoints, Cp#checkpoint_args.pending_tab), - del(pending_checkpoint_pids, self()), - del(checkpoints, Name), - unset({checkpoint, Name}), - lists:foreach(fun deactivate_tab/1, Cp#checkpoint_args.retainers), - Iters = Cp#checkpoint_args.iterators, - lists:foreach(fun(I) -> retainer_fixtable(I#iter.oid_tab, false) end, Iters). - -deactivate_tab(R) -> - Name = R#retainer.cp_name, - Tab = R#retainer.tab_name, - del({Tab, checkpoints}, Name), %% Keep checkpoint info for table_info & mnesia_session - del_chkp_info(Tab, Name), - unset({Tab, {retainer, Name}}), - Active = lists:member(node(), R#retainer.writers), - case R#retainer.store of - undefined -> - ignore; - Store when Active == true -> - retainer_delete(Store); - _ -> - ignore - end. - -del_chkp_info(Tab, Name) -> - case val({Tab, commit_work}) of - [{checkpoints, ChkList} | Rest] -> - case lists:delete(Name, ChkList) of - [] -> - %% The only checkpoint was deleted - mnesia_lib:set({Tab, commit_work}, Rest); - NewList -> - mnesia_lib:set({Tab, commit_work}, - [{checkpoints, NewList} | Rest]) - end; - _ -> ignore - end. - -do_del_retainers(Cp, Node) -> - Rs = [do_del_retainer2(Cp, R, Node) || R <- Cp#checkpoint_args.retainers], - Cp#checkpoint_args{retainers = Rs, nodes = writers(Rs)}. - -do_del_retainer2(Cp, R, Node) -> - Writers = R#retainer.writers -- [Node], - R2 = R#retainer{writers = Writers}, - set({R2#retainer.tab_name, {retainer, R2#retainer.cp_name}}, R2), - if - Writers == [] -> - Event = {mnesia_checkpoint_deactivated, Cp#checkpoint_args.name}, - mnesia_lib:report_system_event(Event), - do_stop(Cp), - exit(shutdown); - Node == node() -> - deactivate_tab(R), % Avoids unnecessary tm_retain accesses - set({R2#retainer.tab_name, {retainer, R2#retainer.cp_name}}, R2), - R2; - true -> - R2 - end. - -do_del_retainer(Cp, R0, Node) -> - {R, Rest} = find_retainer(R0, Cp#checkpoint_args.retainers, []), - R2 = do_del_retainer2(Cp, R, Node), - Rs = [R2|Rest], - Cp#checkpoint_args{retainers = Rs, nodes = writers(Rs)}. - -do_del_copy(Cp, Tab, ThisNode) when ThisNode == node() -> - Name = Cp#checkpoint_args.name, - Others = Cp#checkpoint_args.nodes -- [ThisNode], - R = val({Tab, {retainer, Name}}), - abcast(Others, Name, {del_retainer, R, ThisNode}), - do_del_retainer(Cp, R, ThisNode). - -do_add_copy(Cp, Tab, Node) when Node /= node()-> - case lists:member(Tab, Cp#checkpoint_args.max) of - false -> - {ok, Cp}; - true -> - Name = Cp#checkpoint_args.name, - R0 = val({Tab, {retainer, Name}}), - W = R0#retainer.writers, - R = R0#retainer{writers = W ++ [Node]}, - - case lists:member(Node, Cp#checkpoint_args.nodes) of - true -> - send_retainer(Cp, R, Node); - false -> - case tm_remote_prepare(Node, Cp) of - {ok, Name, _IgnoreNew, Node} -> - case lists:member(schema, Cp#checkpoint_args.max) of - true -> - %% We need to send schema retainer somewhere - RS0 = val({schema, {retainer, Name}}), - W = RS0#retainer.writers, - RS1 = RS0#retainer{writers = W ++ [Node]}, - case send_retainer(Cp, RS1, Node) of - {ok, Cp1} -> - send_retainer(Cp1, R, Node); - Error -> - Error - end; - false -> - send_retainer(Cp, R, Node) - end; - {badrpc, Reason} -> - {{error, {badrpc, Reason}}, Cp}; - {error, Reason} -> - {{error, Reason}, Cp} - end - end - end. - -tm_remote_prepare(Node, Cp) -> - rpc:call(Node, ?MODULE, tm_prepare, [Cp]). - -do_add_retainer(Cp, R0, Node) -> - Writers = R0#retainer.writers, - {R, Rest} = find_retainer(R0, Cp#checkpoint_args.retainers, []), - NewRet = - if - Node == node() -> - prepare_tab(Cp, R#retainer{writers = Writers}); - true -> - R#retainer{writers = Writers} - end, - Rs = [NewRet | Rest], - set({NewRet#retainer.tab_name, {retainer, NewRet#retainer.cp_name}}, NewRet), - Cp#checkpoint_args{retainers = Rs, nodes = writers(Rs)}. - -find_retainer(#retainer{cp_name = CP, tab_name = Tab}, - [Ret = #retainer{cp_name = CP, tab_name = Tab} | R], Acc) -> - {Ret, R ++ Acc}; -find_retainer(Ret, [H|R], Acc) -> - find_retainer(Ret, R, [H|Acc]). - -send_retainer(Cp, R, Node) -> - Name = Cp#checkpoint_args.name, - Nodes0 = Cp#checkpoint_args.nodes -- [Node], - Nodes1 = Nodes0 ++ [Node], - Nodes = Nodes1 -- [node()], - abcast(Nodes, Name, {add_retainer, R, Node}), - Store = R#retainer.store, -%% send_retainer2(Node, Name, Store, retainer_next_slot(Store, 0)), - send_retainer2(Node, Name, Store, retainer_first(Store)), - Cp2 = do_add_retainer(Cp, R, Node), - {ok, Cp2}. - -send_retainer2(_, _, _, '$end_of_table') -> - ok; -%%send_retainer2(Node, Name, Store, {Slot, Records}) -> -send_retainer2(Node, Name, Store, Key) -> - [{Tab, _, Records}] = retainer_get(Store, Key), - abcast([Node], Name, {retain, {dirty, send_retainer}, Tab, Key, Records}), - send_retainer2(Node, Name, Store, retainer_next(Store, Key)). - -do_change_copy(Cp, Tab, FromType, ToType) -> - Name = Cp#checkpoint_args.name, - R = val({Tab, {retainer, Name}}), - R2 = prepare_tab(Cp, R, ToType), - {_, Old} = R#retainer.store, - {_, New} = R2#retainer.store, - - Fname = tab2retainer({Tab, Name}), - if - FromType == disc_only_copies -> - mnesia_lib:dets_sync_close(Old), - loaded = mnesia_lib:dets_to_ets(Old, New, Fname, set, no, yes), - ok = file:delete(Fname); - ToType == disc_only_copies -> - TabSize = ?ets_info(Old, size), - Props = [{file, Fname}, - {type, set}, - {keypos, 2}, -%% {ram_file, true}, - {estimated_no_objects, TabSize + 256}, - {repair, false}], - {ok, _} = mnesia_lib:dets_sync_open(New, Props), - ok = mnesia_dumper:raw_dump_table(New, Old), - ?ets_delete_table(Old); - true -> - ignore - end, - Pos = #retainer.tab_name, - Rs = lists:keyreplace(Tab, Pos, Cp#checkpoint_args.retainers, R2), - Cp#checkpoint_args{retainers = Rs, nodes = writers(Rs)}. - -check_iter(From, Iter) when Iter#iter.pid == From -> - retainer_fixtable(Iter#iter.oid_tab, false), - false; -check_iter(_From, _Iter) -> - true. - -init_tabs(R, Iter) -> - {Kind, _} = Store = R#retainer.store, - Main = {Kind, Iter#iter.tab_name}, - Ret = Store, - Iter2 = Iter#iter{main_tab = Main, retainer_tab = Ret}, - case Iter#iter.source of - table -> Iter2#iter{oid_tab = Main}; - retainer -> Iter2#iter{oid_tab = Ret} - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Iteration -%% -%% Iterates over a table and applies Fun(ListOfRecords) -%% with a suitable amount of records, e.g. 1000 or so. -%% ListOfRecords is [] when the iteration is over. -%% -%% OidKind affects which internal table to be iterated over and -%% ValKind affects which table to pick the actual records from. Legal -%% values for OidKind and ValKind is the atom table or the atom -%% retainer. -%% -%% The iteration may either be performed over the main table (which -%% contains the latest values of the records, i.e. the values that -%% are visible to the applications) or over the checkpoint retainer -%% (which contains the values as the looked like the timepoint when -%% the checkpoint was activated). -%% -%% It is possible to iterate over the main table and pick values -%% from the retainer and vice versa. - -iterate(Name, Tab, Fun, Acc, Source, Val) -> - Iter0 = #iter{tab_name = Tab, source = Source, val = Val}, - case call(Name, {iter_begin, Iter0}) of - {error, Reason} -> - {error, Reason}; - {ok, Iter, Pid} -> - link(Pid), % We don't want any pending fixtable's - Res = (catch iter(Fun, Acc, Iter)), - unlink(Pid), - call(Name, {iter_end, Iter}), - case Res of - {'EXIT', Reason} -> {error, Reason}; - {error, Reason} -> {error, Reason}; - Acc2 -> {ok, Acc2} - end - end. - -iter(Fun, Acc, Iter)-> - iter(Fun, Acc, Iter, retainer_first(Iter#iter.oid_tab)). - -iter(Fun, Acc, Iter, Key) -> - case get_records(Iter, Key) of - {'$end_of_table', []} -> - Fun([], Acc); - {'$end_of_table', Records} -> - Acc2 = Fun(Records, Acc), - Fun([], Acc2); - {Next, Records} -> - Acc2 = Fun(Records, Acc), - iter(Fun, Acc2, Iter, Next) - end. - -stop_iteration(Reason) -> - throw({error, {stopped, Reason}}). - -get_records(Iter, Key) -> - get_records(Iter, Key, 500, []). % 500 keys - -get_records(_Iter, Key, 0, Acc) -> - {Key, lists:append(lists:reverse(Acc))}; -get_records(_Iter, '$end_of_table', _I, Acc) -> - {'$end_of_table', lists:append(lists:reverse(Acc))}; -get_records(Iter, Key, I, Acc) -> - Recs = get_val(Iter, Key), - Next = retainer_next(Iter#iter.oid_tab, Key), - get_records(Iter, Next, I-1, [Recs | Acc]). - -get_val(Iter, Key) when Iter#iter.val == latest -> - get_latest_val(Iter, Key); -get_val(Iter, Key) when Iter#iter.val == checkpoint -> - get_checkpoint_val(Iter, Key). - -get_latest_val(Iter, Key) when Iter#iter.source == table -> - retainer_get(Iter#iter.main_tab, Key); -get_latest_val(Iter, Key) when Iter#iter.source == retainer -> - DeleteOid = {Iter#iter.tab_name, Key}, - [DeleteOid | retainer_get(Iter#iter.main_tab, Key)]. - -get_checkpoint_val(Iter, Key) when Iter#iter.source == table -> - retainer_get(Iter#iter.main_tab, Key); -get_checkpoint_val(Iter, Key) when Iter#iter.source == retainer -> - DeleteOid = {Iter#iter.tab_name, Key}, - case retainer_get(Iter#iter.retainer_tab, Key) of - [{_, _, []}] -> [DeleteOid]; - [{_, _, Records}] -> [DeleteOid | Records] - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% System upgrade - -system_continue(_Parent, _Debug, Cp) -> - retainer_loop(Cp). - -system_terminate(_Reason, _Parent,_Debug, Cp) -> - do_stop(Cp). - -system_code_change(Cp, _Module, _OldVsn, _Extra) -> - {ok, Cp}. - -convert_cp_record(Cp) when record(Cp, checkpoint) -> - ROD = - case Cp#checkpoint.ram_overrides_dump of - true -> Cp#checkpoint.min ++ Cp#checkpoint.max; - false -> [] - end, - - {ok, #checkpoint_args{name = Cp#checkpoint.name, - allow_remote = Cp#checkpoint.name, - ram_overrides_dump = ROD, - nodes = Cp#checkpoint.nodes, - node = Cp#checkpoint.node, - now = Cp#checkpoint.now, - cookie = ?unique_cookie, - min = Cp#checkpoint.min, - max = Cp#checkpoint.max, - pending_tab = Cp#checkpoint.pending_tab, - wait_for_old = Cp#checkpoint.wait_for_old, - is_activated = Cp#checkpoint.is_activated, - ignore_new = Cp#checkpoint.ignore_new, - retainers = Cp#checkpoint.retainers, - iterators = Cp#checkpoint.iterators, - supervisor = Cp#checkpoint.supervisor, - pid = Cp#checkpoint.pid - }}; -convert_cp_record(Cp) when record(Cp, checkpoint_args) -> - AllTabs = Cp#checkpoint_args.min ++ Cp#checkpoint_args.max, - ROD = case Cp#checkpoint_args.ram_overrides_dump of - [] -> - false; - AllTabs -> - true; - _ -> - error - end, - if - ROD == error -> - {error, {"Old node cannot handle new checkpoint protocol", - ram_overrides_dump}}; - true -> - {ok, #checkpoint{name = Cp#checkpoint_args.name, - allow_remote = Cp#checkpoint_args.name, - ram_overrides_dump = ROD, - nodes = Cp#checkpoint_args.nodes, - node = Cp#checkpoint_args.node, - now = Cp#checkpoint_args.now, - min = Cp#checkpoint_args.min, - max = Cp#checkpoint_args.max, - pending_tab = Cp#checkpoint_args.pending_tab, - wait_for_old = Cp#checkpoint_args.wait_for_old, - is_activated = Cp#checkpoint_args.is_activated, - ignore_new = Cp#checkpoint_args.ignore_new, - retainers = Cp#checkpoint_args.retainers, - iterators = Cp#checkpoint_args.iterators, - supervisor = Cp#checkpoint_args.supervisor, - pid = Cp#checkpoint_args.pid - }} - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%% - -val(Var) -> - case ?catch_val(Var) of - {'EXIT', _ReASoN_} -> mnesia_lib:other_val(Var, _ReASoN_); - _VaLuE_ -> _VaLuE_ - end. - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_checkpoint_sup.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_checkpoint_sup.erl deleted file mode 100644 index 29e31f15a6..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_checkpoint_sup.erl +++ /dev/null @@ -1,39 +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_checkpoint_sup.erl,v 1.1 2008/12/17 09:53:38 mikpe Exp $ -%% --module(mnesia_checkpoint_sup). - --behaviour(supervisor). - --export([start/0, init/1]). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% top supervisor callback functions - -start() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% sub supervisor callback functions - -init([]) -> - Flags = {simple_one_for_one, 0, timer:hours(24)}, % Trust the top supervisor - MFA = {mnesia_checkpoint, start, []}, - Modules = [?MODULE, mnesia_checkpoint, supervisor], - KillAfter = mnesia_kernel_sup:supervisor_timeout(timer:seconds(3)), - Workers = [{?MODULE, MFA, transient, KillAfter, worker, Modules}], - {ok, {Flags, Workers}}. diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_controller.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_controller.erl deleted file mode 100644 index b6f865f0d4..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_controller.erl +++ /dev/null @@ -1,2012 +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_controller.erl,v 1.3 2010/03/04 13:54:19 maria Exp $ -%% -%% The mnesia_init process loads tables from local disc or from -%% another nodes. It also coordinates updates of the info about -%% where we can read and write tables. -%% -%% Tables may need to be loaded initially at startup of the local -%% node or when other nodes announces that they already have loaded -%% tables that we also want. -%% -%% Initially we set the load request queue to those tables that we -%% safely can load locally, i.e. tables where we have the last -%% consistent replica and we have received mnesia_down from all -%% other nodes holding the table. Then we let the mnesia_init -%% process enter its normal working state. -%% -%% When we need to load a table we append a request to the load -%% request queue. All other requests are regarded as high priority -%% and are processed immediately (e.g. update table whereabouts). -%% We processes the load request queue as a "background" job.. - --module(mnesia_controller). - --behaviour(gen_server). - -%% Mnesia internal stuff --export([ - start/0, - i_have_tab/1, - info/0, - get_info/1, - get_workers/1, - force_load_table/1, - async_dump_log/1, - sync_dump_log/1, - connect_nodes/1, - wait_for_schema_commit_lock/0, - release_schema_commit_lock/0, - create_table/1, - get_disc_copy/1, - get_cstructs/0, - sync_and_block_table_whereabouts/4, - sync_del_table_copy_whereabouts/2, - block_table/1, - unblock_table/1, - block_controller/0, - unblock_controller/0, - unannounce_add_table_copy/2, - master_nodes_updated/2, - mnesia_down/1, - add_active_replica/2, - add_active_replica/3, - add_active_replica/4, - change_table_access_mode/1, - del_active_replica/2, - wait_for_tables/2, - get_network_copy/2, - merge_schema/0, - start_remote_sender/4, - schedule_late_disc_load/2 - ]). - -%% gen_server callbacks --export([init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - terminate/2, - code_change/3]). - -%% Module internal stuff --export([call/1, - cast/1, - dump_and_reply/2, - load_and_reply/2, - send_and_reply/2, - wait_for_tables_init/2 - ]). - --import(mnesia_lib, [set/2, add/2]). --import(mnesia_lib, [fatal/2, error/2, verbose/2, dbg_out/2]). - --include("mnesia.hrl"). - --define(SERVER_NAME, ?MODULE). - --record(state, {supervisor, - schema_is_merged = false, - early_msgs = [], - loader_pid, - loader_queue = [], - sender_pid, - sender_queue = [], - late_loader_queue = [], - dumper_pid, % Dumper or schema commit pid - dumper_queue = [], % Dumper or schema commit queue - dump_log_timer_ref, - is_stopping = false - }). - --record(worker_reply, {what, - pid, - result - }). - --record(schema_commit_lock, {owner}). --record(block_controller, {owner}). - --record(dump_log, {initiated_by, - opt_reply_to - }). - --record(net_load, {table, - reason, - opt_reply_to, - cstruct = unknown - }). - --record(send_table, {table, - receiver_pid, - remote_storage - }). - --record(disc_load, {table, - reason, - opt_reply_to - }). - --record(late_load, {table, - reason, - opt_reply_to, - loaders - }). - --record(loader_done, {worker_pid, - is_loaded, - table_name, - needs_announce, - needs_sync, - needs_reply, - reply_to, - reply}). - --record(sender_done, {worker_pid, - worker_res, - table_name - }). - --record(dumper_done, {worker_pid, - worker_res - }). - -val(Var) -> - case ?catch_val(Var) of - {'EXIT', Reason} -> mnesia_lib:other_val(Var, Reason); - Value -> Value - end. - -start() -> - gen_server:start_link({local, ?SERVER_NAME}, ?MODULE, [self()], - [{timeout, infinity} - %% ,{debug, [trace]} - ]). - -sync_dump_log(InitBy) -> - call({sync_dump_log, InitBy}). - -async_dump_log(InitBy) -> - ?SERVER_NAME ! {async_dump_log, InitBy}. - -%% Wait for tables to be active -%% If needed, we will wait for Mnesia to start -%% If Mnesia stops, we will wait for Mnesia to restart -%% We will wait even if the list of tables is empty -%% -wait_for_tables(Tabs, Timeout) when list(Tabs), Timeout == infinity -> - do_wait_for_tables(Tabs, Timeout); -wait_for_tables(Tabs, Timeout) when list(Tabs), - integer(Timeout), Timeout >= 0 -> - do_wait_for_tables(Tabs, Timeout); -wait_for_tables(Tabs, Timeout) -> - {error, {badarg, Tabs, Timeout}}. - -do_wait_for_tables(Tabs, 0) -> - reply_wait(Tabs); -do_wait_for_tables(Tabs, Timeout) -> - Pid = spawn_link(?MODULE, wait_for_tables_init, [self(), Tabs]), - receive - {?SERVER_NAME, Pid, Res} -> - Res; - - {'EXIT', Pid, _} -> - reply_wait(Tabs) - - after Timeout -> - unlink(Pid), - exit(Pid, timeout), - reply_wait(Tabs) - end. - -reply_wait(Tabs) -> - case catch mnesia_lib:active_tables() of - {'EXIT', _} -> - {error, {node_not_running, node()}}; - Active when list(Active) -> - case Tabs -- Active of - [] -> - ok; - BadTabs -> - {timeout, BadTabs} - end - end. - -wait_for_tables_init(From, Tabs) -> - process_flag(trap_exit, true), - Res = wait_for_init(From, Tabs, whereis(?SERVER_NAME)), - From ! {?SERVER_NAME, self(), Res}, - unlink(From), - exit(normal). - -wait_for_init(From, Tabs, Init) -> - case catch link(Init) of - {'EXIT', _} -> - %% Mnesia is not started - {error, {node_not_running, node()}}; - true when pid(Init) -> - cast({sync_tabs, Tabs, self()}), - rec_tabs(Tabs, Tabs, From, Init) - end. - -sync_reply(Waiter, Tab) -> - Waiter ! {?SERVER_NAME, {tab_synced, Tab}}. - -rec_tabs([Tab | Tabs], AllTabs, From, Init) -> - receive - {?SERVER_NAME, {tab_synced, Tab}} -> - rec_tabs(Tabs, AllTabs, From, Init); - - {'EXIT', From, _} -> - %% This will trigger an exit signal - %% to mnesia_init - exit(wait_for_tables_timeout); - - {'EXIT', Init, _} -> - %% Oops, mnesia_init stopped, - exit(mnesia_stopped) - end; -rec_tabs([], _, _, Init) -> - unlink(Init), - ok. - -get_cstructs() -> - call(get_cstructs). - -mnesia_down(Node) -> - case cast({mnesia_down, Node}) of - {error, _} -> mnesia_monitor:mnesia_down(?SERVER_NAME, Node); - _Pid -> ok - end. -wait_for_schema_commit_lock() -> - link(whereis(?SERVER_NAME)), - unsafe_call(wait_for_schema_commit_lock). - -block_controller() -> - call(block_controller). - -unblock_controller() -> - cast(unblock_controller). - -release_schema_commit_lock() -> - cast({release_schema_commit_lock, self()}), - unlink(whereis(?SERVER_NAME)). - -%% Special for preparation of add table copy -get_network_copy(Tab, Cs) -> - Work = #net_load{table = Tab, - reason = {dumper, add_table_copy}, - cstruct = Cs - }, - Res = (catch load_table(Work)), - if Res#loader_done.is_loaded == true -> - Tab = Res#loader_done.table_name, - case Res#loader_done.needs_announce of - true -> - i_have_tab(Tab); - false -> - ignore - end; - true -> ignore - end, - - receive %% Flush copier done message - {copier_done, _Node} -> - ok - after 500 -> %% avoid hanging if something is wrong and we shall fail. - ignore - end, - Res#loader_done.reply. - -%% This functions is invoked from the dumper -%% -%% There are two cases here: -%% startup -> -%% no need for sync, since mnesia_controller not started yet -%% schema_trans -> -%% already synced with mnesia_controller since the dumper -%% is syncronously started from mnesia_controller - -create_table(Tab) -> - {loaded, ok} = mnesia_loader:disc_load_table(Tab, {dumper,create_table}). - -get_disc_copy(Tab) -> - disc_load_table(Tab, {dumper,change_table_copy_type}, undefined). - -%% Returns ok instead of yes -force_load_table(Tab) when atom(Tab), Tab /= schema -> - case ?catch_val({Tab, storage_type}) of - ram_copies -> - do_force_load_table(Tab); - disc_copies -> - do_force_load_table(Tab); - disc_only_copies -> - do_force_load_table(Tab); - unknown -> - set({Tab, load_by_force}, true), - cast({force_load_updated, Tab}), - wait_for_tables([Tab], infinity); - {'EXIT', _} -> - {error, {no_exists, Tab}} - end; -force_load_table(Tab) -> - {error, {bad_type, Tab}}. - -do_force_load_table(Tab) -> - Loaded = ?catch_val({Tab, load_reason}), - case Loaded of - unknown -> - set({Tab, load_by_force}, true), - mnesia_late_loader:async_late_disc_load(node(), [Tab], forced_by_user), - wait_for_tables([Tab], infinity); - {'EXIT', _} -> - set({Tab, load_by_force}, true), - mnesia_late_loader:async_late_disc_load(node(), [Tab], forced_by_user), - wait_for_tables([Tab], infinity); - _ -> - ok - end. -master_nodes_updated(schema, _Masters) -> - ignore; -master_nodes_updated(Tab, Masters) -> - cast({master_nodes_updated, Tab, Masters}). - -schedule_late_disc_load(Tabs, Reason) -> - MsgTag = late_disc_load, - try_schedule_late_disc_load(Tabs, Reason, MsgTag). - -try_schedule_late_disc_load(Tabs, _Reason, MsgTag) - when Tabs == [], MsgTag /= schema_is_merged -> - ignore; -try_schedule_late_disc_load(Tabs, Reason, MsgTag) -> - GetIntents = - fun() -> - Item = mnesia_late_disc_load, - Nodes = val({current, db_nodes}), - mnesia:lock({global, Item, Nodes}, write), - case multicall(Nodes -- [node()], disc_load_intents) of - {Replies, []} -> - call({MsgTag, Tabs, Reason, Replies}), - done; - {_, BadNodes} -> - %% Some nodes did not respond, lets try again - {retry, BadNodes} - end - end, - case mnesia:transaction(GetIntents) of - {'atomic', done} -> - done; - {'atomic', {retry, BadNodes}} -> - verbose("Retry late_load_tables because bad nodes: ~p~n", - [BadNodes]), - try_schedule_late_disc_load(Tabs, Reason, MsgTag); - {aborted, AbortReason} -> - fatal("Cannot late_load_tables~p: ~p~n", - [[Tabs, Reason, MsgTag], AbortReason]) - end. - -connect_nodes(Ns) -> - case mnesia:system_info(is_running) of - no -> - {error, {node_not_running, node()}}; - yes -> - {NewC, OldC} = mnesia_recover:connect_nodes(Ns), - Connected = NewC ++OldC, - New1 = mnesia_lib:intersect(Ns, Connected), - New = New1 -- val({current, db_nodes}), - - case try_merge_schema(New) of - ok -> - mnesia_lib:add_list(extra_db_nodes, New), - {ok, New}; - {aborted, {throw, Str}} when list(Str) -> - %%mnesia_recover:disconnect_nodes(New), - {error, {merge_schema_failed, lists:flatten(Str)}}; - Else -> - %% Unconnect nodes where merge failed!! - %% mnesia_recover:disconnect_nodes(New), - {error, Else} - end - end. - -%% Merge the local schema with the schema on other nodes. -%% But first we must let all processes that want to force -%% load tables wait until the schema merge is done. - -merge_schema() -> - AllNodes = mnesia_lib:all_nodes(), - case try_merge_schema(AllNodes) of - ok -> - schema_is_merged(); - {aborted, {throw, Str}} when list(Str) -> - fatal("Failed to merge schema: ~s~n", [Str]); - Else -> - fatal("Failed to merge schema: ~p~n", [Else]) - end. - -try_merge_schema(Nodes) -> - case mnesia_schema:merge_schema() of - {'atomic', not_merged} -> - %% No more nodes that we need to merge the schema with - ok; - {'atomic', {merged, OldFriends, NewFriends}} -> - %% Check if new nodes has been added to the schema - Diff = mnesia_lib:all_nodes() -- [node() | Nodes], - mnesia_recover:connect_nodes(Diff), - - %% Tell everybody to adopt orphan tables - im_running(OldFriends, NewFriends), - im_running(NewFriends, OldFriends), - - try_merge_schema(Nodes); - {'atomic', {"Cannot get cstructs", Node, Reason}} -> - dbg_out("Cannot get cstructs, Node ~p ~p~n", [Node, Reason]), - timer:sleep(1000), % Avoid a endless loop look alike - try_merge_schema(Nodes); - Other -> - Other - end. - -im_running(OldFriends, NewFriends) -> - abcast(OldFriends, {im_running, node(), NewFriends}). - -schema_is_merged() -> - MsgTag = schema_is_merged, - SafeLoads = initial_safe_loads(), - - %% At this point we do not know anything about - %% which tables that the other nodes already - %% has loaded and therefore we let the normal - %% processing of the loader_queue take care - %% of it, since we at that time point will - %% know the whereabouts. We rely on the fact - %% that all nodes tells each other directly - %% when they have loaded a table and are - %% willing to share it. - - try_schedule_late_disc_load(SafeLoads, initial, MsgTag). - - -cast(Msg) -> - case whereis(?SERVER_NAME) of - undefined ->{error, {node_not_running, node()}}; - Pid -> gen_server:cast(Pid, Msg) - end. - -abcast(Nodes, Msg) -> - gen_server:abcast(Nodes, ?SERVER_NAME, Msg). - -unsafe_call(Msg) -> - case whereis(?SERVER_NAME) of - undefined -> {error, {node_not_running, node()}}; - Pid -> gen_server:call(Pid, Msg, infinity) - end. - -call(Msg) -> - case whereis(?SERVER_NAME) of - undefined -> - {error, {node_not_running, node()}}; - Pid -> - link(Pid), - Res = gen_server:call(Pid, Msg, infinity), - unlink(Pid), - - %% We get an exit signal if server dies - receive - {'EXIT', Pid, _Reason} -> - {error, {node_not_running, node()}} - after 0 -> - ignore - end, - Res - end. - -remote_call(Node, Func, Args) -> - case catch gen_server:call({?MODULE, Node}, {Func, Args, self()}, infinity) of - {'EXIT', Error} -> - {error, Error}; - Else -> - Else - end. - -multicall(Nodes, Msg) -> - {Good, Bad} = gen_server:multi_call(Nodes, ?MODULE, Msg, infinity), - PatchedGood = [Reply || {_Node, Reply} <- Good], - {PatchedGood, Bad}. %% Make the replies look like rpc:multicalls.. -%% rpc:multicall(Nodes, ?MODULE, call, [Msg]). - -%%%---------------------------------------------------------------------- -%%% Callback functions from gen_server -%%%---------------------------------------------------------------------- - -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, State} | -%% {ok, State, Timeout} | -%% {stop, Reason} -%%---------------------------------------------------------------------- -init([Parent]) -> - process_flag(trap_exit, true), - mnesia_lib:verbose("~p starting: ~p~n", [?SERVER_NAME, self()]), - - %% Handshake and initialize transaction recovery - %% for new nodes detected in the schema - All = mnesia_lib:all_nodes(), - Diff = All -- [node() | val(original_nodes)], - mnesia_lib:unset(original_nodes), - mnesia_recover:connect_nodes(Diff), - - Interval = mnesia_monitor:get_env(dump_log_time_threshold), - Msg = {async_dump_log, time_threshold}, - {ok, Ref} = timer:send_interval(Interval, Msg), - mnesia_dumper:start_regulator(), - - {ok, #state{supervisor = Parent, dump_log_timer_ref = Ref}}. - -%%---------------------------------------------------------------------- -%% Func: handle_call/3 -%% Returns: {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | (terminate/2 is called) -%% {stop, Reason, Reply, State} (terminate/2 is called) -%%---------------------------------------------------------------------- - -handle_call({sync_dump_log, InitBy}, From, State) -> - Worker = #dump_log{initiated_by = InitBy, - opt_reply_to = From - }, - State2 = add_worker(Worker, State), - noreply(State2); - -handle_call(wait_for_schema_commit_lock, From, State) -> - Worker = #schema_commit_lock{owner = From}, - State2 = add_worker(Worker, State), - noreply(State2); - -handle_call(block_controller, From, State) -> - Worker = #block_controller{owner = From}, - State2 = add_worker(Worker, State), - noreply(State2); - - -handle_call(get_cstructs, From, State) -> - Tabs = val({schema, tables}), - Cstructs = [val({T, cstruct}) || T <- Tabs], - Running = val({current, db_nodes}), - reply(From, {cstructs, Cstructs, Running}), - noreply(State); - -handle_call({schema_is_merged, TabsR, Reason, RemoteLoaders}, From, State) -> - State2 = late_disc_load(TabsR, Reason, RemoteLoaders, From, State), - - %% Handle early messages - Msgs = State2#state.early_msgs, - State3 = State2#state{early_msgs = [], schema_is_merged = true}, - Ns = val({current, db_nodes}), - dbg_out("Schema is merged ~w, State ~w~n", [Ns, State3]), -%% dbg_out("handle_early_msgs ~p ~n", [Msgs]), % qqqq - handle_early_msgs(lists:reverse(Msgs), State3); - -handle_call(disc_load_intents, From, State) -> - Tabs = disc_load_intents(State#state.loader_queue) ++ - disc_load_intents(State#state.late_loader_queue), - ActiveTabs = mnesia_lib:local_active_tables(), - reply(From, {ok, node(), mnesia_lib:union(Tabs, ActiveTabs)}), - noreply(State); - -handle_call({update_where_to_write, [add, Tab, AddNode], _From}, _Dummy, State) -> -%%% dbg_out("update_w2w ~p", [[add, Tab, AddNode]]), %%% qqqq - Current = val({current, db_nodes}), - Res = - case lists:member(AddNode, Current) and - State#state.schema_is_merged == true of - true -> - mnesia_lib:add({Tab, where_to_write}, AddNode); - false -> - ignore - end, - {reply, Res, State}; - -handle_call({add_active_replica, [Tab, ToNode, RemoteS, AccessMode], From}, - ReplyTo, State) -> - KnownNode = lists:member(ToNode, val({current, db_nodes})), - Merged = State#state.schema_is_merged, - if - KnownNode == false -> - reply(ReplyTo, ignore), - noreply(State); - Merged == true -> - Res = add_active_replica(Tab, ToNode, RemoteS, AccessMode), - reply(ReplyTo, Res), - noreply(State); - true -> %% Schema is not merged - Msg = {add_active_replica, [Tab, ToNode, RemoteS, AccessMode], From}, - Msgs = State#state.early_msgs, - reply(ReplyTo, ignore), %% Reply ignore and add data after schema merge - noreply(State#state{early_msgs = [{call, Msg, undefined} | Msgs]}) - end; - -handle_call({unannounce_add_table_copy, [Tab, Node], From}, ReplyTo, State) -> - KnownNode = lists:member(node(From), val({current, db_nodes})), - Merged = State#state.schema_is_merged, - if - KnownNode == false -> - reply(ReplyTo, ignore), - noreply(State); - Merged == true -> - Res = unannounce_add_table_copy(Tab, Node), - reply(ReplyTo, Res), - noreply(State); - true -> %% Schema is not merged - Msg = {unannounce_add_table_copy, [Tab, Node], From}, - Msgs = State#state.early_msgs, - reply(ReplyTo, ignore), %% Reply ignore and add data after schema merge - %% Set ReplyTO to undefined so we don't reply twice - noreply(State#state{early_msgs = [{call, Msg, undefined} | Msgs]}) - end; - -handle_call(Msg, From, State) when State#state.schema_is_merged == false -> - %% Buffer early messages -%% dbg_out("Buffered early msg ~p ~n", [Msg]), %% qqqq - Msgs = State#state.early_msgs, - noreply(State#state{early_msgs = [{call, Msg, From} | Msgs]}); - -handle_call({net_load, Tab, Cs}, From, State) -> - Worker = #net_load{table = Tab, - opt_reply_to = From, - reason = add_table_copy, - cstruct = Cs - }, - State2 = add_worker(Worker, State), - noreply(State2); - -handle_call({late_disc_load, Tabs, Reason, RemoteLoaders}, From, State) -> - State2 = late_disc_load(Tabs, Reason, RemoteLoaders, From, State), - noreply(State2); - -handle_call({block_table, [Tab], From}, _Dummy, State) -> - case lists:member(node(From), val({current, db_nodes})) of - true -> - block_table(Tab); - false -> - ignore - end, - {reply, ok, State}; - -handle_call({check_w2r, _Node, Tab}, _From, State) -> - {reply, val({Tab, where_to_read}), State}; - -handle_call(Msg, _From, State) -> - error("~p got unexpected call: ~p~n", [?SERVER_NAME, Msg]), - noreply(State). - -disc_load_intents([H | T]) when record(H, disc_load) -> - [H#disc_load.table | disc_load_intents(T)]; -disc_load_intents([H | T]) when record(H, late_load) -> - [H#late_load.table | disc_load_intents(T)]; -disc_load_intents( [H | T]) when record(H, net_load) -> - disc_load_intents(T); -disc_load_intents([]) -> - []. - -late_disc_load(TabsR, Reason, RemoteLoaders, From, State) -> - verbose("Intend to load tables: ~p~n", [TabsR]), - ?eval_debug_fun({?MODULE, late_disc_load}, - [{tabs, TabsR}, - {reason, Reason}, - {loaders, RemoteLoaders}]), - - reply(From, queued), - %% RemoteLoaders is a list of {ok, Node, Tabs} tuples - - %% Remove deleted tabs - LocalTabs = mnesia_lib:val({schema, local_tables}), - Filter = fun({Tab, Reas}, Acc) -> - case lists:member(Tab, LocalTabs) of - true -> [{Tab, Reas} | Acc]; - false -> Acc - end; - (Tab, Acc) -> - case lists:member(Tab, LocalTabs) of - true -> [Tab | Acc]; - false -> Acc - end - end, - - Tabs = lists:foldl(Filter, [], TabsR), - - Nodes = val({current, db_nodes}), - LateLoaders = late_loaders(Tabs, Reason, RemoteLoaders, Nodes), - LateQueue = State#state.late_loader_queue ++ LateLoaders, - State#state{late_loader_queue = LateQueue}. - -late_loaders([{Tab, Reason} | Tabs], DefaultReason, RemoteLoaders, Nodes) -> - LoadNodes = late_load_filter(RemoteLoaders, Tab, Nodes, []), - case LoadNodes of - [] -> - cast({disc_load, Tab, Reason}); % Ugly cast - _ -> - ignore - end, - LateLoad = #late_load{table = Tab, loaders = LoadNodes, reason = Reason}, - [LateLoad | late_loaders(Tabs, DefaultReason, RemoteLoaders, Nodes)]; - -late_loaders([Tab | Tabs], Reason, RemoteLoaders, Nodes) -> - Loaders = late_load_filter(RemoteLoaders, Tab, Nodes, []), - case Loaders of - [] -> - cast({disc_load, Tab, Reason}); % Ugly cast - _ -> - ignore - end, - LateLoad = #late_load{table = Tab, loaders = Loaders, reason = Reason}, - [LateLoad | late_loaders(Tabs, Reason, RemoteLoaders, Nodes)]; -late_loaders([], _Reason, _RemoteLoaders, _Nodes) -> - []. - -late_load_filter([{error, _} | RemoteLoaders], Tab, Nodes, Acc) -> - late_load_filter(RemoteLoaders, Tab, Nodes, Acc); -late_load_filter([{badrpc, _} | RemoteLoaders], Tab, Nodes, Acc) -> - late_load_filter(RemoteLoaders, Tab, Nodes, Acc); -late_load_filter([RL | RemoteLoaders], Tab, Nodes, Acc) -> - {ok, Node, Intents} = RL, - Access = val({Tab, access_mode}), - LocalC = val({Tab, local_content}), - StillActive = lists:member(Node, Nodes), - RemoteIntent = lists:member(Tab, Intents), - if - Access == read_write, - LocalC == false, - StillActive == true, - RemoteIntent == true -> - Masters = mnesia_recover:get_master_nodes(Tab), - case lists:member(Node, Masters) of - true -> - %% The other node is master node for - %% the table, accept his load intent - late_load_filter(RemoteLoaders, Tab, Nodes, [Node | Acc]); - false when Masters == [] -> - %% The table has no master nodes - %% accept his load intent - late_load_filter(RemoteLoaders, Tab, Nodes, [Node | Acc]); - false -> - %% Some one else is master node for - %% the table, ignore his load intent - late_load_filter(RemoteLoaders, Tab, Nodes, Acc) - end; - true -> - late_load_filter(RemoteLoaders, Tab, Nodes, Acc) - end; -late_load_filter([], _Tab, _Nodes, Acc) -> - Acc. - -%%---------------------------------------------------------------------- -%% Func: handle_cast/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- - -handle_cast({release_schema_commit_lock, _Owner}, State) -> - if - State#state.is_stopping == true -> - {stop, shutdown, State}; - true -> - case State#state.dumper_queue of - [#schema_commit_lock{}|Rest] -> - [_Worker | Rest] = State#state.dumper_queue, - State2 = State#state{dumper_pid = undefined, - dumper_queue = Rest}, - State3 = opt_start_worker(State2), - noreply(State3); - _ -> - noreply(State) - end - end; - -handle_cast(unblock_controller, State) -> - if - State#state.is_stopping == true -> - {stop, shutdown, State}; - record(hd(State#state.dumper_queue), block_controller) -> - [_Worker | Rest] = State#state.dumper_queue, - State2 = State#state{dumper_pid = undefined, - dumper_queue = Rest}, - State3 = opt_start_worker(State2), - noreply(State3) - end; - -handle_cast({mnesia_down, Node}, State) -> - maybe_log_mnesia_down(Node), - mnesia_lib:del({current, db_nodes}, Node), - mnesia_checkpoint:tm_mnesia_down(Node), - Alltabs = val({schema, tables}), - State2 = reconfigure_tables(Node, State, Alltabs), - case State#state.sender_pid of - undefined -> ignore; - Pid when pid(Pid) -> Pid ! {copier_done, Node} - end, - case State#state.loader_pid of - undefined -> ignore; - Pid2 when pid(Pid2) -> Pid2 ! {copier_done, Node} - end, - NewSenders = - case State#state.sender_queue of - [OldSender | RestSenders] -> - Remove = fun(ST) -> - node(ST#send_table.receiver_pid) /= Node - end, - NewS = lists:filter(Remove, RestSenders), - %% Keep old sender it will be removed by sender_done - [OldSender | NewS]; - [] -> - [] - end, - Early = remove_early_messages(State2#state.early_msgs, Node), - mnesia_monitor:mnesia_down(?SERVER_NAME, Node), - noreply(State2#state{sender_queue = NewSenders, early_msgs = Early}); - -handle_cast({im_running, _Node, NewFriends}, State) -> - Tabs = mnesia_lib:local_active_tables() -- [schema], - Ns = mnesia_lib:intersect(NewFriends, val({current, db_nodes})), - abcast(Ns, {adopt_orphans, node(), Tabs}), - noreply(State); - -handle_cast(Msg, State) when State#state.schema_is_merged == false -> - %% Buffer early messages - Msgs = State#state.early_msgs, - noreply(State#state{early_msgs = [{cast, Msg} | Msgs]}); - -handle_cast({disc_load, Tab, Reason}, State) -> - Worker = #disc_load{table = Tab, reason = Reason}, - State2 = add_worker(Worker, State), - noreply(State2); - -handle_cast(Worker, State) when record(Worker, send_table) -> - State2 = add_worker(Worker, State), - noreply(State2); - -handle_cast({sync_tabs, Tabs, From}, State) -> - %% user initiated wait_for_tables - handle_sync_tabs(Tabs, From), - noreply(State); - -handle_cast({i_have_tab, Tab, Node}, State) -> - case lists:member(Node, val({current, db_nodes})) of - true -> - State2 = node_has_tabs([Tab], Node, State), - noreply(State2); - false -> - noreply(State) - end; - -handle_cast({force_load_updated, Tab}, State) -> - case val({Tab, active_replicas}) of - [] -> - %% No valid replicas - noreply(State); - [SomeNode | _] -> - State2 = node_has_tabs([Tab], SomeNode, State), - noreply(State2) - end; - -handle_cast({master_nodes_updated, Tab, Masters}, State) -> - Active = val({Tab, active_replicas}), - Valid = - case val({Tab, load_by_force}) of - true -> - Active; - false -> - if - Masters == [] -> - Active; - true -> - mnesia_lib:intersect(Masters, Active) - end - end, - case Valid of - [] -> - %% No valid replicas - noreply(State); - [SomeNode | _] -> - State2 = node_has_tabs([Tab], SomeNode, State), - noreply(State2) - end; - -handle_cast({adopt_orphans, Node, Tabs}, State) -> - - State2 = node_has_tabs(Tabs, Node, State), - - %% Register the other node as up and running - mnesia_recover:log_mnesia_up(Node), - verbose("Logging mnesia_up ~w~n", [Node]), - mnesia_lib:report_system_event({mnesia_up, Node}), - - %% Load orphan tables - LocalTabs = val({schema, local_tables}) -- [schema], - Nodes = val({current, db_nodes}), - {LocalOrphans, RemoteMasters} = - orphan_tables(LocalTabs, Node, Nodes, [], []), - Reason = {adopt_orphan, node()}, - mnesia_late_loader:async_late_disc_load(node(), LocalOrphans, Reason), - - Fun = - fun(N) -> - RemoteOrphans = - [Tab || {Tab, Ns} <- RemoteMasters, - lists:member(N, Ns)], - mnesia_late_loader:maybe_async_late_disc_load(N, RemoteOrphans, Reason) - end, - lists:foreach(Fun, Nodes), - - Queue = State2#state.loader_queue, - State3 = State2#state{loader_queue = Queue}, - noreply(State3); - -handle_cast(Msg, State) -> - error("~p got unexpected cast: ~p~n", [?SERVER_NAME, Msg]), - noreply(State). - -handle_sync_tabs([Tab | Tabs], From) -> - case val({Tab, where_to_read}) of - nowhere -> - case get({sync_tab, Tab}) of - undefined -> - put({sync_tab, Tab}, [From]); - Pids -> - put({sync_tab, Tab}, [From | Pids]) - end; - _ -> - sync_reply(From, Tab) - end, - handle_sync_tabs(Tabs, From); -handle_sync_tabs([], _From) -> - ok. - -%%---------------------------------------------------------------------- -%% Func: handle_info/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- - -handle_info({async_dump_log, InitBy}, State) -> - Worker = #dump_log{initiated_by = InitBy}, - State2 = add_worker(Worker, State), - noreply(State2); - -handle_info(Done, State) when record(Done, dumper_done) -> - Pid = Done#dumper_done.worker_pid, - Res = Done#dumper_done.worker_res, - if - State#state.is_stopping == true -> - {stop, shutdown, State}; - Res == dumped, Pid == State#state.dumper_pid -> - [Worker | Rest] = State#state.dumper_queue, - reply(Worker#dump_log.opt_reply_to, Res), - State2 = State#state{dumper_pid = undefined, - dumper_queue = Rest}, - State3 = opt_start_worker(State2), - noreply(State3); - true -> - fatal("Dumper failed: ~p~n state: ~p~n", [Res, State]), - {stop, fatal, State} - end; - -handle_info(Done, State) when record(Done, loader_done) -> - if - %% Assertion - Done#loader_done.worker_pid == State#state.loader_pid -> ok - end, - - [_Worker | Rest] = LoadQ0 = State#state.loader_queue, - LateQueue0 = State#state.late_loader_queue, - {LoadQ, LateQueue} = - case Done#loader_done.is_loaded of - true -> - Tab = Done#loader_done.table_name, - - %% Optional user sync - case Done#loader_done.needs_sync of - true -> user_sync_tab(Tab); - false -> ignore - end, - - %% Optional table announcement - case Done#loader_done.needs_announce of - true -> - i_have_tab(Tab), - case Tab of - schema -> - ignore; - _ -> - %% Local node needs to perform user_sync_tab/1 - Ns = val({current, db_nodes}), - abcast(Ns, {i_have_tab, Tab, node()}) - end; - false -> - case Tab of - schema -> - ignore; - _ -> - %% Local node needs to perform user_sync_tab/1 - Ns = val({current, db_nodes}), - AlreadyKnows = val({Tab, active_replicas}), - abcast(Ns -- AlreadyKnows, {i_have_tab, Tab, node()}) - end - end, - - %% Optional client reply - case Done#loader_done.needs_reply of - true -> - reply(Done#loader_done.reply_to, - Done#loader_done.reply); - false -> - ignore - end, - {Rest, reply_late_load(Tab, LateQueue0)}; - false -> - case Done#loader_done.reply of - restart -> - {LoadQ0, LateQueue0}; - _ -> - {Rest, LateQueue0} - end - end, - - State2 = State#state{loader_pid = undefined, - loader_queue = LoadQ, - late_loader_queue = LateQueue}, - - State3 = opt_start_worker(State2), - noreply(State3); - -handle_info(Done, State) when record(Done, sender_done) -> - Pid = Done#sender_done.worker_pid, - Res = Done#sender_done.worker_res, - if - Res == ok, Pid == State#state.sender_pid -> - [Worker | Rest] = State#state.sender_queue, - Worker#send_table.receiver_pid ! {copier_done, node()}, - State2 = State#state{sender_pid = undefined, - sender_queue = Rest}, - State3 = opt_start_worker(State2), - noreply(State3); - true -> - %% No need to send any message to the table receiver - %% since it will soon get a mnesia_down anyway - fatal("Sender failed: ~p~n state: ~p~n", [Res, State]), - {stop, fatal, State} - end; - -handle_info({'EXIT', Pid, R}, State) when Pid == State#state.supervisor -> - catch set(mnesia_status, stopping), - case State#state.dumper_pid of - undefined -> - dbg_out("~p was ~p~n", [?SERVER_NAME, R]), - {stop, shutdown, State}; - _ -> - noreply(State#state{is_stopping = true}) - end; - -handle_info({'EXIT', Pid, R}, State) when Pid == State#state.dumper_pid -> - case State#state.dumper_queue of - [#schema_commit_lock{}|Workers] -> %% Schema trans crashed or was killed - State2 = State#state{dumper_queue = Workers, dumper_pid = undefined}, - State3 = opt_start_worker(State2), - noreply(State3); - _Other -> - fatal("Dumper or schema commit crashed: ~p~n state: ~p~n", [R, State]), - {stop, fatal, State} - end; - -handle_info({'EXIT', Pid, R}, State) when Pid == State#state.loader_pid -> - fatal("Loader crashed: ~p~n state: ~p~n", [R, State]), - {stop, fatal, State}; - -handle_info({'EXIT', Pid, R}, State) when Pid == State#state.sender_pid -> - %% No need to send any message to the table receiver - %% since it will soon get a mnesia_down anyway - fatal("Sender crashed: ~p~n state: ~p~n", [R, State]), - {stop, fatal, State}; - -handle_info({From, get_state}, State) -> - From ! {?SERVER_NAME, State}, - noreply(State); - -%% No real need for buffering -handle_info(Msg, State) when State#state.schema_is_merged == false -> - %% Buffer early messages - Msgs = State#state.early_msgs, - noreply(State#state{early_msgs = [{info, Msg} | Msgs]}); - -handle_info({'EXIT', Pid, wait_for_tables_timeout}, State) -> - sync_tab_timeout(Pid, get()), - noreply(State); - -handle_info(Msg, State) -> - error("~p got unexpected info: ~p~n", [?SERVER_NAME, Msg]), - noreply(State). - -reply_late_load(Tab, [H | T]) when H#late_load.table == Tab -> - reply(H#late_load.opt_reply_to, ok), - reply_late_load(Tab, T); -reply_late_load(Tab, [H | T]) -> - [H | reply_late_load(Tab, T)]; -reply_late_load(_Tab, []) -> - []. - -sync_tab_timeout(Pid, [{{sync_tab, Tab}, Pids} | Tail]) -> - case lists:delete(Pid, Pids) of - [] -> - erase({sync_tab, Tab}); - Pids2 -> - put({sync_tab, Tab}, Pids2) - end, - sync_tab_timeout(Pid, Tail); -sync_tab_timeout(Pid, [_ | Tail]) -> - sync_tab_timeout(Pid, Tail); -sync_tab_timeout(_Pid, []) -> - ok. - -%% Pick the load record that has the highest load order -%% Returns {BestLoad, RemainingQueue} or {none, []} if queue is empty -pick_next(Queue) -> - pick_next(Queue, none, none, []). - -pick_next([Head | Tail], Load, Order, Rest) when record(Head, net_load) -> - Tab = Head#net_load.table, - select_best(Head, Tail, val({Tab, load_order}), Load, Order, Rest); -pick_next([Head | Tail], Load, Order, Rest) when record(Head, disc_load) -> - Tab = Head#disc_load.table, - select_best(Head, Tail, val({Tab, load_order}), Load, Order, Rest); -pick_next([], Load, _Order, Rest) -> - {Load, Rest}. - -select_best(Load, Tail, Order, none, none, Rest) -> - pick_next(Tail, Load, Order, Rest); -select_best(Load, Tail, Order, OldLoad, OldOrder, Rest) when Order > OldOrder -> - pick_next(Tail, Load, Order, [OldLoad | Rest]); -select_best(Load, Tail, _Order, OldLoad, OldOrder, Rest) -> - pick_next(Tail, OldLoad, OldOrder, [Load | Rest]). - -%%---------------------------------------------------------------------- -%% Func: terminate/2 -%% Purpose: Shutdown the server -%% Returns: any (ignored by gen_server) -%%---------------------------------------------------------------------- -terminate(Reason, State) -> - mnesia_monitor:terminate_proc(?SERVER_NAME, Reason, State). - -%%---------------------------------------------------------------------- -%% Func: code_change/3 -%% Purpose: Upgrade process when its code is to be changed -%% Returns: {ok, NewState} -%%---------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%%---------------------------------------------------------------------- -%%% Internal functions -%%%---------------------------------------------------------------------- - -maybe_log_mnesia_down(N) -> - %% We use mnesia_down when deciding which tables to load locally, - %% so if we are not running (i.e haven't decided which tables - %% to load locally), don't log mnesia_down yet. - case mnesia_lib:is_running() of - yes -> - verbose("Logging mnesia_down ~w~n", [N]), - mnesia_recover:log_mnesia_down(N), - ok; - _ -> - Filter = fun(Tab) -> - inactive_copy_holders(Tab, N) - end, - HalfLoadedTabs = lists:any(Filter, val({schema, local_tables}) -- [schema]), - if - HalfLoadedTabs == true -> - verbose("Logging mnesia_down ~w~n", [N]), - mnesia_recover:log_mnesia_down(N), - ok; - true -> - %% Unfortunately we have not loaded some common - %% tables yet, so we cannot rely on the nodedown - log_later %% BUGBUG handle this case!!! - end - end. - -inactive_copy_holders(Tab, Node) -> - Cs = val({Tab, cstruct}), - case mnesia_lib:cs_to_storage_type(Node, Cs) of - unknown -> - false; - _Storage -> - mnesia_lib:not_active_here(Tab) - end. - -orphan_tables([Tab | Tabs], Node, Ns, Local, Remote) -> - Cs = val({Tab, cstruct}), - CopyHolders = mnesia_lib:copy_holders(Cs), - RamCopyHolders = Cs#cstruct.ram_copies, - DiscCopyHolders = CopyHolders -- RamCopyHolders, - DiscNodes = val({schema, disc_copies}), - LocalContent = Cs#cstruct.local_content, - RamCopyHoldersOnDiscNodes = mnesia_lib:intersect(RamCopyHolders, DiscNodes), - Active = val({Tab, active_replicas}), - case lists:member(Node, DiscCopyHolders) of - true when Active == [] -> - case DiscCopyHolders -- Ns of - [] -> - %% We're last up and the other nodes have not - %% loaded the table. Lets load it if we are - %% the smallest node. - case lists:min(DiscCopyHolders) of - Min when Min == node() -> - case mnesia_recover:get_master_nodes(Tab) of - [] -> - L = [Tab | Local], - orphan_tables(Tabs, Node, Ns, L, Remote); - Masters -> - R = [{Tab, Masters} | Remote], - orphan_tables(Tabs, Node, Ns, Local, R) - end; - _ -> - orphan_tables(Tabs, Node, Ns, Local, Remote) - end; - _ -> - orphan_tables(Tabs, Node, Ns, Local, Remote) - end; - false when Active == [], DiscCopyHolders == [], RamCopyHoldersOnDiscNodes == [] -> - %% Special case when all replicas resides on disc less nodes - orphan_tables(Tabs, Node, Ns, [Tab | Local], Remote); - _ when LocalContent == true -> - orphan_tables(Tabs, Node, Ns, [Tab | Local], Remote); - _ -> - orphan_tables(Tabs, Node, Ns, Local, Remote) - end; -orphan_tables([], _, _, LocalOrphans, RemoteMasters) -> - {LocalOrphans, RemoteMasters}. - -node_has_tabs([Tab | Tabs], Node, State) when Node /= node() -> - State2 = update_whereabouts(Tab, Node, State), - node_has_tabs(Tabs, Node, State2); -node_has_tabs([Tab | Tabs], Node, State) -> - user_sync_tab(Tab), - node_has_tabs(Tabs, Node, State); -node_has_tabs([], _Node, State) -> - State. - -update_whereabouts(Tab, Node, State) -> - Storage = val({Tab, storage_type}), - Read = val({Tab, where_to_read}), - LocalC = val({Tab, local_content}), - BeingCreated = (?catch_val({Tab, create_table}) == true), - Masters = mnesia_recover:get_master_nodes(Tab), - ByForce = val({Tab, load_by_force}), - GoGetIt = - if - ByForce == true -> - true; - Masters == [] -> - true; - true -> - lists:member(Node, Masters) - end, - - dbg_out("Table ~w is loaded on ~w. s=~w, r=~w, lc=~w, f=~w, m=~w~n", - [Tab, Node, Storage, Read, LocalC, ByForce, GoGetIt]), - if - LocalC == true -> - %% Local contents, don't care about other node - State; - Storage == unknown, Read == nowhere -> - %% No own copy, time to read remotely - %% if the other node is a good node - add_active_replica(Tab, Node), - case GoGetIt of - true -> - set({Tab, where_to_read}, Node), - user_sync_tab(Tab), - State; - false -> - State - end; - Storage == unknown -> - %% No own copy, continue to read remotely - add_active_replica(Tab, Node), - NodeST = mnesia_lib:storage_type_at_node(Node, Tab), - ReadST = mnesia_lib:storage_type_at_node(Read, Tab), - if %% Avoid reading from disc_only_copies - NodeST == disc_only_copies -> - ignore; - ReadST == disc_only_copies -> - mnesia_lib:set_remote_where_to_read(Tab); - true -> - ignore - end, - user_sync_tab(Tab), - State; - BeingCreated == true -> - %% The table is currently being created - %% and we shall have an own copy of it. - %% We will load the (empty) table locally. - add_active_replica(Tab, Node), - State; - Read == nowhere -> - %% Own copy, go and get a copy of the table - %% if the other node is master or if there - %% are no master at all - add_active_replica(Tab, Node), - case GoGetIt of - true -> - Worker = #net_load{table = Tab, - reason = {active_remote, Node}}, - add_worker(Worker, State); - false -> - State - end; - true -> - %% We already have an own copy - add_active_replica(Tab, Node), - user_sync_tab(Tab), - State - end. - -initial_safe_loads() -> - case val({schema, storage_type}) of - ram_copies -> - Downs = [], - Tabs = val({schema, local_tables}) -- [schema], - LastC = fun(T) -> last_consistent_replica(T, Downs) end, - lists:zf(LastC, Tabs); - - disc_copies -> - Downs = mnesia_recover:get_mnesia_downs(), - dbg_out("mnesia_downs = ~p~n", [Downs]), - - Tabs = val({schema, local_tables}) -- [schema], - LastC = fun(T) -> last_consistent_replica(T, Downs) end, - lists:zf(LastC, Tabs) - end. - -last_consistent_replica(Tab, Downs) -> - Cs = val({Tab, cstruct}), - Storage = mnesia_lib:cs_to_storage_type(node(), Cs), - Ram = Cs#cstruct.ram_copies, - Disc = Cs#cstruct.disc_copies, - DiscOnly = Cs#cstruct.disc_only_copies, - BetterCopies0 = mnesia_lib:remote_copy_holders(Cs) -- Downs, - BetterCopies = BetterCopies0 -- Ram, - AccessMode = Cs#cstruct.access_mode, - Copies = mnesia_lib:copy_holders(Cs), - Masters = mnesia_recover:get_master_nodes(Tab), - LocalMaster0 = lists:member(node(), Masters), - LocalContent = Cs#cstruct.local_content, - RemoteMaster = - if - Masters == [] -> false; - true -> not LocalMaster0 - end, - LocalMaster = - if - Masters == [] -> false; - true -> LocalMaster0 - end, - if - Copies == [node()] -> - %% Only one copy holder and it is local. - %% It may also be a local contents table - {true, {Tab, local_only}}; - LocalContent == true -> - {true, {Tab, local_content}}; - LocalMaster == true -> - %% We have a local master - {true, {Tab, local_master}}; - RemoteMaster == true -> - %% Wait for remote master copy - false; - Storage == ram_copies -> - if - Disc == [], DiscOnly == [] -> - %% Nobody has copy on disc - {true, {Tab, ram_only}}; - true -> - %% Some other node has copy on disc - false - end; - AccessMode == read_only -> - %% No one has been able to update the table, - %% i.e. all disc resident copies are equal - {true, {Tab, read_only}}; - BetterCopies /= [], Masters /= [node()] -> - %% There are better copies on other nodes - %% and we do not have the only master copy - false; - true -> - {true, {Tab, initial}} - end. - -reconfigure_tables(N, State, [Tab |Tail]) -> - del_active_replica(Tab, N), - case val({Tab, where_to_read}) of - N -> mnesia_lib:set_remote_where_to_read(Tab); - _ -> ignore - end, - LateQ = drop_loaders(Tab, N, State#state.late_loader_queue), - reconfigure_tables(N, State#state{late_loader_queue = LateQ}, Tail); - -reconfigure_tables(_, State, []) -> - State. - -remove_early_messages([], _Node) -> - []; -remove_early_messages([{call, {add_active_replica, [_, Node, _, _], _}, _}|R], Node) -> - remove_early_messages(R, Node); %% Does a reply before queuing -remove_early_messages([{call, {block_table, _, From}, ReplyTo}|R], Node) - when node(From) == Node -> - reply(ReplyTo, ok), %% Remove gen:server waits.. - remove_early_messages(R, Node); -remove_early_messages([{cast, {i_have_tab, _Tab, Node}}|R], Node) -> - remove_early_messages(R, Node); -remove_early_messages([{cast, {adopt_orphans, Node, _Tabs}}|R], Node) -> - remove_early_messages(R, Node); -remove_early_messages([M|R],Node) -> - [M|remove_early_messages(R,Node)]. - -%% Drop loader from late load queue and possibly trigger a disc_load -drop_loaders(Tab, Node, [H | T]) when H#late_load.table == Tab -> - %% Check if it is time to issue a disc_load request - case H#late_load.loaders of - [Node] -> - Reason = {H#late_load.reason, last_loader_down, Node}, - cast({disc_load, Tab, Reason}); % Ugly cast - _ -> - ignore - end, - %% Drop the node from the list of loaders - H2 = H#late_load{loaders = H#late_load.loaders -- [Node]}, - [H2 | drop_loaders(Tab, Node, T)]; -drop_loaders(Tab, Node, [H | T]) -> - [H | drop_loaders(Tab, Node, T)]; -drop_loaders(_, _, []) -> - []. - -add_active_replica(Tab, Node) -> - add_active_replica(Tab, Node, val({Tab, cstruct})). - -add_active_replica(Tab, Node, Cs) when record(Cs, cstruct) -> - Storage = mnesia_lib:schema_cs_to_storage_type(Node, Cs), - AccessMode = Cs#cstruct.access_mode, - add_active_replica(Tab, Node, Storage, AccessMode). - -%% Block table primitives - -block_table(Tab) -> - Var = {Tab, where_to_commit}, - Old = val(Var), - New = {blocked, Old}, - set(Var, New). % where_to_commit - -unblock_table(Tab) -> - Var = {Tab, where_to_commit}, - New = - case val(Var) of - {blocked, List} -> - List; - List -> - List - end, - set(Var, New). % where_to_commit - -is_tab_blocked(W2C) when list(W2C) -> - {false, W2C}; -is_tab_blocked({blocked, W2C}) when list(W2C) -> - {true, W2C}. - -mark_blocked_tab(true, Value) -> - {blocked, Value}; -mark_blocked_tab(false, Value) -> - Value. - -%% - -add_active_replica(Tab, Node, Storage, AccessMode) -> - Var = {Tab, where_to_commit}, - {Blocked, Old} = is_tab_blocked(val(Var)), - Del = lists:keydelete(Node, 1, Old), - case AccessMode of - read_write -> - New = lists:sort([{Node, Storage} | Del]), - set(Var, mark_blocked_tab(Blocked, New)), % where_to_commit - add({Tab, where_to_write}, Node); - read_only -> - set(Var, mark_blocked_tab(Blocked, Del)), - mnesia_lib:del({Tab, where_to_write}, Node) - end, - add({Tab, active_replicas}, Node). - -del_active_replica(Tab, Node) -> - Var = {Tab, where_to_commit}, - {Blocked, Old} = is_tab_blocked(val(Var)), - Del = lists:keydelete(Node, 1, Old), - New = lists:sort(Del), - set(Var, mark_blocked_tab(Blocked, New)), % where_to_commit - mnesia_lib:del({Tab, active_replicas}, Node), - mnesia_lib:del({Tab, where_to_write}, Node). - -change_table_access_mode(Cs) -> - Tab = Cs#cstruct.name, - lists:foreach(fun(N) -> add_active_replica(Tab, N, Cs) end, - val({Tab, active_replicas})). - -%% node To now has tab loaded, but this must be undone -%% This code is rpc:call'ed from the tab_copier process -%% when it has *not* released it's table lock -unannounce_add_table_copy(Tab, To) -> - del_active_replica(Tab, To), - case val({Tab , where_to_read}) of - To -> - mnesia_lib:set_remote_where_to_read(Tab); - _ -> - ignore - end. - -user_sync_tab(Tab) -> - case val(debug) of - trace -> - mnesia_subscr:subscribe(whereis(mnesia_event), {table, Tab}); - _ -> - ignore - end, - - case erase({sync_tab, Tab}) of - undefined -> - ok; - Pids -> - lists:foreach(fun(Pid) -> sync_reply(Pid, Tab) end, Pids) - end. - -i_have_tab(Tab) -> - case val({Tab, local_content}) of - true -> - mnesia_lib:set_local_content_whereabouts(Tab); - false -> - set({Tab, where_to_read}, node()) - end, - add_active_replica(Tab, node()). - -sync_and_block_table_whereabouts(Tab, ToNode, RemoteS, AccessMode) when Tab /= schema -> - Current = val({current, db_nodes}), - Ns = - case lists:member(ToNode, Current) of - true -> Current -- [ToNode]; - false -> Current - end, - remote_call(ToNode, block_table, [Tab]), - [remote_call(Node, add_active_replica, [Tab, ToNode, RemoteS, AccessMode]) || - Node <- [ToNode | Ns]], - ok. - -sync_del_table_copy_whereabouts(Tab, ToNode) when Tab /= schema -> - Current = val({current, db_nodes}), - Ns = - case lists:member(ToNode, Current) of - true -> Current; - false -> [ToNode | Current] - end, - Args = [Tab, ToNode], - [remote_call(Node, unannounce_add_table_copy, Args) || Node <- Ns], - ok. - -get_info(Timeout) -> - case whereis(?SERVER_NAME) of - undefined -> - {timeout, Timeout}; - Pid -> - Pid ! {self(), get_state}, - receive - {?SERVER_NAME, State} when record(State, state) -> - {info,State} - after Timeout -> - {timeout, Timeout} - end - end. - -get_workers(Timeout) -> - case whereis(?SERVER_NAME) of - undefined -> - {timeout, Timeout}; - Pid -> - Pid ! {self(), get_state}, - receive - {?SERVER_NAME, State} when record(State, state) -> - {workers, State#state.loader_pid, State#state.sender_pid, State#state.dumper_pid} - after Timeout -> - {timeout, Timeout} - end - end. - -info() -> - Tabs = mnesia_lib:local_active_tables(), - io:format( "---> Active tables <--- ~n", []), - info(Tabs). - -info([Tab | Tail]) -> - case val({Tab, storage_type}) of - disc_only_copies -> - info_format(Tab, - dets:info(Tab, size), - dets:info(Tab, file_size), - "bytes on disc"); - _ -> - info_format(Tab, - ?ets_info(Tab, size), - ?ets_info(Tab, memory), - "words of mem") - end, - info(Tail); -info([]) -> ok; -info(Tab) -> info([Tab]). - -info_format(Tab, Size, Mem, Media) -> - StrT = mnesia_lib:pad_name(atom_to_list(Tab), 15, []), - StrS = mnesia_lib:pad_name(integer_to_list(Size), 8, []), - StrM = mnesia_lib:pad_name(integer_to_list(Mem), 8, []), - io:format("~s: with ~s records occupying ~s ~s~n", - [StrT, StrS, StrM, Media]). - -%% Handle early arrived messages -handle_early_msgs([Msg | Msgs], State) -> - %% The messages are in reverse order - case handle_early_msg(Msg, State) of - {stop, Reason, Reply, State2} -> - {stop, Reason, Reply, State2}; - {stop, Reason, State2} -> - {stop, Reason, State2}; - {noreply, State2} -> - handle_early_msgs(Msgs, State2); - {noreply, State2, _Timeout} -> - handle_early_msgs(Msgs, State2); - Else -> - dbg_out("handle_early_msgs case clause ~p ~n", [Else]), - erlang:error(Else, [[Msg | Msgs], State]) - end; -handle_early_msgs([], State) -> - noreply(State). - -handle_early_msg({call, Msg, From}, State) -> - handle_call(Msg, From, State); -handle_early_msg({cast, Msg}, State) -> - handle_cast(Msg, State); -handle_early_msg({info, Msg}, State) -> - handle_info(Msg, State). - -noreply(State) -> - {noreply, State}. - -reply(undefined, Reply) -> - Reply; -reply(ReplyTo, Reply) -> - gen_server:reply(ReplyTo, Reply), - Reply. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Worker management - -%% Returns new State -add_worker(Worker, State) when record(Worker, dump_log) -> - InitBy = Worker#dump_log.initiated_by, - Queue = State#state.dumper_queue, - case lists:keymember(InitBy, #dump_log.initiated_by, Queue) of - false -> - ignore; - true when Worker#dump_log.opt_reply_to == undefined -> - %% The same threshold has been exceeded again, - %% before we have had the possibility to - %% process the older one. - DetectedBy = {dump_log, InitBy}, - Event = {mnesia_overload, DetectedBy}, - mnesia_lib:report_system_event(Event) - end, - Queue2 = Queue ++ [Worker], - State2 = State#state{dumper_queue = Queue2}, - opt_start_worker(State2); -add_worker(Worker, State) when record(Worker, schema_commit_lock) -> - Queue = State#state.dumper_queue, - Queue2 = Queue ++ [Worker], - State2 = State#state{dumper_queue = Queue2}, - opt_start_worker(State2); -add_worker(Worker, State) when record(Worker, net_load) -> - Queue = State#state.loader_queue, - State2 = State#state{loader_queue = Queue ++ [Worker]}, - opt_start_worker(State2); -add_worker(Worker, State) when record(Worker, send_table) -> - Queue = State#state.sender_queue, - State2 = State#state{sender_queue = Queue ++ [Worker]}, - opt_start_worker(State2); -add_worker(Worker, State) when record(Worker, disc_load) -> - Queue = State#state.loader_queue, - State2 = State#state{loader_queue = Queue ++ [Worker]}, - opt_start_worker(State2); -% Block controller should be used for upgrading mnesia. -add_worker(Worker, State) when record(Worker, block_controller) -> - Queue = State#state.dumper_queue, - Queue2 = [Worker | Queue], - State2 = State#state{dumper_queue = Queue2}, - opt_start_worker(State2). - -%% Optionally start a worker -%% -%% Dumpers and loaders may run simultaneously -%% but neither of them may run during schema commit. -%% Loaders may not start if a schema commit is enqueued. -opt_start_worker(State) when State#state.is_stopping == true -> - State; -opt_start_worker(State) -> - %% Prioritize dumper and schema commit - %% by checking them first - case State#state.dumper_queue of - [Worker | _Rest] when State#state.dumper_pid == undefined -> - %% Great, a worker in queue and neither - %% a schema transaction is being - %% committed and nor a dumper is running - - %% Start worker but keep him in the queue - if - record(Worker, schema_commit_lock) -> - ReplyTo = Worker#schema_commit_lock.owner, - reply(ReplyTo, granted), - {Owner, _Tag} = ReplyTo, - State#state{dumper_pid = Owner}; - - record(Worker, dump_log) -> - Pid = spawn_link(?MODULE, dump_and_reply, [self(), Worker]), - State2 = State#state{dumper_pid = Pid}, - - %% If the worker was a dumper we may - %% possibly be able to start a loader - %% or sender - State3 = opt_start_sender(State2), - opt_start_loader(State3); - - record(Worker, block_controller) -> - case {State#state.sender_pid, State#state.loader_pid} of - {undefined, undefined} -> - ReplyTo = Worker#block_controller.owner, - reply(ReplyTo, granted), - {Owner, _Tag} = ReplyTo, - State#state{dumper_pid = Owner}; - _ -> - State - end - end; - _ -> - %% Bad luck, try with a loader or sender instead - State2 = opt_start_sender(State), - opt_start_loader(State2) - end. - -opt_start_sender(State) -> - case State#state.sender_queue of - []-> - %% No need - State; - - _ when State#state.sender_pid /= undefined -> - %% Bad luck, a sender is already running - State; - - [Sender | _SenderRest] -> - case State#state.loader_queue of - [Loader | _LoaderRest] - when State#state.loader_pid /= undefined, - Loader#net_load.table == Sender#send_table.table -> - %% A conflicting loader is running - State; - _ -> - SchemaQueue = State#state.dumper_queue, - case lists:keymember(schema_commit, 1, SchemaQueue) of - false -> - - %% Start worker but keep him in the queue - Pid = spawn_link(?MODULE, send_and_reply, - [self(), Sender]), - State#state{sender_pid = Pid}; - true -> - %% Bad luck, we must wait for the schema commit - State - end - end - end. - -opt_start_loader(State) -> - LoaderQueue = State#state.loader_queue, - if - LoaderQueue == [] -> - %% No need - State; - - State#state.loader_pid /= undefined -> - %% Bad luck, an loader is already running - State; - - true -> - SchemaQueue = State#state.dumper_queue, - case lists:keymember(schema_commit, 1, SchemaQueue) of - false -> - {Worker, Rest} = pick_next(LoaderQueue), - - %% Start worker but keep him in the queue - Pid = spawn_link(?MODULE, load_and_reply, [self(), Worker]), - State#state{loader_pid = Pid, - loader_queue = [Worker | Rest]}; - true -> - %% Bad luck, we must wait for the schema commit - State - end - end. - -start_remote_sender(Node, Tab, Receiver, Storage) -> - Msg = #send_table{table = Tab, - receiver_pid = Receiver, - remote_storage = Storage}, - gen_server:cast({?SERVER_NAME, Node}, Msg). - -dump_and_reply(ReplyTo, Worker) -> - %% No trap_exit, die intentionally instead - Res = mnesia_dumper:opt_dump_log(Worker#dump_log.initiated_by), - ReplyTo ! #dumper_done{worker_pid = self(), - worker_res = Res}, - unlink(ReplyTo), - exit(normal). - -send_and_reply(ReplyTo, Worker) -> - %% No trap_exit, die intentionally instead - Res = mnesia_loader:send_table(Worker#send_table.receiver_pid, - Worker#send_table.table, - Worker#send_table.remote_storage), - ReplyTo ! #sender_done{worker_pid = self(), - worker_res = Res}, - unlink(ReplyTo), - exit(normal). - - -load_and_reply(ReplyTo, Worker) -> - process_flag(trap_exit, true), - Done = load_table(Worker), - ReplyTo ! Done#loader_done{worker_pid = self()}, - unlink(ReplyTo), - exit(normal). - -%% Now it is time to load the table -%% but first we must check if it still is neccessary -load_table(Load) when record(Load, net_load) -> - Tab = Load#net_load.table, - ReplyTo = Load#net_load.opt_reply_to, - Reason = Load#net_load.reason, - LocalC = val({Tab, local_content}), - AccessMode = val({Tab, access_mode}), - ReadNode = val({Tab, where_to_read}), - Active = filter_active(Tab), - Done = #loader_done{is_loaded = true, - table_name = Tab, - needs_announce = false, - needs_sync = false, - needs_reply = true, - reply_to = ReplyTo, - reply = {loaded, ok} - }, - if - ReadNode == node() -> - %% Already loaded locally - Done; - LocalC == true -> - Res = mnesia_loader:disc_load_table(Tab, load_local_content), - Done#loader_done{reply = Res, needs_announce = true, needs_sync = true}; - AccessMode == read_only -> - disc_load_table(Tab, Reason, ReplyTo); - true -> - %% Either we cannot read the table yet - %% or someone is moving a replica between - %% two nodes - Cs = Load#net_load.cstruct, - Res = mnesia_loader:net_load_table(Tab, Reason, Active, Cs), - case Res of - {loaded, ok} -> - Done#loader_done{needs_sync = true, - reply = Res}; - {not_loaded, storage_unknown} -> - Done#loader_done{reply = Res}; - {not_loaded, _} -> - Done#loader_done{is_loaded = false, - needs_reply = false, - reply = Res} - end - end; - -load_table(Load) when record(Load, disc_load) -> - Tab = Load#disc_load.table, - Reason = Load#disc_load.reason, - ReplyTo = Load#disc_load.opt_reply_to, - ReadNode = val({Tab, where_to_read}), - Active = filter_active(Tab), - Done = #loader_done{is_loaded = true, - table_name = Tab, - needs_announce = false, - needs_sync = false, - needs_reply = false - }, - if - Active == [], ReadNode == nowhere -> - %% Not loaded anywhere, lets load it from disc - disc_load_table(Tab, Reason, ReplyTo); - ReadNode == nowhere -> - %% Already loaded on other node, lets get it - Cs = val({Tab, cstruct}), - case mnesia_loader:net_load_table(Tab, Reason, Active, Cs) of - {loaded, ok} -> - Done#loader_done{needs_sync = true}; - {not_loaded, storage_unknown} -> - Done#loader_done{is_loaded = false}; - {not_loaded, ErrReason} -> - Done#loader_done{is_loaded = false, - reply = {not_loaded,ErrReason}} - end; - true -> - %% Already readable, do not worry be happy - Done - end. - -disc_load_table(Tab, Reason, ReplyTo) -> - Done = #loader_done{is_loaded = true, - table_name = Tab, - needs_announce = false, - needs_sync = false, - needs_reply = true, - reply_to = ReplyTo, - reply = {loaded, ok} - }, - Res = mnesia_loader:disc_load_table(Tab, Reason), - if - Res == {loaded, ok} -> - Done#loader_done{needs_announce = true, - needs_sync = true, - reply = Res}; - ReplyTo /= undefined -> - Done#loader_done{is_loaded = false, - reply = Res}; - true -> - fatal("Cannot load table ~p from disc: ~p~n", [Tab, Res]) - end. - -filter_active(Tab) -> - ByForce = val({Tab, load_by_force}), - Active = val({Tab, active_replicas}), - Masters = mnesia_recover:get_master_nodes(Tab), - do_filter_active(ByForce, Active, Masters). - -do_filter_active(true, Active, _Masters) -> - Active; -do_filter_active(false, Active, []) -> - Active; -do_filter_active(false, Active, Masters) -> - mnesia_lib:intersect(Active, Masters). - - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_dumper.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_dumper.erl deleted file mode 100644 index bbdb04589b..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_dumper.erl +++ /dev/null @@ -1,1092 +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_dumper.erl,v 1.1 2008/12/17 09:53:38 mikpe Exp $ -%% --module(mnesia_dumper). - -%% The InitBy arg may be one of the following: -%% scan_decisions Initial scan for decisions -%% startup Initial dump during startup -%% schema_prepare Dump initiated during schema transaction preparation -%% schema_update Dump initiated during schema transaction commit -%% fast_schema_update A schema_update, but ignores the log file -%% user Dump initiated by user -%% write_threshold Automatic dump caused by too many log writes -%% time_threshold Automatic dump caused by timeout - -%% Public interface --export([ - get_log_writes/0, - incr_log_writes/0, - raw_dump_table/2, - raw_named_dump_table/2, - start_regulator/0, - opt_dump_log/1, - update/3 - ]). - - %% Internal stuff --export([regulator_init/1]). - --include("mnesia.hrl"). --include_lib("kernel/include/file.hrl"). - --import(mnesia_lib, [fatal/2, dbg_out/2]). - --define(REGULATOR_NAME, mnesia_dumper_load_regulator). --define(DumpToEtsMultiplier, 4). - --record(state, {initiated_by = nobody, - dumper = nopid, - regulator_pid, - supervisor_pid, - queue = [], - timeout}). - -get_log_writes() -> - Max = mnesia_monitor:get_env(dump_log_write_threshold), - Prev = mnesia_lib:read_counter(trans_log_writes), - Left = mnesia_lib:read_counter(trans_log_writes_left), - Diff = Max - Left, - Prev + Diff. - -incr_log_writes() -> - Left = mnesia_lib:incr_counter(trans_log_writes_left, -1), - if - Left > 0 -> - ignore; - true -> - adjust_log_writes(true) - end. - -adjust_log_writes(DoCast) -> - Token = {mnesia_adjust_log_writes, self()}, - case global:set_lock(Token, [node()], 1) of - false -> - ignore; %% Somebody else is sending a dump request - true -> - case DoCast of - false -> - ignore; - true -> - mnesia_controller:async_dump_log(write_threshold) - end, - Max = mnesia_monitor:get_env(dump_log_write_threshold), - Left = mnesia_lib:read_counter(trans_log_writes_left), - %% Don't care if we lost a few writes - mnesia_lib:set_counter(trans_log_writes_left, Max), - Diff = Max - Left, - mnesia_lib:incr_counter(trans_log_writes, Diff), - global:del_lock(Token, [node()]) - end. - -%% Returns 'ok' or exits -opt_dump_log(InitBy) -> - Reg = case whereis(?REGULATOR_NAME) of - undefined -> - nopid; - Pid when pid(Pid) -> - Pid - end, - perform_dump(InitBy, Reg). - -%% Scan for decisions -perform_dump(InitBy, Regulator) when InitBy == scan_decisions -> - ?eval_debug_fun({?MODULE, perform_dump}, [InitBy]), - - dbg_out("Transaction log dump initiated by ~w~n", [InitBy]), - scan_decisions(mnesia_log:previous_log_file(), InitBy, Regulator), - scan_decisions(mnesia_log:latest_log_file(), InitBy, Regulator); - -%% Propagate the log into the DAT-files -perform_dump(InitBy, Regulator) -> - ?eval_debug_fun({?MODULE, perform_dump}, [InitBy]), - LogState = mnesia_log:prepare_log_dump(InitBy), - dbg_out("Transaction log dump initiated by ~w: ~w~n", - [InitBy, LogState]), - adjust_log_writes(false), - mnesia_recover:allow_garb(), - case LogState of - already_dumped -> - dumped; - {needs_dump, Diff} -> - U = mnesia_monitor:get_env(dump_log_update_in_place), - Cont = mnesia_log:init_log_dump(), - case catch do_perform_dump(Cont, U, InitBy, Regulator, undefined) of - ok -> - ?eval_debug_fun({?MODULE, post_dump}, [InitBy]), - case mnesia_monitor:use_dir() of - true -> - mnesia_recover:dump_decision_tab(); - false -> - mnesia_log:purge_some_logs() - end, - %% And now to the crucial point... - mnesia_log:confirm_log_dump(Diff); - {error, Reason} -> - {error, Reason}; - {'EXIT', {Desc, Reason}} -> - case mnesia_monitor:get_env(auto_repair) of - true -> - mnesia_lib:important(Desc, Reason), - %% Ignore rest of the log - mnesia_log:confirm_log_dump(Diff); - false -> - fatal(Desc, Reason) - end - end; - {error, Reason} -> - {error, {"Cannot prepare log dump", Reason}} - end. - -scan_decisions(Fname, InitBy, Regulator) -> - Exists = mnesia_lib:exists(Fname), - case Exists of - false -> - ok; - true -> - Header = mnesia_log:trans_log_header(), - Name = previous_log, - mnesia_log:open_log(Name, Header, Fname, Exists, - mnesia_monitor:get_env(auto_repair), read_only), - Cont = start, - Res = (catch do_perform_dump(Cont, false, InitBy, Regulator, undefined)), - mnesia_log:close_log(Name), - case Res of - ok -> ok; - {'EXIT', Reason} -> {error, Reason} - end - end. - -do_perform_dump(Cont, InPlace, InitBy, Regulator, OldVersion) -> - case mnesia_log:chunk_log(Cont) of - {C2, Recs} -> - case catch insert_recs(Recs, InPlace, InitBy, Regulator, OldVersion) of - {'EXIT', R} -> - Reason = {"Transaction log dump error: ~p~n", [R]}, - close_files(InPlace, {error, Reason}, InitBy), - exit(Reason); - Version -> - do_perform_dump(C2, InPlace, InitBy, Regulator, Version) - end; - eof -> - close_files(InPlace, ok, InitBy), - ok - end. - -insert_recs([Rec | Recs], InPlace, InitBy, Regulator, LogV) -> - regulate(Regulator), - case insert_rec(Rec, InPlace, InitBy, LogV) of - LogH when record(LogH, log_header) -> - insert_recs(Recs, InPlace, InitBy, Regulator, LogH#log_header.log_version); - _ -> - insert_recs(Recs, InPlace, InitBy, Regulator, LogV) - end; - -insert_recs([], _InPlace, _InitBy, _Regulator, Version) -> - Version. - -insert_rec(Rec, _InPlace, scan_decisions, _LogV) -> - if - record(Rec, commit) -> - ignore; - record(Rec, log_header) -> - ignore; - true -> - mnesia_recover:note_log_decision(Rec, scan_decisions) - end; -insert_rec(Rec, InPlace, InitBy, LogV) when record(Rec, commit) -> - %% Determine the Outcome of the transaction and recover it - D = Rec#commit.decision, - case mnesia_recover:wait_for_decision(D, InitBy) of - {Tid, committed} -> - do_insert_rec(Tid, Rec, InPlace, InitBy, LogV); - {Tid, aborted} -> - mnesia_schema:undo_prepare_commit(Tid, Rec) - end; -insert_rec(H, _InPlace, _InitBy, _LogV) when record(H, log_header) -> - CurrentVersion = mnesia_log:version(), - if - H#log_header.log_kind /= trans_log -> - exit({"Bad kind of transaction log", H}); - H#log_header.log_version == CurrentVersion -> - ok; - H#log_header.log_version == "4.2" -> - ok; - H#log_header.log_version == "4.1" -> - ok; - H#log_header.log_version == "4.0" -> - ok; - true -> - fatal("Bad version of transaction log: ~p~n", [H]) - end, - H; - -insert_rec(_Rec, _InPlace, _InitBy, _LogV) -> - ok. - -do_insert_rec(Tid, Rec, InPlace, InitBy, LogV) -> - case Rec#commit.schema_ops of - [] -> - ignore; - SchemaOps -> - case val({schema, storage_type}) of - ram_copies -> - insert_ops(Tid, schema_ops, SchemaOps, InPlace, InitBy, LogV); - Storage -> - true = open_files(schema, Storage, InPlace, InitBy), - insert_ops(Tid, schema_ops, SchemaOps, InPlace, InitBy, LogV) - end - end, - D = Rec#commit.disc_copies, - insert_ops(Tid, disc_copies, D, InPlace, InitBy, LogV), - case InitBy of - startup -> - DO = Rec#commit.disc_only_copies, - insert_ops(Tid, disc_only_copies, DO, InPlace, InitBy, LogV); - _ -> - ignore - end. - - -update(_Tid, [], _DumperMode) -> - dumped; -update(Tid, SchemaOps, DumperMode) -> - UseDir = mnesia_monitor:use_dir(), - Res = perform_update(Tid, SchemaOps, DumperMode, UseDir), - mnesia_controller:release_schema_commit_lock(), - Res. - -perform_update(_Tid, _SchemaOps, mandatory, true) -> - %% Force a dump of the transaction log in order to let the - %% dumper perform needed updates - - InitBy = schema_update, - ?eval_debug_fun({?MODULE, dump_schema_op}, [InitBy]), - opt_dump_log(InitBy); -perform_update(Tid, SchemaOps, _DumperMode, _UseDir) -> - %% No need for a full transaction log dump. - %% Ignore the log file and perform only perform - %% the corresponding updates. - - InitBy = fast_schema_update, - InPlace = mnesia_monitor:get_env(dump_log_update_in_place), - ?eval_debug_fun({?MODULE, dump_schema_op}, [InitBy]), - case catch insert_ops(Tid, schema_ops, SchemaOps, InPlace, InitBy, - mnesia_log:version()) of - {'EXIT', Reason} -> - Error = {error, {"Schema update error", Reason}}, - close_files(InPlace, Error, InitBy), - fatal("Schema update error ~p ~p", [Reason, SchemaOps]); - _ -> - ?eval_debug_fun({?MODULE, post_dump}, [InitBy]), - close_files(InPlace, ok, InitBy), - ok - end. - -insert_ops(_Tid, _Storage, [], _InPlace, _InitBy, _) -> ok; -insert_ops(Tid, Storage, [Op], InPlace, InitBy, Ver) when Ver >= "4.3"-> - insert_op(Tid, Storage, Op, InPlace, InitBy), - ok; -insert_ops(Tid, Storage, [Op | Ops], InPlace, InitBy, Ver) when Ver >= "4.3"-> - insert_op(Tid, Storage, Op, InPlace, InitBy), - insert_ops(Tid, Storage, Ops, InPlace, InitBy, Ver); -insert_ops(Tid, Storage, [Op | Ops], InPlace, InitBy, Ver) when Ver < "4.3" -> - insert_ops(Tid, Storage, Ops, InPlace, InitBy, Ver), - insert_op(Tid, Storage, Op, InPlace, InitBy). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Normal ops - -disc_insert(_Tid, Storage, Tab, Key, Val, Op, InPlace, InitBy) -> - case open_files(Tab, Storage, InPlace, InitBy) of - true -> - case Storage of - disc_copies when Tab /= schema -> - mnesia_log:append({?MODULE,Tab}, {{Tab, Key}, Val, Op}), - ok; - _ -> - case Op of - write -> - ok = dets:insert(Tab, Val); - delete -> - ok = dets:delete(Tab, Key); - update_counter -> - {RecName, Incr} = Val, - case catch dets:update_counter(Tab, Key, Incr) of - CounterVal when integer(CounterVal) -> - ok; - _ -> - Zero = {RecName, Key, 0}, - ok = dets:insert(Tab, Zero) - end; - delete_object -> - ok = dets:delete_object(Tab, Val); - clear_table -> - ok = dets:match_delete(Tab, '_') - end - end; - false -> - ignore - end. - -insert(Tid, Storage, Tab, Key, [Val | Tail], Op, InPlace, InitBy) -> - insert(Tid, Storage, Tab, Key, Val, Op, InPlace, InitBy), - insert(Tid, Storage, Tab, Key, Tail, Op, InPlace, InitBy); - -insert(_Tid, _Storage, _Tab, _Key, [], _Op, _InPlace, _InitBy) -> - ok; - -insert(Tid, Storage, Tab, Key, Val, Op, InPlace, InitBy) -> - Item = {{Tab, Key}, Val, Op}, - case InitBy of - startup -> - disc_insert(Tid, Storage, Tab, Key, Val, Op, InPlace, InitBy); - - _ when Storage == ram_copies -> - mnesia_tm:do_update_op(Tid, Storage, Item), - Snmp = mnesia_tm:prepare_snmp(Tab, Key, [Item]), - mnesia_tm:do_snmp(Tid, Snmp); - - _ when Storage == disc_copies -> - disc_insert(Tid, Storage, Tab, Key, Val, Op, InPlace, InitBy), - mnesia_tm:do_update_op(Tid, Storage, Item), - Snmp = mnesia_tm:prepare_snmp(Tab, Key, [Item]), - mnesia_tm:do_snmp(Tid, Snmp); - - _ when Storage == disc_only_copies -> - mnesia_tm:do_update_op(Tid, Storage, Item), - Snmp = mnesia_tm:prepare_snmp(Tab, Key, [Item]), - mnesia_tm:do_snmp(Tid, Snmp); - - _ when Storage == unknown -> - ignore - end. - -disc_delete_table(Tab, Storage) -> - case mnesia_monitor:use_dir() of - true -> - if - Storage == disc_only_copies; Tab == schema -> - mnesia_monitor:unsafe_close_dets(Tab), - Dat = mnesia_lib:tab2dat(Tab), - file:delete(Dat); - true -> - DclFile = mnesia_lib:tab2dcl(Tab), - case get({?MODULE,Tab}) of - {opened_dumper, dcl} -> - del_opened_tab(Tab), - mnesia_log:unsafe_close_log(Tab); - _ -> - ok - end, - file:delete(DclFile), - DcdFile = mnesia_lib:tab2dcd(Tab), - file:delete(DcdFile), - ok - end, - erase({?MODULE, Tab}); - false -> - ignore - end. - -disc_delete_indecies(_Tab, _Cs, Storage) when Storage /= disc_only_copies -> - ignore; -disc_delete_indecies(Tab, Cs, disc_only_copies) -> - Indecies = Cs#cstruct.index, - mnesia_index:del_transient(Tab, Indecies, disc_only_copies). - -insert_op(Tid, Storage, {{Tab, Key}, Val, Op}, InPlace, InitBy) -> - %% Propagate to disc only - disc_insert(Tid, Storage, Tab, Key, Val, Op, InPlace, InitBy); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% NOTE that all operations below will only -%% be performed if the dump is initiated by -%% startup or fast_schema_update -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -insert_op(_Tid, schema_ops, _OP, _InPlace, Initby) - when Initby /= startup, - Initby /= fast_schema_update, - Initby /= schema_update -> - ignore; - -insert_op(Tid, _, {op, rec, Storage, Item}, InPlace, InitBy) -> - {{Tab, Key}, ValList, Op} = Item, - insert(Tid, Storage, Tab, Key, ValList, Op, InPlace, InitBy); - -insert_op(Tid, _, {op, change_table_copy_type, N, FromS, ToS, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - Val = mnesia_schema:insert_cstruct(Tid, Cs, true), % Update ram only - {schema, Tab, _} = Val, - if - InitBy /= startup -> - mnesia_controller:add_active_replica(Tab, N, Cs); - true -> - ignore - end, - if - N == node() -> - Dmp = mnesia_lib:tab2dmp(Tab), - Dat = mnesia_lib:tab2dat(Tab), - Dcd = mnesia_lib:tab2dcd(Tab), - Dcl = mnesia_lib:tab2dcl(Tab), - case {FromS, ToS} of - {ram_copies, disc_copies} when Tab == schema -> - ok = ensure_rename(Dmp, Dat); - {ram_copies, disc_copies} -> - file:delete(Dcl), - ok = ensure_rename(Dmp, Dcd); - {disc_copies, ram_copies} when Tab == schema -> - mnesia_lib:set(use_dir, false), - mnesia_monitor:unsafe_close_dets(Tab), - file:delete(Dat); - {disc_copies, ram_copies} -> - file:delete(Dcl), - file:delete(Dcd); - {ram_copies, disc_only_copies} -> - ok = ensure_rename(Dmp, Dat), - true = open_files(Tab, disc_only_copies, InPlace, InitBy), - %% ram_delete_table must be done before init_indecies, - %% it uses info which is reset in init_indecies, - %% it doesn't matter, because init_indecies don't use - %% the ram replica of the table when creating the disc - %% index; Could be improved :) - mnesia_schema:ram_delete_table(Tab, FromS), - PosList = Cs#cstruct.index, - mnesia_index:init_indecies(Tab, disc_only_copies, PosList); - {disc_only_copies, ram_copies} -> - mnesia_monitor:unsafe_close_dets(Tab), - disc_delete_indecies(Tab, Cs, disc_only_copies), - case InitBy of - startup -> - ignore; - _ -> - mnesia_controller:get_disc_copy(Tab) - end, - disc_delete_table(Tab, disc_only_copies); - {disc_copies, disc_only_copies} -> - ok = ensure_rename(Dmp, Dat), - true = open_files(Tab, disc_only_copies, InPlace, InitBy), - mnesia_schema:ram_delete_table(Tab, FromS), - PosList = Cs#cstruct.index, - mnesia_index:init_indecies(Tab, disc_only_copies, PosList), - file:delete(Dcl), - file:delete(Dcd); - {disc_only_copies, disc_copies} -> - mnesia_monitor:unsafe_close_dets(Tab), - disc_delete_indecies(Tab, Cs, disc_only_copies), - case InitBy of - startup -> - ignore; - _ -> - mnesia_log:ets2dcd(Tab), - mnesia_controller:get_disc_copy(Tab), - disc_delete_table(Tab, disc_only_copies) - end - end; - true -> - ignore - end, - S = val({schema, storage_type}), - disc_insert(Tid, S, schema, Tab, Val, write, InPlace, InitBy); - -insert_op(Tid, _, {op, transform, _Fun, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - case mnesia_lib:cs_to_storage_type(node(), Cs) of - disc_copies -> - open_dcl(Cs#cstruct.name); - _ -> - ignore - end, - insert_cstruct(Tid, Cs, true, InPlace, InitBy); - -%%% Operations below this are handled without using the logg. - -insert_op(Tid, _, {op, restore_recreate, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - Tab = Cs#cstruct.name, - Type = Cs#cstruct.type, - Storage = mnesia_lib:cs_to_storage_type(node(), Cs), - %% Delete all possbibly existing files and tables - disc_delete_table(Tab, Storage), - disc_delete_indecies(Tab, Cs, Storage), - case InitBy of - startup -> - ignore; - _ -> - mnesia_schema:ram_delete_table(Tab, Storage), - mnesia_checkpoint:tm_del_copy(Tab, node()) - end, - %% delete_cstruct(Tid, Cs, InPlace, InitBy), - %% And create new ones.. - if - (InitBy == startup) or (Storage == unknown) -> - ignore; - Storage == ram_copies -> - Args = [{keypos, 2}, public, named_table, Type], - mnesia_monitor:mktab(Tab, Args); - Storage == disc_copies -> - Args = [{keypos, 2}, public, named_table, Type], - mnesia_monitor:mktab(Tab, Args), - File = mnesia_lib:tab2dcd(Tab), - FArg = [{file, File}, {name, {mnesia,create}}, - {repair, false}, {mode, read_write}], - {ok, Log} = mnesia_monitor:open_log(FArg), - mnesia_monitor:unsafe_close_log(Log); - Storage == disc_only_copies -> - File = mnesia_lib:tab2dat(Tab), - file:delete(File), - Args = [{file, mnesia_lib:tab2dat(Tab)}, - {type, mnesia_lib:disk_type(Tab, Type)}, - {keypos, 2}, - {repair, mnesia_monitor:get_env(auto_repair)}], - mnesia_monitor:open_dets(Tab, Args) - end, - insert_op(Tid, ignore, {op, create_table, TabDef}, InPlace, InitBy); - -insert_op(Tid, _, {op, create_table, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - insert_cstruct(Tid, Cs, false, InPlace, InitBy), - Tab = Cs#cstruct.name, - Storage = mnesia_lib:cs_to_storage_type(node(), Cs), - case InitBy of - startup -> - case Storage of - unknown -> - ignore; - ram_copies -> - ignore; - disc_copies -> - Dcd = mnesia_lib:tab2dcd(Tab), - case mnesia_lib:exists(Dcd) of - true -> ignore; - false -> - mnesia_log:open_log(temp, - mnesia_log:dcl_log_header(), - Dcd, - false, - false, - read_write), - mnesia_log:unsafe_close_log(temp) - end; - _ -> - Args = [{file, mnesia_lib:tab2dat(Tab)}, - {type, mnesia_lib:disk_type(Tab, Cs#cstruct.type)}, - {keypos, 2}, - {repair, mnesia_monitor:get_env(auto_repair)}], - case mnesia_monitor:open_dets(Tab, Args) of - {ok, _} -> - mnesia_monitor:unsafe_close_dets(Tab); - {error, Error} -> - exit({"Failed to create dets table", Error}) - end - end; - _ -> - Copies = mnesia_lib:copy_holders(Cs), - Active = mnesia_lib:intersect(Copies, val({current, db_nodes})), - [mnesia_controller:add_active_replica(Tab, N, Cs) || N <- Active], - - case Storage of - unknown -> - case Cs#cstruct.local_content of - true -> - ignore; - false -> - mnesia_lib:set_remote_where_to_read(Tab) - end; - _ -> - case Cs#cstruct.local_content of - true -> - mnesia_lib:set_local_content_whereabouts(Tab); - false -> - mnesia_lib:set({Tab, where_to_read}, node()) - end, - case Storage of - ram_copies -> - ignore; - _ -> - %% Indecies are still created by loader - disc_delete_indecies(Tab, Cs, Storage) - %% disc_delete_table(Tab, Storage) - end, - - %% Update whereabouts and create table - mnesia_controller:create_table(Tab) - end - end; - -insert_op(_Tid, _, {op, dump_table, Size, TabDef}, _InPlace, _InitBy) -> - case Size of - unknown -> - ignore; - _ -> - Cs = mnesia_schema:list2cs(TabDef), - Tab = Cs#cstruct.name, - Dmp = mnesia_lib:tab2dmp(Tab), - Dat = mnesia_lib:tab2dcd(Tab), - case Size of - 0 -> - %% Assume that table files already are closed - file:delete(Dmp), - file:delete(Dat); - _ -> - ok = ensure_rename(Dmp, Dat) - end - end; - -insert_op(Tid, _, {op, delete_table, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - Tab = Cs#cstruct.name, - case mnesia_lib:cs_to_storage_type(node(), Cs) of - unknown -> - ignore; - Storage -> - disc_delete_table(Tab, Storage), - disc_delete_indecies(Tab, Cs, Storage), - case InitBy of - startup -> - ignore; - _ -> - mnesia_schema:ram_delete_table(Tab, Storage), - mnesia_checkpoint:tm_del_copy(Tab, node()) - end - end, - delete_cstruct(Tid, Cs, InPlace, InitBy); - -insert_op(Tid, _, {op, clear_table, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - Tab = Cs#cstruct.name, - case mnesia_lib:cs_to_storage_type(node(), Cs) of - unknown -> - ignore; - Storage -> - Oid = '_', %%val({Tab, wild_pattern}), - if Storage == disc_copies -> - open_dcl(Cs#cstruct.name); - true -> - ignore - end, - insert(Tid, Storage, Tab, '_', Oid, clear_table, InPlace, InitBy) - end; - -insert_op(Tid, _, {op, merge_schema, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - insert_cstruct(Tid, Cs, false, InPlace, InitBy); - -insert_op(Tid, _, {op, del_table_copy, Storage, Node, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - Tab = Cs#cstruct.name, - if - Tab == schema, Storage == ram_copies -> - insert_cstruct(Tid, Cs, true, InPlace, InitBy); - Tab /= schema -> - mnesia_controller:del_active_replica(Tab, Node), - mnesia_lib:del({Tab, Storage}, Node), - if - Node == node() -> - case Cs#cstruct.local_content of - true -> mnesia_lib:set({Tab, where_to_read}, nowhere); - false -> mnesia_lib:set_remote_where_to_read(Tab) - end, - mnesia_lib:del({schema, local_tables}, Tab), - mnesia_lib:set({Tab, storage_type}, unknown), - insert_cstruct(Tid, Cs, true, InPlace, InitBy), - disc_delete_table(Tab, Storage), - disc_delete_indecies(Tab, Cs, Storage), - mnesia_schema:ram_delete_table(Tab, Storage), - mnesia_checkpoint:tm_del_copy(Tab, Node); - true -> - case val({Tab, where_to_read}) of - Node -> - mnesia_lib:set_remote_where_to_read(Tab); - _ -> - ignore - end, - insert_cstruct(Tid, Cs, true, InPlace, InitBy) - end - end; - -insert_op(Tid, _, {op, add_table_copy, _Storage, _Node, TabDef}, InPlace, InitBy) -> - %% During prepare commit, the files was created - %% and the replica was announced - Cs = mnesia_schema:list2cs(TabDef), - insert_cstruct(Tid, Cs, true, InPlace, InitBy); - -insert_op(Tid, _, {op, add_snmp, _Us, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - insert_cstruct(Tid, Cs, true, InPlace, InitBy); - -insert_op(Tid, _, {op, del_snmp, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - Tab = Cs#cstruct.name, - Storage = mnesia_lib:cs_to_storage_type(node(), Cs), - if - InitBy /= startup, - Storage /= unknown -> - case ?catch_val({Tab, {index, snmp}}) of - {'EXIT', _} -> - ignore; - Stab -> - mnesia_snmp_hook:delete_table(Tab, Stab), - mnesia_lib:unset({Tab, {index, snmp}}) - end; - true -> - ignore - end, - insert_cstruct(Tid, Cs, true, InPlace, InitBy); - -insert_op(Tid, _, {op, add_index, Pos, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - Tab = insert_cstruct(Tid, Cs, true, InPlace, InitBy), - Storage = mnesia_lib:cs_to_storage_type(node(), Cs), - case InitBy of - startup when Storage == disc_only_copies -> - mnesia_index:init_indecies(Tab, Storage, [Pos]); - startup -> - ignore; - _ -> - mnesia_index:init_indecies(Tab, Storage, [Pos]) - end; - -insert_op(Tid, _, {op, del_index, Pos, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - Tab = Cs#cstruct.name, - Storage = mnesia_lib:cs_to_storage_type(node(), Cs), - case InitBy of - startup when Storage == disc_only_copies -> - mnesia_index:del_index_table(Tab, Storage, Pos); - startup -> - ignore; - _ -> - mnesia_index:del_index_table(Tab, Storage, Pos) - end, - insert_cstruct(Tid, Cs, true, InPlace, InitBy); - -insert_op(Tid, _, {op, change_table_access_mode,TabDef, _OldAccess, _Access}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - case InitBy of - startup -> ignore; - _ -> mnesia_controller:change_table_access_mode(Cs) - end, - insert_cstruct(Tid, Cs, true, InPlace, InitBy); - -insert_op(Tid, _, {op, change_table_load_order, TabDef, _OldLevel, _Level}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - insert_cstruct(Tid, Cs, true, InPlace, InitBy); - -insert_op(Tid, _, {op, delete_property, TabDef, PropKey}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - Tab = Cs#cstruct.name, - mnesia_lib:unset({Tab, user_property, PropKey}), - insert_cstruct(Tid, Cs, true, InPlace, InitBy); - -insert_op(Tid, _, {op, write_property, TabDef, _Prop}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - insert_cstruct(Tid, Cs, true, InPlace, InitBy); - -insert_op(Tid, _, {op, change_table_frag, _Change, TabDef}, InPlace, InitBy) -> - Cs = mnesia_schema:list2cs(TabDef), - insert_cstruct(Tid, Cs, true, InPlace, InitBy). - -open_files(Tab, Storage, UpdateInPlace, InitBy) - when Storage /= unknown, Storage /= ram_copies -> - case get({?MODULE, Tab}) of - undefined -> - case ?catch_val({Tab, setorbag}) of - {'EXIT', _} -> - false; - Type -> - case Storage of - disc_copies when Tab /= schema -> - Bool = open_disc_copies(Tab, InitBy), - Bool; - _ -> - Fname = prepare_open(Tab, UpdateInPlace), - Args = [{file, Fname}, - {keypos, 2}, - {repair, mnesia_monitor:get_env(auto_repair)}, - {type, mnesia_lib:disk_type(Tab, Type)}], - {ok, _} = mnesia_monitor:open_dets(Tab, Args), - put({?MODULE, Tab}, {opened_dumper, dat}), - true - end - end; - already_dumped -> - false; - {opened_dumper, _} -> - true - end; -open_files(_Tab, _Storage, _UpdateInPlace, _InitBy) -> - false. - -open_disc_copies(Tab, InitBy) -> - DclF = mnesia_lib:tab2dcl(Tab), - DumpEts = - case file:read_file_info(DclF) of - {error, enoent} -> - false; - {ok, DclInfo} -> - DcdF = mnesia_lib:tab2dcd(Tab), - case file:read_file_info(DcdF) of - {error, Reason} -> - mnesia_lib:dbg_out("File ~p info_error ~p ~n", - [DcdF, Reason]), - true; - {ok, DcdInfo} -> - DcdInfo#file_info.size =< - (DclInfo#file_info.size * - ?DumpToEtsMultiplier) - end - end, - if - DumpEts == false; InitBy == startup -> - mnesia_log:open_log({?MODULE,Tab}, - mnesia_log:dcl_log_header(), - DclF, - mnesia_lib:exists(DclF), - mnesia_monitor:get_env(auto_repair), - read_write), - put({?MODULE, Tab}, {opened_dumper, dcl}), - true; - true -> - mnesia_log:ets2dcd(Tab), - put({?MODULE, Tab}, already_dumped), - false - end. - -%% Always opens the dcl file for writing overriding already_dumped -%% mechanismen, used for schema transactions. -open_dcl(Tab) -> - case get({?MODULE, Tab}) of - {opened_dumper, _} -> - true; - _ -> %% undefined or already_dumped - DclF = mnesia_lib:tab2dcl(Tab), - mnesia_log:open_log({?MODULE,Tab}, - mnesia_log:dcl_log_header(), - DclF, - mnesia_lib:exists(DclF), - mnesia_monitor:get_env(auto_repair), - read_write), - put({?MODULE, Tab}, {opened_dumper, dcl}), - true - end. - -prepare_open(Tab, UpdateInPlace) -> - Dat = mnesia_lib:tab2dat(Tab), - case UpdateInPlace of - true -> - Dat; - false -> - Tmp = mnesia_lib:tab2tmp(Tab), - case catch mnesia_lib:copy_file(Dat, Tmp) of - ok -> - Tmp; - Error -> - fatal("Cannot copy dets file ~p to ~p: ~p~n", - [Dat, Tmp, Error]) - end - end. - -del_opened_tab(Tab) -> - erase({?MODULE, Tab}). - -close_files(UpdateInPlace, Outcome, InitBy) -> % Update in place - close_files(UpdateInPlace, Outcome, InitBy, get()). - -close_files(InPlace, Outcome, InitBy, [{{?MODULE, Tab}, already_dumped} | Tail]) -> - erase({?MODULE, Tab}), - close_files(InPlace, Outcome, InitBy, Tail); -close_files(InPlace, Outcome, InitBy, [{{?MODULE, Tab}, {opened_dumper, Type}} | Tail]) -> - erase({?MODULE, Tab}), - case val({Tab, storage_type}) of - disc_only_copies when InitBy /= startup -> - ignore; - disc_copies when Tab /= schema -> - mnesia_log:close_log({?MODULE,Tab}); - Storage -> - do_close(InPlace, Outcome, Tab, Type, Storage) - end, - close_files(InPlace, Outcome, InitBy, Tail); - -close_files(InPlace, Outcome, InitBy, [_ | Tail]) -> - close_files(InPlace, Outcome, InitBy, Tail); -close_files(_, _, _InitBy, []) -> - ok. - -%% If storage is unknown during close clean up files, this can happen if timing -%% is right and dirty_write conflicts with schema operations. -do_close(_, _, Tab, dcl, unknown) -> - mnesia_log:close_log({?MODULE,Tab}), - file:delete(mnesia_lib:tab2dcl(Tab)); -do_close(_, _, Tab, dcl, _) -> %% To be safe, can it happen? - mnesia_log:close_log({?MODULE,Tab}); - -do_close(InPlace, Outcome, Tab, dat, Storage) -> - mnesia_monitor:close_dets(Tab), - if - Storage == unknown, InPlace == true -> - file:delete(mnesia_lib:tab2dat(Tab)); - InPlace == true -> - %% Update in place - ok; - Outcome == ok, Storage /= unknown -> - %% Success: swap tmp files with dat files - TabDat = mnesia_lib:tab2dat(Tab), - ok = file:rename(mnesia_lib:tab2tmp(Tab), TabDat); - true -> - file:delete(mnesia_lib:tab2tmp(Tab)) - end. - - -ensure_rename(From, To) -> - case mnesia_lib:exists(From) of - true -> - file:rename(From, To); - false -> - case mnesia_lib:exists(To) of - true -> - ok; - false -> - {error, {rename_failed, From, To}} - end - end. - -insert_cstruct(Tid, Cs, KeepWhereabouts, InPlace, InitBy) -> - Val = mnesia_schema:insert_cstruct(Tid, Cs, KeepWhereabouts), - {schema, Tab, _} = Val, - S = val({schema, storage_type}), - disc_insert(Tid, S, schema, Tab, Val, write, InPlace, InitBy), - Tab. - -delete_cstruct(Tid, Cs, InPlace, InitBy) -> - Val = mnesia_schema:delete_cstruct(Tid, Cs), - {schema, Tab, _} = Val, - S = val({schema, storage_type}), - disc_insert(Tid, S, schema, Tab, Val, delete, InPlace, InitBy), - Tab. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Raw dump of table. Dumper must have unique access to the ets table. - -raw_named_dump_table(Tab, Ftype) -> - case mnesia_monitor:use_dir() of - true -> - mnesia_lib:lock_table(Tab), - TmpFname = mnesia_lib:tab2tmp(Tab), - Fname = - case Ftype of - dat -> mnesia_lib:tab2dat(Tab); - dmp -> mnesia_lib:tab2dmp(Tab) - end, - file:delete(TmpFname), - file:delete(Fname), - TabSize = ?ets_info(Tab, size), - TabRef = Tab, - DiskType = mnesia_lib:disk_type(Tab), - Args = [{file, TmpFname}, - {keypos, 2}, - %% {ram_file, true}, - {estimated_no_objects, TabSize + 256}, - {repair, mnesia_monitor:get_env(auto_repair)}, - {type, DiskType}], - case mnesia_lib:dets_sync_open(TabRef, Args) of - {ok, TabRef} -> - Storage = ram_copies, - mnesia_lib:db_fixtable(Storage, Tab, true), - - case catch raw_dump_table(TabRef, Tab) of - {'EXIT', Reason} -> - mnesia_lib:db_fixtable(Storage, Tab, false), - mnesia_lib:dets_sync_close(Tab), - file:delete(TmpFname), - mnesia_lib:unlock_table(Tab), - exit({"Dump of table to disc failed", Reason}); - ok -> - mnesia_lib:db_fixtable(Storage, Tab, false), - mnesia_lib:dets_sync_close(Tab), - mnesia_lib:unlock_table(Tab), - ok = file:rename(TmpFname, Fname) - end; - {error, Reason} -> - mnesia_lib:unlock_table(Tab), - exit({"Open of file before dump to disc failed", Reason}) - end; - false -> - exit({has_no_disc, node()}) - end. - -raw_dump_table(DetsRef, EtsRef) -> - dets:from_ets(DetsRef, EtsRef). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Load regulator -%% -%% This is a poor mans substitute for a fair scheduler algorithm -%% in the Erlang emulator. The mnesia_dumper process performs many -%% costly BIF invokations and must pay for this. But since the -%% Emulator does not handle this properly we must compensate for -%% this with some form of load regulation of ourselves in order to -%% not steal all computation power in the Erlang Emulator ans make -%% other processes starve. Hopefully this is a temporary solution. - -start_regulator() -> - case mnesia_monitor:get_env(dump_log_load_regulation) of - false -> - nopid; - true -> - N = ?REGULATOR_NAME, - case mnesia_monitor:start_proc(N, ?MODULE, regulator_init, [self()]) of - {ok, Pid} -> - Pid; - {error, Reason} -> - fatal("Failed to start ~n: ~p~n", [N, Reason]) - end - end. - -regulator_init(Parent) -> - %% No need for trapping exits. - %% Using low priority causes the regulation - process_flag(priority, low), - register(?REGULATOR_NAME, self()), - proc_lib:init_ack(Parent, {ok, self()}), - regulator_loop(). - -regulator_loop() -> - receive - {regulate, From} -> - From ! {regulated, self()}, - regulator_loop(); - {stop, From} -> - From ! {stopped, self()}, - exit(normal) - end. - -regulate(nopid) -> - ok; -regulate(RegulatorPid) -> - RegulatorPid ! {regulate, self()}, - receive - {regulated, RegulatorPid} -> ok - end. - -val(Var) -> - case ?catch_val(Var) of - {'EXIT', Reason} -> mnesia_lib:other_val(Var, Reason); - Value -> Value - end. diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_event.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_event.erl deleted file mode 100644 index fc0638e1ad..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_event.erl +++ /dev/null @@ -1,263 +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_event.erl,v 1.1 2008/12/17 09:53:38 mikpe Exp $ -%% --module(mnesia_event). - --behaviour(gen_event). -%-behaviour(mnesia_event). - -%% gen_event callback interface --export([init/1, - handle_event/2, - handle_call/2, - handle_info/2, - terminate/2, - code_change/3]). - --record(state, {nodes = [], - dumped_core = false, %% only dump fatal core once - args}). - -%%%---------------------------------------------------------------- -%%% Callback functions from gen_server -%%%---------------------------------------------------------------- - -%%----------------------------------------------------------------- -%% init(Args) -> -%% {ok, State} | Error -%%----------------------------------------------------------------- - -init(Args) -> - {ok, #state{args = Args}}. - -%%----------------------------------------------------------------- -%% handle_event(Event, State) -> -%% {ok, NewState} | remove_handler | -%% {swap_handler, Args1, State1, Mod2, Args2} -%%----------------------------------------------------------------- - -handle_event(Event, State) -> - handle_any_event(Event, State). - -%%----------------------------------------------------------------- -%% handle_info(Msg, State) -> -%% {ok, NewState} | remove_handler | -%% {swap_handler, Args1, State1, Mod2, Args2} -%%----------------------------------------------------------------- - -handle_info(Msg, State) -> - handle_any_event(Msg, State), - {ok, State}. - -%%----------------------------------------------------------------- -%% handle_call(Event, State) -> -%% {ok, Reply, NewState} | {remove_handler, Reply} | -%% {swap_handler, Reply, Args1, State1, Mod2, Args2} -%%----------------------------------------------------------------- - -handle_call(Msg, State) -> - Reply = ok, - case handle_any_event(Msg, State) of - {ok, NewState} -> - {ok, Reply, NewState}; - remove_handler -> - {remove_handler, Reply}; - {swap_handler,Args1, State1, Mod2, Args2} -> - {swap_handler, Reply, Args1, State1, Mod2, Args2} - end. - -%%----------------------------------------------------------------- -%% terminate(Reason, State) -> -%% AnyVal -%%----------------------------------------------------------------- - -terminate(_Reason, _State) -> - ok. - -%%---------------------------------------------------------------------- -%% Func: code_change/3 -%% Purpose: Upgrade process when its code is to be changed -%% Returns: {ok, NewState} -%%---------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%----------------------------------------------------------------- -%% Internal functions -%%----------------------------------------------------------------- - -handle_any_event({mnesia_system_event, Event}, State) -> - handle_system_event(Event, State); -handle_any_event({mnesia_table_event, Event}, State) -> - handle_table_event(Event, State); -handle_any_event(Msg, State) -> - report_error("~p got unexpected event: ~p~n", [?MODULE, Msg]), - {ok, State}. - -handle_table_event({Oper, Record, TransId}, State) -> - report_info("~p performed by ~p on record:~n\t~p~n", - [Oper, TransId, Record]), - {ok, State}. - -handle_system_event({mnesia_checkpoint_activated, _Checkpoint}, State) -> - {ok, State}; - -handle_system_event({mnesia_checkpoint_deactivated, _Checkpoint}, State) -> - {ok, State}; - -handle_system_event({mnesia_up, Node}, State) -> - Nodes = [Node | State#state.nodes], - {ok, State#state{nodes = Nodes}}; - -handle_system_event({mnesia_down, Node}, State) -> - case mnesia:system_info(fallback_activated) of - true -> - case mnesia_monitor:get_env(fallback_error_function) of - {mnesia, lkill} -> - Msg = "A fallback is installed and Mnesia " - "must be restarted. Forcing shutdown " - "after mnesia_down from ~p...~n", - report_fatal(Msg, [Node], nocore, State#state.dumped_core), - mnesia:lkill(), - exit(fatal); - {UserMod, UserFunc} -> - Msg = "Warning: A fallback is installed and Mnesia got mnesia_down " - "from ~p. ~n", - report_info(Msg, [Node]), - case catch apply(UserMod, UserFunc, [Node]) of - {'EXIT', {undef, _Reason}} -> - %% Backward compatibility - apply(UserMod, UserFunc, []); - {'EXIT', Reason} -> - exit(Reason); - _ -> - ok - end, - Nodes = lists:delete(Node, State#state.nodes), - {ok, State#state{nodes = Nodes}} - end; - false -> - Nodes = lists:delete(Node, State#state.nodes), - {ok, State#state{nodes = Nodes}} - end; - -handle_system_event({mnesia_overload, Details}, State) -> - report_warning("Mnesia is overloaded: ~p~n", [Details]), - {ok, State}; - -handle_system_event({mnesia_info, Format, Args}, State) -> - report_info(Format, Args), - {ok, State}; - -handle_system_event({mnesia_warning, Format, Args}, State) -> - report_warning(Format, Args), - {ok, State}; - -handle_system_event({mnesia_error, Format, Args}, State) -> - report_error(Format, Args), - {ok, State}; - -handle_system_event({mnesia_fatal, Format, Args, BinaryCore}, State) -> - report_fatal(Format, Args, BinaryCore, State#state.dumped_core), - {ok, State#state{dumped_core = true}}; - -handle_system_event({inconsistent_database, Reason, Node}, State) -> - report_error("mnesia_event got {inconsistent_database, ~w, ~w}~n", - [Reason, Node]), - {ok, State}; - -handle_system_event({mnesia_user, Event}, State) -> - report_info("User event: ~p~n", [Event]), - {ok, State}; - -handle_system_event(Msg, State) -> - report_error("mnesia_event got unexpected system event: ~p~n", [Msg]), - {ok, State}. - -report_info(Format0, Args0) -> - Format = "Mnesia(~p): " ++ Format0, - Args = [node() | Args0], - case global:whereis_name(mnesia_global_logger) of - undefined -> - io:format(Format, Args); - Pid -> - io:format(Pid, Format, Args) - end. - -report_warning(Format0, Args0) -> - Format = "Mnesia(~p): ** WARNING ** " ++ Format0, - Args = [node() | Args0], - case erlang:function_exported(error_logger, warning_msg, 2) of - true -> - error_logger:warning_msg(Format, Args); - false -> - error_logger:format(Format, Args) - end, - case global:whereis_name(mnesia_global_logger) of - undefined -> - ok; - Pid -> - io:format(Pid, Format, Args) - end. - -report_error(Format0, Args0) -> - Format = "Mnesia(~p): ** ERROR ** " ++ Format0, - Args = [node() | Args0], - error_logger:format(Format, Args), - case global:whereis_name(mnesia_global_logger) of - undefined -> - ok; - Pid -> - io:format(Pid, Format, Args) - end. - -report_fatal(Format, Args, BinaryCore, CoreDumped) -> - UseDir = mnesia_monitor:use_dir(), - CoreDir = mnesia_monitor:get_env(core_dir), - if - list(CoreDir),CoreDumped == false,binary(BinaryCore) -> - core_file(CoreDir,BinaryCore,Format,Args); - (UseDir == true),CoreDumped == false,binary(BinaryCore) -> - core_file(CoreDir,BinaryCore,Format,Args); - true -> - report_error("(ignoring core) ** FATAL ** " ++ Format, Args) - end. - -core_file(CoreDir,BinaryCore,Format,Args) -> - %% Integers = tuple_to_list(date()) ++ tuple_to_list(time()), - Integers = tuple_to_list(now()), - Fun = fun(I) when I < 10 -> ["_0",I]; - (I) -> ["_",I] - end, - List = lists:append([Fun(I) || I <- Integers]), - CoreFile = if list(CoreDir) -> - filename:absname(lists:concat(["MnesiaCore.", node()] ++ List), - CoreDir); - true -> - filename:absname(lists:concat(["MnesiaCore.", node()] ++ List)) - end, - case file:write_file(CoreFile, BinaryCore) of - ok -> - report_error("(core dumped to file: ~p)~n ** FATAL ** " ++ Format, - [CoreFile] ++ Args); - {error, Reason} -> - report_error("(could not write core file: ~p)~n ** FATAL ** " ++ Format, - [Reason] ++ Args) - end. - - - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_frag.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_frag.erl deleted file mode 100644 index e1f4e96a95..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_frag.erl +++ /dev/null @@ -1,1201 +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_frag.erl,v 1.1 2008/12/17 09:53:38 mikpe Exp $ -%%% -%%%---------------------------------------------------------------------- -%%% Purpose : Support tables so large that they need -%%% to be divided into several fragments. -%%%---------------------------------------------------------------------- - -%header_doc_include - --module(mnesia_frag). --behaviour(mnesia_access). - -%% Callback functions when accessed within an activity --export([ - lock/4, - write/5, delete/5, delete_object/5, - read/5, match_object/5, all_keys/4, - select/5, - index_match_object/6, index_read/6, - foldl/6, foldr/6, - table_info/4 - ]). - -%header_doc_include - --export([ - change_table_frag/2, - remove_node/2, - expand_cstruct/1, - lookup_frag_hash/1, - lookup_foreigners/1, - frag_names/1, - set_frag_hash/2, - local_select/4, - remote_select/4 - ]). - --include("mnesia.hrl"). - --define(OLD_HASH_MOD, mnesia_frag_old_hash). --define(DEFAULT_HASH_MOD, mnesia_frag_hash). -%%-define(DEFAULT_HASH_MOD, ?OLD_HASH_MOD). %% BUGBUG: New should be default - --record(frag_state, - {foreign_key, - n_fragments, - hash_module, - hash_state}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Access functions - -%impl_doc_include - -%% Callback functions which provides transparent -%% access of fragmented tables from any activity -%% access context. - -lock(ActivityId, Opaque, {table , Tab}, LockKind) -> - case frag_names(Tab) of - [Tab] -> - mnesia:lock(ActivityId, Opaque, {table, Tab}, LockKind); - Frags -> - DeepNs = [mnesia:lock(ActivityId, Opaque, {table, F}, LockKind) || - F <- Frags], - mnesia_lib:uniq(lists:append(DeepNs)) - end; - -lock(ActivityId, Opaque, LockItem, LockKind) -> - mnesia:lock(ActivityId, Opaque, LockItem, LockKind). - -write(ActivityId, Opaque, Tab, Rec, LockKind) -> - Frag = record_to_frag_name(Tab, Rec), - mnesia:write(ActivityId, Opaque, Frag, Rec, LockKind). - -delete(ActivityId, Opaque, Tab, Key, LockKind) -> - Frag = key_to_frag_name(Tab, Key), - mnesia:delete(ActivityId, Opaque, Frag, Key, LockKind). - -delete_object(ActivityId, Opaque, Tab, Rec, LockKind) -> - Frag = record_to_frag_name(Tab, Rec), - mnesia:delete_object(ActivityId, Opaque, Frag, Rec, LockKind). - -read(ActivityId, Opaque, Tab, Key, LockKind) -> - Frag = key_to_frag_name(Tab, Key), - mnesia:read(ActivityId, Opaque, Frag, Key, LockKind). - -match_object(ActivityId, Opaque, Tab, HeadPat, LockKind) -> - MatchSpec = [{HeadPat, [], ['$_']}], - select(ActivityId, Opaque, Tab, MatchSpec, LockKind). - -select(ActivityId, Opaque, Tab, MatchSpec, LockKind) -> - do_select(ActivityId, Opaque, Tab, MatchSpec, LockKind). - -all_keys(ActivityId, Opaque, Tab, LockKind) -> - Match = [mnesia:all_keys(ActivityId, Opaque, Frag, LockKind) - || Frag <- frag_names(Tab)], - lists:append(Match). - -index_match_object(ActivityId, Opaque, Tab, Pat, Attr, LockKind) -> - Match = - [mnesia:index_match_object(ActivityId, Opaque, Frag, Pat, Attr, LockKind) - || Frag <- frag_names(Tab)], - lists:append(Match). - -index_read(ActivityId, Opaque, Tab, Key, Attr, LockKind) -> - Match = - [mnesia:index_read(ActivityId, Opaque, Frag, Key, Attr, LockKind) - || Frag <- frag_names(Tab)], - lists:append(Match). - -foldl(ActivityId, Opaque, Fun, Acc, Tab, LockKind) -> - Fun2 = fun(Frag, A) -> - mnesia:foldl(ActivityId, Opaque, Fun, A, Frag, LockKind) - end, - lists:foldl(Fun2, Acc, frag_names(Tab)). - -foldr(ActivityId, Opaque, Fun, Acc, Tab, LockKind) -> - Fun2 = fun(Frag, A) -> - mnesia:foldr(ActivityId, Opaque, Fun, A, Frag, LockKind) - end, - lists:foldr(Fun2, Acc, frag_names(Tab)). - -table_info(ActivityId, Opaque, {Tab, Key}, Item) -> - Frag = key_to_frag_name(Tab, Key), - table_info2(ActivityId, Opaque, Tab, Frag, Item); -table_info(ActivityId, Opaque, Tab, Item) -> - table_info2(ActivityId, Opaque, Tab, Tab, Item). - -table_info2(ActivityId, Opaque, Tab, Frag, Item) -> - case Item of - size -> - SumFun = fun({_, Size}, Acc) -> Acc + Size end, - lists:foldl(SumFun, 0, frag_size(ActivityId, Opaque, Tab)); - memory -> - SumFun = fun({_, Size}, Acc) -> Acc + Size end, - lists:foldl(SumFun, 0, frag_memory(ActivityId, Opaque, Tab)); - base_table -> - lookup_prop(Tab, base_table); - node_pool -> - lookup_prop(Tab, node_pool); - n_fragments -> - FH = lookup_frag_hash(Tab), - FH#frag_state.n_fragments; - foreign_key -> - FH = lookup_frag_hash(Tab), - FH#frag_state.foreign_key; - foreigners -> - lookup_foreigners(Tab); - n_ram_copies -> - length(val({Tab, ram_copies})); - n_disc_copies -> - length(val({Tab, disc_copies})); - n_disc_only_copies -> - length(val({Tab, disc_only_copies})); - - frag_names -> - frag_names(Tab); - frag_dist -> - frag_dist(Tab); - frag_size -> - frag_size(ActivityId, Opaque, Tab); - frag_memory -> - frag_memory(ActivityId, Opaque, Tab); - _ -> - mnesia:table_info(ActivityId, Opaque, Frag, Item) - end. -%impl_doc_include - -frag_size(ActivityId, Opaque, Tab) -> - [{F, remote_table_info(ActivityId, Opaque, F, size)} || F <- frag_names(Tab)]. - -frag_memory(ActivityId, Opaque, Tab) -> - [{F, remote_table_info(ActivityId, Opaque, F, memory)} || F <- frag_names(Tab)]. - - - -remote_table_info(ActivityId, Opaque, Tab, Item) -> - N = val({Tab, where_to_read}), - case rpc:call(N, mnesia, table_info, [ActivityId, Opaque, Tab, Item]) of - {badrpc, _} -> - mnesia:abort({no_exists, Tab, Item}); - Info -> - Info - end. - -do_select(ActivityId, Opaque, Tab, MatchSpec, LockKind) -> - case ?catch_val({Tab, frag_hash}) of - {'EXIT', _} -> - mnesia:select(ActivityId, Opaque, Tab, MatchSpec, LockKind); - FH -> - HashState = FH#frag_state.hash_state, - FragNumbers = - case FH#frag_state.hash_module of - HashMod when HashMod == ?DEFAULT_HASH_MOD -> - ?DEFAULT_HASH_MOD:match_spec_to_frag_numbers(HashState, MatchSpec); - HashMod -> - HashMod:match_spec_to_frag_numbers(HashState, MatchSpec) - end, - N = FH#frag_state.n_fragments, - VerifyFun = fun(F) when integer(F), F >= 1, F =< N -> false; - (_F) -> true - end, - case catch lists:filter(VerifyFun, FragNumbers) of - [] -> - Fun = fun(Num) -> - Name = n_to_frag_name(Tab, Num), - Node = val({Name, where_to_read}), - mnesia:lock(ActivityId, Opaque, {table, Name}, LockKind), - {Name, Node} - end, - NameNodes = lists:map(Fun, FragNumbers), - SelectAllFun = - fun(PatchedMatchSpec) -> - Match = [mnesia:dirty_select(Name, PatchedMatchSpec) - || {Name, _Node} <- NameNodes], - lists:append(Match) - end, - case [{Name, Node} || {Name, Node} <- NameNodes, Node /= node()] of - [] -> - %% All fragments are local - mnesia:fun_select(ActivityId, Opaque, Tab, MatchSpec, none, '_', SelectAllFun); - RemoteNameNodes -> - SelectFun = - fun(PatchedMatchSpec) -> - Ref = make_ref(), - Args = [self(), Ref, RemoteNameNodes, PatchedMatchSpec], - Pid = spawn_link(?MODULE, local_select, Args), - LocalMatch = [mnesia:dirty_select(Name, PatchedMatchSpec) - || {Name, Node} <- NameNodes, Node == node()], - OldSelectFun = fun() -> SelectAllFun(PatchedMatchSpec) end, - local_collect(Ref, Pid, lists:append(LocalMatch), OldSelectFun) - end, - mnesia:fun_select(ActivityId, Opaque, Tab, MatchSpec, none, '_', SelectFun) - end; - BadFrags -> - mnesia:abort({"match_spec_to_frag_numbers: Fragment numbers out of range", - BadFrags, {range, 1, N}}) - end - end. - -local_select(ReplyTo, Ref, RemoteNameNodes, MatchSpec) -> - RemoteNodes = mnesia_lib:uniq([Node || {_Name, Node} <- RemoteNameNodes]), - Args = [ReplyTo, Ref, RemoteNameNodes, MatchSpec], - {Replies, BadNodes} = rpc:multicall(RemoteNodes, ?MODULE, remote_select, Args), - case mnesia_lib:uniq(Replies) -- [ok] of - [] when BadNodes == [] -> - ReplyTo ! {local_select, Ref, ok}; - _ when BadNodes /= [] -> - ReplyTo ! {local_select, Ref, {error, {node_not_running, hd(BadNodes)}}}; - [{badrpc, {'EXIT', Reason}} | _] -> - ReplyTo ! {local_select, Ref, {error, Reason}}; - [Reason | _] -> - ReplyTo ! {local_select, Ref, {error, Reason}} - end, - unlink(ReplyTo), - exit(normal). - -remote_select(ReplyTo, Ref, NameNodes, MatchSpec) -> - do_remote_select(ReplyTo, Ref, NameNodes, MatchSpec). - -do_remote_select(ReplyTo, Ref, [{Name, Node} | NameNodes], MatchSpec) -> - if - Node == node() -> - Res = (catch {ok, mnesia:dirty_select(Name, MatchSpec)}), - ReplyTo ! {remote_select, Ref, Node, Res}, - do_remote_select(ReplyTo, Ref, NameNodes, MatchSpec); - true -> - do_remote_select(ReplyTo, Ref, NameNodes, MatchSpec) - end; -do_remote_select(_ReplyTo, _Ref, [], _MatchSpec) -> - ok. - -local_collect(Ref, Pid, LocalMatch, OldSelectFun) -> - receive - {local_select, Ref, LocalRes} -> - remote_collect(Ref, LocalRes, LocalMatch, OldSelectFun); - {'EXIT', Pid, Reason} -> - remote_collect(Ref, {error, Reason}, [], OldSelectFun) - end. - -remote_collect(Ref, LocalRes = ok, Acc, OldSelectFun) -> - receive - {remote_select, Ref, Node, RemoteRes} -> - case RemoteRes of - {ok, RemoteMatch} -> - remote_collect(Ref, LocalRes, RemoteMatch ++ Acc, OldSelectFun); - _ -> - remote_collect(Ref, {error, {node_not_running, Node}}, [], OldSelectFun) - end - after 0 -> - Acc - end; -remote_collect(Ref, LocalRes = {error, Reason}, _Acc, OldSelectFun) -> - receive - {remote_select, Ref, _Node, _RemoteRes} -> - remote_collect(Ref, LocalRes, [], OldSelectFun) - after 0 -> - mnesia:abort(Reason) - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Returns a list of cstructs - -expand_cstruct(Cs) -> - expand_cstruct(Cs, create). - -expand_cstruct(Cs, Mode) -> - Tab = Cs#cstruct.name, - Props = Cs#cstruct.frag_properties, - mnesia_schema:verify({alt, [nil, list]}, mnesia_lib:etype(Props), - {badarg, Tab, Props}), - %% Verify keys - ValidKeys = [foreign_key, n_fragments, node_pool, - n_ram_copies, n_disc_copies, n_disc_only_copies, - hash_module, hash_state], - Keys = mnesia_schema:check_keys(Tab, Props, ValidKeys), - mnesia_schema:check_duplicates(Tab, Keys), - - %% Pick fragmentation props - ForeignKey = mnesia_schema:pick(Tab, foreign_key, Props, undefined), - {ForeignKey2, N, Pool, DefaultNR, DefaultND, DefaultNDO} = - pick_props(Tab, Cs, ForeignKey), - - %% Verify node_pool - BadPool = {bad_type, Tab, {node_pool, Pool}}, - mnesia_schema:verify(list, mnesia_lib:etype(Pool), BadPool), - NotAtom = fun(A) when atom(A) -> false; - (_A) -> true - end, - mnesia_schema:verify([], [P || P <- Pool, NotAtom(P)], BadPool), - - NR = mnesia_schema:pick(Tab, n_ram_copies, Props, 0), - ND = mnesia_schema:pick(Tab, n_disc_copies, Props, 0), - NDO = mnesia_schema:pick(Tab, n_disc_only_copies, Props, 0), - - PosInt = fun(I) when integer(I), I >= 0 -> true; - (_I) -> false - end, - mnesia_schema:verify(true, PosInt(NR), - {bad_type, Tab, {n_ram_copies, NR}}), - mnesia_schema:verify(true, PosInt(ND), - {bad_type, Tab, {n_disc_copies, ND}}), - mnesia_schema:verify(true, PosInt(NDO), - {bad_type, Tab, {n_disc_only_copies, NDO}}), - - %% Verify n_fragments - Cs2 = verify_n_fragments(N, Cs, Mode), - - %% Verify hash callback - HashMod = mnesia_schema:pick(Tab, hash_module, Props, ?DEFAULT_HASH_MOD), - HashState = mnesia_schema:pick(Tab, hash_state, Props, undefined), - HashState2 = HashMod:init_state(Tab, HashState), %% BUGBUG: Catch? - - FH = #frag_state{foreign_key = ForeignKey2, - n_fragments = 1, - hash_module = HashMod, - hash_state = HashState2}, - if - NR == 0, ND == 0, NDO == 0 -> - do_expand_cstruct(Cs2, FH, N, Pool, DefaultNR, DefaultND, DefaultNDO, Mode); - true -> - do_expand_cstruct(Cs2, FH, N, Pool, NR, ND, NDO, Mode) - end. - -do_expand_cstruct(Cs, FH, N, Pool, NR, ND, NDO, Mode) -> - Tab = Cs#cstruct.name, - - LC = Cs#cstruct.local_content, - mnesia_schema:verify(false, LC, - {combine_error, Tab, {local_content, LC}}), - - Snmp = Cs#cstruct.snmp, - mnesia_schema:verify([], Snmp, - {combine_error, Tab, {snmp, Snmp}}), - - %% Add empty fragments - CommonProps = [{base_table, Tab}], - Cs2 = Cs#cstruct{frag_properties = lists:sort(CommonProps)}, - expand_frag_cstructs(N, NR, ND, NDO, Cs2, Pool, Pool, FH, Mode). - -verify_n_fragments(N, Cs, Mode) when integer(N), N >= 1 -> - case Mode of - create -> - Cs#cstruct{ram_copies = [], - disc_copies = [], - disc_only_copies = []}; - activate -> - Reason = {combine_error, Cs#cstruct.name, {n_fragments, N}}, - mnesia_schema:verify(1, N, Reason), - Cs - end; -verify_n_fragments(N, Cs, _Mode) -> - mnesia:abort({bad_type, Cs#cstruct.name, {n_fragments, N}}). - -pick_props(Tab, Cs, {ForeignTab, Attr}) -> - mnesia_schema:verify(true, ForeignTab /= Tab, - {combine_error, Tab, {ForeignTab, Attr}}), - Props = Cs#cstruct.frag_properties, - Attrs = Cs#cstruct.attributes, - - ForeignKey = lookup_prop(ForeignTab, foreign_key), - ForeignN = lookup_prop(ForeignTab, n_fragments), - ForeignPool = lookup_prop(ForeignTab, node_pool), - N = mnesia_schema:pick(Tab, n_fragments, Props, ForeignN), - Pool = mnesia_schema:pick(Tab, node_pool, Props, ForeignPool), - - mnesia_schema:verify(ForeignN, N, - {combine_error, Tab, {n_fragments, N}, - ForeignTab, {n_fragments, ForeignN}}), - - mnesia_schema:verify(ForeignPool, Pool, - {combine_error, Tab, {node_pool, Pool}, - ForeignTab, {node_pool, ForeignPool}}), - - mnesia_schema:verify(undefined, ForeignKey, - {combine_error, Tab, - "Multiple levels of foreign_key dependencies", - {ForeignTab, Attr}, ForeignKey}), - - Key = {ForeignTab, mnesia_schema:attr_to_pos(Attr, Attrs)}, - DefaultNR = length(val({ForeignTab, ram_copies})), - DefaultND = length(val({ForeignTab, disc_copies})), - DefaultNDO = length(val({ForeignTab, disc_only_copies})), - {Key, N, Pool, DefaultNR, DefaultND, DefaultNDO}; -pick_props(Tab, Cs, undefined) -> - Props = Cs#cstruct.frag_properties, - DefaultN = 1, - DefaultPool = mnesia:system_info(db_nodes), - N = mnesia_schema:pick(Tab, n_fragments, Props, DefaultN), - Pool = mnesia_schema:pick(Tab, node_pool, Props, DefaultPool), - DefaultNR = 1, - DefaultND = 0, - DefaultNDO = 0, - {undefined, N, Pool, DefaultNR, DefaultND, DefaultNDO}; -pick_props(Tab, _Cs, BadKey) -> - mnesia:abort({bad_type, Tab, {foreign_key, BadKey}}). - -expand_frag_cstructs(N, NR, ND, NDO, CommonCs, Dist, Pool, FH, Mode) - when N > 1, Mode == create -> - Frag = n_to_frag_name(CommonCs#cstruct.name, N), - Cs = CommonCs#cstruct{name = Frag}, - {Cs2, RevModDist, RestDist} = set_frag_nodes(NR, ND, NDO, Cs, Dist, []), - ModDist = lists:reverse(RevModDist), - Dist2 = rearrange_dist(Cs, ModDist, RestDist, Pool), - %% Adjusts backwards, but it doesn't matter. - {FH2, _FromFrags, _AdditionalWriteFrags} = adjust_before_split(FH), - CsList = expand_frag_cstructs(N - 1, NR, ND, NDO, CommonCs, Dist2, Pool, FH2, Mode), - [Cs2 | CsList]; -expand_frag_cstructs(1, NR, ND, NDO, CommonCs, Dist, Pool, FH, Mode) -> - BaseProps = CommonCs#cstruct.frag_properties ++ - [{foreign_key, FH#frag_state.foreign_key}, - {hash_module, FH#frag_state.hash_module}, - {hash_state, FH#frag_state.hash_state}, - {n_fragments, FH#frag_state.n_fragments}, - {node_pool, Pool} - ], - BaseCs = CommonCs#cstruct{frag_properties = lists:sort(BaseProps)}, - case Mode of - activate -> - [BaseCs]; - create -> - {BaseCs2, _, _} = set_frag_nodes(NR, ND, NDO, BaseCs, Dist, []), - [BaseCs2] - end. - -set_frag_nodes(NR, ND, NDO, Cs, [Head | Tail], Acc) when NR > 0 -> - Pos = #cstruct.ram_copies, - {Cs2, Head2} = set_frag_node(Cs, Pos, Head), - set_frag_nodes(NR - 1, ND, NDO, Cs2, Tail, [Head2 | Acc]); -set_frag_nodes(NR, ND, NDO, Cs, [Head | Tail], Acc) when ND > 0 -> - Pos = #cstruct.disc_copies, - {Cs2, Head2} = set_frag_node(Cs, Pos, Head), - set_frag_nodes(NR, ND - 1, NDO, Cs2, Tail, [Head2 | Acc]); -set_frag_nodes(NR, ND, NDO, Cs, [Head | Tail], Acc) when NDO > 0 -> - Pos = #cstruct.disc_only_copies, - {Cs2, Head2} = set_frag_node(Cs, Pos, Head), - set_frag_nodes(NR, ND, NDO - 1, Cs2, Tail, [Head2 | Acc]); -set_frag_nodes(0, 0, 0, Cs, RestDist, ModDist) -> - {Cs, ModDist, RestDist}; -set_frag_nodes(_, _, _, Cs, [], _) -> - mnesia:abort({combine_error, Cs#cstruct.name, "Too few nodes in node_pool"}). - -set_frag_node(Cs, Pos, Head) -> - Ns = element(Pos, Cs), - {Node, Count2} = - case Head of - {N, Count} when atom(N), integer(Count), Count >= 0 -> - {N, Count + 1}; - N when atom(N) -> - {N, 1}; - BadNode -> - mnesia:abort({bad_type, Cs#cstruct.name, BadNode}) - end, - Cs2 = setelement(Pos, Cs, [Node | Ns]), - {Cs2, {Node, Count2}}. - -rearrange_dist(Cs, [{Node, Count} | ModDist], Dist, Pool) -> - Dist2 = insert_dist(Cs, Node, Count, Dist, Pool), - rearrange_dist(Cs, ModDist, Dist2, Pool); -rearrange_dist(_Cs, [], Dist, _) -> - Dist. - -insert_dist(Cs, Node, Count, [Head | Tail], Pool) -> - case Head of - {Node2, Count2} when atom(Node2), integer(Count2), Count2 >= 0 -> - case node_diff(Node, Count, Node2, Count2, Pool) of - less -> - [{Node, Count}, Head | Tail]; - greater -> - [Head | insert_dist(Cs, Node, Count, Tail, Pool)] - end; - Node2 when atom(Node2) -> - insert_dist(Cs, Node, Count, [{Node2, 0} | Tail], Pool); - BadNode -> - mnesia:abort({bad_type, Cs#cstruct.name, BadNode}) - end; -insert_dist(_Cs, Node, Count, [], _Pool) -> - [{Node, Count}]; -insert_dist(_Cs, _Node, _Count, Dist, _Pool) -> - mnesia:abort({bad_type, Dist}). - -node_diff(_Node, Count, _Node2, Count2, _Pool) when Count < Count2 -> - less; -node_diff(Node, Count, Node2, Count2, Pool) when Count == Count2 -> - Pos = list_pos(Node, Pool, 1), - Pos2 = list_pos(Node2, Pool, 1), - if - Pos < Pos2 -> - less; - Pos > Pos2 -> - greater - end; -node_diff(_Node, Count, _Node2, Count2, _Pool) when Count > Count2 -> - greater. - -%% Returns position of element in list -list_pos(H, [H | _T], Pos) -> - Pos; -list_pos(E, [_H | T], Pos) -> - list_pos(E, T, Pos + 1). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Switch function for changing of table fragmentation -%% -%% Returns a list of lists of schema ops - -change_table_frag(Tab, {activate, FragProps}) -> - make_activate(Tab, FragProps); -change_table_frag(Tab, deactivate) -> - make_deactivate(Tab); -change_table_frag(Tab, {add_frag, SortedNodes}) -> - make_multi_add_frag(Tab, SortedNodes); -change_table_frag(Tab, del_frag) -> - make_multi_del_frag(Tab); -change_table_frag(Tab, {add_node, Node}) -> - make_multi_add_node(Tab, Node); -change_table_frag(Tab, {del_node, Node}) -> - make_multi_del_node(Tab, Node); -change_table_frag(Tab, Change) -> - mnesia:abort({bad_type, Tab, Change}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Turn a normal table into a fragmented table -%% -%% The storage type must be the same on all nodes - -make_activate(Tab, Props) -> - Cs = mnesia_schema:incr_version(val({Tab, cstruct})), - mnesia_schema:ensure_active(Cs), - case Cs#cstruct.frag_properties of - [] -> - Cs2 = Cs#cstruct{frag_properties = Props}, - [Cs3] = expand_cstruct(Cs2, activate), - TabDef = mnesia_schema:cs2list(Cs3), - Op = {op, change_table_frag, activate, TabDef}, - [[Op]]; - BadProps -> - mnesia:abort({already_exists, Tab, {frag_properties, BadProps}}) - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Turn a table into a normal defragmented table - -make_deactivate(Tab) -> - Cs = mnesia_schema:incr_version(val({Tab, cstruct})), - mnesia_schema:ensure_active(Cs), - Foreigners = lookup_foreigners(Tab), - BaseTab = lookup_prop(Tab, base_table), - FH = lookup_frag_hash(Tab), - if - BaseTab /= Tab -> - mnesia:abort({combine_error, Tab, "Not a base table"}); - Foreigners /= [] -> - mnesia:abort({combine_error, Tab, "Too many foreigners", Foreigners}); - FH#frag_state.n_fragments > 1 -> - mnesia:abort({combine_error, Tab, "Too many fragments"}); - true -> - Cs2 = Cs#cstruct{frag_properties = []}, - TabDef = mnesia_schema:cs2list(Cs2), - Op = {op, change_table_frag, deactivate, TabDef}, - [[Op]] - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Add a fragment to a fragmented table and fill it with half of -%% the records from one of the old fragments - -make_multi_add_frag(Tab, SortedNs) when list(SortedNs) -> - verify_multi(Tab), - Ops = make_add_frag(Tab, SortedNs), - - %% Propagate to foreigners - MoreOps = [make_add_frag(T, SortedNs) || T <- lookup_foreigners(Tab)], - [Ops | MoreOps]; -make_multi_add_frag(Tab, SortedNs) -> - mnesia:abort({bad_type, Tab, SortedNs}). - -verify_multi(Tab) -> - FH = lookup_frag_hash(Tab), - ForeignKey = FH#frag_state.foreign_key, - mnesia_schema:verify(undefined, ForeignKey, - {combine_error, Tab, - "Op only allowed via foreign table", - {foreign_key, ForeignKey}}). - -make_frag_names_and_acquire_locks(Tab, N, FragIndecies, DoNotLockN) -> - mnesia_schema:get_tid_ts_and_lock(Tab, write), - Fun = fun(Index, FN) -> - if - DoNotLockN == true, Index == N -> - Name = n_to_frag_name(Tab, Index), - setelement(Index, FN, Name); - true -> - Name = n_to_frag_name(Tab, Index), - mnesia_schema:get_tid_ts_and_lock(Name, write), - setelement(Index , FN, Name) - end - end, - FragNames = erlang:make_tuple(N, undefined), - lists:foldl(Fun, FragNames, FragIndecies). - -make_add_frag(Tab, SortedNs) -> - Cs = mnesia_schema:incr_version(val({Tab, cstruct})), - mnesia_schema:ensure_active(Cs), - FH = lookup_frag_hash(Tab), - {FH2, FromIndecies, WriteIndecies} = adjust_before_split(FH), - N = FH2#frag_state.n_fragments, - FragNames = make_frag_names_and_acquire_locks(Tab, N, WriteIndecies, true), - NewFrag = element(N, FragNames), - - NR = length(Cs#cstruct.ram_copies), - ND = length(Cs#cstruct.disc_copies), - NDO = length(Cs#cstruct.disc_only_copies), - NewCs = Cs#cstruct{name = NewFrag, - frag_properties = [{base_table, Tab}], - ram_copies = [], - disc_copies = [], - disc_only_copies = []}, - {NewCs2, _, _} = set_frag_nodes(NR, ND, NDO, NewCs, SortedNs, []), - [NewOp] = mnesia_schema:make_create_table(NewCs2), - - SplitOps = split(Tab, FH2, FromIndecies, FragNames, []), - - Cs2 = replace_frag_hash(Cs, FH2), - TabDef = mnesia_schema:cs2list(Cs2), - BaseOp = {op, change_table_frag, {add_frag, SortedNs}, TabDef}, - - [BaseOp, NewOp | SplitOps]. - -replace_frag_hash(Cs, FH) when record(FH, frag_state) -> - Fun = fun(Prop) -> - case Prop of - {n_fragments, _} -> - {true, {n_fragments, FH#frag_state.n_fragments}}; - {hash_module, _} -> - {true, {hash_module, FH#frag_state.hash_module}}; - {hash_state, _} -> - {true, {hash_state, FH#frag_state.hash_state}}; - {next_n_to_split, _} -> - false; - {n_doubles, _} -> - false; - _ -> - true - end - end, - Props = lists:zf(Fun, Cs#cstruct.frag_properties), - Cs#cstruct{frag_properties = Props}. - -%% Adjust table info before split -adjust_before_split(FH) -> - HashState = FH#frag_state.hash_state, - {HashState2, FromFrags, AdditionalWriteFrags} = - case FH#frag_state.hash_module of - HashMod when HashMod == ?DEFAULT_HASH_MOD -> - ?DEFAULT_HASH_MOD:add_frag(HashState); - HashMod -> - HashMod:add_frag(HashState) - end, - N = FH#frag_state.n_fragments + 1, - FromFrags2 = (catch lists:sort(FromFrags)), - UnionFrags = (catch lists:merge(FromFrags2, lists:sort(AdditionalWriteFrags))), - VerifyFun = fun(F) when integer(F), F >= 1, F =< N -> false; - (_F) -> true - end, - case catch lists:filter(VerifyFun, UnionFrags) of - [] -> - FH2 = FH#frag_state{n_fragments = N, - hash_state = HashState2}, - {FH2, FromFrags2, UnionFrags}; - BadFrags -> - mnesia:abort({"add_frag: Fragment numbers out of range", - BadFrags, {range, 1, N}}) - end. - -split(Tab, FH, [SplitN | SplitNs], FragNames, Ops) -> - SplitFrag = element(SplitN, FragNames), - Pat = mnesia:table_info(SplitFrag, wild_pattern), - {_Mod, Tid, Ts} = mnesia_schema:get_tid_ts_and_lock(Tab, none), - Recs = mnesia:match_object(Tid, Ts, SplitFrag, Pat, read), - Ops2 = do_split(FH, SplitN, FragNames, Recs, Ops), - split(Tab, FH, SplitNs, FragNames, Ops2); -split(_Tab, _FH, [], _FragNames, Ops) -> - Ops. - -%% Perform the split of the table -do_split(FH, OldN, FragNames, [Rec | Recs], Ops) -> - Pos = key_pos(FH), - HashKey = element(Pos, Rec), - case key_to_n(FH, HashKey) of - NewN when NewN == OldN -> - %% Keep record in the same fragment. No need to move it. - do_split(FH, OldN, FragNames, Recs, Ops); - NewN -> - case element(NewN, FragNames) of - NewFrag when NewFrag /= undefined -> - OldFrag = element(OldN, FragNames), - Key = element(2, Rec), - NewOid = {NewFrag, Key}, - OldOid = {OldFrag, Key}, - Ops2 = [{op, rec, unknown, {NewOid, [Rec], write}}, - {op, rec, unknown, {OldOid, [OldOid], delete}} | Ops], - do_split(FH, OldN, FragNames, Recs, Ops2); - _NewFrag -> - %% Tried to move record to fragment that not is locked - mnesia:abort({"add_frag: Fragment not locked", NewN}) - end - end; -do_split(_FH, _OldN, _FragNames, [], Ops) -> - Ops. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Delete a fragment from a fragmented table -%% and merge its records with an other fragment - -make_multi_del_frag(Tab) -> - verify_multi(Tab), - Ops = make_del_frag(Tab), - - %% Propagate to foreigners - MoreOps = [make_del_frag(T) || T <- lookup_foreigners(Tab)], - [Ops | MoreOps]. - -make_del_frag(Tab) -> - FH = lookup_frag_hash(Tab), - case FH#frag_state.n_fragments of - N when N > 1 -> - Cs = mnesia_schema:incr_version(val({Tab, cstruct})), - mnesia_schema:ensure_active(Cs), - {FH2, FromIndecies, WriteIndecies} = adjust_before_merge(FH), - FragNames = make_frag_names_and_acquire_locks(Tab, N, WriteIndecies, false), - - MergeOps = merge(Tab, FH2, FromIndecies, FragNames, []), - LastFrag = element(N, FragNames), - [LastOp] = mnesia_schema:make_delete_table(LastFrag, single_frag), - Cs2 = replace_frag_hash(Cs, FH2), - TabDef = mnesia_schema:cs2list(Cs2), - BaseOp = {op, change_table_frag, del_frag, TabDef}, - [BaseOp, LastOp | MergeOps]; - _ -> - %% Cannot remove the last fragment - mnesia:abort({no_exists, Tab}) - end. - -%% Adjust tab info before merge -adjust_before_merge(FH) -> - HashState = FH#frag_state.hash_state, - {HashState2, FromFrags, AdditionalWriteFrags} = - case FH#frag_state.hash_module of - HashMod when HashMod == ?DEFAULT_HASH_MOD -> - ?DEFAULT_HASH_MOD:del_frag(HashState); - HashMod -> - HashMod:del_frag(HashState) - end, - N = FH#frag_state.n_fragments, - FromFrags2 = (catch lists:sort(FromFrags)), - UnionFrags = (catch lists:merge(FromFrags2, lists:sort(AdditionalWriteFrags))), - VerifyFun = fun(F) when integer(F), F >= 1, F =< N -> false; - (_F) -> true - end, - case catch lists:filter(VerifyFun, UnionFrags) of - [] -> - case lists:member(N, FromFrags2) of - true -> - FH2 = FH#frag_state{n_fragments = N - 1, - hash_state = HashState2}, - {FH2, FromFrags2, UnionFrags}; - false -> - mnesia:abort({"del_frag: Last fragment number not included", N}) - end; - BadFrags -> - mnesia:abort({"del_frag: Fragment numbers out of range", - BadFrags, {range, 1, N}}) - end. - -merge(Tab, FH, [FromN | FromNs], FragNames, Ops) -> - FromFrag = element(FromN, FragNames), - Pat = mnesia:table_info(FromFrag, wild_pattern), - {_Mod, Tid, Ts} = mnesia_schema:get_tid_ts_and_lock(Tab, none), - Recs = mnesia:match_object(Tid, Ts, FromFrag, Pat, read), - Ops2 = do_merge(FH, FromN, FragNames, Recs, Ops), - merge(Tab, FH, FromNs, FragNames, Ops2); -merge(_Tab, _FH, [], _FragNames, Ops) -> - Ops. - -%% Perform the merge of the table -do_merge(FH, OldN, FragNames, [Rec | Recs], Ops) -> - Pos = key_pos(FH), - LastN = FH#frag_state.n_fragments + 1, - HashKey = element(Pos, Rec), - case key_to_n(FH, HashKey) of - NewN when NewN == LastN -> - %% Tried to leave a record in the fragment that is to be deleted - mnesia:abort({"del_frag: Fragment number out of range", - NewN, {range, 1, LastN}}); - NewN when NewN == OldN -> - %% Keep record in the same fragment. No need to move it. - do_merge(FH, OldN, FragNames, Recs, Ops); - NewN when OldN == LastN -> - %% Move record from the fragment that is to be deleted - %% No need to create a delete op for each record. - case element(NewN, FragNames) of - NewFrag when NewFrag /= undefined -> - Key = element(2, Rec), - NewOid = {NewFrag, Key}, - Ops2 = [{op, rec, unknown, {NewOid, [Rec], write}} | Ops], - do_merge(FH, OldN, FragNames, Recs, Ops2); - _NewFrag -> - %% Tried to move record to fragment that not is locked - mnesia:abort({"del_frag: Fragment not locked", NewN}) - end; - NewN -> - case element(NewN, FragNames) of - NewFrag when NewFrag /= undefined -> - OldFrag = element(OldN, FragNames), - Key = element(2, Rec), - NewOid = {NewFrag, Key}, - OldOid = {OldFrag, Key}, - Ops2 = [{op, rec, unknown, {NewOid, [Rec], write}}, - {op, rec, unknown, {OldOid, [OldOid], delete}} | Ops], - do_merge(FH, OldN, FragNames, Recs, Ops2); - _NewFrag -> - %% Tried to move record to fragment that not is locked - mnesia:abort({"del_frag: Fragment not locked", NewN}) - end - end; - do_merge(_FH, _OldN, _FragNames, [], Ops) -> - Ops. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Add a node to the node pool of a fragmented table - -make_multi_add_node(Tab, Node) -> - verify_multi(Tab), - Ops = make_add_node(Tab, Node), - - %% Propagate to foreigners - MoreOps = [make_add_node(T, Node) || T <- lookup_foreigners(Tab)], - [Ops | MoreOps]. - -make_add_node(Tab, Node) when atom(Node) -> - Pool = lookup_prop(Tab, node_pool), - case lists:member(Node, Pool) of - false -> - Cs = mnesia_schema:incr_version(val({Tab, cstruct})), - Pool2 = Pool ++ [Node], - Props = Cs#cstruct.frag_properties, - Props2 = lists:keyreplace(node_pool, 1, Props, {node_pool, Pool2}), - Cs2 = Cs#cstruct{frag_properties = Props2}, - TabDef = mnesia_schema:cs2list(Cs2), - Op = {op, change_table_frag, {add_node, Node}, TabDef}, - [Op]; - true -> - mnesia:abort({already_exists, Tab, Node}) - end; -make_add_node(Tab, Node) -> - mnesia:abort({bad_type, Tab, Node}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Delet a node from the node pool of a fragmented table - -make_multi_del_node(Tab, Node) -> - verify_multi(Tab), - Ops = make_del_node(Tab, Node), - - %% Propagate to foreigners - MoreOps = [make_del_node(T, Node) || T <- lookup_foreigners(Tab)], - [Ops | MoreOps]. - -make_del_node(Tab, Node) when atom(Node) -> - Cs = mnesia_schema:incr_version(val({Tab, cstruct})), - mnesia_schema:ensure_active(Cs), - Pool = lookup_prop(Tab, node_pool), - case lists:member(Node, Pool) of - true -> - Pool2 = Pool -- [Node], - Props = lists:keyreplace(node_pool, 1, Cs#cstruct.frag_properties, {node_pool, Pool2}), - Cs2 = Cs#cstruct{frag_properties = Props}, - TabDef = mnesia_schema:cs2list(Cs2), - Op = {op, change_table_frag, {del_node, Node}, TabDef}, - [Op]; - false -> - mnesia:abort({no_exists, Tab, Node}) - end; -make_del_node(Tab, Node) -> - mnesia:abort({bad_type, Tab, Node}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Special case used to remove all references to a node during -%% mnesia:del_table_copy(schema, Node) - -remove_node(Node, Cs) -> - Tab = Cs#cstruct.name, - case is_top_frag(Tab) of - false -> - {Cs, false}; - true -> - Pool = lookup_prop(Tab, node_pool), - case lists:member(Node, Pool) of - true -> - Pool2 = Pool -- [Node], - Props = lists:keyreplace(node_pool, 1, - Cs#cstruct.frag_properties, - {node_pool, Pool2}), - {Cs#cstruct{frag_properties = Props}, true}; - false -> - {Cs, false} - end - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Helpers - -val(Var) -> - case ?catch_val(Var) of - {'EXIT', Reason} -> mnesia_lib:other_val(Var, Reason); - Value -> Value - end. - -set_frag_hash(Tab, Props) -> - case props_to_frag_hash(Tab, Props) of - FH when record(FH, frag_state) -> - mnesia_lib:set({Tab, frag_hash}, FH); - no_hash -> - mnesia_lib:unset({Tab, frag_hash}) - end. - -props_to_frag_hash(_Tab, []) -> - no_hash; -props_to_frag_hash(Tab, Props) -> - case mnesia_schema:pick(Tab, base_table, Props, undefined) of - T when T == Tab -> - Foreign = mnesia_schema:pick(Tab, foreign_key, Props, must), - N = mnesia_schema:pick(Tab, n_fragments, Props, must), - - case mnesia_schema:pick(Tab, hash_module, Props, undefined) of - undefined -> - Split = mnesia_schema:pick(Tab, next_n_to_split, Props, must), - Doubles = mnesia_schema:pick(Tab, n_doubles, Props, must), - FH = {frag_hash, Foreign, N, Split, Doubles}, - HashState = ?OLD_HASH_MOD:init_state(Tab, FH), - #frag_state{foreign_key = Foreign, - n_fragments = N, - hash_module = ?OLD_HASH_MOD, - hash_state = HashState}; - HashMod -> - HashState = mnesia_schema:pick(Tab, hash_state, Props, must), - #frag_state{foreign_key = Foreign, - n_fragments = N, - hash_module = HashMod, - hash_state = HashState} - %% Old style. Kept for backwards compatibility. - end; - _ -> - no_hash - end. - -lookup_prop(Tab, Prop) -> - Props = val({Tab, frag_properties}), - case lists:keysearch(Prop, 1, Props) of - {value, {Prop, Val}} -> - Val; - false -> - mnesia:abort({no_exists, Tab, Prop, {frag_properties, Props}}) - end. - -lookup_frag_hash(Tab) -> - case ?catch_val({Tab, frag_hash}) of - FH when record(FH, frag_state) -> - FH; - {frag_hash, K, N, _S, _D} = FH -> - %% Old style. Kept for backwards compatibility. - HashState = ?OLD_HASH_MOD:init_state(Tab, FH), - #frag_state{foreign_key = K, - n_fragments = N, - hash_module = ?OLD_HASH_MOD, - hash_state = HashState}; - {'EXIT', _} -> - mnesia:abort({no_exists, Tab, frag_properties, frag_hash}) - end. - -is_top_frag(Tab) -> - case ?catch_val({Tab, frag_hash}) of - {'EXIT', _} -> - false; - _ -> - [] == lookup_foreigners(Tab) - end. - -%% Returns a list of tables -lookup_foreigners(Tab) -> - %% First field in HashPat is either frag_hash or frag_state - HashPat = {'_', {Tab, '_'}, '_', '_', '_'}, - [T || [T] <- ?ets_match(mnesia_gvar, {{'$1', frag_hash}, HashPat})]. - -%% Returns name of fragment table -record_to_frag_name(Tab, Rec) -> - case ?catch_val({Tab, frag_hash}) of - {'EXIT', _} -> - Tab; - FH -> - Pos = key_pos(FH), - Key = element(Pos, Rec), - N = key_to_n(FH, Key), - n_to_frag_name(Tab, N) - end. - -key_pos(FH) -> - case FH#frag_state.foreign_key of - undefined -> - 2; - {_ForeignTab, Pos} -> - Pos - end. - -%% Returns name of fragment table -key_to_frag_name({BaseTab, _} = Tab, Key) -> - N = key_to_frag_number(Tab, Key), - n_to_frag_name(BaseTab, N); -key_to_frag_name(Tab, Key) -> - N = key_to_frag_number(Tab, Key), - n_to_frag_name(Tab, N). - -%% Returns name of fragment table -n_to_frag_name(Tab, 1) -> - Tab; -n_to_frag_name(Tab, N) when atom(Tab), integer(N) -> - list_to_atom(atom_to_list(Tab) ++ "_frag" ++ integer_to_list(N)); -n_to_frag_name(Tab, N) -> - mnesia:abort({bad_type, Tab, N}). - -%% Returns name of fragment table -key_to_frag_number({Tab, ForeignKey}, _Key) -> - FH = val({Tab, frag_hash}), - case FH#frag_state.foreign_key of - {_ForeignTab, _Pos} -> - key_to_n(FH, ForeignKey); - undefined -> - mnesia:abort({combine_error, Tab, frag_properties, - {foreign_key, undefined}}) - end; -key_to_frag_number(Tab, Key) -> - case ?catch_val({Tab, frag_hash}) of - {'EXIT', _} -> - 1; - FH -> - key_to_n(FH, Key) - end. - -%% Returns fragment number -key_to_n(FH, Key) -> - HashState = FH#frag_state.hash_state, - N = - case FH#frag_state.hash_module of - HashMod when HashMod == ?DEFAULT_HASH_MOD -> - ?DEFAULT_HASH_MOD:key_to_frag_number(HashState, Key); - HashMod -> - HashMod:key_to_frag_number(HashState, Key) - end, - if - integer(N), N >= 1, N =< FH#frag_state.n_fragments -> - N; - true -> - mnesia:abort({"key_to_frag_number: Fragment number out of range", - N, {range, 1, FH#frag_state.n_fragments}}) - end. - -%% Returns a list of frament table names -frag_names(Tab) -> - case ?catch_val({Tab, frag_hash}) of - {'EXIT', _} -> - [Tab]; - FH -> - N = FH#frag_state.n_fragments, - frag_names(Tab, N, []) - end. - -frag_names(Tab, 1, Acc) -> - [Tab | Acc]; -frag_names(Tab, N, Acc) -> - Frag = n_to_frag_name(Tab, N), - frag_names(Tab, N - 1, [Frag | Acc]). - -%% Returns a list of {Node, FragCount} tuples -%% sorted on FragCounts -frag_dist(Tab) -> - Pool = lookup_prop(Tab, node_pool), - Dist = [{good, Node, 0} || Node <- Pool], - Dist2 = count_frag(frag_names(Tab), Dist), - sort_dist(Dist2). - -count_frag([Frag | Frags], Dist) -> - Dist2 = incr_nodes(val({Frag, ram_copies}), Dist), - Dist3 = incr_nodes(val({Frag, disc_copies}), Dist2), - Dist4 = incr_nodes(val({Frag, disc_only_copies}), Dist3), - count_frag(Frags, Dist4); -count_frag([], Dist) -> - Dist. - -incr_nodes([Node | Nodes], Dist) -> - Dist2 = incr_node(Node, Dist), - incr_nodes(Nodes, Dist2); -incr_nodes([], Dist) -> - Dist. - -incr_node(Node, [{Kind, Node, Count} | Tail]) -> - [{Kind, Node, Count + 1} | Tail]; -incr_node(Node, [Head | Tail]) -> - [Head | incr_node(Node, Tail)]; -incr_node(Node, []) -> - [{bad, Node, 1}]. - -%% Sorts dist according in decreasing count order -sort_dist(Dist) -> - Dist2 = deep_dist(Dist, []), - Dist3 = lists:keysort(1, Dist2), - shallow_dist(Dist3). - -deep_dist([Head | Tail], Deep) -> - {Kind, _Node, Count} = Head, - {Tag, Same, Other} = pick_count(Kind, Count, [Head | Tail]), - deep_dist(Other, [{Tag, Same} | Deep]); -deep_dist([], Deep) -> - Deep. - -pick_count(Kind, Count, [{Kind2, Node2, Count2} | Tail]) -> - Head = {Node2, Count2}, - {_, Same, Other} = pick_count(Kind, Count, Tail), - if - Kind == bad -> - {bad, [Head | Same], Other}; - Kind2 == bad -> - {Count, Same, [{Kind2, Node2, Count2} | Other]}; - Count == Count2 -> - {Count, [Head | Same], Other}; - true -> - {Count, Same, [{Kind2, Node2, Count2} | Other]} - end; -pick_count(_Kind, Count, []) -> - {Count, [], []}. - -shallow_dist([{_Tag, Shallow} | Deep]) -> - Shallow ++ shallow_dist(Deep); -shallow_dist([]) -> - []. diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_frag_hash.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_frag_hash.erl deleted file mode 100644 index 19b97f8d61..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_frag_hash.erl +++ /dev/null @@ -1,118 +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_frag_hash.erl,v 1.1 2008/12/17 09:53:38 mikpe Exp $ -%% -%%%---------------------------------------------------------------------- -%%% Purpose : Implements hashing functionality for fragmented tables -%%%---------------------------------------------------------------------- - -%header_doc_include --module(mnesia_frag_hash). --behaviour(mnesia_frag_hash). - -%% Fragmented Table Hashing callback functions --export([ - init_state/2, - add_frag/1, - del_frag/1, - key_to_frag_number/2, - match_spec_to_frag_numbers/2 - ]). - -%header_doc_include - -%impl_doc_include --record(hash_state, {n_fragments, next_n_to_split, n_doubles}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -init_state(_Tab, State) when State == undefined -> - #hash_state{n_fragments = 1, - next_n_to_split = 1, - n_doubles = 0}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -add_frag(State) when record(State, hash_state) -> - SplitN = State#hash_state.next_n_to_split, - P = SplitN + 1, - L = State#hash_state.n_doubles, - NewN = State#hash_state.n_fragments + 1, - State2 = case trunc(math:pow(2, L)) + 1 of - P2 when P2 == P -> - State#hash_state{n_fragments = NewN, - n_doubles = L + 1, - next_n_to_split = 1}; - _ -> - State#hash_state{n_fragments = NewN, - next_n_to_split = P} - end, - {State2, [SplitN], [NewN]}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -del_frag(State) when record(State, hash_state) -> - P = State#hash_state.next_n_to_split - 1, - L = State#hash_state.n_doubles, - N = State#hash_state.n_fragments, - if - P < 1 -> - L2 = L - 1, - MergeN = trunc(math:pow(2, L2)), - State2 = State#hash_state{n_fragments = N - 1, - next_n_to_split = MergeN, - n_doubles = L2}, - {State2, [N], [MergeN]}; - true -> - MergeN = P, - State2 = State#hash_state{n_fragments = N - 1, - next_n_to_split = MergeN}, - {State2, [N], [MergeN]} - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -key_to_frag_number(State, Key) when record(State, hash_state) -> - L = State#hash_state.n_doubles, - A = erlang:phash(Key, trunc(math:pow(2, L))), - P = State#hash_state.next_n_to_split, - if - A < P -> - erlang:phash(Key, trunc(math:pow(2, L + 1))); - true -> - A - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -match_spec_to_frag_numbers(State, MatchSpec) when record(State, hash_state) -> - case MatchSpec of - [{HeadPat, _, _}] when tuple(HeadPat), size(HeadPat) > 2 -> - KeyPat = element(2, HeadPat), - case has_var(KeyPat) of - false -> - [key_to_frag_number(State, KeyPat)]; - true -> - lists:seq(1, State#hash_state.n_fragments) - end; - _ -> - lists:seq(1, State#hash_state.n_fragments) - end. - -%impl_doc_include - -has_var(Pat) -> - mnesia:has_var(Pat). diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_frag_old_hash.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_frag_old_hash.erl deleted file mode 100644 index 6560613302..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_frag_old_hash.erl +++ /dev/null @@ -1,127 +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_frag_old_hash.erl,v 1.1 2008/12/17 09:53:38 mikpe Exp $ -%% -%%%---------------------------------------------------------------------- -%%% Purpose : Implements hashing functionality for fragmented tables -%%%---------------------------------------------------------------------- - --module(mnesia_frag_old_hash). --behaviour(mnesia_frag_hash). - -%% Hashing callback functions --export([ - init_state/2, - add_frag/1, - del_frag/1, - key_to_frag_number/2, - match_spec_to_frag_numbers/2 - ]). - --record(old_hash_state, - {n_fragments, - next_n_to_split, - n_doubles}). - -%% Old style. Kept for backwards compatibility. --record(frag_hash, - {foreign_key, - n_fragments, - next_n_to_split, - n_doubles}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -init_state(_Tab, InitialState) when InitialState == undefined -> - #old_hash_state{n_fragments = 1, - next_n_to_split = 1, - n_doubles = 0}; -init_state(_Tab, FH) when record(FH, frag_hash) -> - %% Old style. Kept for backwards compatibility. - #old_hash_state{n_fragments = FH#frag_hash.n_fragments, - next_n_to_split = FH#frag_hash.next_n_to_split, - n_doubles = FH#frag_hash.n_doubles}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -add_frag(State) when record(State, old_hash_state) -> - SplitN = State#old_hash_state.next_n_to_split, - P = SplitN + 1, - L = State#old_hash_state.n_doubles, - NewN = State#old_hash_state.n_fragments + 1, - State2 = case trunc(math:pow(2, L)) + 1 of - P2 when P2 == P -> - State#old_hash_state{n_fragments = NewN, - next_n_to_split = 1, - n_doubles = L + 1}; - _ -> - State#old_hash_state{n_fragments = NewN, - next_n_to_split = P} - end, - {State2, [SplitN], [NewN]}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -del_frag(State) when record(State, old_hash_state) -> - P = State#old_hash_state.next_n_to_split - 1, - L = State#old_hash_state.n_doubles, - N = State#old_hash_state.n_fragments, - if - P < 1 -> - L2 = L - 1, - MergeN = trunc(math:pow(2, L2)), - State2 = State#old_hash_state{n_fragments = N - 1, - next_n_to_split = MergeN, - n_doubles = L2}, - {State2, [N], [MergeN]}; - true -> - MergeN = P, - State2 = State#old_hash_state{n_fragments = N - 1, - next_n_to_split = MergeN}, - {State2, [N], [MergeN]} - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -key_to_frag_number(State, Key) when record(State, old_hash_state) -> - L = State#old_hash_state.n_doubles, - A = erlang:hash(Key, trunc(math:pow(2, L))), - P = State#old_hash_state.next_n_to_split, - if - A < P -> - erlang:hash(Key, trunc(math:pow(2, L + 1))); - true -> - A - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -match_spec_to_frag_numbers(State, MatchSpec) when record(State, old_hash_state) -> - case MatchSpec of - [{HeadPat, _, _}] when tuple(HeadPat), size(HeadPat) > 2 -> - KeyPat = element(2, HeadPat), - case has_var(KeyPat) of - false -> - [key_to_frag_number(State, KeyPat)]; - true -> - lists:seq(1, State#old_hash_state.n_fragments) - end; - _ -> - lists:seq(1, State#old_hash_state.n_fragments) - end. - -has_var(Pat) -> - mnesia:has_var(Pat). diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_index.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_index.erl deleted file mode 100644 index 3455a4808a..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_index.erl +++ /dev/null @@ -1,380 +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_index.erl,v 1.1 2008/12/17 09:53:38 mikpe Exp $ -%% -%% Purpose: Handles index functionality in mnesia - --module(mnesia_index). --export([read/5, - add_index/5, - delete_index/3, - del_object_index/5, - clear_index/4, - dirty_match_object/3, - dirty_select/3, - dirty_read/3, - dirty_read2/3, - - db_put/2, - db_get/2, - db_match_erase/2, - get_index_table/2, - get_index_table/3, - - tab2filename/2, - tab2tmp_filename/2, - init_index/2, - init_indecies/3, - del_transient/2, - del_transient/3, - del_index_table/3]). - --import(mnesia_lib, [verbose/2]). --include("mnesia.hrl"). - --record(index, {setorbag, pos_list}). - -val(Var) -> - case ?catch_val(Var) of - {'EXIT', _ReASoN_} -> mnesia_lib:other_val(Var, _ReASoN_); - _VaLuE_ -> _VaLuE_ - end. - -%% read an object list throuh its index table -%% we assume that table Tab has index on attribute number Pos - -read(Tid, Store, Tab, IxKey, Pos) -> - ResList = mnesia_locker:ixrlock(Tid, Store, Tab, IxKey, Pos), - %% Remove all tuples which don't include Ixkey, happens when Tab is a bag - case val({Tab, setorbag}) of - bag -> - mnesia_lib:key_search_all(IxKey, Pos, ResList); - _ -> - ResList - end. - -add_index(Index, Tab, Key, Obj, Old) -> - add_index2(Index#index.pos_list, Index#index.setorbag, Tab, Key, Obj, Old). - -add_index2([{Pos, Ixt} |Tail], bag, Tab, K, Obj, OldRecs) -> - db_put(Ixt, {element(Pos, Obj), K}), - add_index2(Tail, bag, Tab, K, Obj, OldRecs); -add_index2([{Pos, Ixt} |Tail], Type, Tab, K, Obj, OldRecs) -> - %% Remove old tuples in index if Tab is updated - case OldRecs of - undefined -> - Old = mnesia_lib:db_get(Tab, K), - del_ixes(Ixt, Old, Pos, K); - Old -> - del_ixes(Ixt, Old, Pos, K) - end, - db_put(Ixt, {element(Pos, Obj), K}), - add_index2(Tail, Type, Tab, K, Obj, OldRecs); -add_index2([], _, _Tab, _K, _Obj, _) -> ok. - -delete_index(Index, Tab, K) -> - delete_index2(Index#index.pos_list, Tab, K). - -delete_index2([{Pos, Ixt} | Tail], Tab, K) -> - DelObjs = mnesia_lib:db_get(Tab, K), - del_ixes(Ixt, DelObjs, Pos, K), - delete_index2(Tail, Tab, K); -delete_index2([], _Tab, _K) -> ok. - - -del_ixes(_Ixt, [], _Pos, _L) -> ok; -del_ixes(Ixt, [Obj | Tail], Pos, Key) -> - db_match_erase(Ixt, {element(Pos, Obj), Key}), - del_ixes(Ixt, Tail, Pos, Key). - -del_object_index(Index, Tab, K, Obj, Old) -> - del_object_index2(Index#index.pos_list, Index#index.setorbag, Tab, K, Obj, Old). - -del_object_index2([], _, _Tab, _K, _Obj, _Old) -> ok; -del_object_index2([{Pos, Ixt} | Tail], SoB, Tab, K, Obj, Old) -> - case SoB of - bag -> - del_object_bag(Tab, K, Obj, Pos, Ixt, Old); - _ -> %% If set remove the tuple in index table - del_ixes(Ixt, [Obj], Pos, K) - end, - del_object_index2(Tail, SoB, Tab, K, Obj, Old). - -del_object_bag(Tab, Key, Obj, Pos, Ixt, undefined) -> - Old = mnesia_lib:db_get(Tab, Key), - del_object_bag(Tab, Key, Obj, Pos, Ixt, Old); -%% If Tab type is bag we need remove index identifier if Tab -%% contains less than 2 elements. -del_object_bag(_Tab, Key, Obj, Pos, Ixt, Old) when length(Old) < 2 -> - del_ixes(Ixt, [Obj], Pos, Key); -del_object_bag(_Tab, _Key, _Obj, _Pos, _Ixt, _Old) -> ok. - -clear_index(Index, Tab, K, Obj) -> - clear_index2(Index#index.pos_list, Tab, K, Obj). - -clear_index2([], _Tab, _K, _Obj) -> ok; -clear_index2([{_Pos, Ixt} | Tail], Tab, K, Obj) -> - db_match_erase(Ixt, Obj), - clear_index2(Tail, Tab, K, Obj). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -dirty_match_object(Tab, Pat, Pos) -> - %% Assume that we are on the node where the replica is - case element(2, Pat) of - '_' -> - IxKey = element(Pos, Pat), - RealKeys = realkeys(Tab, Pos, IxKey), - merge(RealKeys, Tab, Pat, []); - _Else -> - mnesia_lib:db_match_object(Tab, Pat) - end. - -merge([{_IxKey, RealKey} | Tail], Tab, Pat, Ack) -> - %% Assume that we are on the node where the replica is - Pat2 = setelement(2, Pat, RealKey), - Recs = mnesia_lib:db_match_object(Tab, Pat2), - merge(Tail, Tab, Pat, Recs ++ Ack); -merge([], _, _, Ack) -> - Ack. - -realkeys(Tab, Pos, IxKey) -> - Index = get_index_table(Tab, Pos), - db_get(Index, IxKey). % a list on the form [{IxKey, RealKey1} , .... - -dirty_select(Tab, Spec, Pos) -> - %% Assume that we are on the node where the replica is - %% Returns the records without applying the match spec - %% The actual filtering is handled by the caller - IxKey = element(Pos, Spec), - RealKeys = realkeys(Tab, Pos, IxKey), - StorageType = val({Tab, storage_type}), - lists:append([mnesia_lib:db_get(StorageType, Tab, Key) || Key <- RealKeys]). - -dirty_read(Tab, IxKey, Pos) -> - ResList = mnesia:dirty_rpc(Tab, ?MODULE, dirty_read2, - [Tab, IxKey, Pos]), - case val({Tab, setorbag}) of - bag -> - %% Remove all tuples which don't include Ixkey - mnesia_lib:key_search_all(IxKey, Pos, ResList); - _ -> - ResList - end. - -dirty_read2(Tab, IxKey, Pos) -> - Ix = get_index_table(Tab, Pos), - Keys = db_match(Ix, {IxKey, '$1'}), - r_keys(Keys, Tab, []). - -r_keys([[H]|T],Tab,Ack) -> - V = mnesia_lib:db_get(Tab, H), - r_keys(T, Tab, V ++ Ack); -r_keys([], _, Ack) -> - Ack. - - -%%%%%%% Creation, Init and deletion routines for index tables -%% We can have several indexes on the same table -%% this can be a fairly costly operation if table is *very* large - -tab2filename(Tab, Pos) -> - mnesia_lib:dir(Tab) ++ "_" ++ integer_to_list(Pos) ++ ".DAT". - -tab2tmp_filename(Tab, Pos) -> - mnesia_lib:dir(Tab) ++ "_" ++ integer_to_list(Pos) ++ ".TMP". - -init_index(Tab, Storage) -> - PosList = val({Tab, index}), - init_indecies(Tab, Storage, PosList). - -init_indecies(Tab, Storage, PosList) -> - case Storage of - unknown -> - ignore; - disc_only_copies -> - init_disc_index(Tab, PosList); - ram_copies -> - make_ram_index(Tab, PosList); - disc_copies -> - make_ram_index(Tab, PosList) - end. - -%% works for both ram and disc indexes - -del_index_table(_, unknown, _) -> - ignore; -del_index_table(Tab, Storage, Pos) -> - delete_transient_index(Tab, Pos, Storage), - mnesia_lib:del({Tab, index}, Pos). - -del_transient(Tab, Storage) -> - PosList = val({Tab, index}), - del_transient(Tab, PosList, Storage). - -del_transient(_, [], _) -> done; -del_transient(Tab, [Pos | Tail], Storage) -> - delete_transient_index(Tab, Pos, Storage), - del_transient(Tab, Tail, Storage). - -delete_transient_index(Tab, Pos, disc_only_copies) -> - Tag = {Tab, index, Pos}, - mnesia_monitor:unsafe_close_dets(Tag), - file:delete(tab2filename(Tab, Pos)), - del_index_info(Tab, Pos), %% Uses val(..) - mnesia_lib:unset({Tab, {index, Pos}}); - -delete_transient_index(Tab, Pos, _Storage) -> - Ixt = val({Tab, {index, Pos}}), - ?ets_delete_table(Ixt), - del_index_info(Tab, Pos), - mnesia_lib:unset({Tab, {index, Pos}}). - -%%%%% misc functions for the index create/init/delete functions above - -%% assuming that the file exists. -init_disc_index(_Tab, []) -> - done; -init_disc_index(Tab, [Pos | Tail]) when integer(Pos) -> - Fn = tab2filename(Tab, Pos), - IxTag = {Tab, index, Pos}, - file:delete(Fn), - Args = [{file, Fn}, {keypos, 1}, {type, bag}], - mnesia_monitor:open_dets(IxTag, Args), - Storage = disc_only_copies, - Key = mnesia_lib:db_first(Storage, Tab), - Recs = mnesia_lib:db_get(Storage, Tab, Key), - BinSize = size(term_to_binary(Recs)), - KeysPerChunk = (4000 div BinSize) + 1, - Init = {start, KeysPerChunk}, - mnesia_lib:db_fixtable(Storage, Tab, true), - ok = dets:init_table(IxTag, create_fun(Init, Tab, Pos)), - mnesia_lib:db_fixtable(Storage, Tab, false), - mnesia_lib:set({Tab, {index, Pos}}, IxTag), - add_index_info(Tab, val({Tab, setorbag}), {Pos, {dets, IxTag}}), - init_disc_index(Tab, Tail). - -create_fun(Cont, Tab, Pos) -> - fun(read) -> - Data = - case Cont of - {start, KeysPerChunk} -> - mnesia_lib:db_init_chunk(disc_only_copies, Tab, KeysPerChunk); - '$end_of_table' -> - '$end_of_table'; - _Else -> - mnesia_lib:db_chunk(disc_only_copies, Cont) - end, - case Data of - '$end_of_table' -> - end_of_input; - {Recs, Next} -> - IdxElems = [{element(Pos, Obj), element(2, Obj)} || Obj <- Recs], - {IdxElems, create_fun(Next, Tab, Pos)} - end; - (close) -> - ok - end. - -make_ram_index(_, []) -> - done; -make_ram_index(Tab, [Pos | Tail]) -> - add_ram_index(Tab, Pos), - make_ram_index(Tab, Tail). - -add_ram_index(Tab, Pos) when integer(Pos) -> - verbose("Creating index for ~w ~n", [Tab]), - Index = mnesia_monitor:mktab(mnesia_index, [bag, public]), - Insert = fun(Rec, _Acc) -> - true = ?ets_insert(Index, {element(Pos, Rec), element(2, Rec)}) - end, - mnesia_lib:db_fixtable(ram_copies, Tab, true), - true = ets:foldl(Insert, true, Tab), - mnesia_lib:db_fixtable(ram_copies, Tab, false), - mnesia_lib:set({Tab, {index, Pos}}, Index), - add_index_info(Tab, val({Tab, setorbag}), {Pos, {ram, Index}}); -add_ram_index(_Tab, snmp) -> - ok. - -add_index_info(Tab, Type, IxElem) -> - Commit = val({Tab, commit_work}), - case lists:keysearch(index, 1, Commit) of - false -> - Index = #index{setorbag = Type, - pos_list = [IxElem]}, - %% Check later if mnesia_tm is sensative about the order - mnesia_lib:set({Tab, commit_work}, - mnesia_lib:sort_commit([Index | Commit])); - {value, Old} -> - %% We could check for consistency here - Index = Old#index{pos_list = [IxElem | Old#index.pos_list]}, - NewC = lists:keyreplace(index, 1, Commit, Index), - mnesia_lib:set({Tab, commit_work}, - mnesia_lib:sort_commit(NewC)) - end. - -del_index_info(Tab, Pos) -> - Commit = val({Tab, commit_work}), - case lists:keysearch(index, 1, Commit) of - false -> - %% Something is wrong ignore - skip; - {value, Old} -> - case lists:keydelete(Pos, 1, Old#index.pos_list) of - [] -> - NewC = lists:keydelete(index, 1, Commit), - mnesia_lib:set({Tab, commit_work}, - mnesia_lib:sort_commit(NewC)); - New -> - Index = Old#index{pos_list = New}, - NewC = lists:keyreplace(index, 1, Commit, Index), - mnesia_lib:set({Tab, commit_work}, - mnesia_lib:sort_commit(NewC)) - end - end. - -db_put({ram, Ixt}, V) -> - true = ?ets_insert(Ixt, V); -db_put({dets, Ixt}, V) -> - ok = dets:insert(Ixt, V). - -db_get({ram, Ixt}, K) -> - ?ets_lookup(Ixt, K); -db_get({dets, Ixt}, K) -> - dets:lookup(Ixt, K). - -db_match_erase({ram, Ixt}, Pat) -> - true = ?ets_match_delete(Ixt, Pat); -db_match_erase({dets, Ixt}, Pat) -> - ok = dets:match_delete(Ixt, Pat). - -db_match({ram, Ixt}, Pat) -> - ?ets_match(Ixt, Pat); -db_match({dets, Ixt}, Pat) -> - dets:match(Ixt, Pat). - -get_index_table(Tab, Pos) -> - get_index_table(Tab, val({Tab, storage_type}), Pos). - -get_index_table(Tab, ram_copies, Pos) -> - {ram, val({Tab, {index, Pos}})}; -get_index_table(Tab, disc_copies, Pos) -> - {ram, val({Tab, {index, Pos}})}; -get_index_table(Tab, disc_only_copies, Pos) -> - {dets, val({Tab, {index, Pos}})}; -get_index_table(_Tab, unknown, _Pos) -> - unknown. - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_kernel_sup.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_kernel_sup.erl deleted file mode 100644 index 899d434fdd..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_kernel_sup.erl +++ /dev/null @@ -1,62 +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_kernel_sup.erl,v 1.1 2008/12/17 09:53:38 mikpe Exp $ -%% --module(mnesia_kernel_sup). - --behaviour(supervisor). - --export([start/0, init/1, supervisor_timeout/1]). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% top supervisor callback functions - -start() -> - supervisor:start_link({local, mnesia_kernel_sup}, ?MODULE, []). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% sub supervisor callback functions - -init([]) -> - ProcLib = [mnesia_monitor, proc_lib], - Flags = {one_for_all, 0, timer:hours(24)}, % Trust the top supervisor - Workers = [worker_spec(mnesia_monitor, timer:seconds(3), [gen_server]), - worker_spec(mnesia_subscr, timer:seconds(3), [gen_server]), - worker_spec(mnesia_locker, timer:seconds(3), ProcLib), - worker_spec(mnesia_recover, timer:minutes(3), [gen_server]), - worker_spec(mnesia_tm, timer:seconds(30), ProcLib), - supervisor_spec(mnesia_checkpoint_sup), - supervisor_spec(mnesia_snmp_sup), - worker_spec(mnesia_controller, timer:seconds(3), [gen_server]), - worker_spec(mnesia_late_loader, timer:seconds(3), ProcLib) - ], - {ok, {Flags, Workers}}. - -worker_spec(Name, KillAfter, Modules) -> - KA = supervisor_timeout(KillAfter), - {Name, {Name, start, []}, permanent, KA, worker, [Name] ++ Modules}. - -supervisor_spec(Name) -> - {Name, {Name, start, []}, permanent, infinity, supervisor, - [Name, supervisor]}. - --ifdef(debug_shutdown). -supervisor_timeout(_KillAfter) -> timer:hours(24). --else. -supervisor_timeout(KillAfter) -> KillAfter. --endif. - - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_late_loader.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_late_loader.erl deleted file mode 100644 index 96d00f6e81..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_late_loader.erl +++ /dev/null @@ -1,95 +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_late_loader.erl,v 1.1 2008/12/17 09:53:38 mikpe Exp $ -%% --module(mnesia_late_loader). - --export([ - async_late_disc_load/3, - maybe_async_late_disc_load/3, - init/1, - start/0 - ]). - -%% sys callback functions --export([ - system_continue/3, - system_terminate/4, - system_code_change/4 - ]). - --define(SERVER_NAME, ?MODULE). - --record(state, {supervisor}). - -async_late_disc_load(Node, Tabs, Reason) -> - Msg = {async_late_disc_load, Tabs, Reason}, - catch ({?SERVER_NAME, Node} ! {self(), Msg}). - -maybe_async_late_disc_load(Node, Tabs, Reason) -> - Msg = {maybe_async_late_disc_load, Tabs, Reason}, - catch ({?SERVER_NAME, Node} ! {self(), Msg}). - -start() -> - mnesia_monitor:start_proc(?SERVER_NAME, ?MODULE, init, [self()]). - -init(Parent) -> - %% Trap exit omitted intentionally - register(?SERVER_NAME, self()), - link(whereis(mnesia_controller)), %% We may not hang - mnesia_controller:merge_schema(), - unlink(whereis(mnesia_controller)), - mnesia_lib:set(mnesia_status, running), - proc_lib:init_ack(Parent, {ok, self()}), - loop(#state{supervisor = Parent}). - -loop(State) -> - receive - {_From, {async_late_disc_load, Tabs, Reason}} -> - mnesia_controller:schedule_late_disc_load(Tabs, Reason), - loop(State); - - {_From, {maybe_async_late_disc_load, Tabs, Reason}} -> - GoodTabs = - [T || T <- Tabs, - lists:member(node(), - mnesia_recover:get_master_nodes(T))], - mnesia_controller:schedule_late_disc_load(GoodTabs, Reason), - loop(State); - - {system, From, Msg} -> - mnesia_lib:dbg_out("~p got {system, ~p, ~p}~n", - [?SERVER_NAME, From, Msg]), - Parent = State#state.supervisor, - sys:handle_system_msg(Msg, From, Parent, ?MODULE, [], State); - - Msg -> - mnesia_lib:error("~p got unexpected message: ~p~n", - [?SERVER_NAME, Msg]), - loop(State) - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% System upgrade - -system_continue(_Parent, _Debug, State) -> - loop(State). - -system_terminate(Reason, _Parent, _Debug, _State) -> - exit(Reason). - -system_code_change(State, _Module, _OldVsn, _Extra) -> - {ok, State}. diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_lib.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_lib.erl deleted file mode 100644 index 2c9e4d4fcf..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_lib.erl +++ /dev/null @@ -1,1278 +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_lib.erl,v 1.3 2009/07/01 15:45:40 kostis Exp $ -%% -%% This module contains all sorts of various which doesn't fit -%% anywhere else. Basically everything is exported. - --module(mnesia_lib). - --include("mnesia.hrl"). --include_lib("kernel/include/file.hrl"). - --export([core_file/0]). - --export([ - active_tables/0, - add/2, - add_list/2, - all_nodes/0, -%% catch_val/1, - cleanup_tmp_files/1, - copy_file/2, - copy_holders/1, - coredump/0, - coredump/1, - create_counter/1, - cs_to_nodes/1, - cs_to_storage_type/2, - dets_to_ets/6, - db_chunk/2, - db_init_chunk/1, - db_init_chunk/2, - db_init_chunk/3, - db_erase/2, - db_erase/3, - db_erase_tab/1, - db_erase_tab/2, - db_first/1, - db_first/2, - db_last/1, - db_last/2, - db_fixtable/3, - db_get/2, - db_get/3, - db_match_erase/2, - db_match_erase/3, - db_match_object/2, - db_match_object/3, - db_next_key/2, - db_next_key/3, - db_prev_key/2, - db_prev_key/3, - db_put/2, - db_put/3, - db_select/2, - db_select/3, - db_slot/2, - db_slot/3, - db_update_counter/3, - db_update_counter/4, - dbg_out/2, - del/2, - dets_sync_close/1, - dets_sync_open/2, - dets_sync_open/3, - dir/0, - dir/1, - dir_info/0, - dirty_rpc_error_tag/1, - dist_coredump/0, - disk_type/1, - disk_type/2, - elems/2, - ensure_loaded/1, - error/2, - error_desc/1, - etype/1, - exists/1, - fatal/2, - get_node_number/0, - fix_error/1, - important/2, - incr_counter/1, - incr_counter/2, - intersect/2, - is_running/0, - is_running/1, - is_running_remote/0, - is_string/1, - key_search_delete/3, - key_search_all/3, - last_error/0, - local_active_tables/0, - lock_table/1, - mkcore/1, - not_active_here/1, - other_val/2, - pad_name/3, - random_time/2, - read_counter/1, - readable_indecies/1, - remote_copy_holders/1, - report_fatal/2, - report_system_event/1, - running_nodes/0, - running_nodes/1, - schema_cs_to_storage_type/2, - search_delete/2, - set/2, - set_counter/2, - set_local_content_whereabouts/1, - set_remote_where_to_read/1, - set_remote_where_to_read/2, - show/1, - show/2, - sort_commit/1, - storage_type_at_node/2, - swap_tmp_files/1, - tab2dat/1, - tab2dmp/1, - tab2tmp/1, - tab2dcd/1, - tab2dcl/1, - to_list/1, - union/2, - uniq/1, - unlock_table/1, - unset/1, - update_counter/2, - val/1, - vcore/0, - vcore/1, - verbose/2, - view/0, - view/1, - view/2, - warning/2, - - is_debug_compiled/0, - activate_debug_fun/5, - deactivate_debug_fun/3, - eval_debug_fun/4, - scratch_debug_fun/0 - ]). - - -search_delete(Obj, List) -> - search_delete(Obj, List, [], none). -search_delete(Obj, [Obj|Tail], Ack, _Res) -> - search_delete(Obj, Tail, Ack, Obj); -search_delete(Obj, [H|T], Ack, Res) -> - search_delete(Obj, T, [H|Ack], Res); -search_delete(_, [], Ack, Res) -> - {Res, Ack}. - -key_search_delete(Key, Pos, TupleList) -> - key_search_delete(Key, Pos, TupleList, none, []). -key_search_delete(Key, Pos, [H|T], _Obj, Ack) when element(Pos, H) == Key -> - key_search_delete(Key, Pos, T, H, Ack); -key_search_delete(Key, Pos, [H|T], Obj, Ack) -> - key_search_delete(Key, Pos, T, Obj, [H|Ack]); -key_search_delete(_, _, [], Obj, Ack) -> - {Obj, Ack}. - -key_search_all(Key, Pos, TupleList) -> - key_search_all(Key, Pos, TupleList, []). -key_search_all(Key, N, [H|T], Ack) when element(N, H) == Key -> - key_search_all(Key, N, T, [H|Ack]); -key_search_all(Key, N, [_|T], Ack) -> - key_search_all(Key, N, T, Ack); -key_search_all(_, _, [], Ack) -> Ack. - -intersect(L1, L2) -> - L2 -- (L2 -- L1). - -elems(I, [H|T]) -> - [element(I, H) | elems(I, T)]; -elems(_, []) -> - []. - -%% sort_commit see to that checkpoint info is always first in -%% commit_work structure the other info don't need to be sorted. -sort_commit(List) -> - sort_commit2(List, []). - -sort_commit2([{checkpoints, ChkpL}| Rest], Acc) -> - [{checkpoints, ChkpL}| Rest] ++ Acc; -sort_commit2([H | R], Acc) -> - sort_commit2(R, [H | Acc]); -sort_commit2([], Acc) -> Acc. - -is_string([H|T]) -> - if - 0 =< H, H < 256, integer(H) -> is_string(T); - true -> false - end; -is_string([]) -> true. - -%%% - -union([H|L1], L2) -> - case lists:member(H, L2) of - true -> union(L1, L2); - false -> [H | union(L1, L2)] - end; -union([], L2) -> L2. - -uniq([]) -> - []; -uniq(List) -> - [H|T] = lists:sort(List), - uniq1(H, T, []). - -uniq1(H, [H|R], Ack) -> - uniq1(H, R, Ack); -uniq1(Old, [H|R], Ack) -> - uniq1(H, R, [Old|Ack]); -uniq1(Old, [], Ack) -> - [Old| Ack]. - -to_list(X) when list(X) -> X; -to_list(X) -> atom_to_list(X). - -all_nodes() -> - Ns = mnesia:system_info(db_nodes) ++ - mnesia:system_info(extra_db_nodes), - mnesia_lib:uniq(Ns). - -running_nodes() -> - running_nodes(all_nodes()). - -running_nodes(Ns) -> - {Replies, _BadNs} = rpc:multicall(Ns, ?MODULE, is_running_remote, []), - [N || {GoodState, N} <- Replies, GoodState == true]. - -is_running_remote() -> - IsRunning = is_running(), - {IsRunning == yes, node()}. - -is_running(Node) when atom(Node) -> - case rpc:call(Node, ?MODULE, is_running, []) of - {badrpc, _} -> no; - X -> X - end. - -is_running() -> - case ?catch_val(mnesia_status) of - {'EXIT', _} -> no; - running -> yes; - starting -> starting; - stopping -> stopping - end. - -show(X) -> - show(X, []). -show(F, A) -> - io:format(user, F, A). - - -pad_name([Char | Chars], Len, Tail) -> - [Char | pad_name(Chars, Len - 1, Tail)]; -pad_name([], Len, Tail) when Len =< 0 -> - Tail; -pad_name([], Len, Tail) -> - [$ | pad_name([], Len - 1, Tail)]. - -%% Some utility functions ..... -active_here(Tab) -> - case val({Tab, where_to_read}) of - Node when Node == node() -> true; - _ -> false - end. - -not_active_here(Tab) -> - not active_here(Tab). - -exists(Fname) -> - case file:open(Fname, [raw,read]) of - {ok, F} ->file:close(F), true; - _ -> false - end. - -dir() -> mnesia_monitor:get_env(dir). - -dir(Fname) -> - filename:join([dir(), to_list(Fname)]). - -tab2dat(Tab) -> %% DETS files - dir(lists:concat([Tab, ".DAT"])). - -tab2tmp(Tab) -> - dir(lists:concat([Tab, ".TMP"])). - -tab2dmp(Tab) -> %% Dumped ets tables - dir(lists:concat([Tab, ".DMP"])). - -tab2dcd(Tab) -> %% Disc copies data - dir(lists:concat([Tab, ".DCD"])). - -tab2dcl(Tab) -> %% Disc copies log - dir(lists:concat([Tab, ".DCL"])). - -storage_type_at_node(Node, Tab) -> - search_key(Node, [{disc_copies, val({Tab, disc_copies})}, - {ram_copies, val({Tab, ram_copies})}, - {disc_only_copies, val({Tab, disc_only_copies})}]). - -cs_to_storage_type(Node, Cs) -> - search_key(Node, [{disc_copies, Cs#cstruct.disc_copies}, - {ram_copies, Cs#cstruct.ram_copies}, - {disc_only_copies, Cs#cstruct.disc_only_copies}]). - -schema_cs_to_storage_type(Node, Cs) -> - case cs_to_storage_type(Node, Cs) of - unknown when Cs#cstruct.name == schema -> ram_copies; - Other -> Other - end. - - -search_key(Key, [{Val, List} | Tail]) -> - case lists:member(Key, List) of - true -> Val; - false -> search_key(Key, Tail) - end; -search_key(_Key, []) -> - unknown. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% ops, we've got some global variables here :-) - -%% They are -%% -%% {Tab, setorbag}, -> set | bag -%% {Tab, storage_type} -> disc_copies |ram_copies | unknown (**) -%% {Tab, disc_copies} -> node list (from schema) -%% {Tab, ram_copies}, -> node list (from schema) -%% {Tab, arity}, -> number -%% {Tab, attributes}, -> atom list -%% {Tab, wild_pattern}, -> record tuple with '_'s -%% {Tab, {index, Pos}} -> ets table -%% {Tab, index} -> integer list -%% {Tab, cstruct} -> cstruct structure -%% - -%% The following fields are dynamic according to the -%% the current node/table situation - -%% {Tab, where_to_write} -> node list -%% {Tab, where_to_read} -> node | nowhere -%% -%% {schema, tables} -> tab list -%% {schema, local_tables} -> tab list (**) -%% -%% {current, db_nodes} -> node list -%% -%% dir -> directory path (**) -%% mnesia_status -> status | running | stopping (**) -%% (**) == (Different on all nodes) -%% - -val(Var) -> - case ?catch_val(Var) of - {'EXIT', _ReASoN_} -> mnesia_lib:other_val(Var, _ReASoN_); - _VaLuE_ -> _VaLuE_ - end. - -set(Var, Val) -> - ?ets_insert(mnesia_gvar, {Var, Val}). - -unset(Var) -> - ?ets_delete(mnesia_gvar, Var). - -other_val(Var, Other) -> - case Var of - {_, where_to_read} -> nowhere; - {_, where_to_write} -> []; - {_, active_replicas} -> []; - _ -> - pr_other(Var, Other) - end. - -pr_other(Var, Other) -> - Why = - case is_running() of - no -> {node_not_running, node()}; - _ -> {no_exists, Var} - end, - verbose("~p (~p) val(mnesia_gvar, ~w) -> ~p ~p ~n", - [self(), process_info(self(), registered_name), - Var, Other, Why]), - case Other of - {badarg, [{ets, lookup_element, _}|_]} -> - exit(Why); - _ -> - erlang:error(Why) - end. - -%% Some functions for list valued variables -add(Var, Val) -> - L = val(Var), - set(Var, [Val | lists:delete(Val, L)]). - -add_list(Var, List) -> - L = val(Var), - set(Var, union(L, List)). - -del(Var, Val) -> - L = val(Var), - set(Var, lists:delete(Val, L)). - -%% This function is needed due to the fact -%% that the application_controller enters -%% a deadlock now and then. ac is implemented -%% as a rather naive server. -ensure_loaded(Appl) -> - case application_controller:get_loaded(Appl) of - {true, _} -> - ok; - false -> - case application:load(Appl) of - ok -> - ok; - {error, {already_loaded, Appl}} -> - ok; - {error, Reason} -> - {error, {application_load_error, Reason}} - end - end. - -local_active_tables() -> - Tabs = val({schema, local_tables}), - lists:zf(fun(Tab) -> active_here(Tab) end, Tabs). - -active_tables() -> - Tabs = val({schema, tables}), - F = fun(Tab) -> - case val({Tab, where_to_read}) of - nowhere -> false; - _ -> {true, Tab} - end - end, - lists:zf(F, Tabs). - -etype(X) when integer(X) -> integer; -etype([]) -> nil; -etype(X) when list(X) -> list; -etype(X) when tuple(X) -> tuple; -etype(X) when atom(X) -> atom; -etype(_) -> othertype. - -remote_copy_holders(Cs) -> - copy_holders(Cs) -- [node()]. - -copy_holders(Cs) when Cs#cstruct.local_content == false -> - cs_to_nodes(Cs); -copy_holders(Cs) when Cs#cstruct.local_content == true -> - case lists:member(node(), cs_to_nodes(Cs)) of - true -> [node()]; - false -> [] - end. - - -set_remote_where_to_read(Tab) -> - set_remote_where_to_read(Tab, []). - -set_remote_where_to_read(Tab, Ignore) -> - Active = val({Tab, active_replicas}), - Valid = - case mnesia_recover:get_master_nodes(Tab) of - [] -> Active; - Masters -> mnesia_lib:intersect(Masters, Active) - end, - Available = mnesia_lib:intersect(val({current, db_nodes}), Valid -- Ignore), - DiscOnlyC = val({Tab, disc_only_copies}), - Prefered = Available -- DiscOnlyC, - if - Prefered /= [] -> - set({Tab, where_to_read}, hd(Prefered)); - Available /= [] -> - set({Tab, where_to_read}, hd(Available)); - true -> - set({Tab, where_to_read}, nowhere) - end. - -%%% Local only -set_local_content_whereabouts(Tab) -> - add({schema, local_tables}, Tab), - add({Tab, active_replicas}, node()), - set({Tab, where_to_write}, [node()]), - set({Tab, where_to_read}, node()). - -%%% counter routines - -create_counter(Name) -> - set_counter(Name, 0). - -set_counter(Name, Val) -> - ?ets_insert(mnesia_gvar, {Name, Val}). - -incr_counter(Name) -> - ?ets_update_counter(mnesia_gvar, Name, 1). - -incr_counter(Name, I) -> - ?ets_update_counter(mnesia_gvar, Name, I). - -update_counter(Name, Val) -> - ?ets_update_counter(mnesia_gvar, Name, Val). - -read_counter(Name) -> - ?ets_lookup_element(mnesia_gvar, Name, 2). - -cs_to_nodes(Cs) -> - Cs#cstruct.disc_only_copies ++ - Cs#cstruct.disc_copies ++ - Cs#cstruct.ram_copies. - -dist_coredump() -> - dist_coredump(all_nodes()). -dist_coredump(Ns) -> - {Replies, _} = rpc:multicall(Ns, ?MODULE, coredump, []), - Replies. - -coredump() -> - coredump({crashinfo, {"user initiated~n", []}}). -coredump(CrashInfo) -> - Core = mkcore(CrashInfo), - Out = core_file(), - important("Writing Mnesia core to file: ~p...~p~n", [Out, CrashInfo]), - file:write_file(Out, Core), - Out. - -core_file() -> - Integers = tuple_to_list(date()) ++ tuple_to_list(time()), - Fun = fun(I) when I < 10 -> ["_0", I]; - (I) -> ["_", I] - end, - List = lists:append([Fun(I) || I <- Integers]), - filename:absname(lists:concat(["MnesiaCore.", node()] ++ List)). - -mkcore(CrashInfo) -> -% dbg_out("Making a Mnesia core dump...~p~n", [CrashInfo]), - Nodes = [node() |nodes()], - TidLocks = (catch ets:tab2list(mnesia_tid_locks)), - Core = [ - CrashInfo, - {time, {date(), time()}}, - {self, catch process_info(self())}, - {nodes, catch rpc:multicall(Nodes, ?MODULE, get_node_number, [])}, - {applications, catch lists:sort(application:loaded_applications())}, - {flags, catch init:get_arguments()}, - {code_path, catch code:get_path()}, - {code_loaded, catch lists:sort(code:all_loaded())}, - {etsinfo, catch ets_info(ets:all())}, - - {version, catch mnesia:system_info(version)}, - {schema, catch ets:tab2list(schema)}, - {gvar, catch ets:tab2list(mnesia_gvar)}, - {master_nodes, catch mnesia_recover:get_master_node_info()}, - - {processes, catch procs()}, - {relatives, catch relatives()}, - {workers, catch workers(mnesia_controller:get_workers(2000))}, - {locking_procs, catch locking_procs(TidLocks)}, - - {held_locks, catch mnesia:system_info(held_locks)}, - {tid_locks, TidLocks}, - {lock_queue, catch mnesia:system_info(lock_queue)}, - {load_info, catch mnesia_controller:get_info(2000)}, - {trans_info, catch mnesia_tm:get_info(2000)}, - - {schema_file, catch file:read_file(tab2dat(schema))}, - {dir_info, catch dir_info()}, - {logfile, catch {ok, read_log_files()}} - ], - term_to_binary(Core). - -procs() -> - Fun = fun(P) -> {P, (catch lists:zf(fun proc_info/1, process_info(P)))} end, - lists:map(Fun, processes()). - -proc_info({registered_name, Val}) -> {true, Val}; -proc_info({message_queue_len, Val}) -> {true, Val}; -proc_info({status, Val}) -> {true, Val}; -proc_info({current_function, Val}) -> {true, Val}; -proc_info(_) -> false. - -get_node_number() -> - {node(), self()}. - -read_log_files() -> - [{F, catch file:read_file(F)} || F <- mnesia_log:log_files()]. - -dir_info() -> - {ok, Cwd} = file:get_cwd(), - Dir = dir(), - [{cwd, Cwd, file:read_file_info(Cwd)}, - {mnesia_dir, Dir, file:read_file_info(Dir)}] ++ - case file:list_dir(Dir) of - {ok, Files} -> - [{mnesia_file, F, catch file:read_file_info(dir(F))} || F <- Files]; - Other -> - [Other] - end. - -ets_info([H|T]) -> - [{table, H, ets:info(H)} | ets_info(T)]; -ets_info([]) -> []. - -relatives() -> - Info = fun(Name) -> - case whereis(Name) of - undefined -> false; - Pid -> {true, {Name, Pid, catch process_info(Pid)}} - end - end, - lists:zf(Info, mnesia:ms()). - -workers({workers, Loader, Sender, Dumper}) -> - Info = fun({Name, Pid}) -> - case Pid of - undefined -> false; - Pid -> {true, {Name, Pid, catch process_info(Pid)}} - end - end, - lists:zf(Info, [{loader, Loader}, {sender, Sender}, {dumper, Dumper}]). - -locking_procs(LockList) when list(LockList) -> - Tids = [element(1, Lock) || Lock <- LockList], - UT = uniq(Tids), - Info = fun(Tid) -> - Pid = Tid#tid.pid, - case node(Pid) == node() of - true -> - {true, {Pid, catch process_info(Pid)}}; - _ -> - false - end - end, - lists:zf(Info, UT). - -view() -> - Bin = mkcore({crashinfo, {"view only~n", []}}), - vcore(Bin). - -%% Displays a Mnesia file on the tty. The file may be repaired. -view(File) -> - case suffix([".DAT", ".RET", ".DMP", ".TMP"], File) of - true -> - view(File, dat); - false -> - case suffix([".LOG", ".BUP", ".ETS"], File) of - true -> - view(File, log); - false -> - case lists:prefix("MnesiaCore.", File) of - true -> - view(File, core); - false -> - {error, "Unknown file name"} - end - end - end. - -view(File, dat) -> - dets:view(File); -view(File, log) -> - mnesia_log:view(File); -view(File, core) -> - vcore(File). - -suffix(Suffixes, File) -> - Fun = fun(S) -> lists:suffix(S, File) end, - lists:any(Fun, Suffixes). - -%% View a core file - -vcore() -> - Prefix = lists:concat(["MnesiaCore.", node()]), - Filter = fun(F) -> lists:prefix(Prefix, F) end, - {ok, Cwd} = file:get_cwd(), - case file:list_dir(Cwd) of - {ok, Files}-> - CoreFiles = lists:sort(lists:zf(Filter, Files)), - show("Mnesia core files: ~p~n", [CoreFiles]), - vcore(lists:last(CoreFiles)); - Error -> - Error - end. - -vcore(Bin) when binary(Bin) -> - Core = binary_to_term(Bin), - Fun = fun({Item, Info}) -> - show("***** ~p *****~n", [Item]), - case catch vcore_elem({Item, Info}) of - {'EXIT', Reason} -> - show("{'EXIT', ~p}~n", [Reason]); - _ -> ok - end - end, - lists:foreach(Fun, Core); - -vcore(File) -> - show("~n***** Mnesia core: ~p *****~n", [File]), - case file:read_file(File) of - {ok, Bin} -> - vcore(Bin); - _ -> - nocore - end. - -vcore_elem({schema_file, {ok, B}}) -> - Fname = "/tmp/schema.DAT", - file:write_file(Fname, B), - dets:view(Fname), - file:delete(Fname); - -vcore_elem({logfile, {ok, BinList}}) -> - Fun = fun({F, Info}) -> - show("----- logfile: ~p -----~n", [F]), - case Info of - {ok, B} -> - Fname = "/tmp/mnesia_vcore_elem.TMP", - file:write_file(Fname, B), - mnesia_log:view(Fname), - file:delete(Fname); - _ -> - show("~p~n", [Info]) - end - end, - lists:foreach(Fun, BinList); - -vcore_elem({crashinfo, {Format, Args}}) -> - show(Format, Args); -vcore_elem({gvar, L}) -> - show("~p~n", [lists:sort(L)]); -vcore_elem({transactions, Info}) -> - mnesia_tm:display_info(user, Info); - -vcore_elem({_Item, Info}) -> - show("~p~n", [Info]). - -fix_error(X) -> - set(last_error, X), %% for debugabililty - case X of - {aborted, Reason} -> Reason; - {abort, Reason} -> Reason; - Y when atom(Y) -> Y; - {'EXIT', {_Reason, {Mod, _, _}}} when atom(Mod) -> - save(X), - case atom_to_list(Mod) of - [$m, $n, $e|_] -> badarg; - _ -> X - end; - _ -> X - end. - -last_error() -> - val(last_error). - -%% The following is a list of possible mnesia errors and what they -%% actually mean - -error_desc(nested_transaction) -> "Nested transactions are not allowed"; -error_desc(badarg) -> "Bad or invalid argument, possibly bad type"; -error_desc(no_transaction) -> "Operation not allowed outside transactions"; -error_desc(combine_error) -> "Table options were ilegally combined"; -error_desc(bad_index) -> "Index already exists or was out of bounds"; -error_desc(already_exists) -> "Some schema option we try to set is already on"; -error_desc(index_exists)-> "Some ops can not be performed on tabs with index"; -error_desc(no_exists)-> "Tried to perform op on non-existing (non alive) item"; -error_desc(system_limit) -> "Some system_limit was exhausted"; -error_desc(mnesia_down) -> "A transaction involving objects at some remote " - "node which died while transaction was executing" - "*and* object(s) are no longer available elsewhere" - "in the network"; -error_desc(not_a_db_node) -> "A node which is non existant in " - "the schema was mentioned"; -error_desc(bad_type) -> "Bad type on some provided arguments"; -error_desc(node_not_running) -> "Node not running"; -error_desc(truncated_binary_file) -> "Truncated binary in file"; -error_desc(active) -> "Some delete ops require that " - "all active objects are removed"; -error_desc(illegal) -> "Operation not supported on object"; -error_desc({'EXIT', Reason}) -> - error_desc(Reason); -error_desc({error, Reason}) -> - error_desc(Reason); -error_desc({aborted, Reason}) -> - error_desc(Reason); -error_desc(Reason) when tuple(Reason), size(Reason) > 0 -> - setelement(1, Reason, error_desc(element(1, Reason))); -error_desc(Reason) -> - Reason. - -dirty_rpc_error_tag(Reason) -> - case Reason of - {'EXIT', _} -> badarg; - no_variable -> badarg; - _ -> no_exists - end. - -fatal(Format, Args) -> - catch set(mnesia_status, stopping), - Core = mkcore({crashinfo, {Format, Args}}), - report_fatal(Format, Args, Core), - timer:sleep(10000), % Enough to write the core dump to disc? - mnesia:lkill(), - exit(fatal). - -report_fatal(Format, Args) -> - report_fatal(Format, Args, nocore). - -report_fatal(Format, Args, Core) -> - report_system_event({mnesia_fatal, Format, Args, Core}), - catch exit(whereis(mnesia_monitor), fatal). - -%% We sleep longer and longer the more we try -%% Made some testing and came up with the following constants -random_time(Retries, _Counter0) -> -% UpperLimit = 2000, -% MaxIntv = trunc(UpperLimit * (1-(4/((Retries*Retries)+4)))), - UpperLimit = 500, - Dup = Retries * Retries, - MaxIntv = trunc(UpperLimit * (1-(50/((Dup)+50)))), - - case get(random_seed) of - undefined -> - {X, Y, Z} = erlang:now(), %% time() - random:seed(X, Y, Z), - Time = Dup + random:uniform(MaxIntv), - %% dbg_out("---random_test rs ~w max ~w val ~w---~n", [Retries, MaxIntv, Time]), - Time; - _ -> - Time = Dup + random:uniform(MaxIntv), - %% dbg_out("---random_test rs ~w max ~w val ~w---~n", [Retries, MaxIntv, Time]), - Time - end. - -report_system_event(Event0) -> - Event = {mnesia_system_event, Event0}, - report_system_event(catch_notify(Event), Event), - case ?catch_val(subscribers) of - {'EXIT', _} -> ignore; - Pids -> lists:foreach(fun(Pid) -> Pid ! Event end, Pids) - end, - ok. - -catch_notify(Event) -> - case whereis(mnesia_event) of - undefined -> - {'EXIT', {badarg, {mnesia_event, Event}}}; - Pid -> - gen_event:notify(Pid, Event) - end. - -report_system_event({'EXIT', Reason}, Event) -> - Mod = mnesia_monitor:get_env(event_module), - case mnesia_sup:start_event() of - {ok, Pid} -> - link(Pid), - gen_event:call(mnesia_event, Mod, Event, infinity), - unlink(Pid), - - %% We get an exit signal if server dies - receive - {'EXIT', Pid, _Reason} -> - {error, {node_not_running, node()}} - after 0 -> - gen_event:stop(mnesia_event), - ok - end; - - Error -> - Msg = "Mnesia(~p): Cannot report event ~p: ~p (~p)~n", - error_logger:format(Msg, [node(), Event, Reason, Error]) - end; -report_system_event(_Res, _Event) -> - ignore. - -%% important messages are reported regardless of debug level -important(Format, Args) -> - save({Format, Args}), - report_system_event({mnesia_info, Format, Args}). - -%% Warning messages are reported regardless of debug level -warning(Format, Args) -> - save({Format, Args}), - report_system_event({mnesia_warning, Format, Args}). - -%% error messages are reported regardless of debug level -error(Format, Args) -> - save({Format, Args}), - report_system_event({mnesia_error, Format, Args}). - -%% verbose messages are reported if debug level == debug or verbose -verbose(Format, Args) -> - case mnesia_monitor:get_env(debug) of - none -> save({Format, Args}); - verbose -> important(Format, Args); - debug -> important(Format, Args); - trace -> important(Format, Args) - end. - -%% debug message are display if debug level == 2 -dbg_out(Format, Args) -> - case mnesia_monitor:get_env(debug) of - none -> ignore; - verbose -> save({Format, Args}); - _ -> report_system_event({mnesia_info, Format, Args}) - end. - -%% Keep the last 10 debug print outs -save(DbgInfo) -> - catch save2(DbgInfo). - -save2(DbgInfo) -> - Key = {'$$$_report', current_pos}, - P = - case ?ets_lookup_element(mnesia_gvar, Key, 2) of - 30 -> -1; - I -> I - end, - set({'$$$_report', current_pos}, P+1), - set({'$$$_report', P+1}, {date(), time(), DbgInfo}). - -copy_file(From, To) -> - case file:open(From, [raw, binary, read]) of - {ok, F} -> - case file:open(To, [raw, binary, write]) of - {ok, T} -> - Res = copy_file_loop(F, T, 8000), - file:close(F), - file:close(T), - Res; - {error, Reason} -> - {error, Reason} - end; - {error, Reason} -> - {error, Reason} - end. - -copy_file_loop(F, T, ChunkSize) -> - case file:read(F, ChunkSize) of - {ok, {0, _}} -> - ok; - {ok, {_, Bin}} -> - file:write(T, Bin), - copy_file_loop(F, T, ChunkSize); - {ok, Bin} -> - file:write(T, Bin), - copy_file_loop(F, T, ChunkSize); - eof -> - ok; - {error, Reason} -> - {error, Reason} - end. - - -%%%%%%%%%%%% -%% versions of all the lowlevel db funcs that determine whether we -%% shall go to disc or ram to do the actual operation. - -db_get(Tab, Key) -> - db_get(val({Tab, storage_type}), Tab, Key). -db_get(ram_copies, Tab, Key) -> ?ets_lookup(Tab, Key); -db_get(disc_copies, Tab, Key) -> ?ets_lookup(Tab, Key); -db_get(disc_only_copies, Tab, Key) -> dets:lookup(Tab, Key). - -db_init_chunk(Tab) -> - db_init_chunk(val({Tab, storage_type}), Tab, 1000). -db_init_chunk(Tab, N) -> - db_init_chunk(val({Tab, storage_type}), Tab, N). - -db_init_chunk(disc_only_copies, Tab, N) -> - dets:select(Tab, [{'_', [], ['$_']}], N); -db_init_chunk(_, Tab, N) -> - ets:select(Tab, [{'_', [], ['$_']}], N). - -db_chunk(disc_only_copies, State) -> - dets:select(State); -db_chunk(_, State) -> - ets:select(State). - -db_put(Tab, Val) -> - db_put(val({Tab, storage_type}), Tab, Val). - -db_put(ram_copies, Tab, Val) -> ?ets_insert(Tab, Val), ok; -db_put(disc_copies, Tab, Val) -> ?ets_insert(Tab, Val), ok; -db_put(disc_only_copies, Tab, Val) -> dets:insert(Tab, Val). - -db_match_object(Tab, Pat) -> - db_match_object(val({Tab, storage_type}), Tab, Pat). -db_match_object(Storage, Tab, Pat) -> - db_fixtable(Storage, Tab, true), - Res = catch_match_object(Storage, Tab, Pat), - db_fixtable(Storage, Tab, false), - case Res of - {'EXIT', Reason} -> exit(Reason); - _ -> Res - end. - -catch_match_object(disc_only_copies, Tab, Pat) -> - catch dets:match_object(Tab, Pat); -catch_match_object(_, Tab, Pat) -> - catch ets:match_object(Tab, Pat). - -db_select(Tab, Pat) -> - db_select(val({Tab, storage_type}), Tab, Pat). - -db_select(Storage, Tab, Pat) -> - db_fixtable(Storage, Tab, true), - Res = catch_select(Storage, Tab, Pat), - db_fixtable(Storage, Tab, false), - case Res of - {'EXIT', Reason} -> exit(Reason); - _ -> Res - end. - -catch_select(disc_only_copies, Tab, Pat) -> - dets:select(Tab, Pat); -catch_select(_, Tab, Pat) -> - ets:select(Tab, Pat). - -db_fixtable(ets, Tab, Bool) -> - ets:safe_fixtable(Tab, Bool); -db_fixtable(ram_copies, Tab, Bool) -> - ets:safe_fixtable(Tab, Bool); -db_fixtable(disc_copies, Tab, Bool) -> - ets:safe_fixtable(Tab, Bool); -db_fixtable(dets, Tab, Bool) -> - dets:safe_fixtable(Tab, Bool); -db_fixtable(disc_only_copies, Tab, Bool) -> - dets:safe_fixtable(Tab, Bool). - -db_erase(Tab, Key) -> - db_erase(val({Tab, storage_type}), Tab, Key). -db_erase(ram_copies, Tab, Key) -> ?ets_delete(Tab, Key), ok; -db_erase(disc_copies, Tab, Key) -> ?ets_delete(Tab, Key), ok; -db_erase(disc_only_copies, Tab, Key) -> dets:delete(Tab, Key). - -db_match_erase(Tab, Pat) -> - db_match_erase(val({Tab, storage_type}), Tab, Pat). -db_match_erase(ram_copies, Tab, Pat) -> ?ets_match_delete(Tab, Pat), ok; -db_match_erase(disc_copies, Tab, Pat) -> ?ets_match_delete(Tab, Pat), ok; -db_match_erase(disc_only_copies, Tab, Pat) -> dets:match_delete(Tab, Pat). - -db_first(Tab) -> - db_first(val({Tab, storage_type}), Tab). -db_first(ram_copies, Tab) -> ?ets_first(Tab); -db_first(disc_copies, Tab) -> ?ets_first(Tab); -db_first(disc_only_copies, Tab) -> dets:first(Tab). - -db_next_key(Tab, Key) -> - db_next_key(val({Tab, storage_type}), Tab, Key). -db_next_key(ram_copies, Tab, Key) -> ?ets_next(Tab, Key); -db_next_key(disc_copies, Tab, Key) -> ?ets_next(Tab, Key); -db_next_key(disc_only_copies, Tab, Key) -> dets:next(Tab, Key). - -db_last(Tab) -> - db_last(val({Tab, storage_type}), Tab). -db_last(ram_copies, Tab) -> ?ets_last(Tab); -db_last(disc_copies, Tab) -> ?ets_last(Tab); -db_last(disc_only_copies, Tab) -> dets:first(Tab). %% Dets don't have order - -db_prev_key(Tab, Key) -> - db_prev_key(val({Tab, storage_type}), Tab, Key). -db_prev_key(ram_copies, Tab, Key) -> ?ets_prev(Tab, Key); -db_prev_key(disc_copies, Tab, Key) -> ?ets_prev(Tab, Key); -db_prev_key(disc_only_copies, Tab, Key) -> dets:next(Tab, Key). %% Dets don't have order - -db_slot(Tab, Pos) -> - db_slot(val({Tab, storage_type}), Tab, Pos). -db_slot(ram_copies, Tab, Pos) -> ?ets_slot(Tab, Pos); -db_slot(disc_copies, Tab, Pos) -> ?ets_slot(Tab, Pos); -db_slot(disc_only_copies, Tab, Pos) -> dets:slot(Tab, Pos). - -db_update_counter(Tab, C, Val) -> - db_update_counter(val({Tab, storage_type}), Tab, C, Val). -db_update_counter(ram_copies, Tab, C, Val) -> - ?ets_update_counter(Tab, C, Val); -db_update_counter(disc_copies, Tab, C, Val) -> - ?ets_update_counter(Tab, C, Val); -db_update_counter(disc_only_copies, Tab, C, Val) -> - dets:update_counter(Tab, C, Val). - -db_erase_tab(Tab) -> - db_erase_tab(val({Tab, storage_type}), Tab). -db_erase_tab(ram_copies, Tab) -> ?ets_delete_table(Tab); -db_erase_tab(disc_copies, Tab) -> ?ets_delete_table(Tab); -db_erase_tab(disc_only_copies, _Tab) -> ignore. - -%% assuming that Tab is a valid ets-table -dets_to_ets(Tabname, Tab, File, Type, Rep, Lock) -> - {Open, Close} = mkfuns(Lock), - case Open(Tabname, [{file, File}, {type, disk_type(Tab, Type)}, - {keypos, 2}, {repair, Rep}]) of - {ok, Tabname} -> - Res = dets:to_ets(Tabname, Tab), - Close(Tabname), - trav_ret(Res, Tab); - Other -> - Other - end. - -trav_ret(Tabname, Tabname) -> loaded; -trav_ret(Other, _Tabname) -> Other. - -mkfuns(yes) -> - {fun(Tab, Args) -> dets_sync_open(Tab, Args) end, - fun(Tab) -> dets_sync_close(Tab) end}; -mkfuns(no) -> - {fun(Tab, Args) -> dets:open_file(Tab, Args) end, - fun(Tab) -> dets:close(Tab) end}. - -disk_type(Tab) -> - disk_type(Tab, val({Tab, setorbag})). - -disk_type(_Tab, ordered_set) -> - set; -disk_type(_, Type) -> - Type. - -dets_sync_open(Tab, Ref, File) -> - Args = [{file, File}, - {keypos, 2}, - {repair, mnesia_monitor:get_env(auto_repair)}, - {type, disk_type(Tab)}], - dets_sync_open(Ref, Args). - -lock_table(Tab) -> - global:set_lock({{mnesia_table_lock, Tab}, self()}, [node()], infinity). -% dbg_out("dets_sync_open: ~p ~p~n", [T, self()]), - -unlock_table(Tab) -> - global:del_lock({{mnesia_table_lock, Tab}, self()}, [node()]). -% dbg_out("unlock_table: ~p ~p~n", [T, self()]), - -dets_sync_open(Tab, Args) -> - lock_table(Tab), - case dets:open_file(Tab, Args) of - {ok, Tab} -> - {ok, Tab}; - Other -> - dets_sync_close(Tab), - Other - end. - -dets_sync_close(Tab) -> - catch dets:close(Tab), - unlock_table(Tab), - ok. - -cleanup_tmp_files([Tab | Tabs]) -> - dets_sync_close(Tab), - file:delete(tab2tmp(Tab)), - cleanup_tmp_files(Tabs); -cleanup_tmp_files([]) -> - ok. - -%% Returns a list of bad tables -swap_tmp_files([Tab | Tabs]) -> - dets_sync_close(Tab), - Tmp = tab2tmp(Tab), - Dat = tab2dat(Tab), - case file:rename(Tmp, Dat) of - ok -> - swap_tmp_files(Tabs); - _ -> - file:delete(Tmp), - [Tab | swap_tmp_files(Tabs)] - end; -swap_tmp_files([]) -> - []. - -readable_indecies(Tab) -> - val({Tab, index}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Managing conditional debug functions -%% -%% The main idea with the debug_fun's is to allow test programs -%% to control the internal behaviour of Mnesia. This is needed -%% to make the test programs independent of system load, swapping -%% and other circumstances that may affect the behaviour of Mnesia. -%% -%% First should calls to ?eval_debug_fun be inserted at well -%% defined places in Mnesia's code. E.g. in critical situations -%% of startup, transaction commit, backups etc. -%% -%% Then compile Mnesia with the compiler option 'debug'. -%% -%% In test programs ?activate_debug_fun should be called -%% in order to bind a fun to the debug identifier stated -%% in the call to ?eval_debug_fun. -%% -%% If eval_debug_fun finds that the fun is activated it -%% invokes the fun as NewContext = Fun(PreviousContext, EvalContext) -%% and replaces the PreviousContext with the NewContext. -%% The initial context of a debug_fun is given as argument to -%% activate_debug_fun. - --define(DEBUG_TAB, mnesia_debug). --record(debug_info, {id, function, context, file, line}). - -scratch_debug_fun() -> - dbg_out("scratch_debug_fun(): ~p~n", [?DEBUG_TAB]), - (catch ?ets_delete_table(?DEBUG_TAB)), - ?ets_new_table(?DEBUG_TAB, [set, public, named_table, {keypos, 2}]). - -activate_debug_fun(FunId, Fun, InitialContext, File, Line) -> - Info = #debug_info{id = FunId, - function = Fun, - context = InitialContext, - file = File, - line = Line - }, - update_debug_info(Info). - -update_debug_info(Info) -> - case catch ?ets_insert(?DEBUG_TAB, Info) of - {'EXIT', _} -> - scratch_debug_fun(), - ?ets_insert(?DEBUG_TAB, Info); - _ -> - ok - end, - dbg_out("update_debug_info(~p)~n", [Info]), - ok. - -deactivate_debug_fun(FunId, _File, _Line) -> - catch ?ets_delete(?DEBUG_TAB, FunId), - ok. - -eval_debug_fun(FunId, EvalContext, EvalFile, EvalLine) -> - case catch ?ets_lookup(?DEBUG_TAB, FunId) of - [] -> - ok; - [Info] -> - OldContext = Info#debug_info.context, - dbg_out("~s(~p): ~w " - "activated in ~s(~p)~n " - "eval_debug_fun(~w, ~w)~n", - [filename:basename(EvalFile), EvalLine, Info#debug_info.id, - filename:basename(Info#debug_info.file), Info#debug_info.line, - OldContext, EvalContext]), - Fun = Info#debug_info.function, - NewContext = Fun(OldContext, EvalContext), - - case catch ?ets_lookup(?DEBUG_TAB, FunId) of - [Info] when NewContext /= OldContext -> - NewInfo = Info#debug_info{context = NewContext}, - update_debug_info(NewInfo); - _ -> - ok - end; - {'EXIT', _} -> ok - end. - --ifdef(debug). - is_debug_compiled() -> true. --else. - is_debug_compiled() -> false. --endif. - - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_loader.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_loader.erl deleted file mode 100644 index df3309cfa6..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_loader.erl +++ /dev/null @@ -1,805 +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_loader.erl,v 1.2 2010/03/04 13:54:19 maria Exp $ -%% -%%% Purpose : Loads tables from local disc or from remote node - --module(mnesia_loader). - -%% Mnesia internal stuff --export([disc_load_table/2, - net_load_table/4, - send_table/3]). - --export([old_node_init_table/6]). %% Spawned old node protocol conversion hack --export([spawned_receiver/8]). %% Spawned lock taking process - --import(mnesia_lib, [set/2, fatal/2, verbose/2, dbg_out/2]). - --include("mnesia.hrl"). - -val(Var) -> - case ?catch_val(Var) of - {'EXIT', Reason} -> mnesia_lib:other_val(Var, Reason); - Value -> Value - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Load a table from local disc - -disc_load_table(Tab, Reason) -> - Storage = val({Tab, storage_type}), - Type = val({Tab, setorbag}), - dbg_out("Getting table ~p (~p) from disc: ~p~n", - [Tab, Storage, Reason]), - ?eval_debug_fun({?MODULE, do_get_disc_copy}, - [{tab, Tab}, - {reason, Reason}, - {storage, Storage}, - {type, Type}]), - do_get_disc_copy2(Tab, Reason, Storage, Type). - -do_get_disc_copy2(Tab, _Reason, Storage, _Type) when Storage == unknown -> - verbose("Local table copy of ~p has recently been deleted, ignored.~n", - [Tab]), - {loaded, ok}; %% ? -do_get_disc_copy2(Tab, Reason, Storage, Type) when Storage == disc_copies -> - %% NOW we create the actual table - Repair = mnesia_monitor:get_env(auto_repair), - Args = [{keypos, 2}, public, named_table, Type], - case Reason of - {dumper, _} -> %% Resources allready allocated - ignore; - _ -> - mnesia_monitor:mktab(Tab, Args), - Count = mnesia_log:dcd2ets(Tab, Repair), - case ets:info(Tab, size) of - X when X < Count * 4 -> - ok = mnesia_log:ets2dcd(Tab); - _ -> - ignore - end - end, - mnesia_index:init_index(Tab, Storage), - snmpify(Tab, Storage), - set({Tab, load_node}, node()), - set({Tab, load_reason}, Reason), - {loaded, ok}; - -do_get_disc_copy2(Tab, Reason, Storage, Type) when Storage == ram_copies -> - Args = [{keypos, 2}, public, named_table, Type], - case Reason of - {dumper, _} -> %% Resources allready allocated - ignore; - _ -> - mnesia_monitor:mktab(Tab, Args), - Fname = mnesia_lib:tab2dcd(Tab), - Datname = mnesia_lib:tab2dat(Tab), - Repair = mnesia_monitor:get_env(auto_repair), - case mnesia_monitor:use_dir() of - true -> - case mnesia_lib:exists(Fname) of - true -> mnesia_log:dcd2ets(Tab, Repair); - false -> - case mnesia_lib:exists(Datname) of - true -> - mnesia_lib:dets_to_ets(Tab, Tab, Datname, - Type, Repair, no); - false -> - false - end - end; - false -> - false - end - end, - mnesia_index:init_index(Tab, Storage), - snmpify(Tab, Storage), - set({Tab, load_node}, node()), - set({Tab, load_reason}, Reason), - {loaded, ok}; - -do_get_disc_copy2(Tab, Reason, Storage, Type) when Storage == disc_only_copies -> - Args = [{file, mnesia_lib:tab2dat(Tab)}, - {type, mnesia_lib:disk_type(Tab, Type)}, - {keypos, 2}, - {repair, mnesia_monitor:get_env(auto_repair)}], - case Reason of - {dumper, _} -> - mnesia_index:init_index(Tab, Storage), - snmpify(Tab, Storage), - set({Tab, load_node}, node()), - set({Tab, load_reason}, Reason), - {loaded, ok}; - _ -> - case mnesia_monitor:open_dets(Tab, Args) of - {ok, _} -> - mnesia_index:init_index(Tab, Storage), - snmpify(Tab, Storage), - set({Tab, load_node}, node()), - set({Tab, load_reason}, Reason), - {loaded, ok}; - {error, Error} -> - {not_loaded, {"Failed to create dets table", Error}} - end - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Load a table from a remote node -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% -%% Receiver Sender -%% -------- ------ -%% Grab schema lock on table -%% Determine table size -%% Create empty pre-grown table -%% Grab read lock on table -%% Let receiver subscribe on updates done on sender node -%% Disable rehashing of table -%% Release read lock on table -%% Send table to receiver in chunks -%% -%% Grab read lock on table -%% Block dirty updates -%% Update wherabouts -%% -%% Cancel the update subscription -%% Process the subscription events -%% Optionally dump to disc -%% Unblock dirty updates -%% Release read lock on table -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - --define(MAX_TRANSFER_SIZE, 7500). --define(MAX_RAM_FILE_SIZE, 1000000). --define(MAX_RAM_TRANSFERS, (?MAX_RAM_FILE_SIZE div ?MAX_TRANSFER_SIZE) + 1). --define(MAX_NOPACKETS, 20). - -net_load_table(Tab, Reason, Ns, Cs) - when Reason == {dumper,add_table_copy} -> - try_net_load_table(Tab, Reason, Ns, Cs); -net_load_table(Tab, Reason, Ns, _Cs) -> - try_net_load_table(Tab, Reason, Ns, val({Tab, cstruct})). - -try_net_load_table(Tab, _Reason, [], _Cs) -> - verbose("Copy failed. No active replicas of ~p are available.~n", [Tab]), - {not_loaded, none_active}; -try_net_load_table(Tab, Reason, Ns, Cs) -> - Storage = mnesia_lib:cs_to_storage_type(node(), Cs), - do_get_network_copy(Tab, Reason, Ns, Storage, Cs). - -do_get_network_copy(Tab, _Reason, _Ns, unknown, _Cs) -> - verbose("Local table copy of ~p has recently been deleted, ignored.~n", [Tab]), - {not_loaded, storage_unknown}; -do_get_network_copy(Tab, Reason, Ns, Storage, Cs) -> - [Node | Tail] = Ns, - dbg_out("Getting table ~p (~p) from node ~p: ~p~n", - [Tab, Storage, Node, Reason]), - ?eval_debug_fun({?MODULE, do_get_network_copy}, - [{tab, Tab}, {reason, Reason}, - {nodes, Ns}, {storage, Storage}]), - mnesia_controller:start_remote_sender(Node, Tab, self(), Storage), - put(mnesia_table_sender_node, {Tab, Node}), - case init_receiver(Node, Tab, Storage, Cs, Reason) of - ok -> - set({Tab, load_node}, Node), - set({Tab, load_reason}, Reason), - mnesia_controller:i_have_tab(Tab), - dbg_out("Table ~p copied from ~p to ~p~n", [Tab, Node, node()]), - {loaded, ok}; - Err = {error, _} when element(1, Reason) == dumper -> - {not_loaded,Err}; - restart -> - try_net_load_table(Tab, Reason, Tail, Cs); - down -> - try_net_load_table(Tab, Reason, Tail, Cs) - end. - -snmpify(Tab, Storage) -> - do_snmpify(Tab, val({Tab, snmp}), Storage). - -do_snmpify(_Tab, [], _Storage) -> - ignore; -do_snmpify(Tab, Us, Storage) -> - Snmp = mnesia_snmp_hook:create_table(Us, Tab, Storage), - set({Tab, {index, snmp}}, Snmp). - -%% Start the recieiver -%% Sender should be started first, so we don't have the schema-read -%% lock to long (or get stuck in a deadlock) -init_receiver(Node, Tab, Storage, Cs, Reason) -> - receive - {SenderPid, {first, TabSize}} -> - spawn_receiver(Tab,Storage,Cs,SenderPid, - TabSize,false,Reason); - {SenderPid, {first, TabSize, DetsData}} -> - spawn_receiver(Tab,Storage,Cs,SenderPid, - TabSize,DetsData,Reason); - %% Protocol conversion hack - {copier_done, Node} -> - dbg_out("Sender of table ~p crashed on node ~p ~n", [Tab, Node]), - down(Tab, Storage) - end. - - -table_init_fun(SenderPid) -> - PConv = mnesia_monitor:needs_protocol_conversion(node(SenderPid)), - MeMyselfAndI = self(), - fun(read) -> - Receiver = - if - PConv == true -> - MeMyselfAndI ! {actual_tabrec, self()}, - MeMyselfAndI; %% Old mnesia - PConv == false -> self() - end, - SenderPid ! {Receiver, more}, - get_data(SenderPid, Receiver) - end. - - -%% Add_table_copy get's it's own locks. -spawn_receiver(Tab,Storage,Cs,SenderPid,TabSize,DetsData,{dumper,add_table_copy}) -> - Init = table_init_fun(SenderPid), - case do_init_table(Tab,Storage,Cs,SenderPid,TabSize,DetsData,self(), Init) of - Err = {error, _} -> - SenderPid ! {copier_done, node()}, - Err; - Else -> - Else - end; - -spawn_receiver(Tab,Storage,Cs,SenderPid, - TabSize,DetsData,Reason) -> - %% Grab a schema lock to avoid deadlock between table_loader and schema_commit dumping. - %% Both may grab tables-locks in different order. - Load = fun() -> - {_,Tid,Ts} = get(mnesia_activity_state), - mnesia_locker:rlock(Tid, Ts#tidstore.store, - {schema, Tab}), - Init = table_init_fun(SenderPid), - Pid = spawn_link(?MODULE, spawned_receiver, - [self(),Tab,Storage,Cs, - SenderPid,TabSize,DetsData, - Init]), - put(mnesia_real_loader, Pid), - wait_on_load_complete(Pid) - end, - Res = case mnesia:transaction(Load, 20) of - {'atomic', {error,Result}} when element(1,Reason) == dumper -> - SenderPid ! {copier_done, node()}, - {error,Result}; - {'atomic', {error,Result}} -> - SenderPid ! {copier_done, node()}, - fatal("Cannot create table ~p: ~p~n", - [[Tab, Storage], Result]); - {'atomic', Result} -> Result; - {aborted, nomore} -> - SenderPid ! {copier_done, node()}, - restart; - {aborted, _ } -> - SenderPid ! {copier_done, node()}, - down %% either this node or sender is dying - end, - unlink(whereis(mnesia_tm)), %% Avoid late unlink from tm - Res. - -spawned_receiver(ReplyTo,Tab,Storage,Cs, - SenderPid,TabSize,DetsData, Init) -> - process_flag(trap_exit, true), - Done = do_init_table(Tab,Storage,Cs, - SenderPid,TabSize,DetsData, - ReplyTo, Init), - ReplyTo ! {self(),Done}, - unlink(ReplyTo), - unlink(whereis(mnesia_controller)), - exit(normal). - -wait_on_load_complete(Pid) -> - receive - {Pid, Res} -> - Res; - {'EXIT', Pid, Reason} -> - exit(Reason); - Else -> - Pid ! Else, - wait_on_load_complete(Pid) - end. - -tab_receiver(Node, Tab, Storage, Cs, PConv, OrigTabRec) -> - receive - {SenderPid, {no_more, DatBin}} when PConv == false -> - finish_copy(Storage,Tab,Cs,SenderPid,DatBin,OrigTabRec); - - %% Protocol conversion hack - {SenderPid, {no_more, DatBin}} when pid(PConv) -> - PConv ! {SenderPid, no_more}, - receive - {old_init_table_complete, ok} -> - finish_copy(Storage, Tab, Cs, SenderPid, DatBin,OrigTabRec); - {old_init_table_complete, Reason} -> - Msg = "OLD: [d]ets:init table failed", - dbg_out("~s: ~p: ~p~n", [Msg, Tab, Reason]), - down(Tab, Storage) - end; - - {actual_tabrec, Pid} -> - tab_receiver(Node, Tab, Storage, Cs, Pid,OrigTabRec); - - {SenderPid, {more, [Recs]}} when pid(PConv) -> - PConv ! {SenderPid, {more, Recs}}, %% Forward Msg to OldNodes - tab_receiver(Node, Tab, Storage, Cs, PConv,OrigTabRec); - - {'EXIT', PConv, Reason} -> %% [d]ets:init process crashed - Msg = "Receiver crashed", - dbg_out("~s: ~p: ~p~n", [Msg, Tab, Reason]), - down(Tab, Storage); - - %% Protocol conversion hack - {copier_done, Node} -> - dbg_out("Sender of table ~p crashed on node ~p ~n", [Tab, Node]), - down(Tab, Storage); - - {'EXIT', Pid, Reason} -> - handle_exit(Pid, Reason), - tab_receiver(Node, Tab, Storage, Cs, PConv,OrigTabRec) - end. - -create_table(Tab, TabSize, Storage, Cs) -> - if - Storage == disc_only_copies -> - mnesia_lib:lock_table(Tab), - Tmp = mnesia_lib:tab2tmp(Tab), - Size = lists:max([TabSize, 256]), - Args = [{file, Tmp}, - {keypos, 2}, -%% {ram_file, true}, - {estimated_no_objects, Size}, - {repair, mnesia_monitor:get_env(auto_repair)}, - {type, mnesia_lib:disk_type(Tab, Cs#cstruct.type)}], - file:delete(Tmp), - case mnesia_lib:dets_sync_open(Tab, Args) of - {ok, _} -> - mnesia_lib:unlock_table(Tab), - {Storage, Tab}; - Else -> - mnesia_lib:unlock_table(Tab), - Else - end; - (Storage == ram_copies) or (Storage == disc_copies) -> - Args = [{keypos, 2}, public, named_table, Cs#cstruct.type], - case mnesia_monitor:unsafe_mktab(Tab, Args) of - Tab -> - {Storage, Tab}; - Else -> - Else - end - end. - -do_init_table(Tab,Storage,Cs,SenderPid, - TabSize,DetsInfo,OrigTabRec,Init) -> - case create_table(Tab, TabSize, Storage, Cs) of - {Storage,Tab} -> - %% Debug info - Node = node(SenderPid), - put(mnesia_table_receiver, {Tab, Node, SenderPid}), - mnesia_tm:block_tab(Tab), - PConv = mnesia_monitor:needs_protocol_conversion(Node), - - case init_table(Tab,Storage,Init,PConv,DetsInfo,SenderPid) of - ok -> - tab_receiver(Node,Tab,Storage,Cs,PConv,OrigTabRec); - Reason -> - Msg = "[d]ets:init table failed", - dbg_out("~s: ~p: ~p~n", [Msg, Tab, Reason]), - down(Tab, Storage) - end; - Error -> - Error - end. - -make_table_fun(Pid, TabRec) -> - fun(close) -> - ok; - (read) -> - get_data(Pid, TabRec) - end. - -get_data(Pid, TabRec) -> - receive - {Pid, {more, Recs}} -> - Pid ! {TabRec, more}, - {Recs, make_table_fun(Pid,TabRec)}; - {Pid, no_more} -> - end_of_input; - {copier_done, Node} -> - case node(Pid) of - Node -> - {copier_done, Node}; - _ -> - get_data(Pid, TabRec) - end; - {'EXIT', Pid, Reason} -> - handle_exit(Pid, Reason), - get_data(Pid, TabRec) - end. - -init_table(Tab, disc_only_copies, Fun, false, DetsInfo,Sender) -> - ErtsVer = erlang:system_info(version), - case DetsInfo of - {ErtsVer, DetsData} -> - Res = (catch dets:is_compatible_bchunk_format(Tab, DetsData)), - case Res of - {'EXIT',{undef,[{dets,_,_}|_]}} -> - Sender ! {self(), {old_protocol, Tab}}, - dets:init_table(Tab, Fun); %% Old dets version - {'EXIT', What} -> - exit(What); - false -> - Sender ! {self(), {old_protocol, Tab}}, - dets:init_table(Tab, Fun); %% Old dets version - true -> - dets:init_table(Tab, Fun, [{format, bchunk}]) - end; - Old when Old /= false -> - Sender ! {self(), {old_protocol, Tab}}, - dets:init_table(Tab, Fun); %% Old dets version - _ -> - dets:init_table(Tab, Fun) - end; -init_table(Tab, _, Fun, false, _DetsInfo,_) -> - case catch ets:init_table(Tab, Fun) of - true -> - ok; - {'EXIT', Else} -> Else - end; -init_table(Tab, Storage, Fun, true, _DetsInfo, Sender) -> %% Old Nodes - spawn_link(?MODULE, old_node_init_table, - [Tab, Storage, Fun, self(), false, Sender]), - ok. - -old_node_init_table(Tab, Storage, Fun, TabReceiver, DetsInfo,Sender) -> - Res = init_table(Tab, Storage, Fun, false, DetsInfo,Sender), - TabReceiver ! {old_init_table_complete, Res}, - unlink(TabReceiver), - ok. - -finish_copy(Storage,Tab,Cs,SenderPid,DatBin,OrigTabRec) -> - TabRef = {Storage, Tab}, - subscr_receiver(TabRef, Cs#cstruct.record_name), - case handle_last(TabRef, Cs#cstruct.type, DatBin) of - ok -> - mnesia_index:init_index(Tab, Storage), - snmpify(Tab, Storage), - %% OrigTabRec must not be the spawned tab-receiver - %% due to old protocol. - SenderPid ! {OrigTabRec, no_more}, - mnesia_tm:unblock_tab(Tab), - ok; - {error, Reason} -> - Msg = "Failed to handle last", - dbg_out("~s: ~p: ~p~n", [Msg, Tab, Reason]), - down(Tab, Storage) - end. - -subscr_receiver(TabRef = {_, Tab}, RecName) -> - receive - {mnesia_table_event, {Op, Val, _Tid}} -> - if - Tab == RecName -> - handle_event(TabRef, Op, Val); - true -> - handle_event(TabRef, Op, setelement(1, Val, RecName)) - end, - subscr_receiver(TabRef, RecName); - - {'EXIT', Pid, Reason} -> - handle_exit(Pid, Reason), - subscr_receiver(TabRef, RecName) - after 0 -> - ok - end. - -handle_event(TabRef, write, Rec) -> - db_put(TabRef, Rec); -handle_event(TabRef, delete, {_Tab, Key}) -> - db_erase(TabRef, Key); -handle_event(TabRef, delete_object, OldRec) -> - db_match_erase(TabRef, OldRec); -handle_event(TabRef, clear_table, {_Tab, _Key}) -> - db_match_erase(TabRef, '_'). - -handle_last({disc_copies, Tab}, _Type, nobin) -> - Ret = mnesia_log:ets2dcd(Tab), - Fname = mnesia_lib:tab2dat(Tab), - case mnesia_lib:exists(Fname) of - true -> %% Remove old .DAT files. - file:delete(Fname); - false -> - ok - end, - Ret; - -handle_last({disc_only_copies, Tab}, Type, nobin) -> - case mnesia_lib:swap_tmp_files([Tab]) of - [] -> - Args = [{file, mnesia_lib:tab2dat(Tab)}, - {type, mnesia_lib:disk_type(Tab, Type)}, - {keypos, 2}, - {repair, mnesia_monitor:get_env(auto_repair)}], - mnesia_monitor:open_dets(Tab, Args), - ok; - L when list(L) -> - {error, {"Cannot swap tmp files", Tab, L}} - end; - -handle_last({ram_copies, _Tab}, _Type, nobin) -> - ok; -handle_last({ram_copies, Tab}, _Type, DatBin) -> - case mnesia_monitor:use_dir() of - true -> - mnesia_lib:lock_table(Tab), - Tmp = mnesia_lib:tab2tmp(Tab), - ok = file:write_file(Tmp, DatBin), - ok = file:rename(Tmp, mnesia_lib:tab2dcd(Tab)), - mnesia_lib:unlock_table(Tab), - ok; - false -> - ok - end. - -down(Tab, Storage) -> - case Storage of - ram_copies -> - catch ?ets_delete_table(Tab); - disc_copies -> - catch ?ets_delete_table(Tab); - disc_only_copies -> - mnesia_lib:cleanup_tmp_files([Tab]) - end, - mnesia_checkpoint:tm_del_copy(Tab, node()), - mnesia_controller:sync_del_table_copy_whereabouts(Tab, node()), - mnesia_tm:unblock_tab(Tab), - flush_subcrs(), - down. - -flush_subcrs() -> - receive - {mnesia_table_event, _} -> - flush_subcrs(); - - {'EXIT', Pid, Reason} -> - handle_exit(Pid, Reason), - flush_subcrs() - after 0 -> - done - end. - -db_erase({ram_copies, Tab}, Key) -> - true = ?ets_delete(Tab, Key); -db_erase({disc_copies, Tab}, Key) -> - true = ?ets_delete(Tab, Key); -db_erase({disc_only_copies, Tab}, Key) -> - ok = dets:delete(Tab, Key). - -db_match_erase({ram_copies, Tab} , Pat) -> - true = ?ets_match_delete(Tab, Pat); -db_match_erase({disc_copies, Tab} , Pat) -> - true = ?ets_match_delete(Tab, Pat); -db_match_erase({disc_only_copies, Tab}, Pat) -> - ok = dets:match_delete(Tab, Pat). - -db_put({ram_copies, Tab}, Val) -> - true = ?ets_insert(Tab, Val); -db_put({disc_copies, Tab}, Val) -> - true = ?ets_insert(Tab, Val); -db_put({disc_only_copies, Tab}, Val) -> - ok = dets:insert(Tab, Val). - -%% This code executes at the remote site where the data is -%% executes in a special copier process. - -calc_nokeys(Storage, Tab) -> - %% Calculate #keys per transfer - Key = mnesia_lib:db_first(Storage, Tab), - Recs = mnesia_lib:db_get(Storage, Tab, Key), - BinSize = size(term_to_binary(Recs)), - (?MAX_TRANSFER_SIZE div BinSize) + 1. - -send_table(Pid, Tab, RemoteS) -> - case ?catch_val({Tab, storage_type}) of - {'EXIT', _} -> - {error, {no_exists, Tab}}; - unknown -> - {error, {no_exists, Tab}}; - Storage -> - %% Send first - TabSize = mnesia:table_info(Tab, size), - Pconvert = mnesia_monitor:needs_protocol_conversion(node(Pid)), - KeysPerTransfer = calc_nokeys(Storage, Tab), - ChunkData = dets:info(Tab, bchunk_format), - - UseDetsChunk = - Storage == RemoteS andalso - Storage == disc_only_copies andalso - ChunkData /= undefined andalso - Pconvert == false, - if - UseDetsChunk == true -> - DetsInfo = erlang:system_info(version), - Pid ! {self(), {first, TabSize, {DetsInfo, ChunkData}}}; - true -> - Pid ! {self(), {first, TabSize}} - end, - - %% Debug info - put(mnesia_table_sender, {Tab, node(Pid), Pid}), - {Init, Chunk} = reader_funcs(UseDetsChunk, Tab, Storage, KeysPerTransfer), - - SendIt = fun() -> - prepare_copy(Pid, Tab, Storage), - send_more(Pid, 1, Chunk, Init(), Tab, Pconvert), - finish_copy(Pid, Tab, Storage, RemoteS) - end, - - case catch SendIt() of - receiver_died -> - cleanup_tab_copier(Pid, Storage, Tab), - unlink(whereis(mnesia_tm)), - ok; - {_, receiver_died} -> - unlink(whereis(mnesia_tm)), - ok; - {'atomic', no_more} -> - unlink(whereis(mnesia_tm)), - ok; - Reason -> - cleanup_tab_copier(Pid, Storage, Tab), - unlink(whereis(mnesia_tm)), - {error, Reason} - end - end. - -prepare_copy(Pid, Tab, Storage) -> - Trans = - fun() -> - mnesia:write_lock_table(Tab), - mnesia_subscr:subscribe(Pid, {table, Tab}), - update_where_to_write(Tab, node(Pid)), - mnesia_lib:db_fixtable(Storage, Tab, true), - ok - end, - case mnesia:transaction(Trans) of - {'atomic', ok} -> - ok; - {aborted, Reason} -> - exit({tab_copier_prepare, Tab, Reason}) - end. - -update_where_to_write(Tab, Node) -> - case val({Tab, access_mode}) of - read_only -> - ignore; - read_write -> - Current = val({current, db_nodes}), - Ns = - case lists:member(Node, Current) of - true -> Current; - false -> [Node | Current] - end, - update_where_to_write(Ns, Tab, Node) - end. - -update_where_to_write([], _, _) -> - ok; -update_where_to_write([H|T], Tab, AddNode) -> - rpc:call(H, mnesia_controller, call, - [{update_where_to_write, [add, Tab, AddNode], self()}]), - update_where_to_write(T, Tab, AddNode). - -send_more(Pid, N, Chunk, DataState, Tab, OldNode) -> - receive - {NewPid, more} -> - case send_packet(N - 1, NewPid, Chunk, DataState, OldNode) of - New when integer(New) -> - New - 1; - NewData -> - send_more(NewPid, ?MAX_NOPACKETS, Chunk, NewData, Tab, OldNode) - end; - {_NewPid, {old_protocol, Tab}} -> - Storage = val({Tab, storage_type}), - {Init, NewChunk} = - reader_funcs(false, Tab, Storage, calc_nokeys(Storage, Tab)), - send_more(Pid, 1, NewChunk, Init(), Tab, OldNode); - - {copier_done, Node} when Node == node(Pid)-> - verbose("Receiver of table ~p crashed on ~p (more)~n", [Tab, Node]), - throw(receiver_died) - end. - -reader_funcs(UseDetsChunk, Tab, Storage, KeysPerTransfer) -> - case UseDetsChunk of - false -> - {fun() -> mnesia_lib:db_init_chunk(Storage, Tab, KeysPerTransfer) end, - fun(Cont) -> mnesia_lib:db_chunk(Storage, Cont) end}; - true -> - {fun() -> dets_bchunk(Tab, start) end, - fun(Cont) -> dets_bchunk(Tab, Cont) end} - end. - -dets_bchunk(Tab, Chunk) -> %% Arrg - case dets:bchunk(Tab, Chunk) of - {Cont, Data} -> {Data, Cont}; - Else -> Else - end. - -send_packet(N, Pid, _Chunk, '$end_of_table', OldNode) -> - case OldNode of - true -> ignore; %% Old nodes can't handle the new no_more - false -> Pid ! {self(), no_more} - end, - N; -send_packet(N, Pid, Chunk, {[], Cont}, OldNode) -> - send_packet(N, Pid, Chunk, Chunk(Cont), OldNode); -send_packet(N, Pid, Chunk, {Recs, Cont}, OldNode) when N < ?MAX_NOPACKETS -> - case OldNode of - true -> Pid ! {self(), {more, [Recs]}}; %% Old need's wrapping list - false -> Pid ! {self(), {more, Recs}} - end, - send_packet(N+1, Pid, Chunk, Chunk(Cont), OldNode); -send_packet(_N, _Pid, _Chunk, DataState, _OldNode) -> - DataState. - -finish_copy(Pid, Tab, Storage, RemoteS) -> - RecNode = node(Pid), - DatBin = dat2bin(Tab, Storage, RemoteS), - Trans = - fun() -> - mnesia:read_lock_table(Tab), - A = val({Tab, access_mode}), - mnesia_controller:sync_and_block_table_whereabouts(Tab, RecNode, RemoteS, A), - cleanup_tab_copier(Pid, Storage, Tab), - mnesia_checkpoint:tm_add_copy(Tab, RecNode), - Pid ! {self(), {no_more, DatBin}}, - receive - {Pid, no_more} -> % Dont bother about the spurious 'more' message - no_more; - {copier_done, Node} when Node == node(Pid)-> - verbose("Tab receiver ~p crashed (more): ~p~n", [Tab, Node]), - receiver_died - end - end, - mnesia:transaction(Trans). - -cleanup_tab_copier(Pid, Storage, Tab) -> - mnesia_lib:db_fixtable(Storage, Tab, false), - mnesia_subscr:unsubscribe(Pid, {table, Tab}). - -dat2bin(Tab, ram_copies, ram_copies) -> - mnesia_lib:lock_table(Tab), - Res = file:read_file(mnesia_lib:tab2dcd(Tab)), - mnesia_lib:unlock_table(Tab), - case Res of - {ok, DatBin} -> DatBin; - _ -> nobin - end; -dat2bin(_Tab, _LocalS, _RemoteS) -> - nobin. - -handle_exit(Pid, Reason) when node(Pid) == node() -> - exit(Reason); -handle_exit(_Pid, _Reason) -> %% Not from our node, this will be handled by - ignore. %% mnesia_down soon. 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}. - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_log.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_log.erl deleted file mode 100644 index 79bd8d3812..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_log.erl +++ /dev/null @@ -1,1019 +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_log.erl,v 1.2 2009/07/01 15:45:40 kostis Exp $ -%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% -%% This module administers three kinds of log files: -%% -%% 1 The transaction log -%% mnesia_tm appends to the log (via mnesia_log) at the -%% end of each transaction (or dirty write) and -%% mnesia_dumper reads the log and performs the ops in -%% the dat files. The dump_log is done at startup and -%% at intervals controlled by the user. -%% -%% 2 The mnesia_down log -%% mnesia_tm appends to the log (via mnesia_log) when it -%% realizes that mnesia goes up or down on another node. -%% mnesia_init reads the log (via mnesia_log) at startup. -%% -%% 3 The backup log -%% mnesia_schema produces one tiny log when the schema is -%% initially created. mnesia_schema also reads the log -%% when the user wants tables (possibly incl the schema) -%% to be restored. mnesia_log appends to the log when the -%% user wants to produce a real backup. -%% -%% The actual access to the backup media is performed via the -%% mnesia_backup module for both read and write. mnesia_backup -%% uses the disk_log (*), BUT the user may write an own module -%% with the same interface as mnesia_backup and configure -%% Mnesia so the alternate module performs the actual accesses -%% to the backup media. This means that the user may put the -%% backup on medias that Mnesia does not know about possibly on -%% hosts where Erlang is not running. -%% -%% All these logs have to some extent a common structure. -%% They are all using the disk_log module (*) for the basic -%% file structure. The disk_log has a repair feature that -%% can be used to skip erroneous log records if one comes to -%% the conclusion that it is more important to reuse some -%% of the log records than the risque of obtaining inconsistent -%% data. If the data becomes inconsistent it is solely up to the -%% application to make it consistent again. The automatic -%% reparation of the disk_log is very powerful, but use it -%% with extreme care. -%% -%% First in all Mnesia's log file is a mnesia log header. -%% It contains a list with a log_header record as single -%% element. The structure of the log_header may never be -%% changed since it may be written to very old backup files. -%% By holding this record definition stable we can be -%% able to comprahend backups from timepoint 0. It also -%% allows us to use the backup format as an interchange -%% format between Mnesia releases. -%% -%% An op-list is a list of tuples with arity 3. Each tuple -%% has this structure: {Oid, Recs, Op} where Oid is the tuple -%% {Tab, Key}, Recs is a (possibly empty) list of records and -%% Op is an atom. -%% -%% The log file structure for the transaction log is as follows. -%% -%% After the mnesia log section follows an extended record section -%% containing op-lists. There are several values that Op may -%% have, such as write, delete, update_counter, delete_object, -%% and replace. There is no special end of section marker. -%% -%% +-----------------+ -%% | mnesia log head | -%% +-----------------+ -%% | extended record | -%% | section | -%% +-----------------+ -%% -%% The log file structure for the mnesia_down log is as follows. -%% -%% After the mnesia log section follows a mnesia_down section -%% containg lists with yoyo records as single element. -%% -%% +-----------------+ -%% | mnesia log head | -%% +-----------------+ -%% | mnesia_down | -%% | section | -%% +-----------------+ -%% -%% The log file structure for the backup log is as follows. -%% -%% After the mnesia log section follows a schema section -%% containing record lists. A record list is a list of tuples -%% where {schema, Tab} is interpreted as a delete_table(Tab) and -%% {schema, Tab, CreateList} are interpreted as create_table. -%% -%% The record section also contains record lists. In this section -%% {Tab, Key} is interpreted as delete({Tab, Key}) and other tuples -%% as write(Tuple). There is no special end of section marker. -%% -%% +-----------------+ -%% | mnesia log head | -%% +-----------------+ -%% | schema section | -%% +-----------------+ -%% | record section | -%% +-----------------+ -%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - --module(mnesia_log). - --export([ - append/2, - backup/1, - backup/2, - backup_checkpoint/2, - backup_checkpoint/3, - backup_log_header/0, - backup_master/2, - chunk_decision_log/1, - chunk_decision_tab/1, - chunk_log/1, - chunk_log/2, - close_decision_log/0, - close_decision_tab/0, - close_log/1, - unsafe_close_log/1, - confirm_log_dump/1, - confirm_decision_log_dump/0, - previous_log_file/0, - previous_decision_log_file/0, - latest_log_file/0, - decision_log_version/0, - decision_log_file/0, - decision_tab_file/0, - decision_tab_version/0, - dcl_version/0, - dcd_version/0, - ets2dcd/1, - ets2dcd/2, - dcd2ets/1, - dcd2ets/2, - init/0, - init_log_dump/0, - log/1, - slog/1, - log_decision/1, - log_files/0, - open_decision_log/0, - trans_log_header/0, - open_decision_tab/0, - dcl_log_header/0, - dcd_log_header/0, - open_log/4, - open_log/6, - prepare_decision_log_dump/0, - prepare_log_dump/1, - save_decision_tab/1, - purge_all_logs/0, - purge_some_logs/0, - stop/0, - tab_copier/3, - version/0, - view/0, - view/1, - write_trans_log_header/0 - ]). - - --include("mnesia.hrl"). --import(mnesia_lib, [val/1, dir/1]). --import(mnesia_lib, [exists/1, fatal/2, error/2, dbg_out/2]). - -trans_log_header() -> log_header(trans_log, version()). -backup_log_header() -> log_header(backup_log, "1.2"). -decision_log_header() -> log_header(decision_log, decision_log_version()). -decision_tab_header() -> log_header(decision_tab, decision_tab_version()). -dcl_log_header() -> log_header(dcl_log, dcl_version()). -dcd_log_header() -> log_header(dcd_log, dcd_version()). - -log_header(Kind, Version) -> - #log_header{log_version=Version, - log_kind=Kind, - mnesia_version=mnesia:system_info(version), - node=node(), - now=now()}. - -version() -> "4.3". - -decision_log_version() -> "3.0". - -decision_tab_version() -> "1.0". - -dcl_version() -> "1.0". -dcd_version() -> "1.0". - -append(Log, Bin) when binary(Bin) -> - disk_log:balog(Log, Bin); -append(Log, Term) -> - disk_log:alog(Log, Term). - -%% Synced append -sappend(Log, Bin) when binary(Bin) -> - ok = disk_log:blog(Log, Bin); -sappend(Log, Term) -> - ok = disk_log:log(Log, Term). - -%% Write commit records to the latest_log -log(C) when C#commit.disc_copies == [], - C#commit.disc_only_copies == [], - C#commit.schema_ops == [] -> - ignore; -log(C) -> - case mnesia_monitor:use_dir() of - true -> - if - record(C, commit) -> - C2 = C#commit{ram_copies = [], snmp = []}, - append(latest_log, C2); - true -> - %% Either a commit record as binary - %% or some decision related info - append(latest_log, C) - end, - mnesia_dumper:incr_log_writes(); - false -> - ignore - end. - -%% Synced - -slog(C) when C#commit.disc_copies == [], - C#commit.disc_only_copies == [], - C#commit.schema_ops == [] -> - ignore; -slog(C) -> - case mnesia_monitor:use_dir() of - true -> - if - record(C, commit) -> - C2 = C#commit{ram_copies = [], snmp = []}, - sappend(latest_log, C2); - true -> - %% Either a commit record as binary - %% or some decision related info - sappend(latest_log, C) - end, - mnesia_dumper:incr_log_writes(); - false -> - ignore - end. - - -%% Stuff related to the file LOG - -%% Returns a list of logfiles. The oldest is first. -log_files() -> [previous_log_file(), - latest_log_file(), - decision_tab_file() - ]. - -latest_log_file() -> dir(latest_log_name()). - -previous_log_file() -> dir("PREVIOUS.LOG"). - -decision_log_file() -> dir(decision_log_name()). - -decision_tab_file() -> dir(decision_tab_name()). - -previous_decision_log_file() -> dir("PDECISION.LOG"). - -latest_log_name() -> "LATEST.LOG". - -decision_log_name() -> "DECISION.LOG". - -decision_tab_name() -> "DECISION_TAB.LOG". - -init() -> - case mnesia_monitor:use_dir() of - true -> - Prev = previous_log_file(), - verify_no_exists(Prev), - - Latest = latest_log_file(), - verify_no_exists(Latest), - - Header = trans_log_header(), - open_log(latest_log, Header, Latest); - false -> - ok - end. - -verify_no_exists(Fname) -> - case exists(Fname) of - false -> - ok; - true -> - fatal("Log file exists: ~p~n", [Fname]) - end. - -open_log(Name, Header, Fname) -> - Exists = exists(Fname), - open_log(Name, Header, Fname, Exists). - -open_log(Name, Header, Fname, Exists) -> - Repair = mnesia_monitor:get_env(auto_repair), - open_log(Name, Header, Fname, Exists, Repair). - -open_log(Name, Header, Fname, Exists, Repair) -> - case Name == previous_log of - true -> - open_log(Name, Header, Fname, Exists, Repair, read_only); - false -> - open_log(Name, Header, Fname, Exists, Repair, read_write) - end. - -open_log(Name, Header, Fname, Exists, Repair, Mode) -> - Args = [{file, Fname}, {name, Name}, {repair, Repair}, {mode, Mode}], -%% io:format("~p:open_log: ~p ~p~n", [?MODULE, Name, Fname]), - case mnesia_monitor:open_log(Args) of - {ok, Log} when Exists == true -> - Log; - {ok, Log} -> - write_header(Log, Header), - Log; - {repaired, Log, _, {badbytes, 0}} when Exists == true -> - Log; - {repaired, Log, _, {badbytes, 0}} -> - write_header(Log, Header), - Log; - {repaired, Log, _Recover, BadBytes} -> - mnesia_lib:important("Data may be missing, log ~p repaired: Lost ~p bytes~n", - [Fname, BadBytes]), - Log; - {error, Reason} when Repair == true -> - file:delete(Fname), - mnesia_lib:important("Data may be missing, Corrupt logfile deleted: ~p, ~p ~n", - [Fname, Reason]), - %% Create a new - open_log(Name, Header, Fname, false, false, read_write); - {error, Reason} -> - fatal("Cannot open log file ~p: ~p~n", [Fname, Reason]) - end. - -write_header(Log, Header) -> - append(Log, Header). - -write_trans_log_header() -> - write_header(latest_log, trans_log_header()). - -stop() -> - case mnesia_monitor:use_dir() of - true -> - close_log(latest_log); - false -> - ok - end. - -close_log(Log) -> -%% io:format("mnesia_log:close_log ~p~n", [Log]), -%% io:format("mnesia_log:close_log ~p~n", [Log]), - case disk_log:sync(Log) of - ok -> ok; - {error, {read_only_mode, Log}} -> - ok; - {error, Reason} -> - mnesia_lib:important("Failed syncing ~p to_disk reason ~p ~n", - [Log, Reason]) - end, - mnesia_monitor:close_log(Log). - -unsafe_close_log(Log) -> -%% io:format("mnesia_log:close_log ~p~n", [Log]), - mnesia_monitor:unsafe_close_log(Log). - - -purge_some_logs() -> - mnesia_monitor:unsafe_close_log(latest_log), - file:delete(latest_log_file()), - file:delete(decision_tab_file()). - -purge_all_logs() -> - file:delete(previous_log_file()), - file:delete(latest_log_file()), - file:delete(decision_tab_file()). - -%% Prepare dump by renaming the open logfile if possible -%% Returns a tuple on the following format: {Res, OpenLog} -%% where OpenLog is the file descriptor to log file, ready for append -%% and Res is one of the following: already_dumped, needs_dump or {error, Reason} -prepare_log_dump(InitBy) -> - Diff = mnesia_dumper:get_log_writes() - - mnesia_lib:read_counter(trans_log_writes_prev), - if - Diff == 0, InitBy /= startup -> - already_dumped; - true -> - case mnesia_monitor:use_dir() of - true -> - Prev = previous_log_file(), - prepare_prev(Diff, InitBy, Prev, exists(Prev)); - false -> - already_dumped - end - end. - -prepare_prev(Diff, _, _, true) -> - {needs_dump, Diff}; -prepare_prev(Diff, startup, Prev, false) -> - Latest = latest_log_file(), - case exists(Latest) of - true -> - case file:rename(Latest, Prev) of - ok -> - {needs_dump, Diff}; - {error, Reason} -> - {error, Reason} - end; - false -> - already_dumped - end; -prepare_prev(Diff, _InitBy, Prev, false) -> - Head = trans_log_header(), - case mnesia_monitor:reopen_log(latest_log, Prev, Head) of - ok -> - {needs_dump, Diff}; - {error, Reason} -> - Latest = latest_log_file(), - {error, {"Cannot rename log file", - [Latest, Prev, Reason]}} - end. - -%% Init dump and return PrevLogFileDesc or exit. -init_log_dump() -> - Fname = previous_log_file(), - open_log(previous_log, trans_log_header(), Fname), - start. - - -chunk_log(Cont) -> - chunk_log(previous_log, Cont). - -chunk_log(_Log, eof) -> - eof; -chunk_log(Log, Cont) -> - case catch disk_log:chunk(Log, Cont) of - {error, Reason} -> - fatal("Possibly truncated ~p file: ~p~n", - [Log, Reason]); - {C2, Chunk, _BadBytes} -> - %% Read_only case, should we warn about the bad log file? - %% BUGBUG Should we crash if Repair == false ?? - %% We got to check this !! - mnesia_lib:important("~p repaired, lost ~p bad bytes~n", [Log, _BadBytes]), - {C2, Chunk}; - Other -> - Other - end. - -%% Confirms the dump by closing prev log and delete the file -confirm_log_dump(Updates) -> - case mnesia_monitor:close_log(previous_log) of - ok -> - file:delete(previous_log_file()), - mnesia_lib:incr_counter(trans_log_writes_prev, Updates), - dumped; - {error, Reason} -> - {error, Reason} - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Decision log - -open_decision_log() -> - Latest = decision_log_file(), - open_log(decision_log, decision_log_header(), Latest), - start. - -prepare_decision_log_dump() -> - Prev = previous_decision_log_file(), - prepare_decision_log_dump(exists(Prev), Prev). - -prepare_decision_log_dump(false, Prev) -> - Head = decision_log_header(), - case mnesia_monitor:reopen_log(decision_log, Prev, Head) of - ok -> - prepare_decision_log_dump(true, Prev); - {error, Reason} -> - fatal("Cannot rename decision log file ~p -> ~p: ~p~n", - [decision_log_file(), Prev, Reason]) - end; -prepare_decision_log_dump(true, Prev) -> - open_log(previous_decision_log, decision_log_header(), Prev), - start. - -chunk_decision_log(Cont) -> - %% dbg_out("chunk log ~p~n", [Cont]), - chunk_log(previous_decision_log, Cont). - -%% Confirms dump of the decision log -confirm_decision_log_dump() -> - case mnesia_monitor:close_log(previous_decision_log) of - ok -> - file:delete(previous_decision_log_file()); - {error, Reason} -> - fatal("Cannot confirm decision log dump: ~p~n", - [Reason]) - end. - -save_decision_tab(Decisions) -> - Log = decision_tab, - Tmp = mnesia_lib:dir("DECISION_TAB.TMP"), - file:delete(Tmp), - open_log(Log, decision_tab_header(), Tmp), - append(Log, Decisions), - close_log(Log), - TabFile = decision_tab_file(), - ok = file:rename(Tmp, TabFile). - -open_decision_tab() -> - TabFile = decision_tab_file(), - open_log(decision_tab, decision_tab_header(), TabFile), - start. - -close_decision_tab() -> - close_log(decision_tab). - -chunk_decision_tab(Cont) -> - %% dbg_out("chunk tab ~p~n", [Cont]), - chunk_log(decision_tab, Cont). - -close_decision_log() -> - close_log(decision_log). - -log_decision(Decision) -> - append(decision_log, Decision). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Debug functions - -view() -> - lists:foreach(fun(F) -> view(F) end, log_files()). - -view(File) -> - mnesia_lib:show("***** ~p ***** ~n", [File]), - case exists(File) of - false -> - nolog; - true -> - N = view_only, - Args = [{file, File}, {name, N}, {mode, read_only}], - case disk_log:open(Args) of - {ok, N} -> - view_file(start, N); - {repaired, _, _, _} -> - view_file(start, N); - {error, Reason} -> - error("Cannot open log ~p: ~p~n", [File, Reason]) - end - end. - -view_file(C, Log) -> - case disk_log:chunk(Log, C) of - {error, Reason} -> - error("** Possibly truncated FILE ~p~n", [Reason]), - error; - eof -> - disk_log:close(Log), - eof; - {C2, Terms, _BadBytes} -> - dbg_out("Lost ~p bytes in ~p ~n", [_BadBytes, Log]), - lists:foreach(fun(X) -> mnesia_lib:show("~p~n", [X]) end, - Terms), - view_file(C2, Log); - {C2, Terms} -> - lists:foreach(fun(X) -> mnesia_lib:show("~p~n", [X]) end, - Terms), - view_file(C2, Log) - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Backup - --record(backup_args, {name, module, opaque, scope, prev_name, tables, cookie}). - -backup(Opaque) -> - backup(Opaque, []). - -backup(Opaque, Mod) when atom(Mod) -> - backup(Opaque, [{module, Mod}]); -backup(Opaque, Args) when list(Args) -> - %% Backup all tables with max redundancy - CpArgs = [{ram_overrides_dump, false}, {max, val({schema, tables})}], - case mnesia_checkpoint:activate(CpArgs) of - {ok, Name, _Nodes} -> - Res = backup_checkpoint(Name, Opaque, Args), - mnesia_checkpoint:deactivate(Name), - Res; - {error, Reason} -> - {error, Reason} - end. - -backup_checkpoint(Name, Opaque) -> - backup_checkpoint(Name, Opaque, []). - -backup_checkpoint(Name, Opaque, Mod) when atom(Mod) -> - backup_checkpoint(Name, Opaque, [{module, Mod}]); -backup_checkpoint(Name, Opaque, Args) when list(Args) -> - DefaultMod = mnesia_monitor:get_env(backup_module), - B = #backup_args{name = Name, - module = DefaultMod, - opaque = Opaque, - scope = global, - tables = all, - prev_name = Name}, - case check_backup_args(Args, B) of - {ok, B2} -> - %% Decentralized backup - %% Incremental - - Self = self(), - Pid = spawn_link(?MODULE, backup_master, [Self, B2]), - receive - {Pid, Self, Res} -> Res - end; - {error, Reason} -> - {error, Reason} - end. - -check_backup_args([Arg | Tail], B) -> - case catch check_backup_arg_type(Arg, B) of - {'EXIT', _Reason} -> - {error, {badarg, Arg}}; - B2 -> - check_backup_args(Tail, B2) - end; - -check_backup_args([], B) -> - {ok, B}. - -check_backup_arg_type(Arg, B) -> - case Arg of - {scope, global} -> - B#backup_args{scope = global}; - {scope, local} -> - B#backup_args{scope = local}; - {module, Mod} -> - Mod2 = mnesia_monitor:do_check_type(backup_module, Mod), - B#backup_args{module = Mod2}; - {incremental, Name} -> - B#backup_args{prev_name = Name}; - {tables, Tabs} when list(Tabs) -> - B#backup_args{tables = Tabs} - end. - -backup_master(ClientPid, B) -> - process_flag(trap_exit, true), - case catch do_backup_master(B) of - {'EXIT', Reason} -> - ClientPid ! {self(), ClientPid, {error, {'EXIT', Reason}}}; - Res -> - ClientPid ! {self(), ClientPid, Res} - end, - unlink(ClientPid), - exit(normal). - -do_backup_master(B) -> - Name = B#backup_args.name, - B2 = safe_apply(B, open_write, [B#backup_args.opaque]), - B3 = safe_write(B2, [backup_log_header()]), - case mnesia_checkpoint:tables_and_cookie(Name) of - {ok, AllTabs, Cookie} -> - Tabs = select_tables(AllTabs, B3), - B4 = B3#backup_args{cookie = Cookie}, - %% Always put schema first in backup file - B5 = backup_schema(B4, Tabs), - B6 = lists:foldl(fun backup_tab/2, B5, Tabs -- [schema]), - safe_apply(B6, commit_write, [B6#backup_args.opaque]), - ok; - {error, Reason} -> - abort_write(B3, {?MODULE, backup_master}, [B], {error, Reason}) - end. - -select_tables(AllTabs, B) -> - Tabs = - case B#backup_args.tables of - all -> AllTabs; - SomeTabs when list(SomeTabs) -> SomeTabs - end, - case B#backup_args.scope of - global -> - Tabs; - local -> - Name = B#backup_args.name, - [T || T <- Tabs, mnesia_checkpoint:most_local_node(Name, T) == node()] - end. - -safe_write(B, []) -> - B; -safe_write(B, Recs) -> - safe_apply(B, write, [B#backup_args.opaque, Recs]). - -backup_schema(B, Tabs) -> - case lists:member(schema, Tabs) of - true -> - backup_tab(schema, B); - false -> - Defs = [{schema, T, mnesia_schema:get_create_list(T)} || T <- Tabs], - safe_write(B, Defs) - end. - -safe_apply(B, write, [_, Items]) when Items == [] -> - B; -safe_apply(B, What, Args) -> - Abort = fun(R) -> abort_write(B, What, Args, R) end, - receive - {'EXIT', Pid, R} -> Abort({'EXIT', Pid, R}) - after 0 -> - Mod = B#backup_args.module, - case catch apply(Mod, What, Args) of - {ok, Opaque} -> B#backup_args{opaque=Opaque}; - {error, R} -> Abort(R); - R -> Abort(R) - end - end. - -abort_write(B, What, Args, Reason) -> - Mod = B#backup_args.module, - Opaque = B#backup_args.opaque, - dbg_out("Failed to perform backup. M=~p:F=~p:A=~p -> ~p~n", - [Mod, What, Args, Reason]), - case catch apply(Mod, abort_write, [Opaque]) of - {ok, _Res} -> - throw({error, Reason}); - Other -> - error("Failed to abort backup. ~p:~p~p -> ~p~n", - [Mod, abort_write, [Opaque], Other]), - throw({error, Reason}) - end. - -backup_tab(Tab, B) -> - Name = B#backup_args.name, - case mnesia_checkpoint:most_local_node(Name, Tab) of - {ok, Node} when Node == node() -> - tab_copier(self(), B, Tab); - {ok, Node} -> - RemoteB = B, - Pid = spawn_link(Node, ?MODULE, tab_copier, [self(), RemoteB, Tab]), - RecName = val({Tab, record_name}), - tab_receiver(Pid, B, Tab, RecName, 0); - {error, Reason} -> - abort_write(B, {?MODULE, backup_tab}, [Tab, B], {error, Reason}) - end. - -tab_copier(Pid, B, Tab) when record(B, backup_args) -> - %% Intentional crash at exit - Name = B#backup_args.name, - PrevName = B#backup_args.prev_name, - {FirstName, FirstSource} = select_source(Tab, Name, PrevName), - - ?eval_debug_fun({?MODULE, tab_copier, pre}, [{name, Name}, {tab, Tab}]), - Res = handle_more(Pid, B, Tab, FirstName, FirstSource, Name), - ?eval_debug_fun({?MODULE, tab_copier, post}, [{name, Name}, {tab, Tab}]), - - handle_last(Pid, Res). - -select_source(Tab, Name, PrevName) -> - if - Tab == schema -> - %% Always full backup of schema - {Name, table}; - Name == PrevName -> - %% Full backup - {Name, table}; - true -> - %% Wants incremental backup - case mnesia_checkpoint:most_local_node(PrevName, Tab) of - {ok, Node} when Node == node() -> - %% Accept incremental backup - {PrevName, retainer}; - _ -> - %% Do a full backup anyway - dbg_out("Incremental backup escalated to full backup: ~p~n", [Tab]), - {Name, table} - end - end. - -handle_more(Pid, B, Tab, FirstName, FirstSource, Name) -> - Acc = {0, B}, - case {mnesia_checkpoint:really_retain(Name, Tab), - mnesia_checkpoint:really_retain(FirstName, Tab)} of - {true, true} -> - Acc2 = iterate(B, FirstName, Tab, Pid, FirstSource, latest, first, Acc), - iterate(B, Name, Tab, Pid, retainer, checkpoint, last, Acc2); - {false, false}-> - %% Put the dumped file in the backup - %% instead of the ram table. Does - %% only apply to ram_copies. - iterate(B, Name, Tab, Pid, retainer, checkpoint, last, Acc); - Bad -> - Reason = {"Checkpoints for incremental backup must have same " - "setting of ram_overrides_dump", - Tab, Name, FirstName, Bad}, - abort_write(B, {?MODULE, backup_tab}, [Tab, B], {error, Reason}) - end. - -handle_last(Pid, {_Count, B}) when Pid == self() -> - B; -handle_last(Pid, _Acc) -> - unlink(Pid), - Pid ! {self(), {last, {ok, dummy}}}, - exit(normal). - -iterate(B, Name, Tab, Pid, Source, Age, Pass, Acc) -> - Fun = - if - Pid == self() -> - RecName = val({Tab, record_name}), - fun(Recs, A) -> copy_records(RecName, Tab, Recs, A) end; - true -> - fun(Recs, A) -> send_records(Pid, Tab, Recs, Pass, A) end - end, - case mnesia_checkpoint:iterate(Name, Tab, Fun, Acc, Source, Age) of - {ok, Acc2} -> - Acc2; - {error, Reason} -> - R = {error, {"Tab copier iteration failed", Reason}}, - abort_write(B, {?MODULE, iterate}, [self(), B, Tab], R) - end. - -copy_records(_RecName, _Tab, [], Acc) -> - Acc; -copy_records(RecName, Tab, Recs, {Count, B}) -> - Recs2 = rec_filter(B, Tab, RecName, Recs), - B2 = safe_write(B, Recs2), - {Count + 1, B2}. - -send_records(Pid, Tab, Recs, Pass, {Count, B}) -> - receive - {Pid, more, Count} -> - if - Pass == last, Recs == [] -> - {Count, B}; - true -> - Next = Count + 1, - Pid ! {self(), {more, Next, Recs}}, - {Next, B} - end; - Msg -> - exit({send_records_unexpected_msg, Tab, Msg}) - end. - -tab_receiver(Pid, B, Tab, RecName, Slot) -> - Pid ! {self(), more, Slot}, - receive - {Pid, {more, Next, Recs}} -> - Recs2 = rec_filter(B, Tab, RecName, Recs), - B2 = safe_write(B, Recs2), - tab_receiver(Pid, B2, Tab, RecName, Next); - - {Pid, {last, {ok,_}}} -> - B; - - {'EXIT', Pid, {error, R}} -> - Reason = {error, {"Tab copier crashed", R}}, - abort_write(B, {?MODULE, remote_tab_sender}, [self(), B, Tab], Reason); - {'EXIT', Pid, R} -> - Reason = {error, {"Tab copier crashed", {'EXIT', R}}}, - abort_write(B, {?MODULE, remote_tab_sender}, [self(), B, Tab], Reason); - Msg -> - R = {error, {"Tab receiver got unexpected msg", Msg}}, - abort_write(B, {?MODULE, remote_tab_sender}, [self(), B, Tab], R) - end. - -rec_filter(B, schema, _RecName, Recs) -> - case catch mnesia_bup:refresh_cookie(Recs, B#backup_args.cookie) of - Recs2 when list(Recs2) -> - Recs2; - {error, _Reason} -> - %% No schema table cookie - Recs - end; -rec_filter(_B, Tab, Tab, Recs) -> - Recs; -rec_filter(_B, Tab, _RecName, Recs) -> - [setelement(1, Rec, Tab) || Rec <- Recs]. - -ets2dcd(Tab) -> - ets2dcd(Tab, dcd). - -ets2dcd(Tab, Ftype) -> - Fname = - case Ftype of - dcd -> mnesia_lib:tab2dcd(Tab); - dmp -> mnesia_lib:tab2dmp(Tab) - end, - TmpF = mnesia_lib:tab2tmp(Tab), - file:delete(TmpF), - Log = open_log({Tab, ets2dcd}, dcd_log_header(), TmpF, false), - mnesia_lib:db_fixtable(ram_copies, Tab, true), - ok = ets2dcd(mnesia_lib:db_init_chunk(ram_copies, Tab, 1000), Tab, Log), - mnesia_lib:db_fixtable(ram_copies, Tab, false), - close_log(Log), - ok = file:rename(TmpF, Fname), - %% Remove old log data which is now in the new dcd. - %% No one else should be accessing this file! - file:delete(mnesia_lib:tab2dcl(Tab)), - ok. - -ets2dcd('$end_of_table', _Tab, _Log) -> - ok; -ets2dcd({Recs, Cont}, Tab, Log) -> - ok = disk_log:alog_terms(Log, Recs), - ets2dcd(mnesia_lib:db_chunk(ram_copies, Cont), Tab, Log). - -dcd2ets(Tab) -> - dcd2ets(Tab, mnesia_monitor:get_env(auto_repair)). - -dcd2ets(Tab, Rep) -> - Dcd = mnesia_lib:tab2dcd(Tab), - case mnesia_lib:exists(Dcd) of - true -> - Log = open_log({Tab, dcd2ets}, dcd_log_header(), Dcd, - true, Rep, read_only), - Data = chunk_log(Log, start), - ok = insert_dcdchunk(Data, Log, Tab), - close_log(Log), - load_dcl(Tab, Rep); - false -> %% Handle old dets files, and conversion from disc_only to disc. - Fname = mnesia_lib:tab2dat(Tab), - Type = val({Tab, setorbag}), - case mnesia_lib:dets_to_ets(Tab, Tab, Fname, Type, Rep, yes) of - loaded -> - ets2dcd(Tab), - file:delete(Fname), - 0; - {error, Error} -> - erlang:error({"Failed to load table from disc", [Tab, Error]}) - end - end. - -insert_dcdchunk({Cont, [LogH | Rest]}, Log, Tab) - when record(LogH, log_header), - LogH#log_header.log_kind == dcd_log, - LogH#log_header.log_version >= "1.0" -> - insert_dcdchunk({Cont, Rest}, Log, Tab); - -insert_dcdchunk({Cont, Recs}, Log, Tab) -> - true = ets:insert(Tab, Recs), - insert_dcdchunk(chunk_log(Log, Cont), Log, Tab); -insert_dcdchunk(eof, _Log, _Tab) -> - ok. - -load_dcl(Tab, Rep) -> - FName = mnesia_lib:tab2dcl(Tab), - case mnesia_lib:exists(FName) of - true -> - Name = {load_dcl,Tab}, - open_log(Name, - dcl_log_header(), - FName, - true, - Rep, - read_only), - FirstChunk = chunk_log(Name, start), - N = insert_logchunk(FirstChunk, Name, 0), - close_log(Name), - N; - false -> - 0 - end. - -insert_logchunk({C2, Recs}, Tab, C) -> - N = add_recs(Recs, C), - insert_logchunk(chunk_log(Tab, C2), Tab, C+N); -insert_logchunk(eof, _Tab, C) -> - C. - -add_recs([{{Tab, _Key}, Val, write} | Rest], N) -> - true = ets:insert(Tab, Val), - add_recs(Rest, N+1); -add_recs([{{Tab, Key}, _Val, delete} | Rest], N) -> - true = ets:delete(Tab, Key), - add_recs(Rest, N+1); -add_recs([{{Tab, _Key}, Val, delete_object} | Rest], N) -> - true = ets:match_delete(Tab, Val), - add_recs(Rest, N+1); -add_recs([{{Tab, Key}, Val, update_counter} | Rest], N) -> - {RecName, Incr} = Val, - case catch ets:update_counter(Tab, Key, Incr) of - CounterVal when integer(CounterVal) -> - ok; - _ -> - Zero = {RecName, Key, 0}, - true = ets:insert(Tab, Zero) - end, - add_recs(Rest, N+1); -add_recs([LogH|Rest], N) - when record(LogH, log_header), - LogH#log_header.log_kind == dcl_log, - LogH#log_header.log_version >= "1.0" -> - add_recs(Rest, N); -add_recs([{{Tab, _Key}, _Val, clear_table} | Rest], N) -> - true = ets:match_delete(Tab, '_'), - add_recs(Rest, N+ets:info(Tab, size)); -add_recs([], N) -> - N. diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_monitor.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_monitor.erl deleted file mode 100644 index 554f020ffb..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_monitor.erl +++ /dev/null @@ -1,776 +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_monitor.erl,v 1.1 2008/12/17 09:53:38 mikpe Exp $ -%% --module(mnesia_monitor). - --behaviour(gen_server). - -%% Public exports --export([ - close_dets/1, - close_log/1, - detect_inconcistency/2, - get_env/1, - init/0, - mktab/2, - unsafe_mktab/2, - mnesia_down/2, - needs_protocol_conversion/1, - negotiate_protocol/1, - disconnect/1, - open_dets/2, - unsafe_open_dets/2, - open_log/1, - patch_env/2, - protocol_version/0, - reopen_log/3, - set_env/2, - start/0, - start_proc/4, - terminate_proc/3, - unsafe_close_dets/1, - unsafe_close_log/1, - use_dir/0, - do_check_type/2 - ]). - -%% gen_server callbacks --export([ - init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - terminate/2, - code_change/3 - ]). - -%% Internal exports --export([ - call/1, - cast/1, - detect_partitioned_network/2, - has_remote_mnesia_down/1 - ]). - --import(mnesia_lib, [dbg_out/2, verbose/2, error/2, fatal/2, set/2]). - --include("mnesia.hrl"). - --record(state, {supervisor, pending_negotiators = [], - going_down = [], tm_started = false, early_connects = []}). - --define(current_protocol_version, {7,6}). - --define(previous_protocol_version, {7,5}). - -start() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, - [self()], [{timeout, infinity} - %% ,{debug, [trace]} - ]). - -init() -> - call(init). - -mnesia_down(From, Node) -> - cast({mnesia_down, From, Node}). - -mktab(Tab, Args) -> - unsafe_call({mktab, Tab, Args}). -unsafe_mktab(Tab, Args) -> - unsafe_call({unsafe_mktab, Tab, Args}). - -open_dets(Tab, Args) -> - unsafe_call({open_dets, Tab, Args}). -unsafe_open_dets(Tab, Args) -> - unsafe_call({unsafe_open_dets, Tab, Args}). - -close_dets(Tab) -> - unsafe_call({close_dets, Tab}). - -unsafe_close_dets(Name) -> - unsafe_call({unsafe_close_dets, Name}). - -open_log(Args) -> - unsafe_call({open_log, Args}). - -reopen_log(Name, Fname, Head) -> - unsafe_call({reopen_log, Name, Fname, Head}). - -close_log(Name) -> - unsafe_call({close_log, Name}). - -unsafe_close_log(Name) -> - unsafe_call({unsafe_close_log, Name}). - - -disconnect(Node) -> - cast({disconnect, Node}). - -%% Returns GoodNoodes -%% Creates a link to each compatible monitor and -%% protocol_version to agreed version upon success - -negotiate_protocol(Nodes) -> - Version = mnesia:system_info(version), - Protocols = acceptable_protocol_versions(), - MonitorPid = whereis(?MODULE), - Msg = {negotiate_protocol, MonitorPid, Version, Protocols}, - {Replies, _BadNodes} = multicall(Nodes, Msg), - check_protocol(Replies, Protocols). - -check_protocol([{Node, {accept, Mon, _Version, Protocol}} | Tail], Protocols) -> - case lists:member(Protocol, Protocols) of - true -> - case Protocol == protocol_version() of - true -> - set({protocol, Node}, {Protocol, false}); - false -> - set({protocol, Node}, {Protocol, true}) - end, - [node(Mon) | check_protocol(Tail, Protocols)]; - false -> - unlink(Mon), % Get rid of unneccessary link - check_protocol(Tail, Protocols) - end; -check_protocol([{Node, {reject, _Mon, Version, Protocol}} | Tail], Protocols) -> - verbose("Failed to connect with ~p. ~p protocols rejected. " - "expected version = ~p, expected protocol = ~p~n", - [Node, Protocols, Version, Protocol]), - check_protocol(Tail, Protocols); -check_protocol([{error, _Reason} | Tail], Protocols) -> - check_protocol(Tail, Protocols); -check_protocol([{badrpc, _Reason} | Tail], Protocols) -> - check_protocol(Tail, Protocols); -check_protocol([], [Protocol | _Protocols]) -> - set(protocol_version, Protocol), - []; -check_protocol([], []) -> - set(protocol_version, protocol_version()), - []. - -protocol_version() -> - case ?catch_val(protocol_version) of - {'EXIT', _} -> ?current_protocol_version; - Version -> Version - end. - -%% A sorted list of acceptable protocols the -%% preferred protocols are first in the list -acceptable_protocol_versions() -> - [protocol_version(), ?previous_protocol_version]. - -needs_protocol_conversion(Node) -> - case {?catch_val({protocol, Node}), protocol_version()} of - {{'EXIT', _}, _} -> - false; - {{_, Bool}, ?current_protocol_version} -> - Bool; - {{_, Bool}, _} -> - not Bool - end. - -cast(Msg) -> - case whereis(?MODULE) of - undefined -> ignore; - Pid -> gen_server:cast(Pid, Msg) - end. - -unsafe_call(Msg) -> - case whereis(?MODULE) of - undefined -> {error, {node_not_running, node()}}; - Pid -> gen_server:call(Pid, Msg, infinity) - end. - -call(Msg) -> - case whereis(?MODULE) of - undefined -> - {error, {node_not_running, node()}}; - Pid -> - link(Pid), - Res = gen_server:call(Pid, Msg, infinity), - unlink(Pid), - - %% We get an exit signal if server dies - receive - {'EXIT', Pid, _Reason} -> - {error, {node_not_running, node()}} - after 0 -> - ignore - end, - Res - end. - -multicall(Nodes, Msg) -> - rpc:multicall(Nodes, ?MODULE, call, [Msg]). - -start_proc(Who, Mod, Fun, Args) -> - Args2 = [Who, Mod, Fun, Args], - proc_lib:start_link(mnesia_sp, init_proc, Args2, infinity). - -terminate_proc(Who, R, State) when R /= shutdown, R /= killed -> - fatal("~p crashed: ~p state: ~p~n", [Who, R, State]); - -terminate_proc(Who, Reason, _State) -> - mnesia_lib:verbose("~p terminated: ~p~n", [Who, Reason]), - ok. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%% Callback functions from gen_server - -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, State} | -%% {ok, State, Timeout} | -%% {stop, Reason} -%%---------------------------------------------------------------------- -init([Parent]) -> - process_flag(trap_exit, true), - ?ets_new_table(mnesia_gvar, [set, public, named_table]), - set(subscribers, []), - mnesia_lib:verbose("~p starting: ~p~n", [?MODULE, self()]), - Version = mnesia:system_info(version), - set(version, Version), - dbg_out("Version: ~p~n", [Version]), - - case catch process_config_args(env()) of - ok -> - mnesia_lib:set({'$$$_report', current_pos}, 0), - Level = mnesia_lib:val(debug), - mnesia_lib:verbose("Mnesia debug level set to ~p\n", [Level]), - set(mnesia_status, starting), %% set start status - set({current, db_nodes}, [node()]), - set(use_dir, use_dir()), - mnesia_lib:create_counter(trans_aborts), - mnesia_lib:create_counter(trans_commits), - mnesia_lib:create_counter(trans_log_writes), - Left = get_env(dump_log_write_threshold), - mnesia_lib:set_counter(trans_log_writes_left, Left), - mnesia_lib:create_counter(trans_log_writes_prev), - mnesia_lib:create_counter(trans_restarts), - mnesia_lib:create_counter(trans_failures), - ?ets_new_table(mnesia_held_locks, [bag, public, named_table]), - ?ets_new_table(mnesia_tid_locks, [bag, public, named_table]), - ?ets_new_table(mnesia_sticky_locks, [set, public, named_table]), - ?ets_new_table(mnesia_lock_queue, - [bag, public, named_table, {keypos, 2}]), - ?ets_new_table(mnesia_lock_counter, [set, public, named_table]), - set(checkpoints, []), - set(pending_checkpoints, []), - set(pending_checkpoint_pids, []), - - {ok, #state{supervisor = Parent}}; - {'EXIT', Reason} -> - mnesia_lib:report_fatal("Bad configuration: ~p~n", [Reason]), - {stop, {bad_config, Reason}} - end. - -use_dir() -> - case ?catch_val(use_dir) of - {'EXIT', _} -> - case get_env(schema_location) of - disc -> true; - opt_disc -> non_empty_dir(); - ram -> false - end; - Bool -> - Bool - end. - -%% Returns true if the Mnesia directory contains -%% important files -non_empty_dir() -> - mnesia_lib:exists(mnesia_bup:fallback_bup()) or - mnesia_lib:exists(mnesia_lib:tab2dmp(schema)) or - mnesia_lib:exists(mnesia_lib:tab2dat(schema)). - -%%---------------------------------------------------------------------- -%% Func: handle_call/3 -%% Returns: {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | (terminate/2 is called) -%%---------------------------------------------------------------------- - -handle_call({mktab, Tab, Args}, _From, State) -> - case catch ?ets_new_table(Tab, Args) of - {'EXIT', ExitReason} -> - Msg = "Cannot create ets table", - Reason = {system_limit, Msg, Tab, Args, ExitReason}, - fatal("~p~n", [Reason]), - {noreply, State}; - Reply -> - {reply, Reply, State} - end; - -handle_call({unsafe_mktab, Tab, Args}, _From, State) -> - case catch ?ets_new_table(Tab, Args) of - {'EXIT', ExitReason} -> - {reply, {error, ExitReason}, State}; - Reply -> - {reply, Reply, State} - end; - - -handle_call({open_dets, Tab, Args}, _From, State) -> - case mnesia_lib:dets_sync_open(Tab, Args) of - {ok, Tab} -> - {reply, {ok, Tab}, State}; - - {error, Reason} -> - Msg = "Cannot open dets table", - Error = {error, {Msg, Tab, Args, Reason}}, - fatal("~p~n", [Error]), - {noreply, State} - end; - -handle_call({unsafe_open_dets, Tab, Args}, _From, State) -> - case mnesia_lib:dets_sync_open(Tab, Args) of - {ok, Tab} -> - {reply, {ok, Tab}, State}; - {error, Reason} -> - {reply, {error,Reason}, State} - end; - -handle_call({close_dets, Tab}, _From, State) -> - case mnesia_lib:dets_sync_close(Tab) of - ok -> - {reply, ok, State}; - {error, Reason} -> - Msg = "Cannot close dets table", - Error = {error, {Msg, Tab, Reason}}, - fatal("~p~n", [Error]), - {noreply, State} - end; - -handle_call({unsafe_close_dets, Tab}, _From, State) -> - mnesia_lib:dets_sync_close(Tab), - {reply, ok, State}; - -handle_call({open_log, Args}, _From, State) -> - Res = disk_log:open([{notify, true}|Args]), - {reply, Res, State}; - -handle_call({reopen_log, Name, Fname, Head}, _From, State) -> - case disk_log:reopen(Name, Fname, Head) of - ok -> - {reply, ok, State}; - - {error, Reason} -> - Msg = "Cannot rename disk_log file", - Error = {error, {Msg, Name, Fname, Head, Reason}}, - fatal("~p~n", [Error]), - {noreply, State} - end; - -handle_call({close_log, Name}, _From, State) -> - case disk_log:close(Name) of - ok -> - {reply, ok, State}; - - {error, Reason} -> - Msg = "Cannot close disk_log file", - Error = {error, {Msg, Name, Reason}}, - fatal("~p~n", [Error]), - {noreply, State} - end; - -handle_call({unsafe_close_log, Name}, _From, State) -> - disk_log:close(Name), - {reply, ok, State}; - -handle_call({negotiate_protocol, Mon, _Version, _Protocols}, _From, State) - when State#state.tm_started == false -> - State2 = State#state{early_connects = [node(Mon) | State#state.early_connects]}, - {reply, {node(), {reject, self(), uninitialized, uninitialized}}, State2}; - -handle_call({negotiate_protocol, Mon, Version, Protocols}, From, State) - when node(Mon) /= node() -> - Protocol = protocol_version(), - MyVersion = mnesia:system_info(version), - case lists:member(Protocol, Protocols) of - true -> - accept_protocol(Mon, MyVersion, Protocol, From, State); - false -> - %% in this release we should be able to handle the previous - %% protocol - case hd(Protocols) of - ?previous_protocol_version -> - accept_protocol(Mon, MyVersion, ?previous_protocol_version, From, State); - _ -> - verbose("Connection with ~p rejected. " - "version = ~p, protocols = ~p, " - "expected version = ~p, expected protocol = ~p~n", - [node(Mon), Version, Protocols, MyVersion, Protocol]), - {reply, {node(), {reject, self(), MyVersion, Protocol}}, State} - end - end; - -handle_call(init, _From, State) -> - net_kernel:monitor_nodes(true), - EarlyNodes = State#state.early_connects, - State2 = State#state{tm_started = true}, - {reply, EarlyNodes, State2}; - -handle_call(Msg, _From, State) -> - error("~p got unexpected call: ~p~n", [?MODULE, Msg]), - {noreply, State}. - -accept_protocol(Mon, Version, Protocol, From, State) -> - Reply = {node(), {accept, self(), Version, Protocol}}, - Node = node(Mon), - Pending0 = State#state.pending_negotiators, - Pending = lists:keydelete(Node, 1, Pending0), - case lists:member(Node, State#state.going_down) of - true -> - %% Wait for the mnesia_down to be processed, - %% before we reply - P = Pending ++ [{Node, Mon, From, Reply}], - {noreply, State#state{pending_negotiators = P}}; - false -> - %% No need for wait - link(Mon), %% link to remote Monitor - case Protocol == protocol_version() of - true -> - set({protocol, Node}, {Protocol, false}); - false -> - set({protocol, Node}, {Protocol, true}) - end, - {reply, Reply, State#state{pending_negotiators = Pending}} - end. - -%%---------------------------------------------------------------------- -%% Func: handle_cast/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- - -handle_cast({mnesia_down, mnesia_controller, Node}, State) -> - mnesia_tm:mnesia_down(Node), - {noreply, State}; - -handle_cast({mnesia_down, mnesia_tm, {Node, Pending}}, State) -> - mnesia_locker:mnesia_down(Node, Pending), - {noreply, State}; - -handle_cast({mnesia_down, mnesia_locker, Node}, State) -> - Down = {mnesia_down, Node}, - mnesia_lib:report_system_event(Down), - GoingDown = lists:delete(Node, State#state.going_down), - State2 = State#state{going_down = GoingDown}, - Pending = State#state.pending_negotiators, - case lists:keysearch(Node, 1, Pending) of - {value, {Node, Mon, ReplyTo, Reply}} -> - %% Late reply to remote monitor - link(Mon), %% link to remote Monitor - gen_server:reply(ReplyTo, Reply), - P2 = lists:keydelete(Node, 1,Pending), - State3 = State2#state{pending_negotiators = P2}, - {noreply, State3}; - false -> - %% No pending remote monitors - {noreply, State2} - end; - -handle_cast({disconnect, Node}, State) -> - case rpc:call(Node, erlang, whereis, [?MODULE]) of - {badrpc, _} -> - ignore; - RemoteMon when pid(RemoteMon) -> - unlink(RemoteMon) - end, - {noreply, State}; - -handle_cast({inconsistent_database, Context, Node}, State) -> - Msg = {inconsistent_database, Context, Node}, - mnesia_lib:report_system_event(Msg), - {noreply, State}; - -handle_cast(Msg, State) -> - error("~p got unexpected cast: ~p~n", [?MODULE, Msg]), - {noreply, State}. - -%%---------------------------------------------------------------------- -%% Func: handle_info/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- - -handle_info({'EXIT', Pid, R}, State) when Pid == State#state.supervisor -> - dbg_out("~p was ~p by supervisor~n",[?MODULE, R]), - {stop, R, State}; - -handle_info({'EXIT', Pid, fatal}, State) when node(Pid) == node() -> - dbg_out("~p got FATAL ERROR from: ~p~n",[?MODULE, Pid]), - exit(State#state.supervisor, shutdown), - {noreply, State}; - -handle_info({'EXIT', Pid, Reason}, State) -> - Node = node(Pid), - if - Node /= node() -> - %% Remotly linked process died, assume that it was a mnesia_monitor - mnesia_recover:mnesia_down(Node), - mnesia_controller:mnesia_down(Node), - {noreply, State#state{going_down = [Node | State#state.going_down]}}; - true -> - %% We have probably got an exit signal from from - %% disk_log or dets - Hint = "Hint: check that the disk still is writable", - Msg = {'EXIT', Pid, Reason}, - fatal("~p got unexpected info: ~p; ~p~n", - [?MODULE, Msg, Hint]) - end; - -handle_info({nodeup, Node}, State) -> - %% Ok, we are connected to yet another Erlang node - %% Let's check if Mnesia is running there in order - %% to detect if the network has been partitioned - %% due to communication failure. - - HasDown = mnesia_recover:has_mnesia_down(Node), - ImRunning = mnesia_lib:is_running(), - - if - %% If I'm not running the test will be made later. - HasDown == true, ImRunning == yes -> - spawn_link(?MODULE, detect_partitioned_network, [self(), Node]); - true -> - ignore - end, - {noreply, State}; - -handle_info({nodedown, _Node}, State) -> - %% Ignore, we are only caring about nodeup's - {noreply, State}; - -handle_info({disk_log, _Node, Log, Info}, State) -> - case Info of - {truncated, _No} -> - ok; - _ -> - mnesia_lib:important("Warning Log file ~p error reason ~s~n", - [Log, disk_log:format_error(Info)]) - end, - {noreply, State}; - -handle_info(Msg, State) -> - error("~p got unexpected info (~p): ~p~n", [?MODULE, State, Msg]). - -%%---------------------------------------------------------------------- -%% Func: terminate/2 -%% Purpose: Shutdown the server -%% Returns: any (ignored by gen_server) -%%---------------------------------------------------------------------- -terminate(Reason, State) -> - terminate_proc(?MODULE, Reason, State). - -%%---------------------------------------------------------------------- -%% Func: code_change/3 -%% Purpose: Upgrade process when its code is to be changed -%% Returns: {ok, NewState} -%%---------------------------------------------------------------------- - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%%---------------------------------------------------------------------- -%%% Internal functions -%%%---------------------------------------------------------------------- - -process_config_args([]) -> - ok; -process_config_args([C|T]) -> - V = get_env(C), - dbg_out("Env ~p: ~p~n", [C, V]), - mnesia_lib:set(C, V), - process_config_args(T). - -set_env(E,Val) -> - mnesia_lib:set(E, check_type(E,Val)), - ok. - -get_env(E) -> - case ?catch_val(E) of - {'EXIT', _} -> - case application:get_env(mnesia, E) of - {ok, Val} -> - check_type(E, Val); - undefined -> - check_type(E, default_env(E)) - end; - Val -> - Val - end. - -env() -> - [ - access_module, - auto_repair, - backup_module, - debug, - dir, - dump_log_load_regulation, - dump_log_time_threshold, - dump_log_update_in_place, - dump_log_write_threshold, - embedded_mnemosyne, - event_module, - extra_db_nodes, - ignore_fallback_at_startup, - fallback_error_function, - max_wait_for_decision, - schema_location, - core_dir - ]. - -default_env(access_module) -> - mnesia; -default_env(auto_repair) -> - true; -default_env(backup_module) -> - mnesia_backup; -default_env(debug) -> - none; -default_env(dir) -> - Name = lists:concat(["Mnesia.", node()]), - filename:absname(Name); -default_env(dump_log_load_regulation) -> - false; -default_env(dump_log_time_threshold) -> - timer:minutes(3); -default_env(dump_log_update_in_place) -> - true; -default_env(dump_log_write_threshold) -> - 1000; -default_env(embedded_mnemosyne) -> - false; -default_env(event_module) -> - mnesia_event; -default_env(extra_db_nodes) -> - []; -default_env(ignore_fallback_at_startup) -> - false; -default_env(fallback_error_function) -> - {mnesia, lkill}; -default_env(max_wait_for_decision) -> - infinity; -default_env(schema_location) -> - opt_disc; -default_env(core_dir) -> - false. - -check_type(Env, Val) -> - case catch do_check_type(Env, Val) of - {'EXIT', _Reason} -> - exit({bad_config, Env, Val}); - NewVal -> - NewVal - end. - -do_check_type(access_module, A) when atom(A) -> A; -do_check_type(auto_repair, B) -> bool(B); -do_check_type(backup_module, B) when atom(B) -> B; -do_check_type(debug, debug) -> debug; -do_check_type(debug, false) -> none; -do_check_type(debug, none) -> none; -do_check_type(debug, trace) -> trace; -do_check_type(debug, true) -> debug; -do_check_type(debug, verbose) -> verbose; -do_check_type(dir, V) -> filename:absname(V); -do_check_type(dump_log_load_regulation, B) -> bool(B); -do_check_type(dump_log_time_threshold, I) when integer(I), I > 0 -> I; -do_check_type(dump_log_update_in_place, B) -> bool(B); -do_check_type(dump_log_write_threshold, I) when integer(I), I > 0 -> I; -do_check_type(event_module, A) when atom(A) -> A; -do_check_type(ignore_fallback_at_startup, B) -> bool(B); -do_check_type(fallback_error_function, {Mod, Func}) - when atom(Mod), atom(Func) -> {Mod, Func}; -do_check_type(embedded_mnemosyne, B) -> bool(B); -do_check_type(extra_db_nodes, L) when list(L) -> - Fun = fun(N) when N == node() -> false; - (A) when atom(A) -> true - end, - lists:filter(Fun, L); -do_check_type(max_wait_for_decision, infinity) -> infinity; -do_check_type(max_wait_for_decision, I) when integer(I), I > 0 -> I; -do_check_type(schema_location, M) -> media(M); -do_check_type(core_dir, "false") -> false; -do_check_type(core_dir, false) -> false; -do_check_type(core_dir, Dir) when list(Dir) -> Dir. - - -bool(true) -> true; -bool(false) -> false. - -media(disc) -> disc; -media(opt_disc) -> opt_disc; -media(ram) -> ram. - -patch_env(Env, Val) -> - case catch do_check_type(Env, Val) of - {'EXIT', _Reason} -> - {error, {bad_type, Env, Val}}; - NewVal -> - application_controller:set_env(mnesia, Env, NewVal), - NewVal - end. - -detect_partitioned_network(Mon, Node) -> - GoodNodes = negotiate_protocol([Node]), - detect_inconcistency(GoodNodes, running_partitioned_network), - unlink(Mon), - exit(normal). - -detect_inconcistency([], _Context) -> - ok; -detect_inconcistency(Nodes, Context) -> - Downs = [N || N <- Nodes, mnesia_recover:has_mnesia_down(N)], - {Replies, _BadNodes} = - rpc:multicall(Downs, ?MODULE, has_remote_mnesia_down, [node()]), - report_inconsistency(Replies, Context, ok). - -has_remote_mnesia_down(Node) -> - HasDown = mnesia_recover:has_mnesia_down(Node), - Master = mnesia_recover:get_master_nodes(schema), - if - HasDown == true, Master == [] -> - {true, node()}; - true -> - {false, node()} - end. - -report_inconsistency([{true, Node} | Replies], Context, _Status) -> - %% Oops, Mnesia is already running on the - %% other node AND we both regard each - %% other as down. The database is - %% potentially inconsistent and we has to - %% do tell the applications about it, so - %% they may perform some clever recovery - %% action. - Msg = {inconsistent_database, Context, Node}, - mnesia_lib:report_system_event(Msg), - report_inconsistency(Replies, Context, inconsistent_database); -report_inconsistency([{false, _Node} | Replies], Context, Status) -> - report_inconsistency(Replies, Context, Status); -report_inconsistency([{badrpc, _Reason} | Replies], Context, Status) -> - report_inconsistency(Replies, Context, Status); -report_inconsistency([], _Context, Status) -> - Status. diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_recover.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_recover.erl deleted file mode 100644 index b3e8f1c386..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_recover.erl +++ /dev/null @@ -1,1175 +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_recover.erl,v 1.1 2008/12/17 09:53:39 mikpe Exp $ -%% --module(mnesia_recover). - --behaviour(gen_server). - --export([ - allow_garb/0, - call/1, - connect_nodes/1, - disconnect/1, - dump_decision_tab/0, - get_master_node_info/0, - get_master_node_tables/0, - get_master_nodes/1, - get_mnesia_downs/0, - has_mnesia_down/1, - incr_trans_tid_serial/0, - init/0, - log_decision/1, - log_master_nodes/3, - log_mnesia_down/1, - log_mnesia_up/1, - mnesia_down/1, - note_decision/2, - note_log_decision/2, - outcome/2, - start/0, - start_garb/0, - still_pending/1, - sync_trans_tid_serial/1, - wait_for_decision/2, - what_happened/3 - ]). - -%% gen_server callbacks --export([init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - terminate/2, - code_change/3 - ]). - - --include("mnesia.hrl"). --import(mnesia_lib, [set/2, verbose/2, error/2, fatal/2]). - --record(state, {supervisor, - unclear_pid, - unclear_decision, - unclear_waitfor, - tm_queue_len = 0, - initiated = false, - early_msgs = [] - }). - -%%-define(DBG(F, A), mnesia:report_event(list_to_atom(lists:flatten(io_lib:format(F, A))))). -%%-define(DBG(F, A), io:format("DBG: " ++ F, A)). - --record(transient_decision, {tid, outcome}). - -start() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [self()], - [{timeout, infinity} - %%, {debug, [trace]} - ]). - -init() -> - call(init). - -start_garb() -> - Pid = whereis(mnesia_recover), - {ok, _} = timer:send_interval(timer:minutes(2), Pid, garb_decisions), - {ok, _} = timer:send_interval(timer:seconds(10), Pid, check_overload). - -allow_garb() -> - cast(allow_garb). - - -%% The transaction log has either been swiched (latest -> previous) or -%% there is nothing to be dumped. This means that the previous -%% transaction log only may contain commit records which refers to -%% transactions noted in the last two of the 'Prev' tables. All other -%% tables may now be garbed by 'garb_decisions' (after 2 minutes). -%% Max 10 tables are kept. -do_allow_garb() -> - %% The order of the following stuff is important! - Curr = val(latest_transient_decision), - Old = val(previous_transient_decisions), - Next = create_transient_decision(), - {Prev, ReallyOld} = sublist([Curr | Old], 10, []), - [?ets_delete_table(Tab) || Tab <- ReallyOld], - set(previous_transient_decisions, Prev), - set(latest_transient_decision, Next). - -sublist([H|R], N, Acc) when N > 0 -> - sublist(R, N-1, [H| Acc]); -sublist(List, _N, Acc) -> - {lists:reverse(Acc), List}. - -do_garb_decisions() -> - case val(previous_transient_decisions) of - [First, Second | Rest] -> - set(previous_transient_decisions, [First, Second]), - [?ets_delete_table(Tab) || Tab <- Rest]; - _ -> - ignore - end. - -connect_nodes([]) -> - []; -connect_nodes(Ns) -> - %% Determine which nodes we should try to connect - AlreadyConnected = val(recover_nodes), - {_, Nodes} = mnesia_lib:search_delete(node(), Ns), - Check = Nodes -- AlreadyConnected, - GoodNodes = mnesia_monitor:negotiate_protocol(Check), - if - GoodNodes == [] -> - %% No good noodes to connect to - ignore; - true -> - %% Now we have agreed upon a protocol with some new nodes - %% and we may use them when we recover transactions - mnesia_lib:add_list(recover_nodes, GoodNodes), - cast({announce_all, GoodNodes}), - case get_master_nodes(schema) of - [] -> - Context = starting_partitioned_network, - mnesia_monitor:detect_inconcistency(GoodNodes, Context); - _ -> %% If master_nodes is set ignore old inconsistencies - ignore - end - end, - {GoodNodes, AlreadyConnected}. - -disconnect(Node) -> - mnesia_monitor:disconnect(Node), - mnesia_lib:del(recover_nodes, Node). - -val(Var) -> - case ?catch_val(Var) of - {'EXIT', Reason} -> mnesia_lib:other_val(Var, Reason); - Value -> Value - end. - -call(Msg) -> - Pid = whereis(?MODULE), - case Pid of - undefined -> - {error, {node_not_running, node()}}; - Pid -> - link(Pid), - Res = gen_server:call(Pid, Msg, infinity), - unlink(Pid), - - %% We get an exit signal if server dies - receive - {'EXIT', Pid, _Reason} -> - {error, {node_not_running, node()}} - after 0 -> - ignore - end, - Res - end. - -multicall(Nodes, Msg) -> - rpc:multicall(Nodes, ?MODULE, call, [Msg]). - -cast(Msg) -> - case whereis(?MODULE) of - undefined -> ignore; - Pid -> gen_server:cast(Pid, Msg) - end. - -abcast(Nodes, Msg) -> - gen_server:abcast(Nodes, ?MODULE, Msg). - -note_decision(Tid, Outcome) -> - Tab = val(latest_transient_decision), - ?ets_insert(Tab, #transient_decision{tid = Tid, outcome = Outcome}). - -note_up(Node, _Date, _Time) -> - ?ets_delete(mnesia_decision, Node). - -note_down(Node, Date, Time) -> - ?ets_insert(mnesia_decision, {mnesia_down, Node, Date, Time}). - -note_master_nodes(Tab, []) -> - ?ets_delete(mnesia_decision, Tab); -note_master_nodes(Tab, Nodes) when list(Nodes) -> - Master = {master_nodes, Tab, Nodes}, - ?ets_insert(mnesia_decision, Master). - -note_outcome(D) when D#decision.disc_nodes == [] -> -%% ?DBG("~w: note_tmp_decision: ~w~n", [node(), D]), - note_decision(D#decision.tid, filter_outcome(D#decision.outcome)), - ?ets_delete(mnesia_decision, D#decision.tid); -note_outcome(D) when D#decision.disc_nodes /= [] -> -%% ?DBG("~w: note_decision: ~w~n", [node(), D]), - ?ets_insert(mnesia_decision, D). - -log_decision(D) when D#decision.outcome /= unclear -> - OldD = decision(D#decision.tid), - MergedD = merge_decisions(node(), OldD, D), - do_log_decision(MergedD, true); -log_decision(D) -> - do_log_decision(D, false). - -do_log_decision(D, DoTell) -> - RamNs = D#decision.ram_nodes, - DiscNs = D#decision.disc_nodes -- [node()], - Outcome = D#decision.outcome, - D2 = - case Outcome of - aborted -> D#decision{disc_nodes = DiscNs}; - committed -> D#decision{disc_nodes = DiscNs}; - _ -> D - end, - note_outcome(D2), - case mnesia_monitor:use_dir() of - true -> - mnesia_log:append(latest_log, D2), - if - DoTell == true, Outcome /= unclear -> - tell_im_certain(DiscNs, D2), - tell_im_certain(RamNs, D2); - true -> - ignore - end; - false -> - ignore - end. - -tell_im_certain([], _D) -> - ignore; -tell_im_certain(Nodes, D) -> - Msg = {im_certain, node(), D}, -%% ?DBG("~w: ~w: tell: ~w~n", [node(), Msg, Nodes]), - abcast(Nodes, Msg). - -log_mnesia_up(Node) -> - call({log_mnesia_up, Node}). - -log_mnesia_down(Node) -> - call({log_mnesia_down, Node}). - -get_mnesia_downs() -> - Tab = mnesia_decision, - Pat = {mnesia_down, '_', '_', '_'}, - Downs = ?ets_match_object(Tab, Pat), - [Node || {mnesia_down, Node, _Date, _Time} <- Downs]. - -%% Check if we have got a mnesia_down from Node -has_mnesia_down(Node) -> - case ?ets_lookup(mnesia_decision, Node) of - [{mnesia_down, Node, _Date, _Time}] -> - true; - [] -> - false - end. - -mnesia_down(Node) -> - case ?catch_val(recover_nodes) of - {'EXIT', _} -> - %% Not started yet - ignore; - _ -> - mnesia_lib:del(recover_nodes, Node), - cast({mnesia_down, Node}) - end. - -log_master_nodes(Args, UseDir, IsRunning) -> - if - IsRunning == yes -> - log_master_nodes2(Args, UseDir, IsRunning, ok); - UseDir == false -> - ok; - true -> - Name = latest_log, - Fname = mnesia_log:latest_log_file(), - Exists = mnesia_lib:exists(Fname), - Repair = mnesia:system_info(auto_repair), - OpenArgs = [{file, Fname}, {name, Name}, {repair, Repair}], - case disk_log:open(OpenArgs) of - {ok, Name} -> - log_master_nodes2(Args, UseDir, IsRunning, ok); - {repaired, Name, {recovered, _R}, {badbytes, _B}} - when Exists == true -> - log_master_nodes2(Args, UseDir, IsRunning, ok); - {repaired, Name, {recovered, _R}, {badbytes, _B}} - when Exists == false -> - mnesia_log:write_trans_log_header(), - log_master_nodes2(Args, UseDir, IsRunning, ok); - {error, Reason} -> - {error, Reason} - end - end. - -log_master_nodes2([{Tab, Nodes} | Tail], UseDir, IsRunning, WorstRes) -> - Res = - case IsRunning of - yes -> - R = call({log_master_nodes, Tab, Nodes, UseDir, IsRunning}), - mnesia_controller:master_nodes_updated(Tab, Nodes), - R; - _ -> - do_log_master_nodes(Tab, Nodes, UseDir, IsRunning) - end, - case Res of - ok -> - log_master_nodes2(Tail, UseDir, IsRunning, WorstRes); - {error, Reason} -> - log_master_nodes2(Tail, UseDir, IsRunning, {error, Reason}) - end; -log_master_nodes2([], _UseDir, IsRunning, WorstRes) -> - case IsRunning of - yes -> - WorstRes; - _ -> - disk_log:close(latest_log), - WorstRes - end. - -get_master_node_info() -> - Tab = mnesia_decision, - Pat = {master_nodes, '_', '_'}, - case catch mnesia_lib:db_match_object(ram_copies,Tab, Pat) of - {'EXIT', _} -> - []; - Masters -> - Masters - end. - -get_master_node_tables() -> - Masters = get_master_node_info(), - [Tab || {master_nodes, Tab, _Nodes} <- Masters]. - -get_master_nodes(Tab) -> - case catch ?ets_lookup_element(mnesia_decision, Tab, 3) of - {'EXIT', _} -> []; - Nodes -> Nodes - end. - -%% Determine what has happened to the transaction -what_happened(Tid, Protocol, Nodes) -> - Default = - case Protocol of - asym_trans -> aborted; - _ -> unclear %% sym_trans and sync_sym_trans - end, - This = node(), - case lists:member(This, Nodes) of - true -> - {ok, Outcome} = call({what_happened, Default, Tid}), - Others = Nodes -- [This], - case filter_outcome(Outcome) of - unclear -> what_happened_remotely(Tid, Default, Others); - aborted -> aborted; - committed -> committed - end; - false -> - what_happened_remotely(Tid, Default, Nodes) - end. - -what_happened_remotely(Tid, Default, Nodes) -> - {Replies, _} = multicall(Nodes, {what_happened, Default, Tid}), - check_what_happened(Replies, 0, 0). - -check_what_happened([H | T], Aborts, Commits) -> - case H of - {ok, R} -> - case filter_outcome(R) of - committed -> - check_what_happened(T, Aborts, Commits + 1); - aborted -> - check_what_happened(T, Aborts + 1, Commits); - unclear -> - check_what_happened(T, Aborts, Commits) - end; - {error, _} -> - check_what_happened(T, Aborts, Commits); - {badrpc, _} -> - check_what_happened(T, Aborts, Commits) - end; -check_what_happened([], Aborts, Commits) -> - if - Aborts == 0, Commits == 0 -> aborted; % None of the active nodes knows - Aborts > 0 -> aborted; % Someody has aborted - Aborts == 0, Commits > 0 -> committed % All has committed - end. - -%% Determine what has happened to the transaction -%% and possibly wait forever for the decision. -wait_for_decision(presume_commit, _InitBy) -> - %% sym_trans - {{presume_commit, self()}, committed}; - -wait_for_decision(D, InitBy) when D#decision.outcome == presume_abort -> - %% asym_trans - Tid = D#decision.tid, - Outcome = filter_outcome(outcome(Tid, D#decision.outcome)), - if - Outcome /= unclear -> - {Tid, Outcome}; - - InitBy /= startup -> - %% Wait a while for active transactions - %% to end and try again - timer:sleep(200), - wait_for_decision(D, InitBy); - - InitBy == startup -> - {ok, Res} = call({wait_for_decision, D}), - {Tid, Res} - end. - -still_pending([Tid | Pending]) -> - case filter_outcome(outcome(Tid, unclear)) of - unclear -> [Tid | still_pending(Pending)]; - _ -> still_pending(Pending) - end; -still_pending([]) -> - []. - -load_decision_tab() -> - Cont = mnesia_log:open_decision_tab(), - load_decision_tab(Cont, load_decision_tab), - mnesia_log:close_decision_tab(). - -load_decision_tab(eof, _InitBy) -> - ok; -load_decision_tab(Cont, InitBy) -> - case mnesia_log:chunk_decision_tab(Cont) of - {Cont2, Decisions} -> - note_log_decisions(Decisions, InitBy), - load_decision_tab(Cont2, InitBy); - eof -> - ok - end. - -%% Dumps DECISION.LOG and PDECISION.LOG and removes them. -%% From now on all decisions are logged in the transaction log file -convert_old() -> - HasOldStuff = - mnesia_lib:exists(mnesia_log:previous_decision_log_file()) or - mnesia_lib:exists(mnesia_log:decision_log_file()), - case HasOldStuff of - true -> - mnesia_log:open_decision_log(), - dump_decision_log(startup), - dump_decision_log(startup), - mnesia_log:close_decision_log(), - Latest = mnesia_log:decision_log_file(), - ok = file:delete(Latest); - false -> - ignore - end. - -dump_decision_log(InitBy) -> - %% Assumed to be run in transaction log dumper process - Cont = mnesia_log:prepare_decision_log_dump(), - perform_dump_decision_log(Cont, InitBy). - -perform_dump_decision_log(eof, _InitBy) -> - confirm_decision_log_dump(); -perform_dump_decision_log(Cont, InitBy) when InitBy == startup -> - case mnesia_log:chunk_decision_log(Cont) of - {Cont2, Decisions} -> - note_log_decisions(Decisions, InitBy), - perform_dump_decision_log(Cont2, InitBy); - eof -> - confirm_decision_log_dump() - end; -perform_dump_decision_log(_Cont, _InitBy) -> - confirm_decision_log_dump(). - -confirm_decision_log_dump() -> - dump_decision_tab(), - mnesia_log:confirm_decision_log_dump(). - -dump_decision_tab() -> - Tab = mnesia_decision, - All = mnesia_lib:db_match_object(ram_copies,Tab, '_'), - mnesia_log:save_decision_tab({decision_list, All}). - -note_log_decisions([What | Tail], InitBy) -> - note_log_decision(What, InitBy), - note_log_decisions(Tail, InitBy); -note_log_decisions([], _InitBy) -> - ok. - -note_log_decision(NewD, InitBy) when NewD#decision.outcome == pre_commit -> - note_log_decision(NewD#decision{outcome = unclear}, InitBy); - -note_log_decision(NewD, _InitBy) when record(NewD, decision) -> - Tid = NewD#decision.tid, - sync_trans_tid_serial(Tid), - OldD = decision(Tid), - MergedD = merge_decisions(node(), OldD, NewD), - note_outcome(MergedD); - -note_log_decision({trans_tid, serial, _Serial}, startup) -> - ignore; - -note_log_decision({trans_tid, serial, Serial}, _InitBy) -> - sync_trans_tid_serial(Serial); - -note_log_decision({mnesia_up, Node, Date, Time}, _InitBy) -> - note_up(Node, Date, Time); - -note_log_decision({mnesia_down, Node, Date, Time}, _InitBy) -> - note_down(Node, Date, Time); - -note_log_decision({master_nodes, Tab, Nodes}, _InitBy) -> - note_master_nodes(Tab, Nodes); - -note_log_decision(H, _InitBy) when H#log_header.log_kind == decision_log -> - V = mnesia_log:decision_log_version(), - if - H#log_header.log_version == V-> - ok; - H#log_header.log_version == "2.0" -> - verbose("Accepting an old version format of decision log: ~p~n", - [V]), - ok; - true -> - fatal("Bad version of decision log: ~p~n", [H]) - end; - -note_log_decision(H, _InitBy) when H#log_header.log_kind == decision_tab -> - V = mnesia_log:decision_tab_version(), - if - V == H#log_header.log_version -> - ok; - true -> - fatal("Bad version of decision tab: ~p~n", [H]) - end; -note_log_decision({decision_list, ItemList}, InitBy) -> - note_log_decisions(ItemList, InitBy); -note_log_decision(BadItem, InitBy) -> - exit({"Bad decision log item", BadItem, InitBy}). - -trans_tid_serial() -> - ?ets_lookup_element(mnesia_decision, serial, 3). - -set_trans_tid_serial(Val) -> - ?ets_insert(mnesia_decision, {trans_tid, serial, Val}). - -incr_trans_tid_serial() -> - ?ets_update_counter(mnesia_decision, serial, 1). - -sync_trans_tid_serial(ThatCounter) when integer(ThatCounter) -> - ThisCounter = trans_tid_serial(), - if - ThatCounter > ThisCounter -> - set_trans_tid_serial(ThatCounter + 1); - true -> - ignore - end; -sync_trans_tid_serial(Tid) -> - sync_trans_tid_serial(Tid#tid.counter). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%% Callback functions from gen_server - -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, State} | -%% {ok, State, Timeout} | -%% {stop, Reason} -%%---------------------------------------------------------------------- -init([Parent]) -> - process_flag(trap_exit, true), - mnesia_lib:verbose("~p starting: ~p~n", [?MODULE, self()]), - set(latest_transient_decision, create_transient_decision()), - set(previous_transient_decisions, []), - set(recover_nodes, []), - State = #state{supervisor = Parent}, - {ok, State}. - -create_transient_decision() -> - ?ets_new_table(mnesia_transient_decision, [{keypos, 2}, set, public]). - -%%---------------------------------------------------------------------- -%% Func: handle_call/3 -%% Returns: {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | (terminate/2 is called) -%%---------------------------------------------------------------------- - -handle_call(init, From, State) when State#state.initiated == false -> - Args = [{keypos, 2}, set, public, named_table], - case mnesia_monitor:use_dir() of - true -> - ?ets_new_table(mnesia_decision, Args), - set_trans_tid_serial(0), - TabFile = mnesia_log:decision_tab_file(), - case mnesia_lib:exists(TabFile) of - true -> - load_decision_tab(); - false -> - ignore - end, - convert_old(), - mnesia_dumper:opt_dump_log(scan_decisions); - false -> - ?ets_new_table(mnesia_decision, Args), - set_trans_tid_serial(0) - end, - handle_early_msgs(State, From); - -handle_call(Msg, From, State) when State#state.initiated == false -> - %% Buffer early messages - Msgs = State#state.early_msgs, - {noreply, State#state{early_msgs = [{call, Msg, From} | Msgs]}}; - -handle_call({what_happened, Default, Tid}, _From, State) -> - sync_trans_tid_serial(Tid), - Outcome = outcome(Tid, Default), - {reply, {ok, Outcome}, State}; - -handle_call({wait_for_decision, D}, From, State) -> - Recov = val(recover_nodes), - AliveRam = (mnesia_lib:intersect(D#decision.ram_nodes, Recov) -- [node()]), - RemoteDisc = D#decision.disc_nodes -- [node()], - if - AliveRam == [], RemoteDisc == [] -> - %% No more else to wait for and we may safely abort - {reply, {ok, aborted}, State}; - true -> - verbose("Transaction ~p is unclear. " - "Wait for disc nodes: ~w ram: ~w~n", - [D#decision.tid, RemoteDisc, AliveRam]), - AliveDisc = mnesia_lib:intersect(RemoteDisc, Recov), - Msg = {what_decision, node(), D}, - abcast(AliveRam, Msg), - abcast(AliveDisc, Msg), - case val(max_wait_for_decision) of - infinity -> - ignore; - MaxWait -> - ForceMsg = {force_decision, D#decision.tid}, - {ok, _} = timer:send_after(MaxWait, ForceMsg) - end, - State2 = State#state{unclear_pid = From, - unclear_decision = D, - unclear_waitfor = (RemoteDisc ++ AliveRam)}, - {noreply, State2} - end; - -handle_call({log_mnesia_up, Node}, _From, State) -> - do_log_mnesia_up(Node), - {reply, ok, State}; - -handle_call({log_mnesia_down, Node}, _From, State) -> - do_log_mnesia_down(Node), - {reply, ok, State}; - -handle_call({log_master_nodes, Tab, Nodes, UseDir, IsRunning}, _From, State) -> - do_log_master_nodes(Tab, Nodes, UseDir, IsRunning), - {reply, ok, State}; - -handle_call(Msg, _From, State) -> - error("~p got unexpected call: ~p~n", [?MODULE, Msg]), - {noreply, State}. - -do_log_mnesia_up(Node) -> - Yoyo = {mnesia_up, Node, Date = date(), Time = time()}, - case mnesia_monitor:use_dir() of - true -> - mnesia_log:append(latest_log, Yoyo), - disk_log:sync(latest_log); - false -> - ignore - end, - note_up(Node, Date, Time). - -do_log_mnesia_down(Node) -> - Yoyo = {mnesia_down, Node, Date = date(), Time = time()}, - case mnesia_monitor:use_dir() of - true -> - mnesia_log:append(latest_log, Yoyo), - disk_log:sync(latest_log); - false -> - ignore - end, - note_down(Node, Date, Time). - -do_log_master_nodes(Tab, Nodes, UseDir, IsRunning) -> - Master = {master_nodes, Tab, Nodes}, - Res = - case UseDir of - true -> - LogRes = mnesia_log:append(latest_log, Master), - disk_log:sync(latest_log), - LogRes; - false -> - ok - end, - case IsRunning of - yes -> - note_master_nodes(Tab, Nodes); - _NotRunning -> - ignore - end, - Res. - -%%---------------------------------------------------------------------- -%% Func: handle_cast/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- - -handle_cast(Msg, State) when State#state.initiated == false -> - %% Buffer early messages - Msgs = State#state.early_msgs, - {noreply, State#state{early_msgs = [{cast, Msg} | Msgs]}}; - -handle_cast({im_certain, Node, NewD}, State) -> - OldD = decision(NewD#decision.tid), - MergedD = merge_decisions(Node, OldD, NewD), - do_log_decision(MergedD, false), - {noreply, State}; - -handle_cast(allow_garb, State) -> - do_allow_garb(), - {noreply, State}; - -handle_cast({decisions, Node, Decisions}, State) -> - mnesia_lib:add(recover_nodes, Node), - State2 = add_remote_decisions(Node, Decisions, State), - {noreply, State2}; - -handle_cast({what_decision, Node, OtherD}, State) -> - Tid = OtherD#decision.tid, - sync_trans_tid_serial(Tid), - Decision = - case decision(Tid) of - no_decision -> OtherD; - MyD when record(MyD, decision) -> MyD - end, - announce([Node], [Decision], [], true), - {noreply, State}; - -handle_cast({mnesia_down, Node}, State) -> - case State#state.unclear_decision of - undefined -> - {noreply, State}; - D -> - case lists:member(Node, D#decision.ram_nodes) of - false -> - {noreply, State}; - true -> - State2 = add_remote_decision(Node, D, State), - {noreply, State2} - end - end; - -handle_cast({announce_all, Nodes}, State) -> - announce_all(Nodes, tabs()), - {noreply, State}; - -handle_cast(Msg, State) -> - error("~p got unexpected cast: ~p~n", [?MODULE, Msg]), - {noreply, State}. - -%%---------------------------------------------------------------------- -%% Func: handle_info/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- - -%% No need for buffering -%% handle_info(Msg, State) when State#state.initiated == false -> -%% %% Buffer early messages -%% Msgs = State#state.early_msgs, -%% {noreply, State#state{early_msgs = [{info, Msg} | Msgs]}}; - -handle_info(check_overload, S) -> - %% Time to check if mnesia_tm is overloaded - case whereis(mnesia_tm) of - Pid when pid(Pid) -> - - Threshold = 100, - Prev = S#state.tm_queue_len, - {message_queue_len, Len} = - process_info(Pid, message_queue_len), - if - Len > Threshold, Prev > Threshold -> - What = {mnesia_tm, message_queue_len, [Prev, Len]}, - mnesia_lib:report_system_event({mnesia_overload, What}), - {noreply, S#state{tm_queue_len = 0}}; - - Len > Threshold -> - {noreply, S#state{tm_queue_len = Len}}; - - true -> - {noreply, S#state{tm_queue_len = 0}} - end; - undefined -> - {noreply, S} - end; - -handle_info(garb_decisions, State) -> - do_garb_decisions(), - {noreply, State}; - -handle_info({force_decision, Tid}, State) -> - %% Enforce a transaction recovery decision, - %% if we still are waiting for the outcome - - case State#state.unclear_decision of - U when U#decision.tid == Tid -> - verbose("Decided to abort transaction ~p since " - "max_wait_for_decision has been exceeded~n", - [Tid]), - D = U#decision{outcome = aborted}, - State2 = add_remote_decision(node(), D, State), - {noreply, State2}; - _ -> - {noreply, State} - end; - -handle_info({'EXIT', Pid, R}, State) when Pid == State#state.supervisor -> - mnesia_lib:dbg_out("~p was ~p~n",[?MODULE, R]), - {stop, shutdown, State}; - -handle_info(Msg, State) -> - error("~p got unexpected info: ~p~n", [?MODULE, Msg]), - {noreply, State}. - -%%---------------------------------------------------------------------- -%% Func: terminate/2 -%% Purpose: Shutdown the server -%% Returns: any (ignored by gen_server) -%%---------------------------------------------------------------------- - -terminate(Reason, State) -> - mnesia_monitor:terminate_proc(?MODULE, Reason, State). - -%%---------------------------------------------------------------------- -%% Func: code_change/3 -%% Purpose: Upgrade process when its code is to be changed -%% Returns: {ok, NewState} -%%---------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%%---------------------------------------------------------------------- -%%% Internal functions -%%%---------------------------------------------------------------------- - -handle_early_msgs(State, From) -> - Res = do_handle_early_msgs(State#state.early_msgs, - State#state{early_msgs = [], - initiated = true}), - gen_server:reply(From, ok), - Res. - -do_handle_early_msgs([Msg | Msgs], State) -> - %% The messages are in reverted order - case do_handle_early_msgs(Msgs, State) of - {stop, Reason, Reply, State2} -> - {stop, Reason, Reply, State2}; - {stop, Reason, State2} -> - {stop, Reason, State2}; - {noreply, State2} -> - handle_early_msg(Msg, State2) - end; - -do_handle_early_msgs([], State) -> - {noreply, State}. - -handle_early_msg({call, Msg, From}, State) -> - case handle_call(Msg, From, State) of - {reply, R, S} -> - gen_server:reply(From, R), - {noreply, S}; - Other -> - Other - end; -handle_early_msg({cast, Msg}, State) -> - handle_cast(Msg, State); -handle_early_msg({info, Msg}, State) -> - handle_info(Msg, State). - -tabs() -> - Curr = val(latest_transient_decision), % Do not miss any trans even - Prev = val(previous_transient_decisions), % if the tabs are switched - [Curr, mnesia_decision | Prev]. % Ordered by hit probability - -decision(Tid) -> - decision(Tid, tabs()). - -decision(Tid, [Tab | Tabs]) -> - case catch ?ets_lookup(Tab, Tid) of - [D] when record(D, decision) -> - D; - [C] when record(C, transient_decision) -> - #decision{tid = C#transient_decision.tid, - outcome = C#transient_decision.outcome, - disc_nodes = [], - ram_nodes = [] - }; - [] -> - decision(Tid, Tabs); - {'EXIT', _} -> - %% Recently switched transient decision table - decision(Tid, Tabs) - end; -decision(_Tid, []) -> - no_decision. - -outcome(Tid, Default) -> - outcome(Tid, Default, tabs()). - -outcome(Tid, Default, [Tab | Tabs]) -> - case catch ?ets_lookup_element(Tab, Tid, 3) of - {'EXIT', _} -> - outcome(Tid, Default, Tabs); - Val -> - Val - end; -outcome(_Tid, Default, []) -> - Default. - -filter_outcome(Val) -> - case Val of - unclear -> unclear; - aborted -> aborted; - presume_abort -> aborted; - committed -> committed; - pre_commit -> unclear - end. - -filter_aborted(D) when D#decision.outcome == presume_abort -> - D#decision{outcome = aborted}; -filter_aborted(D) -> - D. - -%% Merge old decision D with new (probably remote) decision -merge_decisions(Node, D, NewD0) -> - NewD = filter_aborted(NewD0), - if - D == no_decision, node() /= Node -> - %% We did not know anything about this txn - NewD#decision{disc_nodes = []}; - D == no_decision -> - NewD; - record(D, decision) -> - DiscNs = D#decision.disc_nodes -- ([node(), Node]), - OldD = filter_aborted(D#decision{disc_nodes = DiscNs}), -%% mnesia_lib:dbg_out("merge ~w: NewD = ~w~n D = ~w~n OldD = ~w~n", -%% [Node, NewD, D, OldD]), - if - OldD#decision.outcome == unclear, - NewD#decision.outcome == unclear -> - D; - - OldD#decision.outcome == NewD#decision.outcome -> - %% We have come to the same decision - OldD; - - OldD#decision.outcome == committed, - NewD#decision.outcome == aborted -> - %% Interesting! We have already committed, - %% but someone else has aborted. Now we - %% have a nice little inconcistency. The - %% other guy (or some one else) has - %% enforced a recovery decision when - %% max_wait_for_decision was exceeded. - %% We will pretend that we have obeyed - %% the forced recovery decision, but we - %% will also generate an event in case the - %% application wants to do something clever. - Msg = {inconsistent_database, bad_decision, Node}, - mnesia_lib:report_system_event(Msg), - OldD#decision{outcome = aborted}; - - OldD#decision.outcome == aborted -> - %% aborted overrrides anything - OldD#decision{outcome = aborted}; - - NewD#decision.outcome == aborted -> - %% aborted overrrides anything - OldD#decision{outcome = aborted}; - - OldD#decision.outcome == committed, - NewD#decision.outcome == unclear -> - %% committed overrides unclear - OldD#decision{outcome = committed}; - - OldD#decision.outcome == unclear, - NewD#decision.outcome == committed -> - %% committed overrides unclear - OldD#decision{outcome = committed} - end - end. - -add_remote_decisions(Node, [D | Tail], State) when record(D, decision) -> - State2 = add_remote_decision(Node, D, State), - add_remote_decisions(Node, Tail, State2); - -add_remote_decisions(Node, [C | Tail], State) - when record(C, transient_decision) -> - D = #decision{tid = C#transient_decision.tid, - outcome = C#transient_decision.outcome, - disc_nodes = [], - ram_nodes = []}, - State2 = add_remote_decision(Node, D, State), - add_remote_decisions(Node, Tail, State2); - -add_remote_decisions(Node, [{mnesia_down, _, _, _} | Tail], State) -> - add_remote_decisions(Node, Tail, State); - -add_remote_decisions(Node, [{trans_tid, serial, Serial} | Tail], State) -> - sync_trans_tid_serial(Serial), - case State#state.unclear_decision of - undefined -> - ignored; - D -> - case lists:member(Node, D#decision.ram_nodes) of - true -> - ignore; - false -> - abcast([Node], {what_decision, node(), D}) - end - end, - add_remote_decisions(Node, Tail, State); - -add_remote_decisions(_Node, [], State) -> - State. - -add_remote_decision(Node, NewD, State) -> - Tid = NewD#decision.tid, - OldD = decision(Tid), - D = merge_decisions(Node, OldD, NewD), - do_log_decision(D, false), - Outcome = D#decision.outcome, - if - OldD == no_decision -> - ignore; - Outcome == unclear -> - ignore; - true -> - case lists:member(node(), NewD#decision.disc_nodes) or - lists:member(node(), NewD#decision.ram_nodes) of - true -> - tell_im_certain([Node], D); - false -> - ignore - end - end, - case State#state.unclear_decision of - U when U#decision.tid == Tid -> - WaitFor = State#state.unclear_waitfor -- [Node], - if - Outcome == unclear, WaitFor == [] -> - %% Everybody are uncertain, lets abort - NewOutcome = aborted, - CertainD = D#decision{outcome = NewOutcome, - disc_nodes = [], - ram_nodes = []}, - tell_im_certain(D#decision.disc_nodes, CertainD), - tell_im_certain(D#decision.ram_nodes, CertainD), - do_log_decision(CertainD, false), - verbose("Decided to abort transaction ~p " - "since everybody are uncertain ~p~n", - [Tid, CertainD]), - gen_server:reply(State#state.unclear_pid, {ok, NewOutcome}), - State#state{unclear_pid = undefined, - unclear_decision = undefined, - unclear_waitfor = undefined}; - Outcome /= unclear -> - verbose("~p told us that transaction ~p was ~p~n", - [Node, Tid, Outcome]), - gen_server:reply(State#state.unclear_pid, {ok, Outcome}), - State#state{unclear_pid = undefined, - unclear_decision = undefined, - unclear_waitfor = undefined}; - Outcome == unclear -> - State#state{unclear_waitfor = WaitFor} - end; - _ -> - State - end. - -announce_all([], _Tabs) -> - ok; -announce_all(ToNodes, [Tab | Tabs]) -> - case catch mnesia_lib:db_match_object(ram_copies, Tab, '_') of - {'EXIT', _} -> - %% Oops, we are in the middle of a 'garb_decisions' - announce_all(ToNodes, Tabs); - List -> - announce(ToNodes, List, [], false), - announce_all(ToNodes, Tabs) - end; -announce_all(_ToNodes, []) -> - ok. - -announce(ToNodes, [Head | Tail], Acc, ForceSend) -> - Acc2 = arrange(ToNodes, Head, Acc, ForceSend), - announce(ToNodes, Tail, Acc2, ForceSend); - -announce(_ToNodes, [], Acc, _ForceSend) -> - send_decisions(Acc). - -send_decisions([{Node, Decisions} | Tail]) -> - abcast([Node], {decisions, node(), Decisions}), - send_decisions(Tail); -send_decisions([]) -> - ok. - -arrange([To | ToNodes], D, Acc, ForceSend) when record(D, decision) -> - NeedsAdd = (ForceSend or - lists:member(To, D#decision.disc_nodes) or - lists:member(To, D#decision.ram_nodes)), - case NeedsAdd of - true -> - Acc2 = add_decision(To, D, Acc), - arrange(ToNodes, D, Acc2, ForceSend); - false -> - arrange(ToNodes, D, Acc, ForceSend) - end; - -arrange([To | ToNodes], C, Acc, ForceSend) when record(C, transient_decision) -> - Acc2 = add_decision(To, C, Acc), - arrange(ToNodes, C, Acc2, ForceSend); - -arrange([_To | _ToNodes], {mnesia_down, _Node, _Date, _Time}, Acc, _ForceSend) -> - %% The others have their own info about this - Acc; - -arrange([_To | _ToNodes], {master_nodes, _Tab, _Nodes}, Acc, _ForceSend) -> - %% The others have their own info about this - Acc; - -arrange([To | ToNodes], {trans_tid, serial, Serial}, Acc, ForceSend) -> - %% Do the lamport thing plus release the others - %% from uncertainity. - Acc2 = add_decision(To, {trans_tid, serial, Serial}, Acc), - arrange(ToNodes, {trans_tid, serial, Serial}, Acc2, ForceSend); - -arrange([], _Decision, Acc, _ForceSend) -> - Acc. - -add_decision(Node, Decision, [{Node, Decisions} | Tail]) -> - [{Node, [Decision | Decisions]} | Tail]; -add_decision(Node, Decision, [Head | Tail]) -> - [Head | add_decision(Node, Decision, Tail)]; -add_decision(Node, Decision, []) -> - [{Node, [Decision]}]. - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_registry.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_registry.erl deleted file mode 100644 index c16603f344..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_registry.erl +++ /dev/null @@ -1,277 +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_registry.erl,v 1.2 2010/03/04 13:54:19 maria Exp $ -%% --module(mnesia_registry). - -%%%---------------------------------------------------------------------- -%%% File : mnesia_registry.erl -%%% Purpose : Support dump and restore of a registry on a C-node -%%% This is an OTP internal module and is not public available. -%%% -%%% Example : Dump some hardcoded records into the Mnesia table Tab -%%% -%%% case rpc:call(Node, mnesia_registry, start_dump, [Tab, self()]) of -%%% Pid when pid(Pid) -> -%%% Pid ! {write, key1, key_size1, val_type1, val_size1, val1}, -%%% Pid ! {delete, key3}, -%%% Pid ! {write, key2, key_size2, val_type2, val_size2, val2}, -%%% Pid ! {write, key4, key_size4, val_type4, val_size4, val4}, -%%% Pid ! {commit, self()}, -%%% receive -%%% {ok, Pid} -> -%%% ok; -%%% {'EXIT', Pid, Reason} -> -%%% exit(Reason) -%%% end; -%%% {badrpc, Reason} -> -%%% exit(Reason) -%%% end. -%%% -%%% Example : Restore the corresponding Mnesia table Tab -%%% -%%% case rpc:call(Node, mnesia_registry, start_restore, [Tab, self()]) of -%%% {size, Pid, N, LargestKey, LargestVal} -> -%%% Pid ! {send_records, self()}, -%%% Fun = fun() -> -%%% receive -%%% {restore, KeySize, ValSize, ValType, Key, Val} -> -%%% {Key, Val}; -%%% {'EXIT', Pid, Reason} -> -%%% exit(Reason) -%%% end -%%% end, -%%% lists:map(Fun, lists:seq(1, N)); -%%% {badrpc, Reason} -> -%%% exit(Reason) -%%% end. -%%% -%%%---------------------------------------------------------------------- - -%% External exports --export([start_dump/2, start_restore/2]). --export([create_table/1, create_table/2]). - -%% Internal exports --export([init/4]). - --record(state, {table, ops = [], link_to}). - --record(registry_entry, {key, key_size, val_type, val_size, val}). - --record(size, {pid = self(), n_values = 0, largest_key = 0, largest_val = 0}). - -%%%---------------------------------------------------------------------- -%%% Client -%%%---------------------------------------------------------------------- - -start(Type, Tab, LinkTo) -> - Starter = self(), - Args = [Type, Starter, LinkTo, Tab], - Pid = spawn_link(?MODULE, init, Args), - %% The receiver process may unlink the current process - receive - {ok, Res} -> - Res; - {'EXIT', Pid, Reason} when LinkTo == Starter -> - exit(Reason) - end. - -%% Starts a receiver process and optionally creates a Mnesia table -%% with suitable default values. Returns the Pid of the receiver process -%% -%% The receiver process accumulates Mnesia operations and performs -%% all operations or none at commit. The understood messages are: -%% -%% {write, Key, KeySize, ValType, ValSize, Val} -> -%% accumulates mnesia:write({Tab, Key, KeySize, ValType, ValSize, Val}) -%% (no reply) -%% {delete, Key} -> -%% accumulates mnesia:delete({Tab, Key}) (no reply) -%% {commit, ReplyTo} -> -%% commits all accumulated operations -%% and stops the process (replies {ok, Pid}) -%% abort -> -%% stops the process (no reply) -%% -%% The receiver process is linked to the process with the process identifier -%% LinkTo. If some error occurs the receiver process will invoke exit(Reason) -%% and it is up to he LinkTo process to act properly when it receives an exit -%% signal. - -start_dump(Tab, LinkTo) -> - start(dump, Tab, LinkTo). - -%% Starts a sender process which sends restore messages back to the -%% LinkTo process. But first are some statistics about the table -%% determined and returned as a 5-tuple: -%% -%% {size, SenderPid, N, LargestKeySize, LargestValSize} -%% -%% where N is the number of records in the table. Then the sender process -%% waits for a 2-tuple message: -%% -%% {send_records, ReplyTo} -%% -%% At last N 6-tuple messages is sent to the ReplyTo process: -%% -%% ReplyTo ! {restore, KeySize, ValSize, ValType, Key, Val} -%% -%% If some error occurs the receiver process will invoke exit(Reason) -%% and it is up to he LinkTo process to act properly when it receives an -%% exit signal. - -start_restore(Tab, LinkTo) -> - start(restore, Tab, LinkTo). - - -%% Optionally creates the Mnesia table Tab with suitable default values. -%% Returns ok or EXIT's -create_table(Tab) -> - Storage = mnesia:table_info(schema, storage_type), - create_table(Tab, [{Storage, [node()]}]). - -create_table(Tab, TabDef) -> - Attrs = record_info(fields, registry_entry), - case mnesia:create_table(Tab, [{attributes, Attrs} | TabDef]) of - {'atomic', ok} -> - ok; - {aborted, {already_exists, Tab}} -> - ok; - {aborted, Reason} -> - exit(Reason) - end. - -%%%---------------------------------------------------------------------- -%%% Server -%%%---------------------------------------------------------------------- - -init(Type, Starter, LinkTo, Tab) -> - if - LinkTo /= Starter -> - link(LinkTo), - unlink(Starter); - true -> - ignore - end, - case Type of - dump -> - Starter ! {ok, self()}, - dump_loop(#state{table = Tab, link_to = LinkTo}); - restore -> - restore_table(Tab, Starter, LinkTo) - end. - -%%%---------------------------------------------------------------------- -%%% Dump loop -%%%---------------------------------------------------------------------- - -dump_loop(S) -> - Tab = S#state.table, - Ops = S#state.ops, - receive - {write, Key, KeySize, ValType, ValSize, Val} -> - RE = #registry_entry{key = Key, - key_size = KeySize, - val_type = ValType, - val_size = ValSize, - val = Val}, - dump_loop(S#state{ops = [{write, RE} | Ops]}); - {delete, Key} -> - dump_loop(S#state{ops = [{delete, Key} | Ops]}); - {commit, ReplyTo} -> - create_table(Tab), - RecName = mnesia:table_info(Tab, record_name), - %% The Ops are in reverse order, but there is no need - %% for reversing the list of accumulated operations - case mnesia:transaction(fun handle_ops/3, [Tab, RecName, Ops]) of - {'atomic', ok} -> - ReplyTo ! {ok, self()}, - stop(S#state.link_to); - {aborted, Reason} -> - exit({aborted, Reason}) - end; - abort -> - stop(S#state.link_to); - BadMsg -> - exit({bad_message, BadMsg}) - end. - -stop(LinkTo) -> - unlink(LinkTo), - exit(normal). - -%% Grab a write lock for the entire table -%% and iterate over all accumulated operations -handle_ops(Tab, RecName, Ops) -> - mnesia:write_lock_table(Tab), - do_handle_ops(Tab, RecName, Ops). - -do_handle_ops(Tab, RecName, [{write, RegEntry} | Ops]) -> - Record = setelement(1, RegEntry, RecName), - mnesia:write(Tab, Record, write), - do_handle_ops(Tab, RecName, Ops); -do_handle_ops(Tab, RecName, [{delete, Key} | Ops]) -> - mnesia:delete(Tab, Key, write), - do_handle_ops(Tab, RecName, Ops); -do_handle_ops(_Tab, _RecName, []) -> - ok. - -%%%---------------------------------------------------------------------- -%%% Restore table -%%%---------------------------------------------------------------------- - -restore_table(Tab, Starter, LinkTo) -> - Pat = mnesia:table_info(Tab, wild_pattern), - Fun = fun() -> mnesia:match_object(Tab, Pat, read) end, - case mnesia:transaction(Fun) of - {'atomic', AllRecords} -> - Size = calc_size(AllRecords, #size{}), - Starter ! {ok, Size}, - receive - {send_records, ReplyTo} -> - send_records(AllRecords, ReplyTo), - unlink(LinkTo), - exit(normal); - BadMsg -> - exit({bad_message, BadMsg}) - end; - {aborted, Reason} -> - exit(Reason) - end. - -calc_size([H | T], S) -> - KeySize = max(element(#registry_entry.key_size, H), S#size.largest_key), - ValSize = max(element(#registry_entry.val_size, H), S#size.largest_val), - N = S#size.n_values + 1, - calc_size(T, S#size{n_values = N, largest_key = KeySize, largest_val = ValSize}); -calc_size([], Size) -> - Size. - -max(New, Old) when New > Old -> New; -max(_New, Old) -> Old. - -send_records([H | T], ReplyTo) -> - KeySize = element(#registry_entry.key_size, H), - ValSize = element(#registry_entry.val_size, H), - ValType = element(#registry_entry.val_type, H), - Key = element(#registry_entry.key, H), - Val = element(#registry_entry.val, H), - ReplyTo ! {restore, KeySize, ValSize, ValType, Key, Val}, - send_records(T, ReplyTo); -send_records([], _ReplyTo) -> - ok. - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_schema.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_schema.erl deleted file mode 100644 index cceb6bf0d1..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_schema.erl +++ /dev/null @@ -1,2899 +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_schema.erl,v 1.2 2010/03/04 13:54:20 maria Exp $ -%% -%% In this module we provide a number of explicit functions -%% to maninpulate the schema. All these functions are called -%% within a special schema transaction. -%% -%% We also have an init/1 function defined here, this func is -%% used by mnesia:start() to initialize the entire schema. - --module(mnesia_schema). - --export([ - add_snmp/2, - add_table_copy/3, - add_table_index/2, - arrange_restore/3, - attr_tab_to_pos/2, - attr_to_pos/2, - change_table_copy_type/3, - change_table_access_mode/2, - change_table_load_order/2, - change_table_frag/2, - clear_table/1, - create_table/1, - cs2list/1, - del_snmp/1, - del_table_copy/2, - del_table_index/2, - delete_cstruct/2, - delete_schema/1, - delete_schema2/0, - delete_table/1, - delete_table_property/2, - dump_tables/1, - ensure_no_schema/1, - get_create_list/1, - get_initial_schema/2, - get_table_properties/1, - info/0, - info/1, - init/1, - insert_cstruct/3, - is_remote_member/1, - list2cs/1, - lock_schema/0, - lock_del_table/4, % Spawned - merge_schema/0, - move_table/3, - opt_create_dir/2, - prepare_commit/3, - purge_dir/2, - purge_tmp_files/0, - ram_delete_table/2, -% ram_delete_table/3, - read_cstructs_from_disc/0, - read_nodes/0, - remote_read_schema/0, - restore/1, - restore/2, - restore/3, - schema_coordinator/3, - set_where_to_read/3, - transform_table/4, - undo_prepare_commit/2, - unlock_schema/0, - version/0, - write_table_property/2 - ]). - -%% Exports for mnesia_frag --export([ - get_tid_ts_and_lock/2, - make_create_table/1, - ensure_active/1, - pick/4, - verify/3, - incr_version/1, - check_keys/3, - check_duplicates/2, - make_delete_table/2 - ]). - -%% Needed outside to be able to use/set table_properties -%% from user (not supported) --export([schema_transaction/1, - insert_schema_ops/2, - do_create_table/1, - do_delete_table/1, - do_delete_table_property/2, - do_write_table_property/2]). - --include("mnesia.hrl"). --include_lib("kernel/include/file.hrl"). - --import(mnesia_lib, [set/2, del/2, verbose/2, dbg_out/2]). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Here comes the init function which also resides in -%% this module, it is called upon by the trans server -%% at startup of the system -%% -%% We have a meta table which looks like -%% {table, schema, -%% {type, set}, -%% {disc_copies, all}, -%% {arity, 2} -%% {attributes, [key, val]} -%% -%% This means that we have a series of {schema, Name, Cs} tuples -%% in a table called schema !! - -init(IgnoreFallback) -> - Res = read_schema(true, false, IgnoreFallback), - {ok, Source, _CreateList} = exit_on_error(Res), - verbose("Schema initiated from: ~p~n", [Source]), - set({schema, tables}, []), - set({schema, local_tables}, []), - Tabs = set_schema(?ets_first(schema)), - lists:foreach(fun(Tab) -> clear_whereabouts(Tab) end, Tabs), - set({schema, where_to_read}, node()), - set({schema, load_node}, node()), - set({schema, load_reason}, initial), - mnesia_controller:add_active_replica(schema, node()). - -exit_on_error({error, Reason}) -> - exit(Reason); -exit_on_error(GoodRes) -> - GoodRes. - -val(Var) -> - case ?catch_val(Var) of - {'EXIT', Reason} -> mnesia_lib:other_val(Var, Reason); - Value -> Value - end. - -%% This function traverses all cstructs in the schema and -%% sets all values in mnesia_gvar accordingly for each table/cstruct - -set_schema('$end_of_table') -> - []; -set_schema(Tab) -> - do_set_schema(Tab), - [Tab | set_schema(?ets_next(schema, Tab))]. - -get_create_list(Tab) -> - ?ets_lookup_element(schema, Tab, 3). - -do_set_schema(Tab) -> - List = get_create_list(Tab), - Cs = list2cs(List), - do_set_schema(Tab, Cs). - -do_set_schema(Tab, Cs) -> - Type = Cs#cstruct.type, - set({Tab, setorbag}, Type), - set({Tab, local_content}, Cs#cstruct.local_content), - set({Tab, ram_copies}, Cs#cstruct.ram_copies), - set({Tab, disc_copies}, Cs#cstruct.disc_copies), - set({Tab, disc_only_copies}, Cs#cstruct.disc_only_copies), - set({Tab, load_order}, Cs#cstruct.load_order), - set({Tab, access_mode}, Cs#cstruct.access_mode), - set({Tab, snmp}, Cs#cstruct.snmp), - set({Tab, user_properties}, Cs#cstruct.user_properties), - [set({Tab, user_property, element(1, P)}, P) || P <- Cs#cstruct.user_properties], - set({Tab, frag_properties}, Cs#cstruct.frag_properties), - mnesia_frag:set_frag_hash(Tab, Cs#cstruct.frag_properties), - set({Tab, attributes}, Cs#cstruct.attributes), - Arity = length(Cs#cstruct.attributes) + 1, - set({Tab, arity}, Arity), - RecName = Cs#cstruct.record_name, - set({Tab, record_name}, RecName), - set({Tab, record_validation}, {RecName, Arity, Type}), - set({Tab, wild_pattern}, wild(RecName, Arity)), - set({Tab, index}, Cs#cstruct.index), - %% create actual index tabs later - set({Tab, cookie}, Cs#cstruct.cookie), - set({Tab, version}, Cs#cstruct.version), - set({Tab, cstruct}, Cs), - Storage = mnesia_lib:schema_cs_to_storage_type(node(), Cs), - set({Tab, storage_type}, Storage), - mnesia_lib:add({schema, tables}, Tab), - Ns = mnesia_lib:cs_to_nodes(Cs), - case lists:member(node(), Ns) of - true -> - mnesia_lib:add({schema, local_tables}, Tab); - false when Tab == schema -> - mnesia_lib:add({schema, local_tables}, Tab); - false -> - ignore - end. - -wild(RecName, Arity) -> - Wp0 = list_to_tuple(lists:duplicate(Arity, '_')), - setelement(1, Wp0, RecName). - -%% Temporarily read the local schema and return a list -%% of all nodes mentioned in the schema.DAT file -read_nodes() -> - %% Ensure that we access the intended Mnesia - %% directory. This function may not be called - %% during startup since it will cause the - %% application_controller to get into deadlock - case mnesia_lib:ensure_loaded(?APPLICATION) of - ok -> - case read_schema(false, false) of - {ok, _Source, CreateList} -> - Cs = list2cs(CreateList), - {ok, Cs#cstruct.disc_copies ++ Cs#cstruct.ram_copies}; - {error, Reason} -> - {error, Reason} - end; - {error, Reason} -> - {error, Reason} - end. - -%% Returns Version from the tuple {Version,MasterNodes} -version() -> - case read_schema(false, false) of - {ok, Source, CreateList} when Source /= default -> - Cs = list2cs(CreateList), - {Version, _Details} = Cs#cstruct.version, - Version; - _ -> - case dir_exists(mnesia_lib:dir()) of - true -> {1,0}; - false -> {0,0} - end - end. - -%% Calculate next table version from old cstruct -incr_version(Cs) -> - {{Major, Minor}, _} = Cs#cstruct.version, - Nodes = mnesia_lib:intersect(val({schema, disc_copies}), - mnesia_lib:cs_to_nodes(Cs)), - V = - case Nodes -- val({Cs#cstruct.name, active_replicas}) of - [] -> {Major + 1, 0}; % All replicas are active - _ -> {Major, Minor + 1} % Some replicas are inactive - end, - Cs#cstruct{version = {V, {node(), now()}}}. - -%% Returns table name -insert_cstruct(Tid, Cs, KeepWhereabouts) -> - Tab = Cs#cstruct.name, - TabDef = cs2list(Cs), - Val = {schema, Tab, TabDef}, - mnesia_checkpoint:tm_retain(Tid, schema, Tab, write), - mnesia_subscr:report_table_event(schema, Tid, Val, write), - Active = val({Tab, active_replicas}), - - case KeepWhereabouts of - true -> - ignore; - false when Active == [] -> - clear_whereabouts(Tab); - false -> - %% Someone else has initiated table - ignore - end, - set({Tab, cstruct}, Cs), - ?ets_insert(schema, Val), - do_set_schema(Tab, Cs), - Val. - -clear_whereabouts(Tab) -> - set({Tab, checkpoints}, []), - set({Tab, subscribers}, []), - set({Tab, where_to_read}, nowhere), - set({Tab, active_replicas}, []), - set({Tab, commit_work}, []), - set({Tab, where_to_write}, []), - set({Tab, where_to_commit}, []), - set({Tab, load_by_force}, false), - set({Tab, load_node}, unknown), - set({Tab, load_reason}, unknown). - -%% Returns table name -delete_cstruct(Tid, Cs) -> - Tab = Cs#cstruct.name, - TabDef = cs2list(Cs), - Val = {schema, Tab, TabDef}, - mnesia_checkpoint:tm_retain(Tid, schema, Tab, delete), - mnesia_subscr:report_table_event(schema, Tid, Val, delete), - ?ets_match_delete(mnesia_gvar, {{Tab, '_'}, '_'}), - ?ets_match_delete(mnesia_gvar, {{Tab, '_', '_'}, '_'}), - del({schema, local_tables}, Tab), - del({schema, tables}, Tab), - ?ets_delete(schema, Tab), - Val. - -%% Delete the Mnesia directory on all given nodes -%% Requires that Mnesia is not running anywhere -%% Returns ok | {error,Reason} -delete_schema(Ns) when list(Ns), Ns /= [] -> - RunningNs = mnesia_lib:running_nodes(Ns), - Reason = "Cannot delete schema on all nodes", - if - RunningNs == [] -> - case rpc:multicall(Ns, ?MODULE, delete_schema2, []) of - {Replies, []} -> - case [R || R <- Replies, R /= ok] of - [] -> - ok; - BadReplies -> - verbose("~s: ~p~n", [Reason, BadReplies]), - {error, {"All nodes not running", BadReplies}} - end; - {_Replies, BadNs} -> - verbose("~s: ~p~n", [Reason, BadNs]), - {error, {"All nodes not running", BadNs}} - end; - true -> - verbose("~s: ~p~n", [Reason, RunningNs]), - {error, {"Mnesia is not stopped everywhere", RunningNs}} - end; -delete_schema(Ns) -> - {error, {badarg, Ns}}. - -delete_schema2() -> - %% Ensure that we access the intended Mnesia - %% directory. This function may not be called - %% during startup since it will cause the - %% application_controller to get into deadlock - case mnesia_lib:ensure_loaded(?APPLICATION) of - ok -> - case mnesia_lib:is_running() of - no -> - Dir = mnesia_lib:dir(), - purge_dir(Dir, []), - ok; - _ -> - {error, {"Mnesia still running", node()}} - end; - {error, Reason} -> - {error, Reason} - end. - -ensure_no_schema([H|T]) when atom(H) -> - case rpc:call(H, ?MODULE, remote_read_schema, []) of - {badrpc, Reason} -> - {H, {"All nodes not running", H, Reason}}; - {ok,Source, _} when Source /= default -> - {H, {already_exists, H}}; - _ -> - ensure_no_schema(T) - end; -ensure_no_schema([H|_]) -> - {error,{badarg, H}}; -ensure_no_schema([]) -> - ok. - -remote_read_schema() -> - %% Ensure that we access the intended Mnesia - %% directory. This function may not be called - %% during startup since it will cause the - %% application_controller to get into deadlock - case mnesia_lib:ensure_loaded(?APPLICATION) of - ok -> - case mnesia_monitor:get_env(schema_location) of - opt_disc -> - read_schema(false, true); - _ -> - read_schema(false, false) - end; - {error, Reason} -> - {error, Reason} - end. - -dir_exists(Dir) -> - dir_exists(Dir, mnesia_monitor:use_dir()). -dir_exists(Dir, true) -> - case file:read_file_info(Dir) of - {ok, _} -> true; - _ -> false - end; -dir_exists(_Dir, false) -> - false. - -opt_create_dir(UseDir, Dir) when UseDir == true-> - case dir_exists(Dir, UseDir) of - true -> - check_can_write(Dir); - false -> - case file:make_dir(Dir) of - ok -> - verbose("Create Directory ~p~n", [Dir]), - ok; - {error, Reason} -> - verbose("Cannot create mnesia dir ~p~n", [Reason]), - {error, {"Cannot create Mnesia dir", Dir, Reason}} - end - end; -opt_create_dir(false, _) -> - {error, {has_no_disc, node()}}. - -check_can_write(Dir) -> - case file:read_file_info(Dir) of - {ok, FI} when FI#file_info.type == directory, - FI#file_info.access == read_write -> - ok; - {ok, _} -> - {error, "Not allowed to write in Mnesia dir", Dir}; - _ -> - {error, "Non existent Mnesia dir", Dir} - end. - -lock_schema() -> - mnesia_lib:lock_table(schema). - -unlock_schema() -> - mnesia_lib:unlock_table(schema). - -read_schema(Keep, _UseDirAnyway) -> - read_schema(Keep, false, false). - -%% The schema may be read for several reasons. -%% If Mnesia is not already started the read intention -%% we normally do not want the ets table named schema -%% be left around. -%% If Keep == true, the ets table schema is kept -%% If Keep == false, the ets table schema is removed -%% -%% Returns {ok, Source, SchemaCstruct} or {error, Reason} -%% Source may be: default | ram | disc | fallback - -read_schema(Keep, UseDirAnyway, IgnoreFallback) -> - lock_schema(), - Res = - case mnesia:system_info(is_running) of - yes -> - {ok, ram, get_create_list(schema)}; - _IsRunning -> - case mnesia_monitor:use_dir() of - true -> - read_disc_schema(Keep, IgnoreFallback); - false when UseDirAnyway == true -> - read_disc_schema(Keep, IgnoreFallback); - false when Keep == true -> - Args = [{keypos, 2}, public, named_table, set], - mnesia_monitor:mktab(schema, Args), - CreateList = get_initial_schema(ram_copies, []), - ?ets_insert(schema,{schema, schema, CreateList}), - {ok, default, CreateList}; - false when Keep == false -> - CreateList = get_initial_schema(ram_copies, []), - {ok, default, CreateList} - end - end, - unlock_schema(), - Res. - -read_disc_schema(Keep, IgnoreFallback) -> - Running = mnesia:system_info(is_running), - case mnesia_bup:fallback_exists() of - true when IgnoreFallback == false, Running /= yes -> - mnesia_bup:fallback_to_schema(); - _ -> - %% If we're running, we read the schema file even - %% if fallback exists - Dat = mnesia_lib:tab2dat(schema), - case mnesia_lib:exists(Dat) of - true -> - do_read_disc_schema(Dat, Keep); - false -> - Dmp = mnesia_lib:tab2dmp(schema), - case mnesia_lib:exists(Dmp) of - true -> - %% May only happen when toggling of - %% schema storage type has been - %% interrupted - do_read_disc_schema(Dmp, Keep); - false -> - {error, "No schema file exists"} - end - end - end. - -do_read_disc_schema(Fname, Keep) -> - T = - case Keep of - false -> - Args = [{keypos, 2}, public, set], - ?ets_new_table(schema, Args); - true -> - Args = [{keypos, 2}, public, named_table, set], - mnesia_monitor:mktab(schema, Args) - end, - Repair = mnesia_monitor:get_env(auto_repair), - Res = % BUGBUG Fixa till dcl! - case mnesia_lib:dets_to_ets(schema, T, Fname, set, Repair, no) of - loaded -> {ok, disc, ?ets_lookup_element(T, schema, 3)}; - Other -> {error, {"Cannot read schema", Fname, Other}} - end, - case Keep of - true -> ignore; - false -> ?ets_delete_table(T) - end, - Res. - -get_initial_schema(SchemaStorage, Nodes) -> - Cs = #cstruct{name = schema, - record_name = schema, - attributes = [table, cstruct]}, - Cs2 = - case SchemaStorage of - ram_copies -> Cs#cstruct{ram_copies = Nodes}; - disc_copies -> Cs#cstruct{disc_copies = Nodes} - end, - cs2list(Cs2). - -read_cstructs_from_disc() -> - %% Assumptions: - %% - local schema lock in global - %% - use_dir is true - %% - Mnesia is not running - %% - Ignore fallback - - Fname = mnesia_lib:tab2dat(schema), - case mnesia_lib:exists(Fname) of - true -> - Args = [{file, Fname}, - {keypos, 2}, - {repair, mnesia_monitor:get_env(auto_repair)}, - {type, set}], - case dets:open_file(make_ref(), Args) of - {ok, Tab} -> - Fun = fun({_, _, List}) -> - {continue, list2cs(List)} - end, - Cstructs = dets:traverse(Tab, Fun), - dets:close(Tab), - {ok, Cstructs}; - {error, Reason} -> - {error, Reason} - end; - false -> - {error, "No schema file exists"} - end. - -%% We run a very special type of transactions when we -%% we want to manipulate the schema. - -get_tid_ts_and_lock(Tab, Intent) -> - TidTs = get(mnesia_activity_state), - case TidTs of - {_Mod, Tid, Ts} when record(Ts, tidstore)-> - Store = Ts#tidstore.store, - case Intent of - read -> mnesia_locker:rlock_table(Tid, Store, Tab); - write -> mnesia_locker:wlock_table(Tid, Store, Tab); - none -> ignore - end, - TidTs; - _ -> - mnesia:abort(no_transaction) - end. - -schema_transaction(Fun) -> - case get(mnesia_activity_state) of - undefined -> - Args = [self(), Fun, whereis(mnesia_controller)], - Pid = spawn_link(?MODULE, schema_coordinator, Args), - receive - {transaction_done, Res, Pid} -> Res; - {'EXIT', Pid, R} -> {aborted, {transaction_crashed, R}} - end; - _ -> - {aborted, nested_transaction} - end. - -%% This process may dump the transaction log, and should -%% therefore not be run in an application process -%% -schema_coordinator(Client, _Fun, undefined) -> - Res = {aborted, {node_not_running, node()}}, - Client ! {transaction_done, Res, self()}, - unlink(Client); - -schema_coordinator(Client, Fun, Controller) when pid(Controller) -> - %% Do not trap exit in order to automatically die - %% when the controller dies - - link(Controller), - unlink(Client), - - %% Fulfull the transaction even if the client dies - Res = mnesia:transaction(Fun), - Client ! {transaction_done, Res, self()}, - unlink(Controller), % Avoids spurious exit message - unlink(whereis(mnesia_tm)), % Avoids spurious exit message - exit(normal). - -%% The make* rotines return a list of ops, this function -%% inserts em all in the Store and maintains the local order -%% of ops. - -insert_schema_ops({_Mod, _Tid, Ts}, SchemaIOps) -> - do_insert_schema_ops(Ts#tidstore.store, SchemaIOps). - -do_insert_schema_ops(Store, [Head | Tail]) -> - ?ets_insert(Store, Head), - do_insert_schema_ops(Store, Tail); -do_insert_schema_ops(_Store, []) -> - ok. - -cs2list(Cs) when record(Cs, cstruct) -> - Tags = record_info(fields, cstruct), - rec2list(Tags, 2, Cs); -cs2list(CreateList) when list(CreateList) -> - CreateList. - -rec2list([Tag | Tags], Pos, Rec) -> - Val = element(Pos, Rec), - [{Tag, Val} | rec2list(Tags, Pos + 1, Rec)]; -rec2list([], _Pos, _Rec) -> - []. - -list2cs(List) when list(List) -> - Name = pick(unknown, name, List, must), - Type = pick(Name, type, List, set), - Rc0 = pick(Name, ram_copies, List, []), - Dc = pick(Name, disc_copies, List, []), - Doc = pick(Name, disc_only_copies, List, []), - Rc = case {Rc0, Dc, Doc} of - {[], [], []} -> [node()]; - _ -> Rc0 - end, - LC = pick(Name, local_content, List, false), - RecName = pick(Name, record_name, List, Name), - Attrs = pick(Name, attributes, List, [key, val]), - Snmp = pick(Name, snmp, List, []), - LoadOrder = pick(Name, load_order, List, 0), - AccessMode = pick(Name, access_mode, List, read_write), - UserProps = pick(Name, user_properties, List, []), - verify({alt, [nil, list]}, mnesia_lib:etype(UserProps), - {bad_type, Name, {user_properties, UserProps}}), - Cookie = pick(Name, cookie, List, ?unique_cookie), - Version = pick(Name, version, List, {{2, 0}, []}), - Ix = pick(Name, index, List, []), - verify({alt, [nil, list]}, mnesia_lib:etype(Ix), - {bad_type, Name, {index, [Ix]}}), - Ix2 = [attr_to_pos(I, Attrs) || I <- Ix], - - Frag = pick(Name, frag_properties, List, []), - verify({alt, [nil, list]}, mnesia_lib:etype(Frag), - {badarg, Name, {frag_properties, Frag}}), - - Keys = check_keys(Name, List, record_info(fields, cstruct)), - check_duplicates(Name, Keys), - #cstruct{name = Name, - ram_copies = Rc, - disc_copies = Dc, - disc_only_copies = Doc, - type = Type, - index = Ix2, - snmp = Snmp, - load_order = LoadOrder, - access_mode = AccessMode, - local_content = LC, - record_name = RecName, - attributes = Attrs, - user_properties = lists:sort(UserProps), - frag_properties = lists:sort(Frag), - cookie = Cookie, - version = Version}; -list2cs(Other) -> - mnesia:abort({badarg, Other}). - -pick(Tab, Key, List, Default) -> - case lists:keysearch(Key, 1, List) of - false when Default == must -> - mnesia:abort({badarg, Tab, "Missing key", Key, List}); - false -> - Default; - {value, {Key, Value}} -> - Value; - {value, BadArg} -> - mnesia:abort({bad_type, Tab, BadArg}) - end. - -%% Convert attribute name to integer if neccessary -attr_tab_to_pos(_Tab, Pos) when integer(Pos) -> - Pos; -attr_tab_to_pos(Tab, Attr) -> - attr_to_pos(Attr, val({Tab, attributes})). - -%% Convert attribute name to integer if neccessary -attr_to_pos(Pos, _Attrs) when integer(Pos) -> - Pos; -attr_to_pos(Attr, Attrs) when atom(Attr) -> - attr_to_pos(Attr, Attrs, 2); -attr_to_pos(Attr, _) -> - mnesia:abort({bad_type, Attr}). - -attr_to_pos(Attr, [Attr | _Attrs], Pos) -> - Pos; -attr_to_pos(Attr, [_ | Attrs], Pos) -> - attr_to_pos(Attr, Attrs, Pos + 1); -attr_to_pos(Attr, _, _) -> - mnesia:abort({bad_type, Attr}). - -check_keys(Tab, [{Key, _Val} | Tail], Items) -> - case lists:member(Key, Items) of - true -> [Key | check_keys(Tab, Tail, Items)]; - false -> mnesia:abort({badarg, Tab, Key}) - end; -check_keys(_, [], _) -> - []; -check_keys(Tab, Arg, _) -> - mnesia:abort({badarg, Tab, Arg}). - -check_duplicates(Tab, Keys) -> - case has_duplicates(Keys) of - false -> ok; - true -> mnesia:abort({badarg, Tab, "Duplicate keys", Keys}) - end. - -has_duplicates([H | T]) -> - case lists:member(H, T) of - true -> true; - false -> has_duplicates(T) - end; -has_duplicates([]) -> - false. - -%% This is the only place where we check the validity of data -verify_cstruct(Cs) when record(Cs, cstruct) -> - verify_nodes(Cs), - - Tab = Cs#cstruct.name, - verify(atom, mnesia_lib:etype(Tab), {bad_type, Tab}), - Type = Cs#cstruct.type, - verify(true, lists:member(Type, [set, bag, ordered_set]), - {bad_type, Tab, {type, Type}}), - - %% Currently ordered_set is not supported for disk_only_copies. - if - Type == ordered_set, Cs#cstruct.disc_only_copies /= [] -> - mnesia:abort({bad_type, Tab, {not_supported, Type, disc_only_copies}}); - true -> - ok - end, - - RecName = Cs#cstruct.record_name, - verify(atom, mnesia_lib:etype(RecName), - {bad_type, Tab, {record_name, RecName}}), - - Attrs = Cs#cstruct.attributes, - verify(list, mnesia_lib:etype(Attrs), - {bad_type, Tab, {attributes, Attrs}}), - - Arity = length(Attrs) + 1, - verify(true, Arity > 2, {bad_type, Tab, {attributes, Attrs}}), - - lists:foldl(fun(Attr,_Other) when Attr == snmp -> - mnesia:abort({bad_type, Tab, {attributes, [Attr]}}); - (Attr,Other) -> - verify(atom, mnesia_lib:etype(Attr), - {bad_type, Tab, {attributes, [Attr]}}), - verify(false, lists:member(Attr, Other), - {combine_error, Tab, {attributes, [Attr | Other]}}), - [Attr | Other] - end, - [], - Attrs), - - Index = Cs#cstruct.index, - verify({alt, [nil, list]}, mnesia_lib:etype(Index), - {bad_type, Tab, {index, Index}}), - - IxFun = - fun(Pos) -> - verify(true, fun() -> - if - integer(Pos), - Pos > 2, - Pos =< Arity -> - true; - true -> false - end - end, - {bad_type, Tab, {index, [Pos]}}) - end, - lists:foreach(IxFun, Index), - - LC = Cs#cstruct.local_content, - verify({alt, [true, false]}, LC, - {bad_type, Tab, {local_content, LC}}), - Access = Cs#cstruct.access_mode, - verify({alt, [read_write, read_only]}, Access, - {bad_type, Tab, {access_mode, Access}}), - - Snmp = Cs#cstruct.snmp, - verify(true, mnesia_snmp_hook:check_ustruct(Snmp), - {badarg, Tab, {snmp, Snmp}}), - - CheckProp = fun(Prop) when tuple(Prop), size(Prop) >= 1 -> ok; - (Prop) -> mnesia:abort({bad_type, Tab, {user_properties, [Prop]}}) - end, - lists:foreach(CheckProp, Cs#cstruct.user_properties), - - case Cs#cstruct.cookie of - {{MegaSecs, Secs, MicroSecs}, _Node} - when integer(MegaSecs), integer(Secs), - integer(MicroSecs), atom(node) -> - ok; - Cookie -> - mnesia:abort({bad_type, Tab, {cookie, Cookie}}) - end, - case Cs#cstruct.version of - {{Major, Minor}, _Detail} - when integer(Major), integer(Minor) -> - ok; - Version -> - mnesia:abort({bad_type, Tab, {version, Version}}) - end. - -verify_nodes(Cs) -> - Tab = Cs#cstruct.name, - Ram = Cs#cstruct.ram_copies, - Disc = Cs#cstruct.disc_copies, - DiscOnly = Cs#cstruct.disc_only_copies, - LoadOrder = Cs#cstruct.load_order, - - verify({alt, [nil, list]}, mnesia_lib:etype(Ram), - {bad_type, Tab, {ram_copies, Ram}}), - verify({alt, [nil, list]}, mnesia_lib:etype(Disc), - {bad_type, Tab, {disc_copies, Disc}}), - case Tab of - schema -> - verify([], DiscOnly, {bad_type, Tab, {disc_only_copies, DiscOnly}}); - _ -> - verify({alt, [nil, list]}, - mnesia_lib:etype(DiscOnly), - {bad_type, Tab, {disc_only_copies, DiscOnly}}) - end, - verify(integer, mnesia_lib:etype(LoadOrder), - {bad_type, Tab, {load_order, LoadOrder}}), - - Nodes = Ram ++ Disc ++ DiscOnly, - verify(list, mnesia_lib:etype(Nodes), - {combine_error, Tab, - [{ram_copies, []}, {disc_copies, []}, {disc_only_copies, []}]}), - verify(false, has_duplicates(Nodes), {combine_error, Tab, Nodes}), - AtomCheck = fun(N) -> verify(atom, mnesia_lib:etype(N), {bad_type, Tab, N}) end, - lists:foreach(AtomCheck, Nodes). - -verify(Expected, Fun, Error) when function(Fun) -> - do_verify(Expected, catch Fun(), Error); -verify(Expected, Actual, Error) -> - do_verify(Expected, Actual, Error). - -do_verify({alt, Values}, Value, Error) -> - case lists:member(Value, Values) of - true -> ok; - false -> mnesia:abort(Error) - end; -do_verify(Value, Value, _) -> - ok; -do_verify(_Value, _, Error) -> - mnesia:abort(Error). - -ensure_writable(Tab) -> - case val({Tab, where_to_write}) of - [] -> mnesia:abort({read_only, Tab}); - _ -> ok - end. - -%% Ensure that all replicas on disk full nodes are active -ensure_active(Cs) -> - ensure_active(Cs, active_replicas). - -ensure_active(Cs, What) -> - Tab = Cs#cstruct.name, - case val({Tab, What}) of - [] -> mnesia:abort({no_exists, Tab}); - _ -> ok - end, - Nodes = mnesia_lib:intersect(val({schema, disc_copies}), - mnesia_lib:cs_to_nodes(Cs)), - W = {Tab, What}, - case Nodes -- val(W) of - [] -> - ok; - Ns -> - Expl = "All replicas on diskfull nodes are not active yet", - case val({Tab, local_content}) of - true -> - case rpc:multicall(Ns, ?MODULE, is_remote_member, [W]) of - {Replies, []} -> - check_active(Replies, Expl, Tab); - {_Replies, BadNs} -> - mnesia:abort({not_active, Expl, Tab, BadNs}) - end; - false -> - mnesia:abort({not_active, Expl, Tab, Ns}) - end - end. - -ensure_not_active(schema, Node) -> - case lists:member(Node, val({schema, active_replicas})) of - false -> - ok; - true -> - Expl = "Mnesia is running", - mnesia:abort({active, Expl, Node}) - end. - -is_remote_member(Key) -> - IsActive = lists:member(node(), val(Key)), - {IsActive, node()}. - -check_active([{true, _Node} | Replies], Expl, Tab) -> - check_active(Replies, Expl, Tab); -check_active([{false, Node} | _Replies], Expl, Tab) -> - mnesia:abort({not_active, Expl, Tab, [Node]}); -check_active([{badrpc, Reason} | _Replies], Expl, Tab) -> - mnesia:abort({not_active, Expl, Tab, Reason}); -check_active([], _Expl, _Tab) -> - ok. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Here's the real interface function to create a table - -create_table(TabDef) -> - schema_transaction(fun() -> do_multi_create_table(TabDef) end). - -%% And the corresponding do routines .... - -do_multi_create_table(TabDef) -> - get_tid_ts_and_lock(schema, write), - ensure_writable(schema), - Cs = list2cs(TabDef), - case Cs#cstruct.frag_properties of - [] -> - do_create_table(Cs); - _Props -> - CsList = mnesia_frag:expand_cstruct(Cs), - lists:foreach(fun do_create_table/1, CsList) - end, - ok. - -do_create_table(Cs) -> - {_Mod, _Tid, Ts} = get_tid_ts_and_lock(schema, none), - Store = Ts#tidstore.store, - do_insert_schema_ops(Store, make_create_table(Cs)). - -make_create_table(Cs) -> - Tab = Cs#cstruct.name, - verify('EXIT', element(1, ?catch_val({Tab, cstruct})), - {already_exists, Tab}), - unsafe_make_create_table(Cs). - -% unsafe_do_create_table(Cs) -> -% {_Mod, Tid, Ts} = get_tid_ts_and_lock(schema, none), -% Store = Ts#tidstore.store, -% do_insert_schema_ops(Store, unsafe_make_create_table(Cs)). - -unsafe_make_create_table(Cs) -> - {_Mod, Tid, Ts} = get_tid_ts_and_lock(schema, none), - verify_cstruct(Cs), - Tab = Cs#cstruct.name, - - %% Check that we have all disc replica nodes running - DiscNodes = Cs#cstruct.disc_copies ++ Cs#cstruct.disc_only_copies, - RunningNodes = val({current, db_nodes}), - CheckDisc = fun(N) -> - verify(true, lists:member(N, RunningNodes), - {not_active, Tab, N}) - end, - lists:foreach(CheckDisc, DiscNodes), - - Nodes = mnesia_lib:intersect(mnesia_lib:cs_to_nodes(Cs), RunningNodes), - Store = Ts#tidstore.store, - mnesia_locker:wlock_no_exist(Tid, Store, Tab, Nodes), - [{op, create_table, cs2list(Cs)}]. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Delete a table entirely on all nodes. - -delete_table(Tab) -> - schema_transaction(fun() -> do_delete_table(Tab) end). - -do_delete_table(schema) -> - mnesia:abort({bad_type, schema}); -do_delete_table(Tab) -> - TidTs = get_tid_ts_and_lock(schema, write), - ensure_writable(schema), - insert_schema_ops(TidTs, make_delete_table(Tab, whole_table)). - -make_delete_table(Tab, Mode) -> - case Mode of - whole_table -> - case val({Tab, frag_properties}) of - [] -> - [make_delete_table2(Tab)]; - _Props -> - %% Check if it is a base table - mnesia_frag:lookup_frag_hash(Tab), - - %% Check for foreigners - F = mnesia_frag:lookup_foreigners(Tab), - verify([], F, {combine_error, Tab, "Too many foreigners", F}), - [make_delete_table2(T) || T <- mnesia_frag:frag_names(Tab)] - end; - single_frag -> - [make_delete_table2(Tab)] - end. - -make_delete_table2(Tab) -> - get_tid_ts_and_lock(Tab, write), - Cs = val({Tab, cstruct}), - ensure_active(Cs), - ensure_writable(Tab), - {op, delete_table, cs2list(Cs)}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Change fragmentation of a table - -change_table_frag(Tab, Change) -> - schema_transaction(fun() -> do_change_table_frag(Tab, Change) end). - -do_change_table_frag(Tab, Change) when atom(Tab), Tab /= schema -> - TidTs = get_tid_ts_and_lock(schema, write), - Ops = mnesia_frag:change_table_frag(Tab, Change), - [insert_schema_ops(TidTs, Op) || Op <- Ops], - ok; -do_change_table_frag(Tab, _Change) -> - mnesia:abort({bad_type, Tab}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Clear a table - -clear_table(Tab) -> - schema_transaction(fun() -> do_clear_table(Tab) end). - -do_clear_table(schema) -> - mnesia:abort({bad_type, schema}); -do_clear_table(Tab) -> - TidTs = get_tid_ts_and_lock(schema, write), - get_tid_ts_and_lock(Tab, write), - insert_schema_ops(TidTs, make_clear_table(Tab)). - -make_clear_table(Tab) -> - ensure_writable(schema), - Cs = val({Tab, cstruct}), - ensure_active(Cs), - ensure_writable(Tab), - [{op, clear_table, cs2list(Cs)}]. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -add_table_copy(Tab, Node, Storage) -> - schema_transaction(fun() -> do_add_table_copy(Tab, Node, Storage) end). - -do_add_table_copy(Tab, Node, Storage) when atom(Tab), atom(Node) -> - TidTs = get_tid_ts_and_lock(schema, write), - insert_schema_ops(TidTs, make_add_table_copy(Tab, Node, Storage)); -do_add_table_copy(Tab,Node,_) -> - mnesia:abort({badarg, Tab, Node}). - -make_add_table_copy(Tab, Node, Storage) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - Ns = mnesia_lib:cs_to_nodes(Cs), - verify(false, lists:member(Node, Ns), {already_exists, Tab, Node}), - Cs2 = new_cs(Cs, Node, Storage, add), - verify_cstruct(Cs2), - - %% Check storage and if node is running - IsRunning = lists:member(Node, val({current, db_nodes})), - if - Storage == unknown -> - mnesia:abort({badarg, Tab, Storage}); - Tab == schema -> - if - Storage /= ram_copies -> - mnesia:abort({badarg, Tab, Storage}); - IsRunning == true -> - mnesia:abort({already_exists, Tab, Node}); - true -> - ignore - end; - Storage == ram_copies -> - ignore; - IsRunning == true -> - ignore; - IsRunning == false -> - mnesia:abort({not_active, schema, Node}) - end, - [{op, add_table_copy, Storage, Node, cs2list(Cs2)}]. - -del_table_copy(Tab, Node) -> - schema_transaction(fun() -> do_del_table_copy(Tab, Node) end). - -do_del_table_copy(Tab, Node) when atom(Node) -> - TidTs = get_tid_ts_and_lock(schema, write), -%% get_tid_ts_and_lock(Tab, write), - insert_schema_ops(TidTs, make_del_table_copy(Tab, Node)); -do_del_table_copy(Tab, Node) -> - mnesia:abort({badarg, Tab, Node}). - -make_del_table_copy(Tab, Node) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - Storage = mnesia_lib:schema_cs_to_storage_type(Node, Cs), - Cs2 = new_cs(Cs, Node, Storage, del), - case mnesia_lib:cs_to_nodes(Cs2) of - [] when Tab == schema -> - mnesia:abort({combine_error, Tab, "Last replica"}); - [] -> - ensure_active(Cs), - dbg_out("Last replica deleted in table ~p~n", [Tab]), - make_delete_table(Tab, whole_table); - _ when Tab == schema -> - ensure_active(Cs2), - ensure_not_active(Tab, Node), - verify_cstruct(Cs2), - Ops = remove_node_from_tabs(val({schema, tables}), Node), - [{op, del_table_copy, ram_copies, Node, cs2list(Cs2)} | Ops]; - _ -> - ensure_active(Cs), - verify_cstruct(Cs2), - [{op, del_table_copy, Storage, Node, cs2list(Cs2)}] - end. - -remove_node_from_tabs([], _Node) -> - []; -remove_node_from_tabs([schema|Rest], Node) -> - remove_node_from_tabs(Rest, Node); -remove_node_from_tabs([Tab|Rest], Node) -> - {Cs, IsFragModified} = - mnesia_frag:remove_node(Node, incr_version(val({Tab, cstruct}))), - case mnesia_lib:schema_cs_to_storage_type(Node, Cs) of - unknown -> - case IsFragModified of - true -> - [{op, change_table_frag, {del_node, Node}, cs2list(Cs)} | - remove_node_from_tabs(Rest, Node)]; - false -> - remove_node_from_tabs(Rest, Node) - end; - Storage -> - Cs2 = new_cs(Cs, Node, Storage, del), - case mnesia_lib:cs_to_nodes(Cs2) of - [] -> - [{op, delete_table, cs2list(Cs)} | - remove_node_from_tabs(Rest, Node)]; - _Ns -> - verify_cstruct(Cs2), - [{op, del_table_copy, ram_copies, Node, cs2list(Cs2)}| - remove_node_from_tabs(Rest, Node)] - end - end. - -new_cs(Cs, Node, ram_copies, add) -> - Cs#cstruct{ram_copies = opt_add(Node, Cs#cstruct.ram_copies)}; -new_cs(Cs, Node, disc_copies, add) -> - Cs#cstruct{disc_copies = opt_add(Node, Cs#cstruct.disc_copies)}; -new_cs(Cs, Node, disc_only_copies, add) -> - Cs#cstruct{disc_only_copies = opt_add(Node, Cs#cstruct.disc_only_copies)}; -new_cs(Cs, Node, ram_copies, del) -> - Cs#cstruct{ram_copies = lists:delete(Node , Cs#cstruct.ram_copies)}; -new_cs(Cs, Node, disc_copies, del) -> - Cs#cstruct{disc_copies = lists:delete(Node , Cs#cstruct.disc_copies)}; -new_cs(Cs, Node, disc_only_copies, del) -> - Cs#cstruct{disc_only_copies = - lists:delete(Node , Cs#cstruct.disc_only_copies)}; -new_cs(Cs, _Node, Storage, _Op) -> - mnesia:abort({badarg, Cs#cstruct.name, Storage}). - - -opt_add(N, L) -> [N | lists:delete(N, L)]. - -move_table(Tab, FromNode, ToNode) -> - schema_transaction(fun() -> do_move_table(Tab, FromNode, ToNode) end). - -do_move_table(schema, _FromNode, _ToNode) -> - mnesia:abort({bad_type, schema}); -do_move_table(Tab, FromNode, ToNode) when atom(FromNode), atom(ToNode) -> - TidTs = get_tid_ts_and_lock(schema, write), - insert_schema_ops(TidTs, make_move_table(Tab, FromNode, ToNode)); -do_move_table(Tab, FromNode, ToNode) -> - mnesia:abort({badarg, Tab, FromNode, ToNode}). - -make_move_table(Tab, FromNode, ToNode) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - Ns = mnesia_lib:cs_to_nodes(Cs), - verify(false, lists:member(ToNode, Ns), {already_exists, Tab, ToNode}), - verify(true, lists:member(FromNode, val({Tab, where_to_write})), - {not_active, Tab, FromNode}), - verify(false, val({Tab,local_content}), - {"Cannot move table with local content", Tab}), - ensure_active(Cs), - Running = val({current, db_nodes}), - Storage = mnesia_lib:schema_cs_to_storage_type(FromNode, Cs), - verify(true, lists:member(ToNode, Running), {not_active, schema, ToNode}), - - Cs2 = new_cs(Cs, ToNode, Storage, add), - Cs3 = new_cs(Cs2, FromNode, Storage, del), - verify_cstruct(Cs3), - [{op, add_table_copy, Storage, ToNode, cs2list(Cs2)}, - {op, sync_trans}, - {op, del_table_copy, Storage, FromNode, cs2list(Cs3)}]. - -%% end of functions to add and delete nodes to tables -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% - -change_table_copy_type(Tab, Node, ToS) -> - schema_transaction(fun() -> do_change_table_copy_type(Tab, Node, ToS) end). - -do_change_table_copy_type(Tab, Node, ToS) when atom(Node) -> - TidTs = get_tid_ts_and_lock(schema, write), - get_tid_ts_and_lock(Tab, write), % ensure global sync - %% get_tid_ts_and_lock(Tab, read), - insert_schema_ops(TidTs, make_change_table_copy_type(Tab, Node, ToS)); -do_change_table_copy_type(Tab, Node, _ToS) -> - mnesia:abort({badarg, Tab, Node}). - -make_change_table_copy_type(Tab, Node, unknown) -> - make_del_table_copy(Tab, Node); -make_change_table_copy_type(Tab, Node, ToS) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - FromS = mnesia_lib:storage_type_at_node(Node, Tab), - - case compare_storage_type(false, FromS, ToS) of - {same, _} -> - mnesia:abort({already_exists, Tab, Node, ToS}); - {diff, _} -> - ignore; - incompatible -> - ensure_active(Cs) - end, - - Cs2 = new_cs(Cs, Node, FromS, del), - Cs3 = new_cs(Cs2, Node, ToS, add), - verify_cstruct(Cs3), - - if - FromS == unknown -> - make_add_table_copy(Tab, Node, ToS); - true -> - ignore - end, - - [{op, change_table_copy_type, Node, FromS, ToS, cs2list(Cs3)}]. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% change index functions .... -%% Pos is allready added by 1 in both of these functions - -add_table_index(Tab, Pos) -> - schema_transaction(fun() -> do_add_table_index(Tab, Pos) end). - -do_add_table_index(schema, _Attr) -> - mnesia:abort({bad_type, schema}); -do_add_table_index(Tab, Attr) -> - TidTs = get_tid_ts_and_lock(schema, write), - get_tid_ts_and_lock(Tab, read), - Pos = attr_tab_to_pos(Tab, Attr), - insert_schema_ops(TidTs, make_add_table_index(Tab, Pos)). - -make_add_table_index(Tab, Pos) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - ensure_active(Cs), - Ix = Cs#cstruct.index, - verify(false, lists:member(Pos, Ix), {already_exists, Tab, Pos}), - Ix2 = lists:sort([Pos | Ix]), - Cs2 = Cs#cstruct{index = Ix2}, - verify_cstruct(Cs2), - [{op, add_index, Pos, cs2list(Cs2)}]. - -del_table_index(Tab, Pos) -> - schema_transaction(fun() -> do_del_table_index(Tab, Pos) end). - -do_del_table_index(schema, _Attr) -> - mnesia:abort({bad_type, schema}); -do_del_table_index(Tab, Attr) -> - TidTs = get_tid_ts_and_lock(schema, write), - get_tid_ts_and_lock(Tab, read), - Pos = attr_tab_to_pos(Tab, Attr), - insert_schema_ops(TidTs, make_del_table_index(Tab, Pos)). - -make_del_table_index(Tab, Pos) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - ensure_active(Cs), - Ix = Cs#cstruct.index, - verify(true, lists:member(Pos, Ix), {no_exists, Tab, Pos}), - Cs2 = Cs#cstruct{index = lists:delete(Pos, Ix)}, - verify_cstruct(Cs2), - [{op, del_index, Pos, cs2list(Cs2)}]. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -add_snmp(Tab, Ustruct) -> - schema_transaction(fun() -> do_add_snmp(Tab, Ustruct) end). - -do_add_snmp(schema, _Ustruct) -> - mnesia:abort({bad_type, schema}); -do_add_snmp(Tab, Ustruct) -> - TidTs = get_tid_ts_and_lock(schema, write), - get_tid_ts_and_lock(Tab, read), - insert_schema_ops(TidTs, make_add_snmp(Tab, Ustruct)). - -make_add_snmp(Tab, Ustruct) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - ensure_active(Cs), - verify([], Cs#cstruct.snmp, {already_exists, Tab, snmp}), - Error = {badarg, Tab, snmp, Ustruct}, - verify(true, mnesia_snmp_hook:check_ustruct(Ustruct), Error), - Cs2 = Cs#cstruct{snmp = Ustruct}, - verify_cstruct(Cs2), - [{op, add_snmp, Ustruct, cs2list(Cs2)}]. - -del_snmp(Tab) -> - schema_transaction(fun() -> do_del_snmp(Tab) end). - -do_del_snmp(schema) -> - mnesia:abort({bad_type, schema}); -do_del_snmp(Tab) -> - TidTs = get_tid_ts_and_lock(schema, write), - get_tid_ts_and_lock(Tab, read), - insert_schema_ops(TidTs, make_del_snmp(Tab)). - -make_del_snmp(Tab) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - ensure_active(Cs), - Cs2 = Cs#cstruct{snmp = []}, - verify_cstruct(Cs2), - [{op, del_snmp, cs2list(Cs2)}]. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% - -transform_table(Tab, Fun, NewAttrs, NewRecName) - when function(Fun), list(NewAttrs), atom(NewRecName) -> - schema_transaction(fun() -> do_transform_table(Tab, Fun, NewAttrs, NewRecName) end); - -transform_table(Tab, ignore, NewAttrs, NewRecName) - when list(NewAttrs), atom(NewRecName) -> - schema_transaction(fun() -> do_transform_table(Tab, ignore, NewAttrs, NewRecName) end); - -transform_table(Tab, Fun, NewAttrs, NewRecName) -> - {aborted,{bad_type, Tab, Fun, NewAttrs, NewRecName}}. - -do_transform_table(schema, _Fun, _NewAttrs, _NewRecName) -> - mnesia:abort({bad_type, schema}); -do_transform_table(Tab, Fun, NewAttrs, NewRecName) -> - TidTs = get_tid_ts_and_lock(schema, write), - get_tid_ts_and_lock(Tab, write), - insert_schema_ops(TidTs, make_transform(Tab, Fun, NewAttrs, NewRecName)). - -make_transform(Tab, Fun, NewAttrs, NewRecName) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - ensure_active(Cs), - ensure_writable(Tab), - case mnesia_lib:val({Tab, index}) of - [] -> - Cs2 = Cs#cstruct{attributes = NewAttrs, record_name = NewRecName}, - verify_cstruct(Cs2), - [{op, transform, Fun, cs2list(Cs2)}]; - PosList -> - DelIdx = fun(Pos, Ncs) -> - Ix = Ncs#cstruct.index, - Ncs1 = Ncs#cstruct{index = lists:delete(Pos, Ix)}, - Op = {op, del_index, Pos, cs2list(Ncs1)}, - {Op, Ncs1} - end, - AddIdx = fun(Pos, Ncs) -> - Ix = Ncs#cstruct.index, - Ix2 = lists:sort([Pos | Ix]), - Ncs1 = Ncs#cstruct{index = Ix2}, - Op = {op, add_index, Pos, cs2list(Ncs1)}, - {Op, Ncs1} - end, - {DelOps, Cs1} = lists:mapfoldl(DelIdx, Cs, PosList), - Cs2 = Cs1#cstruct{attributes = NewAttrs, record_name = NewRecName}, - {AddOps, Cs3} = lists:mapfoldl(AddIdx, Cs2, PosList), - verify_cstruct(Cs3), - lists:flatten([DelOps, {op, transform, Fun, cs2list(Cs2)}, AddOps]) - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% - -change_table_access_mode(Tab, Mode) -> - schema_transaction(fun() -> do_change_table_access_mode(Tab, Mode) end). - -do_change_table_access_mode(Tab, Mode) -> - {_Mod, Tid, Ts} = get_tid_ts_and_lock(schema, write), - Store = Ts#tidstore.store, - mnesia_locker:wlock_no_exist(Tid, Store, schema, val({schema, active_replicas})), - mnesia_locker:wlock_no_exist(Tid, Store, Tab, val({Tab, active_replicas})), - do_insert_schema_ops(Store, make_change_table_access_mode(Tab, Mode)). - -make_change_table_access_mode(Tab, Mode) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - ensure_active(Cs), - OldMode = Cs#cstruct.access_mode, - verify(false, OldMode == Mode, {already_exists, Tab, Mode}), - Cs2 = Cs#cstruct{access_mode = Mode}, - verify_cstruct(Cs2), - [{op, change_table_access_mode, cs2list(Cs2), OldMode, Mode}]. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -change_table_load_order(Tab, LoadOrder) -> - schema_transaction(fun() -> do_change_table_load_order(Tab, LoadOrder) end). - -do_change_table_load_order(schema, _LoadOrder) -> - mnesia:abort({bad_type, schema}); -do_change_table_load_order(Tab, LoadOrder) -> - TidTs = get_tid_ts_and_lock(schema, write), - get_tid_ts_and_lock(Tab, none), - insert_schema_ops(TidTs, make_change_table_load_order(Tab, LoadOrder)). - -make_change_table_load_order(Tab, LoadOrder) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - ensure_active(Cs), - OldLoadOrder = Cs#cstruct.load_order, - Cs2 = Cs#cstruct{load_order = LoadOrder}, - verify_cstruct(Cs2), - [{op, change_table_load_order, cs2list(Cs2), OldLoadOrder, LoadOrder}]. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -write_table_property(Tab, Prop) when tuple(Prop), size(Prop) >= 1 -> - schema_transaction(fun() -> do_write_table_property(Tab, Prop) end); -write_table_property(Tab, Prop) -> - {aborted, {bad_type, Tab, Prop}}. -do_write_table_property(Tab, Prop) -> - TidTs = get_tid_ts_and_lock(schema, write), - {_, _, Ts} = TidTs, - Store = Ts#tidstore.store, - case change_prop_in_existing_op(Tab, Prop, write_property, Store) of - true -> - dbg_out("change_prop_in_existing_op" - "(~p,~p,write_property,Store) -> true~n", - [Tab,Prop]), - %% we have merged the table prop into the create_table op - ok; - false -> - dbg_out("change_prop_in_existing_op" - "(~p,~p,write_property,Store) -> false~n", - [Tab,Prop]), - %% this must be an existing table - get_tid_ts_and_lock(Tab, none), - insert_schema_ops(TidTs, make_write_table_properties(Tab, [Prop])) - end. - -make_write_table_properties(Tab, Props) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - ensure_active(Cs), - make_write_table_properties(Tab, Props, Cs). - -make_write_table_properties(Tab, [Prop | Props], Cs) -> - OldProps = Cs#cstruct.user_properties, - PropKey = element(1, Prop), - DelProps = lists:keydelete(PropKey, 1, OldProps), - MergedProps = lists:merge(DelProps, [Prop]), - Cs2 = Cs#cstruct{user_properties = MergedProps}, - verify_cstruct(Cs2), - [{op, write_property, cs2list(Cs2), Prop} | - make_write_table_properties(Tab, Props, Cs2)]; -make_write_table_properties(_Tab, [], _Cs) -> - []. - -change_prop_in_existing_op(Tab, Prop, How, Store) -> - Ops = ets:match_object(Store, '_'), - case update_existing_op(Ops, Tab, Prop, How, []) of - {true, Ops1} -> - ets:match_delete(Store, '_'), - [ets:insert(Store, Op) || Op <- Ops1], - true; - false -> - false - end. - -update_existing_op([{op, Op, L = [{name,Tab}|_], _OldProp}|Ops], - Tab, Prop, How, Acc) when Op == write_property; - Op == delete_property -> - %% Apparently, mnesia_dumper doesn't care about OldProp here -- just L, - %% so we will throw away OldProp (not that it matters...) and insert Prop. - %% as element 3. - L1 = insert_prop(Prop, L, How), - NewOp = {op, How, L1, Prop}, - {true, lists:reverse(Acc) ++ [NewOp|Ops]}; -update_existing_op([Op = {op, create_table, L}|Ops], Tab, Prop, How, Acc) -> - case lists:keysearch(name, 1, L) of - {value, {_, Tab}} -> - %% Tab is being created here -- insert Prop into L - L1 = insert_prop(Prop, L, How), - {true, lists:reverse(Acc) ++ [{op, create_table, L1}|Ops]}; - _ -> - update_existing_op(Ops, Tab, Prop, How, [Op|Acc]) - end; -update_existing_op([Op|Ops], Tab, Prop, How, Acc) -> - update_existing_op(Ops, Tab, Prop, How, [Op|Acc]); -update_existing_op([], _, _, _, _) -> - false. - -%% perhaps a misnomer. How could also be delete_property... never mind. -%% Returns the modified L. -insert_prop(Prop, L, How) -> - Prev = find_props(L), - MergedProps = merge_with_previous(How, Prop, Prev), - replace_props(L, MergedProps). - - -find_props([{user_properties, P}|_]) -> P; -find_props([_H|T]) -> find_props(T). -%% we shouldn't reach [] - -replace_props([{user_properties, _}|T], P) -> [{user_properties, P}|T]; -replace_props([H|T], P) -> [H|replace_props(T, P)]. -%% again, we shouldn't reach [] - -merge_with_previous(write_property, Prop, Prev) -> - Key = element(1, Prop), - Prev1 = lists:keydelete(Key, 1, Prev), - lists:sort([Prop|Prev1]); -merge_with_previous(delete_property, PropKey, Prev) -> - lists:keydelete(PropKey, 1, Prev). - -delete_table_property(Tab, PropKey) -> - schema_transaction(fun() -> do_delete_table_property(Tab, PropKey) end). - -do_delete_table_property(Tab, PropKey) -> - TidTs = get_tid_ts_and_lock(schema, write), - {_, _, Ts} = TidTs, - Store = Ts#tidstore.store, - case change_prop_in_existing_op(Tab, PropKey, delete_property, Store) of - true -> - dbg_out("change_prop_in_existing_op" - "(~p,~p,delete_property,Store) -> true~n", - [Tab,PropKey]), - %% we have merged the table prop into the create_table op - ok; - false -> - dbg_out("change_prop_in_existing_op" - "(~p,~p,delete_property,Store) -> false~n", - [Tab,PropKey]), - %% this must be an existing table - get_tid_ts_and_lock(Tab, none), - insert_schema_ops(TidTs, - make_delete_table_properties(Tab, [PropKey])) - end. - -make_delete_table_properties(Tab, PropKeys) -> - ensure_writable(schema), - Cs = incr_version(val({Tab, cstruct})), - ensure_active(Cs), - make_delete_table_properties(Tab, PropKeys, Cs). - -make_delete_table_properties(Tab, [PropKey | PropKeys], Cs) -> - OldProps = Cs#cstruct.user_properties, - Props = lists:keydelete(PropKey, 1, OldProps), - Cs2 = Cs#cstruct{user_properties = Props}, - verify_cstruct(Cs2), - [{op, delete_property, cs2list(Cs2), PropKey} | - make_delete_table_properties(Tab, PropKeys, Cs2)]; -make_delete_table_properties(_Tab, [], _Cs) -> - []. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% Ensure that the transaction can be committed even -%% if the node crashes and Mnesia is restarted -prepare_commit(Tid, Commit, WaitFor) -> - case Commit#commit.schema_ops of - [] -> - {false, Commit, optional}; - OrigOps -> - {Modified, Ops, DumperMode} = - prepare_ops(Tid, OrigOps, WaitFor, false, [], optional), - InitBy = schema_prepare, - GoodRes = {Modified, - Commit#commit{schema_ops = lists:reverse(Ops)}, - DumperMode}, - case DumperMode of - optional -> - dbg_out("Transaction log dump skipped (~p): ~w~n", - [DumperMode, InitBy]); - mandatory -> - case mnesia_controller:sync_dump_log(InitBy) of - dumped -> - GoodRes; - {error, Reason} -> - mnesia:abort(Reason) - end - end, - case Ops of - [] -> - ignore; - _ -> - %% We need to grab a dumper lock here, the log may not - %% be dumped by others, during the schema commit phase. - mnesia_controller:wait_for_schema_commit_lock() - end, - GoodRes - end. - -prepare_ops(Tid, [Op | Ops], WaitFor, Changed, Acc, DumperMode) -> - case prepare_op(Tid, Op, WaitFor) of - {true, mandatory} -> - prepare_ops(Tid, Ops, WaitFor, Changed, [Op | Acc], mandatory); - {true, optional} -> - prepare_ops(Tid, Ops, WaitFor, Changed, [Op | Acc], DumperMode); - {true, Ops2, mandatory} -> - prepare_ops(Tid, Ops, WaitFor, true, Ops2 ++ Acc, mandatory); - {true, Ops2, optional} -> - prepare_ops(Tid, Ops, WaitFor, true, Ops2 ++ Acc, DumperMode); - {false, mandatory} -> - prepare_ops(Tid, Ops, WaitFor, true, Acc, mandatory); - {false, optional} -> - prepare_ops(Tid, Ops, WaitFor, true, Acc, DumperMode) - end; -prepare_ops(_Tid, [], _WaitFor, Changed, Acc, DumperMode) -> - {Changed, Acc, DumperMode}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Prepare for commit -%% returns true if Op should be included, i.e. unmodified -%% {true, Operation} if NewRecs should be included, i.e. modified -%% false if Op should NOT be included, i.e. modified -%% -prepare_op(_Tid, {op, rec, unknown, Rec}, _WaitFor) -> - {{Tab, Key}, Items, _Op} = Rec, - case val({Tab, storage_type}) of - unknown -> - {false, optional}; - Storage -> - mnesia_tm:prepare_snmp(Tab, Key, Items), % May exit - {true, [{op, rec, Storage, Rec}], optional} - end; - -prepare_op(_Tid, {op, announce_im_running, _Node, SchemaDef, Running, RemoteRunning}, _WaitFor) -> - SchemaCs = list2cs(SchemaDef), - case lists:member(node(), Running) of - true -> - announce_im_running(RemoteRunning -- Running, SchemaCs); - false -> - announce_im_running(Running -- RemoteRunning, SchemaCs) - end, - {false, optional}; - -prepare_op(_Tid, {op, sync_trans}, {part, CoordPid}) -> - CoordPid ! {sync_trans, self()}, - receive - {sync_trans, CoordPid} -> - {false, optional}; - Else -> - mnesia_lib:verbose("sync_op terminated due to ~p~n", [Else]), - mnesia:abort(Else) - end; - -prepare_op(_Tid, {op, sync_trans}, {coord, Nodes}) -> - case receive_sync(Nodes, []) of - {abort, Reason} -> - mnesia_lib:verbose("sync_op terminated due to ~p~n", [Reason]), - mnesia:abort(Reason); - Pids -> - [Pid ! {sync_trans, self()} || Pid <- Pids], - {false, optional} - end; -prepare_op(Tid, {op, create_table, TabDef}, _WaitFor) -> - Cs = list2cs(TabDef), - Storage = mnesia_lib:cs_to_storage_type(node(), Cs), - UseDir = mnesia_monitor:use_dir(), - Tab = Cs#cstruct.name, - case Storage of - disc_copies when UseDir == false -> - UseDirReason = {bad_type, Tab, Storage, node()}, - mnesia:abort(UseDirReason); - disc_only_copies when UseDir == false -> - UseDirReason = {bad_type, Tab, Storage, node()}, - mnesia:abort(UseDirReason); - ram_copies -> - create_ram_table(Tab, Cs#cstruct.type), - insert_cstruct(Tid, Cs, false), - {true, optional}; - disc_copies -> - create_ram_table(Tab, Cs#cstruct.type), - create_disc_table(Tab), - insert_cstruct(Tid, Cs, false), - {true, optional}; - disc_only_copies -> - create_disc_only_table(Tab,Cs#cstruct.type), - insert_cstruct(Tid, Cs, false), - {true, optional}; - unknown -> %% No replica on this node - insert_cstruct(Tid, Cs, false), - {true, optional} - end; - -prepare_op(Tid, {op, add_table_copy, Storage, Node, TabDef}, _WaitFor) -> - Cs = list2cs(TabDef), - Tab = Cs#cstruct.name, - - if - Tab == schema -> - {true, optional}; % Nothing to prepare - Node == node() -> - case mnesia_lib:val({schema, storage_type}) of - ram_copies when Storage /= ram_copies -> - Error = {combine_error, Tab, "has no disc", Node}, - mnesia:abort(Error); - _ -> - ok - end, - %% Tables are created by mnesia_loader get_network code - insert_cstruct(Tid, Cs, true), - case mnesia_controller:get_network_copy(Tab, Cs) of - {loaded, ok} -> - {true, optional}; - {not_loaded, ErrReason} -> - Reason = {system_limit, Tab, {Node, ErrReason}}, - mnesia:abort(Reason) - end; - Node /= node() -> - %% Verify that ram table not has been dumped to disc - if - Storage /= ram_copies -> - case mnesia_lib:schema_cs_to_storage_type(node(), Cs) of - ram_copies -> - Dat = mnesia_lib:tab2dcd(Tab), - case mnesia_lib:exists(Dat) of - true -> - mnesia:abort({combine_error, Tab, Storage, - "Table dumped to disc", node()}); - false -> - ok - end; - _ -> - ok - end; - true -> - ok - end, - insert_cstruct(Tid, Cs, true), - {true, optional} - end; - -prepare_op(Tid, {op, del_table_copy, _Storage, Node, TabDef}, _WaitFor) -> - Cs = list2cs(TabDef), - Tab = Cs#cstruct.name, - - if - %% Schema table lock is always required to run a schema op. - %% No need to look it. - node(Tid#tid.pid) == node(), Tab /= schema -> - Pid = spawn_link(?MODULE, lock_del_table, [Tab, Node, Cs, self()]), - receive - {Pid, updated} -> - {true, optional}; - {Pid, FailReason} -> - mnesia:abort(FailReason); - {'EXIT', Pid, Reason} -> - mnesia:abort(Reason) - end; - true -> - {true, optional} - end; - -prepare_op(_Tid, {op, change_table_copy_type, N, FromS, ToS, TabDef}, _WaitFor) - when N == node() -> - Cs = list2cs(TabDef), - Tab = Cs#cstruct.name, - - NotActive = mnesia_lib:not_active_here(Tab), - - if - NotActive == true -> - mnesia:abort({not_active, Tab, node()}); - - Tab == schema -> - case {FromS, ToS} of - {ram_copies, disc_copies} -> - case mnesia:system_info(schema_location) of - opt_disc -> - ignore; - _ -> - mnesia:abort({combine_error, Tab, node(), - "schema_location must be opt_disc"}) - end, - Dir = mnesia_lib:dir(), - case opt_create_dir(true, Dir) of - ok -> - purge_dir(Dir, []), - mnesia_log:purge_all_logs(), - set(use_dir, true), - mnesia_log:init(), - Ns = val({current, db_nodes}), %mnesia_lib:running_nodes(), - F = fun(U) -> mnesia_recover:log_mnesia_up(U) end, - lists:foreach(F, Ns), - - mnesia_dumper:raw_named_dump_table(Tab, dmp), - mnesia_checkpoint:tm_change_table_copy_type(Tab, FromS, ToS); - {error, Reason} -> - mnesia:abort(Reason) - end; - {disc_copies, ram_copies} -> - Ltabs = val({schema, local_tables}) -- [schema], - Dtabs = [L || L <- Ltabs, - val({L, storage_type}) /= ram_copies], - verify([], Dtabs, {"Disc resident tables", Dtabs, N}); - _ -> - mnesia:abort({combine_error, Tab, ToS}) - end; - - FromS == ram_copies -> - case mnesia_monitor:use_dir() of - true -> - Dat = mnesia_lib:tab2dcd(Tab), - case mnesia_lib:exists(Dat) of - true -> - mnesia:abort({combine_error, Tab, node(), - "Table dump exists"}); - false -> - case ToS of - disc_copies -> - mnesia_log:ets2dcd(Tab, dmp); - disc_only_copies -> - mnesia_dumper:raw_named_dump_table(Tab, dmp) - end, - mnesia_checkpoint:tm_change_table_copy_type(Tab, FromS, ToS) - end; - false -> - mnesia:abort({has_no_disc, node()}) - end; - - FromS == disc_copies, ToS == disc_only_copies -> - mnesia_dumper:raw_named_dump_table(Tab, dmp); - FromS == disc_only_copies -> - Type = Cs#cstruct.type, - create_ram_table(Tab, Type), - Datname = mnesia_lib:tab2dat(Tab), - Repair = mnesia_monitor:get_env(auto_repair), - case mnesia_lib:dets_to_ets(Tab, Tab, Datname, Type, Repair, no) of - loaded -> ok; - Reason -> - Err = "Failed to copy disc data to ram", - mnesia:abort({system_limit, Tab, {Err,Reason}}) - end; - true -> - ignore - end, - {true, mandatory}; - -prepare_op(_Tid, {op, change_table_copy_type, N, _FromS, _ToS, _TabDef}, _WaitFor) - when N /= node() -> - {true, mandatory}; - -prepare_op(_Tid, {op, delete_table, _TabDef}, _WaitFor) -> - {true, mandatory}; - -prepare_op(_Tid, {op, dump_table, unknown, TabDef}, _WaitFor) -> - Cs = list2cs(TabDef), - Tab = Cs#cstruct.name, - case lists:member(node(), Cs#cstruct.ram_copies) of - true -> - case mnesia_monitor:use_dir() of - true -> - mnesia_log:ets2dcd(Tab, dmp), - Size = mnesia:table_info(Tab, size), - {true, [{op, dump_table, Size, TabDef}], optional}; - false -> - mnesia:abort({has_no_disc, node()}) - end; - false -> - {false, optional} - end; - -prepare_op(_Tid, {op, add_snmp, Ustruct, TabDef}, _WaitFor) -> - Cs = list2cs(TabDef), - case mnesia_lib:cs_to_storage_type(node(), Cs) of - unknown -> - {true, optional}; - Storage -> - Tab = Cs#cstruct.name, - Stab = mnesia_snmp_hook:create_table(Ustruct, Tab, Storage), - mnesia_lib:set({Tab, {index, snmp}}, Stab), - {true, optional} - end; - -prepare_op(_Tid, {op, transform, ignore, _TabDef}, _WaitFor) -> - {true, mandatory}; %% Apply schema changes only. -prepare_op(_Tid, {op, transform, Fun, TabDef}, _WaitFor) -> - Cs = list2cs(TabDef), - case mnesia_lib:cs_to_storage_type(node(), Cs) of - unknown -> - {true, mandatory}; - Storage -> - Tab = Cs#cstruct.name, - RecName = Cs#cstruct.record_name, - Type = Cs#cstruct.type, - NewArity = length(Cs#cstruct.attributes) + 1, - mnesia_lib:db_fixtable(Storage, Tab, true), - Key = mnesia_lib:db_first(Tab), - Op = {op, transform, Fun, TabDef}, - case catch transform_objs(Fun, Tab, RecName, - Key, NewArity, Storage, Type, [Op]) of - {'EXIT', Reason} -> - mnesia_lib:db_fixtable(Storage, Tab, false), - exit({"Bad transform function", Tab, Fun, node(), Reason}); - Objs -> - mnesia_lib:db_fixtable(Storage, Tab, false), - {true, Objs, mandatory} - end - end; - -prepare_op(_Tid, _Op, _WaitFor) -> - {true, optional}. - - -create_ram_table(Tab, Type) -> - Args = [{keypos, 2}, public, named_table, Type], - case mnesia_monitor:unsafe_mktab(Tab, Args) of - Tab -> - ok; - {error,Reason} -> - Err = "Failed to create ets table", - mnesia:abort({system_limit, Tab, {Err,Reason}}) - end. -create_disc_table(Tab) -> - File = mnesia_lib:tab2dcd(Tab), - file:delete(File), - FArg = [{file, File}, {name, {mnesia,create}}, - {repair, false}, {mode, read_write}], - case mnesia_monitor:open_log(FArg) of - {ok,Log} -> - mnesia_monitor:unsafe_close_log(Log), - ok; - {error,Reason} -> - Err = "Failed to create disc table", - mnesia:abort({system_limit, Tab, {Err,Reason}}) - end. -create_disc_only_table(Tab,Type) -> - File = mnesia_lib:tab2dat(Tab), - file:delete(File), - Args = [{file, mnesia_lib:tab2dat(Tab)}, - {type, mnesia_lib:disk_type(Tab, Type)}, - {keypos, 2}, - {repair, mnesia_monitor:get_env(auto_repair)}], - case mnesia_monitor:unsafe_open_dets(Tab, Args) of - {ok, _} -> - ok; - {error,Reason} -> - Err = "Failed to create disc table", - mnesia:abort({system_limit, Tab, {Err,Reason}}) - end. - - -receive_sync([], Pids) -> - Pids; -receive_sync(Nodes, Pids) -> - receive - {sync_trans, Pid} -> - Node = node(Pid), - receive_sync(lists:delete(Node, Nodes), [Pid | Pids]); - Else -> - {abort, Else} - end. - -lock_del_table(Tab, Node, Cs, Father) -> - Ns = val({schema, active_replicas}), - Lock = fun() -> - mnesia:write_lock_table(Tab), - {Res, []} = rpc:multicall(Ns, ?MODULE, set_where_to_read, [Tab, Node, Cs]), - Filter = fun(ok) -> - false; - ({badrpc, {'EXIT', {undef, _}}}) -> - %% This will be the case we talks with elder nodes - %% than 3.8.2, they will set where_to_read without - %% getting a lock. - false; - (_) -> - true - end, - [] = lists:filter(Filter, Res), - ok - end, - case mnesia:transaction(Lock) of - {'atomic', ok} -> - Father ! {self(), updated}; - {aborted, R} -> - Father ! {self(), R} - end, - unlink(Father), - exit(normal). - -set_where_to_read(Tab, Node, Cs) -> - case mnesia_lib:val({Tab, where_to_read}) of - Node -> - case Cs#cstruct.local_content of - true -> - ok; - false -> - mnesia_lib:set_remote_where_to_read(Tab, [Node]), - ok - end; - _ -> - ok - end. - -%% Build up the list in reverse order. -transform_objs(_Fun, _Tab, _RT, '$end_of_table', _NewArity, _Storage, _Type, Acc) -> - Acc; -transform_objs(Fun, Tab, RecName, Key, A, Storage, Type, Acc) -> - Objs = mnesia_lib:db_get(Tab, Key), - NextKey = mnesia_lib:db_next_key(Tab, Key), - Oid = {Tab, Key}, - NewObjs = {Ws, Ds} = transform_obj(Tab, RecName, Key, Fun, Objs, A, Type, [], []), - if - NewObjs == {[], []} -> - transform_objs(Fun, Tab, RecName, NextKey, A, Storage, Type, Acc); - Type == bag -> - transform_objs(Fun, Tab, RecName, NextKey, A, Storage, Type, - [{op, rec, Storage, {Oid, Ws, write}}, - {op, rec, Storage, {Oid, [Oid], delete}} | Acc]); - Ds == [] -> - %% Type is set or ordered_set, no need to delete the record first - transform_objs(Fun, Tab, RecName, NextKey, A, Storage, Type, - [{op, rec, Storage, {Oid, Ws, write}} | Acc]); - Ws == [] -> - transform_objs(Fun, Tab, RecName, NextKey, A, Storage, Type, - [{op, rec, Storage, {Oid, Ds, write}} | Acc]); - true -> - transform_objs(Fun, Tab, RecName, NextKey, A, Storage, Type, - [{op, rec, Storage, {Oid, Ws, write}}, - {op, rec, Storage, {Oid, Ds, delete}} | Acc]) - end. - -transform_obj(Tab, RecName, Key, Fun, [Obj|Rest], NewArity, Type, Ws, Ds) -> - NewObj = Fun(Obj), - if - size(NewObj) /= NewArity -> - exit({"Bad arity", Obj, NewObj}); - NewObj == Obj -> - transform_obj(Tab, RecName, Key, Fun, Rest, NewArity, Type, Ws, Ds); - RecName == element(1, NewObj), Key == element(2, NewObj) -> - transform_obj(Tab, RecName, Key, Fun, Rest, NewArity, - Type, [NewObj | Ws], Ds); - NewObj == delete -> - case Type of - bag -> %% Just don't write that object - transform_obj(Tab, RecName, Key, Fun, Rest, - NewArity, Type, Ws, Ds); - _ -> - transform_obj(Tab, RecName, Key, Fun, Rest, NewArity, - Type, Ws, [NewObj | Ds]) - end; - true -> - exit({"Bad key or Record Name", Obj, NewObj}) - end; -transform_obj(_Tab, _RecName, _Key, _Fun, [], _NewArity, _Type, Ws, Ds) -> - {lists:reverse(Ws), lists:reverse(Ds)}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Undo prepare of commit -undo_prepare_commit(Tid, Commit) -> - case Commit#commit.schema_ops of - [] -> - ignore; - Ops -> - %% Catch to allow failure mnesia_controller may not be started - catch mnesia_controller:release_schema_commit_lock(), - undo_prepare_ops(Tid, Ops) - end, - Commit. - -%% Undo in reverse order -undo_prepare_ops(Tid, [Op | Ops]) -> - case element(1, Op) of - TheOp when TheOp /= op, TheOp /= restore_op -> - undo_prepare_ops(Tid, Ops); - _ -> - undo_prepare_ops(Tid, Ops), - undo_prepare_op(Tid, Op) - end; -undo_prepare_ops(_Tid, []) -> - []. - -undo_prepare_op(_Tid, {op, announce_im_running, _, _, Running, RemoteRunning}) -> - case lists:member(node(), Running) of - true -> - unannounce_im_running(RemoteRunning -- Running); - false -> - unannounce_im_running(Running -- RemoteRunning) - end; - -undo_prepare_op(_Tid, {op, sync_trans}) -> - ok; - -undo_prepare_op(Tid, {op, create_table, TabDef}) -> - Cs = list2cs(TabDef), - Tab = Cs#cstruct.name, - mnesia_lib:unset({Tab, create_table}), - delete_cstruct(Tid, Cs), - case mnesia_lib:cs_to_storage_type(node(), Cs) of - unknown -> - ok; - ram_copies -> - ram_delete_table(Tab, ram_copies); - disc_copies -> - ram_delete_table(Tab, disc_copies), - DcdFile = mnesia_lib:tab2dcd(Tab), - %% disc_delete_table(Tab, Storage), - file:delete(DcdFile); - disc_only_copies -> - mnesia_monitor:unsafe_close_dets(Tab), - Dat = mnesia_lib:tab2dat(Tab), - %% disc_delete_table(Tab, Storage), - file:delete(Dat) - end; - -undo_prepare_op(Tid, {op, add_table_copy, Storage, Node, TabDef}) -> - Cs = list2cs(TabDef), - Tab = Cs#cstruct.name, - if - Tab == schema -> - true; % Nothing to prepare - Node == node() -> - mnesia_checkpoint:tm_del_copy(Tab, Node), - mnesia_controller:unannounce_add_table_copy(Tab, Node), - if - Storage == disc_only_copies; Tab == schema -> - mnesia_monitor:close_dets(Tab), - file:delete(mnesia_lib:tab2dat(Tab)); - true -> - file:delete(mnesia_lib:tab2dcd(Tab)) - end, - ram_delete_table(Tab, Storage), - Cs2 = new_cs(Cs, Node, Storage, del), - insert_cstruct(Tid, Cs2, true); % Don't care about the version - Node /= node() -> - mnesia_controller:unannounce_add_table_copy(Tab, Node), - Cs2 = new_cs(Cs, Node, Storage, del), - insert_cstruct(Tid, Cs2, true) % Don't care about the version - end; - -undo_prepare_op(_Tid, {op, del_table_copy, _, Node, TabDef}) - when Node == node() -> - Cs = list2cs(TabDef), - Tab = Cs#cstruct.name, - mnesia_lib:set({Tab, where_to_read}, Node); - - -undo_prepare_op(_Tid, {op, change_table_copy_type, N, FromS, ToS, TabDef}) - when N == node() -> - Cs = list2cs(TabDef), - Tab = Cs#cstruct.name, - mnesia_checkpoint:tm_change_table_copy_type(Tab, ToS, FromS), - Dmp = mnesia_lib:tab2dmp(Tab), - - case {FromS, ToS} of - {ram_copies, disc_copies} when Tab == schema -> - file:delete(Dmp), - mnesia_log:purge_some_logs(), - set(use_dir, false); - {ram_copies, disc_copies} -> - file:delete(Dmp); - {ram_copies, disc_only_copies} -> - file:delete(Dmp); - {disc_only_copies, _} -> - ram_delete_table(Tab, ram_copies); - _ -> - ignore - end; - -undo_prepare_op(_Tid, {op, dump_table, _Size, TabDef}) -> - Cs = list2cs(TabDef), - case lists:member(node(), Cs#cstruct.ram_copies) of - true -> - Tab = Cs#cstruct.name, - Dmp = mnesia_lib:tab2dmp(Tab), - file:delete(Dmp); - false -> - ignore - end; - -undo_prepare_op(_Tid, {op, add_snmp, _Ustruct, TabDef}) -> - Cs = list2cs(TabDef), - case mnesia_lib:cs_to_storage_type(node(), Cs) of - unknown -> - true; - _Storage -> - Tab = Cs#cstruct.name, - case ?catch_val({Tab, {index, snmp}}) of - {'EXIT',_} -> - ignore; - Stab -> - mnesia_snmp_hook:delete_table(Tab, Stab), - mnesia_lib:unset({Tab, {index, snmp}}) - end - end; - -undo_prepare_op(_Tid, _Op) -> - ignore. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -ram_delete_table(Tab, Storage) -> - case Storage of - unknown -> - ignore; - disc_only_copies -> - ignore; - _Else -> - %% delete possible index files and data ..... - %% Got to catch this since if no info has been set in the - %% mnesia_gvar it will crash - catch mnesia_index:del_transient(Tab, Storage), - case ?catch_val({Tab, {index, snmp}}) of - {'EXIT', _} -> - ignore; - Etab -> - catch mnesia_snmp_hook:delete_table(Tab, Etab) - end, - catch ?ets_delete_table(Tab) - end. - -purge_dir(Dir, KeepFiles) -> - Suffixes = known_suffixes(), - purge_dir(Dir, KeepFiles, Suffixes). - -purge_dir(Dir, KeepFiles, Suffixes) -> - case dir_exists(Dir) of - true -> - {ok, AllFiles} = file:list_dir(Dir), - purge_known_files(AllFiles, KeepFiles, Dir, Suffixes); - false -> - ok - end. - -purge_tmp_files() -> - case mnesia_monitor:use_dir() of - true -> - Dir = mnesia_lib:dir(), - KeepFiles = [], - Exists = mnesia_lib:exists(mnesia_lib:tab2dat(schema)), - case Exists of - true -> - Suffixes = tmp_suffixes(), - purge_dir(Dir, KeepFiles, Suffixes); - false -> - %% Interrupted change of storage type - %% for schema table - Suffixes = known_suffixes(), - purge_dir(Dir, KeepFiles, Suffixes), - mnesia_lib:set(use_dir, false) - end; - - false -> - ok - end. - -purge_known_files([File | Tail], KeepFiles, Dir, Suffixes) -> - case lists:member(File, KeepFiles) of - true -> - ignore; - false -> - case has_known_suffix(File, Suffixes, false) of - false -> - ignore; - true -> - AbsFile = filename:join([Dir, File]), - file:delete(AbsFile) - end - end, - purge_known_files(Tail, KeepFiles, Dir, Suffixes); -purge_known_files([], _KeepFiles, _Dir, _Suffixes) -> - ok. - -has_known_suffix(_File, _Suffixes, true) -> - true; -has_known_suffix(File, [Suffix | Tail], false) -> - has_known_suffix(File, Tail, lists:suffix(Suffix, File)); -has_known_suffix(_File, [], Bool) -> - Bool. - -known_suffixes() -> real_suffixes() ++ tmp_suffixes(). - -real_suffixes() -> [".DAT", ".LOG", ".BUP", ".DCL", ".DCD"]. - -tmp_suffixes() -> [".TMP", ".BUPTMP", ".RET", ".DMP"]. - -info() -> - Tabs = lists:sort(val({schema, tables})), - lists:foreach(fun(T) -> info(T) end, Tabs), - ok. - -info(Tab) -> - Props = get_table_properties(Tab), - io:format("-- Properties for ~w table --- ~n",[Tab]), - info2(Tab, Props). -info2(Tab, [{cstruct, _V} | Tail]) -> % Ignore cstruct - info2(Tab, Tail); -info2(Tab, [{frag_hash, _V} | Tail]) -> % Ignore frag_hash - info2(Tab, Tail); -info2(Tab, [{P, V} | Tail]) -> - io:format("~-20w -> ~p~n",[P,V]), - info2(Tab, Tail); -info2(_, []) -> - io:format("~n", []). - -get_table_properties(Tab) -> - case catch mnesia_lib:db_match_object(ram_copies, - mnesia_gvar, {{Tab, '_'}, '_'}) of - {'EXIT', _} -> - mnesia:abort({no_exists, Tab, all}); - RawGvar -> - case [{Item, Val} || {{_Tab, Item}, Val} <- RawGvar] of - [] -> - []; - Gvar -> - Size = {size, mnesia:table_info(Tab, size)}, - Memory = {memory, mnesia:table_info(Tab, memory)}, - Master = {master_nodes, mnesia:table_info(Tab, master_nodes)}, - lists:sort([Size, Memory, Master | Gvar]) - end - end. - -%%%%%%%%%%% RESTORE %%%%%%%%%%% - --record(r, {iter = schema, - module, - table_options = [], - default_op = clear_tables, - tables = [], - opaque, - insert_op = error_fun, - recs = error_recs - }). - -restore(Opaque) -> - restore(Opaque, [], mnesia_monitor:get_env(backup_module)). -restore(Opaque, Args) when list(Args) -> - restore(Opaque, Args, mnesia_monitor:get_env(backup_module)); -restore(_Opaque, BadArg) -> - {aborted, {badarg, BadArg}}. -restore(Opaque, Args, Module) when list(Args), atom(Module) -> - InitR = #r{opaque = Opaque, module = Module}, - case catch lists:foldl(fun check_restore_arg/2, InitR, Args) of - R when record(R, r) -> - case mnesia_bup:read_schema(Module, Opaque) of - {error, Reason} -> - {aborted, Reason}; - BupSchema -> - schema_transaction(fun() -> do_restore(R, BupSchema) end) - end; - {'EXIT', Reason} -> - {aborted, Reason} - end; -restore(_Opaque, Args, Module) -> - {aborted, {badarg, Args, Module}}. - -check_restore_arg({module, Mod}, R) when atom(Mod) -> - R#r{module = Mod}; - -check_restore_arg({clear_tables, List}, R) when list(List) -> - case lists:member(schema, List) of - false -> - TableList = [{Tab, clear_tables} || Tab <- List], - R#r{table_options = R#r.table_options ++ TableList}; - true -> - exit({badarg, {clear_tables, schema}}) - end; -check_restore_arg({recreate_tables, List}, R) when list(List) -> - case lists:member(schema, List) of - false -> - TableList = [{Tab, recreate_tables} || Tab <- List], - R#r{table_options = R#r.table_options ++ TableList}; - true -> - exit({badarg, {recreate_tables, schema}}) - end; -check_restore_arg({keep_tables, List}, R) when list(List) -> - TableList = [{Tab, keep_tables} || Tab <- List], - R#r{table_options = R#r.table_options ++ TableList}; -check_restore_arg({skip_tables, List}, R) when list(List) -> - TableList = [{Tab, skip_tables} || Tab <- List], - R#r{table_options = R#r.table_options ++ TableList}; -check_restore_arg({default_op, Op}, R) -> - case Op of - clear_tables -> ok; - recreate_tables -> ok; - keep_tables -> ok; - skip_tables -> ok; - Else -> - exit({badarg, {bad_default_op, Else}}) - end, - R#r{default_op = Op}; - -check_restore_arg(BadArg,_) -> - exit({badarg, BadArg}). - -do_restore(R, BupSchema) -> - TidTs = get_tid_ts_and_lock(schema, write), - R2 = restore_schema(BupSchema, R), - insert_schema_ops(TidTs, [{restore_op, R2}]), - [element(1, TabStruct) || TabStruct <- R2#r.tables]. - -arrange_restore(R, Fun, Recs) -> - R2 = R#r{insert_op = Fun, recs = Recs}, - case mnesia_bup:iterate(R#r.module, fun restore_items/4, R#r.opaque, R2) of - {ok, R3} -> R3#r.recs; - {error, Reason} -> mnesia:abort(Reason); - Reason -> mnesia:abort(Reason) - end. - -restore_items([Rec | Recs], Header, Schema, R) -> - Tab = element(1, Rec), - case lists:keysearch(Tab, 1, R#r.tables) of - {value, {Tab, Where, Snmp, RecName}} -> - {Rest, NRecs} = - restore_tab_items([Rec | Recs], Tab, RecName, Where, Snmp, - R#r.recs, R#r.insert_op), - restore_items(Rest, Header, Schema, R#r{recs = NRecs}); - false -> - Rest = skip_tab_items(Recs, Tab), - restore_items(Rest, Header, Schema, R) - end; - -restore_items([], _Header, _Schema, R) -> - R. - -restore_func(Tab, R) -> - case lists:keysearch(Tab, 1, R#r.table_options) of - {value, {Tab, OP}} -> - OP; - false -> - R#r.default_op - end. - -where_to_commit(Tab, CsList) -> - Ram = [{N, ram_copies} || N <- pick(Tab, ram_copies, CsList, [])], - Disc = [{N, disc_copies} || N <- pick(Tab, disc_copies, CsList, [])], - DiscO = [{N, disc_only_copies} || N <- pick(Tab, disc_only_copies, CsList, [])], - Ram ++ Disc ++ DiscO. - -%% Changes of the Meta info of schema itself is not allowed -restore_schema([{schema, schema, _List} | Schema], R) -> - restore_schema(Schema, R); -restore_schema([{schema, Tab, List} | Schema], R) -> - case restore_func(Tab, R) of - clear_tables -> - do_clear_table(Tab), - Where = val({Tab, where_to_commit}), - Snmp = val({Tab, snmp}), - RecName = val({Tab, record_name}), - R2 = R#r{tables = [{Tab, Where, Snmp, RecName} | R#r.tables]}, - restore_schema(Schema, R2); - recreate_tables -> - TidTs = get_tid_ts_and_lock(Tab, write), - NC = {cookie, ?unique_cookie}, - List2 = lists:keyreplace(cookie, 1, List, NC), - Where = where_to_commit(Tab, List2), - Snmp = pick(Tab, snmp, List2, []), - RecName = pick(Tab, record_name, List2, Tab), -% case ?catch_val({Tab, cstruct}) of -% {'EXIT', _} -> -% ignore; -% OldCs when record(OldCs, cstruct) -> -% do_delete_table(Tab) -% end, -% unsafe_do_create_table(list2cs(List2)), - insert_schema_ops(TidTs, [{op, restore_recreate, List2}]), - R2 = R#r{tables = [{Tab, Where, Snmp, RecName} | R#r.tables]}, - restore_schema(Schema, R2); - keep_tables -> - get_tid_ts_and_lock(Tab, write), - Where = val({Tab, where_to_commit}), - Snmp = val({Tab, snmp}), - RecName = val({Tab, record_name}), - R2 = R#r{tables = [{Tab, Where, Snmp, RecName} | R#r.tables]}, - restore_schema(Schema, R2); - skip_tables -> - restore_schema(Schema, R) - end; - -restore_schema([{schema, Tab} | Schema], R) -> - do_delete_table(Tab), - Tabs = lists:delete(Tab,R#r.tables), - restore_schema(Schema, R#r{tables = Tabs}); -restore_schema([], R) -> - R. - -restore_tab_items([Rec | Rest], Tab, RecName, Where, Snmp, Recs, Op) - when element(1, Rec) == Tab -> - NewRecs = Op(Rec, Recs, RecName, Where, Snmp), - restore_tab_items(Rest, Tab, RecName, Where, Snmp, NewRecs, Op); - -restore_tab_items(Rest, _Tab, _RecName, _Where, _Snmp, Recs, _Op) -> - {Rest, Recs}. - -skip_tab_items([Rec| Rest], Tab) - when element(1, Rec) == Tab -> - skip_tab_items(Rest, Tab); -skip_tab_items(Recs, _) -> - Recs. - -%%%%%%%%% Dump tables %%%%%%%%%%%%% -dump_tables(Tabs) when list(Tabs) -> - schema_transaction(fun() -> do_dump_tables(Tabs) end); -dump_tables(Tabs) -> - {aborted, {bad_type, Tabs}}. - -do_dump_tables(Tabs) -> - TidTs = get_tid_ts_and_lock(schema, write), - insert_schema_ops(TidTs, make_dump_tables(Tabs)). - -make_dump_tables([schema | _Tabs]) -> - mnesia:abort({bad_type, schema}); -make_dump_tables([Tab | Tabs]) -> - get_tid_ts_and_lock(Tab, read), - TabDef = get_create_list(Tab), - DiscResident = val({Tab, disc_copies}) ++ val({Tab, disc_only_copies}), - verify([], DiscResident, - {"Only allowed on ram_copies", Tab, DiscResident}), - [{op, dump_table, unknown, TabDef} | make_dump_tables(Tabs)]; -make_dump_tables([]) -> - []. - -%% Merge the local schema with the schema on other nodes -merge_schema() -> - schema_transaction(fun() -> do_merge_schema() end). - -do_merge_schema() -> - {_Mod, Tid, Ts} = get_tid_ts_and_lock(schema, write), - Connected = val(recover_nodes), - Running = val({current, db_nodes}), - Store = Ts#tidstore.store, - case Connected -- Running of - [Node | _] -> - %% Time for a schema merging party! - mnesia_locker:wlock_no_exist(Tid, Store, schema, [Node]), - - case rpc:call(Node, mnesia_controller, get_cstructs, []) of - {cstructs, Cstructs, RemoteRunning1} -> - LockedAlready = Running ++ [Node], - {New, Old} = mnesia_recover:connect_nodes(RemoteRunning1), - RemoteRunning = mnesia_lib:intersect(New ++ Old, RemoteRunning1), - if - RemoteRunning /= RemoteRunning1 -> - mnesia_lib:error("Mnesia on ~p could not connect to node(s) ~p~n", - [node(), RemoteRunning1 -- RemoteRunning]); - true -> ok - end, - NeedsLock = RemoteRunning -- LockedAlready, - mnesia_locker:wlock_no_exist(Tid, Store, schema, NeedsLock), - - {value, SchemaCs} = - lists:keysearch(schema, #cstruct.name, Cstructs), - - %% Announce that Node is running - A = [{op, announce_im_running, node(), - cs2list(SchemaCs), Running, RemoteRunning}], - do_insert_schema_ops(Store, A), - - %% Introduce remote tables to local node - do_insert_schema_ops(Store, make_merge_schema(Node, Cstructs)), - - %% Introduce local tables to remote nodes - Tabs = val({schema, tables}), - Ops = [{op, merge_schema, get_create_list(T)} - || T <- Tabs, - not lists:keymember(T, #cstruct.name, Cstructs)], - do_insert_schema_ops(Store, Ops), - - %% Ensure that the txn will be committed on all nodes - announce_im_running(RemoteRunning, SchemaCs), - {merged, Running, RemoteRunning}; - {error, Reason} -> - {"Cannot get cstructs", Node, Reason}; - {badrpc, Reason} -> - {"Cannot get cstructs", Node, {badrpc, Reason}} - end; - [] -> - %% No more nodes to merge schema with - not_merged - end. - -make_merge_schema(Node, [Cs | Cstructs]) -> - Ops = do_make_merge_schema(Node, Cs), - Ops ++ make_merge_schema(Node, Cstructs); -make_merge_schema(_Node, []) -> - []. - -%% Merge definitions of schema table -do_make_merge_schema(Node, RemoteCs) - when RemoteCs#cstruct.name == schema -> - Cs = val({schema, cstruct}), - Masters = mnesia_recover:get_master_nodes(schema), - HasRemoteMaster = lists:member(Node, Masters), - HasLocalMaster = lists:member(node(), Masters), - Force = HasLocalMaster or HasRemoteMaster, - %% What is the storage types opinions? - StCsLocal = mnesia_lib:cs_to_storage_type(node(), Cs), - StRcsLocal = mnesia_lib:cs_to_storage_type(node(), RemoteCs), - StCsRemote = mnesia_lib:cs_to_storage_type(Node, Cs), - StRcsRemote = mnesia_lib:cs_to_storage_type(Node, RemoteCs), - - if - Cs#cstruct.cookie == RemoteCs#cstruct.cookie, - Cs#cstruct.version == RemoteCs#cstruct.version -> - %% Great, we have the same cookie and version - %% and do not need to merge cstructs - []; - - Cs#cstruct.cookie /= RemoteCs#cstruct.cookie, - Cs#cstruct.disc_copies /= [], - RemoteCs#cstruct.disc_copies /= [] -> - %% Both cstructs involves disc nodes - %% and we cannot merge them - if - HasLocalMaster == true, - HasRemoteMaster == false -> - %% Choose local cstruct, - %% since it's the master - [{op, merge_schema, cs2list(Cs)}]; - - HasRemoteMaster == true, - HasLocalMaster == false -> - %% Choose remote cstruct, - %% since it's the master - [{op, merge_schema, cs2list(RemoteCs)}]; - - true -> - Str = io_lib:format("Incompatible schema cookies. " - "Please, restart from old backup." - "~w = ~w, ~w = ~w~n", - [Node, cs2list(RemoteCs), node(), cs2list(Cs)]), - throw(Str) - end; - - StCsLocal /= StRcsLocal, StRcsLocal /= unknown -> - Str = io_lib:format("Incompatible schema storage types. " - "on ~w storage ~w, on ~w storage ~w~n", - [node(), StCsLocal, Node, StRcsLocal]), - throw(Str); - StCsRemote /= StRcsRemote, StCsRemote /= unknown -> - Str = io_lib:format("Incompatible schema storage types. " - "on ~w storage ~w, on ~w storage ~w~n", - [node(), StCsRemote, Node, StRcsRemote]), - throw(Str); - - Cs#cstruct.disc_copies /= [] -> - %% Choose local cstruct, - %% since it involves disc nodes - MergedCs = merge_cstructs(Cs, RemoteCs, Force), - [{op, merge_schema, cs2list(MergedCs)}]; - - RemoteCs#cstruct.disc_copies /= [] -> - %% Choose remote cstruct, - %% since it involves disc nodes - MergedCs = merge_cstructs(RemoteCs, Cs, Force), - [{op, merge_schema, cs2list(MergedCs)}]; - - Cs > RemoteCs -> - %% Choose remote cstruct - MergedCs = merge_cstructs(RemoteCs, Cs, Force), - [{op, merge_schema, cs2list(MergedCs)}]; - - true -> - %% Choose local cstruct - MergedCs = merge_cstructs(Cs, RemoteCs, Force), - [{op, merge_schema, cs2list(MergedCs)}] - end; - -%% Merge definitions of normal table -do_make_merge_schema(Node, RemoteCs) -> - Tab = RemoteCs#cstruct.name, - Masters = mnesia_recover:get_master_nodes(schema), - HasRemoteMaster = lists:member(Node, Masters), - HasLocalMaster = lists:member(node(), Masters), - Force = HasLocalMaster or HasRemoteMaster, - case ?catch_val({Tab, cstruct}) of - {'EXIT', _} -> - %% A completely new table, created while Node was down - [{op, merge_schema, cs2list(RemoteCs)}]; - Cs when Cs#cstruct.cookie == RemoteCs#cstruct.cookie -> - if - Cs#cstruct.version == RemoteCs#cstruct.version -> - %% We have exactly the same version of the - %% table def - []; - - Cs#cstruct.version > RemoteCs#cstruct.version -> - %% Oops, we have different versions - %% of the table def, lets merge them. - %% The only changes that may have occurred - %% is that new replicas may have been added. - MergedCs = merge_cstructs(Cs, RemoteCs, Force), - [{op, merge_schema, cs2list(MergedCs)}]; - - Cs#cstruct.version < RemoteCs#cstruct.version -> - %% Oops, we have different versions - %% of the table def, lets merge them - MergedCs = merge_cstructs(RemoteCs, Cs, Force), - [{op, merge_schema, cs2list(MergedCs)}] - end; - Cs -> - %% Different cookies, not possible to merge - if - HasLocalMaster == true, - HasRemoteMaster == false -> - %% Choose local cstruct, - %% since it's the master - [{op, merge_schema, cs2list(Cs)}]; - - HasRemoteMaster == true, - HasLocalMaster == false -> - %% Choose remote cstruct, - %% since it's the master - [{op, merge_schema, cs2list(RemoteCs)}]; - - true -> - Str = io_lib:format("Bad cookie in table definition" - " ~w: ~w = ~w, ~w = ~w~n", - [Tab, node(), Cs, Node, RemoteCs]), - throw(Str) - end - end. - -%% Change of table definitions (cstructs) requires all replicas -%% of the table to be active. New replicas, db_nodes and tables -%% may however be added even if some replica is inactive. These -%% invariants must be enforced in order to allow merge of cstructs. -%% -%% Returns a new cstruct or issues a fatal error -merge_cstructs(Cs, RemoteCs, Force) -> - verify_cstruct(Cs), - case catch do_merge_cstructs(Cs, RemoteCs, Force) of - {'EXIT', {aborted, _Reason}} when Force == true -> - Cs; - {'EXIT', Reason} -> - exit(Reason); - MergedCs when record(MergedCs, cstruct) -> - MergedCs; - Other -> - throw(Other) - end. - -do_merge_cstructs(Cs, RemoteCs, Force) -> - verify_cstruct(RemoteCs), - Ns = mnesia_lib:uniq(mnesia_lib:cs_to_nodes(Cs) ++ - mnesia_lib:cs_to_nodes(RemoteCs)), - {AnythingNew, MergedCs} = - merge_storage_type(Ns, false, Cs, RemoteCs, Force), - MergedCs2 = merge_versions(AnythingNew, MergedCs, RemoteCs, Force), - verify_cstruct(MergedCs2), - MergedCs2. - -merge_storage_type([N | Ns], AnythingNew, Cs, RemoteCs, Force) -> - Local = mnesia_lib:cs_to_storage_type(N, Cs), - Remote = mnesia_lib:cs_to_storage_type(N, RemoteCs), - case compare_storage_type(true, Local, Remote) of - {same, _Storage} -> - merge_storage_type(Ns, AnythingNew, Cs, RemoteCs, Force); - {diff, Storage} -> - Cs2 = change_storage_type(N, Storage, Cs), - merge_storage_type(Ns, true, Cs2, RemoteCs, Force); - incompatible when Force == true -> - merge_storage_type(Ns, AnythingNew, Cs, RemoteCs, Force); - Other -> - Str = io_lib:format("Cannot merge storage type for node ~w " - "in cstruct ~w with remote cstruct ~w (~w)~n", - [N, Cs, RemoteCs, Other]), - throw(Str) - end; -merge_storage_type([], AnythingNew, MergedCs, _RemoteCs, _Force) -> - {AnythingNew, MergedCs}. - -compare_storage_type(_Retry, Any, Any) -> - {same, Any}; -compare_storage_type(_Retry, unknown, Any) -> - {diff, Any}; -compare_storage_type(_Retry, ram_copies, disc_copies) -> - {diff, disc_copies}; -compare_storage_type(_Retry, disc_copies, disc_only_copies) -> - {diff, disc_only_copies}; -compare_storage_type(true, One, Another) -> - compare_storage_type(false, Another, One); -compare_storage_type(false, _One, _Another) -> - incompatible. - -change_storage_type(N, ram_copies, Cs) -> - Nodes = [N | Cs#cstruct.ram_copies], - Cs#cstruct{ram_copies = mnesia_lib:uniq(Nodes)}; -change_storage_type(N, disc_copies, Cs) -> - Nodes = [N | Cs#cstruct.disc_copies], - Cs#cstruct{disc_copies = mnesia_lib:uniq(Nodes)}; -change_storage_type(N, disc_only_copies, Cs) -> - Nodes = [N | Cs#cstruct.disc_only_copies], - Cs#cstruct{disc_only_copies = mnesia_lib:uniq(Nodes)}. - -%% BUGBUG: Verify match of frag info; equalit demanded for all but add_node - -merge_versions(AnythingNew, Cs, RemoteCs, Force) -> - if - Cs#cstruct.name == schema -> - ok; - Cs#cstruct.name /= schema, - Cs#cstruct.cookie == RemoteCs#cstruct.cookie -> - ok; - Force == true -> - ok; - true -> - Str = io_lib:format("Bad cookies. Cannot merge definitions of " - "table ~w. Local = ~w, Remote = ~w~n", - [Cs#cstruct.name, Cs, RemoteCs]), - throw(Str) - end, - if - Cs#cstruct.name == RemoteCs#cstruct.name, - Cs#cstruct.type == RemoteCs#cstruct.type, - Cs#cstruct.local_content == RemoteCs#cstruct.local_content, - Cs#cstruct.attributes == RemoteCs#cstruct.attributes, - Cs#cstruct.index == RemoteCs#cstruct.index, - Cs#cstruct.snmp == RemoteCs#cstruct.snmp, - Cs#cstruct.access_mode == RemoteCs#cstruct.access_mode, - Cs#cstruct.load_order == RemoteCs#cstruct.load_order, - Cs#cstruct.user_properties == RemoteCs#cstruct.user_properties -> - do_merge_versions(AnythingNew, Cs, RemoteCs); - Force == true -> - do_merge_versions(AnythingNew, Cs, RemoteCs); - true -> - Str1 = io_lib:format("Cannot merge definitions of " - "table ~w. Local = ~w, Remote = ~w~n", - [Cs#cstruct.name, Cs, RemoteCs]), - throw(Str1) - end. - -do_merge_versions(AnythingNew, MergedCs, RemoteCs) -> - {{Major1, Minor1}, _Detail1} = MergedCs#cstruct.version, - {{Major2, Minor2}, _Detail2} = RemoteCs#cstruct.version, - if - MergedCs#cstruct.version == RemoteCs#cstruct.version -> - MergedCs; - AnythingNew == false -> - MergedCs; - Major1 == Major2 -> - Minor = lists:max([Minor1, Minor2]), - V = {{Major1, Minor}, dummy}, - incr_version(MergedCs#cstruct{version = V}); - Major1 /= Major2 -> - Major = lists:max([Major1, Major2]), - V = {{Major, 0}, dummy}, - incr_version(MergedCs#cstruct{version = V}) - end. - -announce_im_running([N | Ns], SchemaCs) -> - {L1, L2} = mnesia_recover:connect_nodes([N]), - case lists:member(N, L1) or lists:member(N, L2) of - true -> -%% dbg_out("Adding ~p to {current db_nodes} ~n", [N]), %% qqqq - mnesia_lib:add({current, db_nodes}, N), - mnesia_controller:add_active_replica(schema, N, SchemaCs); - false -> - ignore - end, - announce_im_running(Ns, SchemaCs); -announce_im_running([], _) -> - []. - -unannounce_im_running([N | Ns]) -> - mnesia_lib:del({current, db_nodes}, N), - mnesia_controller:del_active_replica(schema, N), - mnesia_recover:disconnect(N), - unannounce_im_running(Ns); -unannounce_im_running([]) -> - []. - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_snmp_hook.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_snmp_hook.erl deleted file mode 100644 index 458323c0e4..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_snmp_hook.erl +++ /dev/null @@ -1,271 +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_snmp_hook.erl,v 1.1 2008/12/17 09:53:39 mikpe Exp $ -%% --module(mnesia_snmp_hook). - -%% Hooks (called from mnesia) --export([check_ustruct/1, create_table/3, delete_table/2, - key_to_oid/3, update/1, start/2, - get_row/2, get_next_index/2, get_mnesia_key/2]). - -%% sys callback functions --export([system_continue/3, - system_terminate/4, - system_code_change/4 - ]). - -%% Internal exports --export([b_init/2]). - -check_ustruct([]) -> - true; %% default value, not SNMP'ified -check_ustruct([{key, Types}]) -> - is_snmp_type(to_list(Types)); -check_ustruct(_) -> false. - -to_list(Tuple) when tuple(Tuple) -> tuple_to_list(Tuple); -to_list(X) -> [X]. - -is_snmp_type([integer | T]) -> is_snmp_type(T); -is_snmp_type([string | T]) -> is_snmp_type(T); -is_snmp_type([fix_string | T]) -> is_snmp_type(T); -is_snmp_type([]) -> true; -is_snmp_type(_) -> false. - -create_table([], MnesiaTab, _Storage) -> - mnesia:abort({badarg, MnesiaTab, {snmp, empty_snmpstruct}}); - -create_table([{key, Us}], MnesiaTab, Storage) -> - Tree = b_new(MnesiaTab, Us), - mnesia_lib:db_fixtable(Storage, MnesiaTab, true), - First = mnesia_lib:db_first(Storage, MnesiaTab), - build_table(First, MnesiaTab, Tree, Us, Storage), - mnesia_lib:db_fixtable(Storage, MnesiaTab, false), - Tree. - -build_table(MnesiaKey, MnesiaTab, Tree, Us, Storage) - when MnesiaKey /= '$end_of_table' -> -%% SnmpKey = key_to_oid(MnesiaTab, MnesiaKey, Us), -%% update(write, Tree, MnesiaKey, SnmpKey), - update(write, Tree, MnesiaKey, MnesiaKey), - Next = mnesia_lib:db_next_key(Storage, MnesiaTab, MnesiaKey), - build_table(Next, MnesiaTab, Tree, Us, Storage); -build_table('$end_of_table', _MnesiaTab, _Tree, _Us, _Storage) -> - ok. - -delete_table(_MnesiaTab, Tree) -> - exit(Tree, shutdown), - ok. - -%%----------------------------------------------------------------- -%% update({Op, MnesiaTab, MnesiaKey, SnmpKey}) -%%----------------------------------------------------------------- - -update({clear_table, MnesiaTab}) -> - Tree = mnesia_lib:val({MnesiaTab, {index, snmp}}), - b_clear(Tree); - -update({Op, MnesiaTab, MnesiaKey, SnmpKey}) -> - Tree = mnesia_lib:val({MnesiaTab, {index, snmp}}), - update(Op, Tree, MnesiaKey, SnmpKey). - -update(Op, Tree, MnesiaKey, _) -> - case Op of - write -> - b_insert(Tree, MnesiaKey, MnesiaKey); - update_counter -> - ignore; - delete -> - b_delete(Tree, MnesiaKey); - delete_object -> - b_delete(Tree, MnesiaKey) - end, - ok. - -%%----------------------------------------------------------------- -%% Func: key_to_oid(Tab, Key, Ustruct) -%% Args: Key ::= key() -%% key() ::= int() | string() | {int() | string()} -%% Type ::= {fix_string | term()} -%% Make an OBJECT IDENTIFIER out of it. -%% Variable length objects are prepended by their length. -%% Ex. Key = {"pelle", 42} AND Type = {string, integer} => -%% OID [5, $p, $e, $l, $l, $e, 42] -%% Key = {"pelle", 42} AND Type = {fix_string, integer} => -%% OID [$p, $e, $l, $l, $e, 42] -%%----------------------------------------------------------------- -key_to_oid(Tab, Key, [{key, Types}]) -> - MnesiaOid = {Tab, Key}, - if - tuple(Key), tuple(Types) -> - case {size(Key), size(Types)} of - {Size, Size} -> - keys_to_oid(MnesiaOid, Size, Key, [], Types); - _ -> - exit({bad_snmp_key, MnesiaOid}) - end; - true -> - key_to_oid_i(MnesiaOid, Key, Types) - end. - -key_to_oid_i(_MnesiaOid, Key, integer) when integer(Key) -> [Key]; -key_to_oid_i(_MnesiaOid, Key, fix_string) when list(Key) -> Key; -key_to_oid_i(_MnesiaOid, Key, string) when list(Key) -> [length(Key) | Key]; -key_to_oid_i(MnesiaOid, Key, Type) -> - exit({bad_snmp_key, [MnesiaOid, Key, Type]}). - -keys_to_oid(_MnesiaOid, 0, _Key, Oid, _Types) -> Oid; -keys_to_oid(MnesiaOid, N, Key, Oid, Types) -> - Type = element(N, Types), - KeyPart = element(N, Key), - Oid2 = key_to_oid_i(MnesiaOid, KeyPart, Type) ++ Oid, - keys_to_oid(MnesiaOid, N-1, Key, Oid2, Types). - -%%----------------------------------------------------------------- -%% Func: get_row/2 -%% Args: Name is the name of the table (atom) -%% RowIndex is an Oid -%% Returns: {ok, Row} | undefined -%% Note that the Row returned might contain columns that -%% are not visible via SNMP. e.g. the first column may be -%% ifIndex, and the last MFA ({ifIndex, col1, col2, MFA}). -%% where ifIndex is used only as index (not as a real col), -%% and MFA as extra info, used by the application. -%%----------------------------------------------------------------- -get_row(Name, RowIndex) -> - Tree = mnesia_lib:val({Name, {index, snmp}}), - case b_lookup(Tree, RowIndex) of - {ok, {_RowIndex, Key}} -> - [Row] = mnesia:dirty_read({Name, Key}), - {ok, Row}; - _ -> - undefined - end. - -%%----------------------------------------------------------------- -%% Func: get_next_index/2 -%% Args: Name is the name of the table (atom) -%% RowIndex is an Oid -%% Returns: {ok, NextIndex} | endOfTable -%%----------------------------------------------------------------- -get_next_index(Name, RowIndex) -> - Tree = mnesia_lib:val({Name, {index, snmp}}), - case b_lookup_next(Tree, RowIndex) of - {ok, {NextIndex, _Key}} -> - {ok, NextIndex}; - _ -> - endOfTable - end. - -%%----------------------------------------------------------------- -%% Func: get_mnesia_key/2 -%% Purpose: Get the mnesia key corresponding to the RowIndex. -%% Args: Name is the name of the table (atom) -%% RowIndex is an Oid -%% Returns: {ok, Key} | undefiend -%%----------------------------------------------------------------- -get_mnesia_key(Name, RowIndex) -> - Tree = mnesia_lib:val({Name, {index, snmp}}), - case b_lookup(Tree, RowIndex) of - {ok, {_RowIndex, Key}} -> - {ok, Key}; - _ -> - undefined - end. - -%%----------------------------------------------------------------- -%% Encapsulate a bplus_tree in a process. -%%----------------------------------------------------------------- - -b_new(MnesiaTab, Us) -> - case supervisor:start_child(mnesia_snmp_sup, [MnesiaTab, Us]) of - {ok, Tree} -> - Tree; - {error, Reason} -> - exit({badsnmp, MnesiaTab, Reason}) - end. - -start(MnesiaTab, Us) -> - Name = {mnesia_snmp, MnesiaTab}, - mnesia_monitor:start_proc(Name, ?MODULE, b_init, [self(), Us]). - -b_insert(Tree, Key, Val) -> Tree ! {insert, Key, Val}. -b_delete(Tree, Key) -> Tree ! {delete, Key}. -b_lookup(Tree, Key) -> - Tree ! {lookup, self(), Key}, - receive - {bplus_res, Res} -> - Res - end. -b_lookup_next(Tree, Key) -> - Tree ! {lookup_next, self(), Key}, - receive - {bplus_res, Res} -> - Res - end. - -b_clear(Tree) -> - Tree ! clear, - ok. - -b_init(Parent, Us) -> - %% Do not trap exit - Tree = snmp_index:new(Us), - proc_lib:init_ack(Parent, {ok, self()}), - b_loop(Parent, Tree, Us). - -b_loop(Parent, Tree, Us) -> - receive - {insert, Key, Val} -> - NTree = snmp_index:insert(Tree, Key, Val), - b_loop(Parent, NTree, Us); - {delete, Key} -> - NTree = snmp_index:delete(Tree, Key), - b_loop(Parent, NTree, Us); - {lookup, From, Key} -> - Res = snmp_index:get(Tree, Key), - From ! {bplus_res, Res}, - b_loop(Parent, Tree, Us); - {lookup_next, From, Key} -> - Res = snmp_index:get_next(Tree, Key), - From ! {bplus_res, Res}, - b_loop(Parent, Tree, Us); - clear -> - catch snmp_index:delete(Tree), %% Catch because delete/1 is not - NewTree = snmp_index:new(Us), %% available in old snmp (before R5) - b_loop(Parent, NewTree, Us); - - {'EXIT', Parent, Reason} -> - exit(Reason); - - {system, From, Msg} -> - mnesia_lib:dbg_out("~p got {system, ~p, ~p}~n", [?MODULE, From, Msg]), - sys:handle_system_msg(Msg, From, Parent, ?MODULE, [], {Tree, Us}) - - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% System upgrade - -system_continue(Parent, _Debug, {Tree, Us}) -> - b_loop(Parent, Tree, Us). - -system_terminate(Reason, _Parent, _Debug, _Tree) -> - exit(Reason). - -system_code_change(State, _Module, _OldVsn, _Extra) -> - {ok, State}. diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_snmp_sup.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_snmp_sup.erl deleted file mode 100644 index 1cbac23e9d..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_snmp_sup.erl +++ /dev/null @@ -1,39 +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_snmp_sup.erl,v 1.1 2008/12/17 09:53:39 mikpe Exp $ -%% --module(mnesia_snmp_sup). - --behaviour(supervisor). - --export([start/0, init/1]). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% top supervisor callback functions - -start() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% sub supervisor callback functions - -init([]) -> - Flags = {simple_one_for_one, 0, timer:hours(24)}, % Trust the top supervisor - MFA = {mnesia_snmp_hook, start, []}, - Modules = [?MODULE, mnesia_snmp_hook, supervisor], - KillAfter = mnesia_kernel_sup:supervisor_timeout(timer:seconds(3)), - Workers = [{?MODULE, MFA, transient, KillAfter, worker, Modules}], - {ok, {Flags, Workers}}. diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_sp.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_sp.erl deleted file mode 100644 index ad29d3cc78..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_sp.erl +++ /dev/null @@ -1,39 +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_sp.erl,v 1.1 2008/12/17 09:53:39 mikpe Exp $ -%% - -%% To able to generate nice crash reports we need a catch on the highest level. -%% This code can't be purged so a code change is not possible. -%% And hence this a simple module. - --module(mnesia_sp). - --export([init_proc/4]). - -init_proc(Who, Mod, Fun, Args) -> - mnesia_lib:verbose("~p starting: ~p~n", [Who, self()]), - case catch apply(Mod, Fun, Args) of - {'EXIT', Reason} -> - mnesia_monitor:terminate_proc(Who, Reason, Args), - exit(Reason); - Other -> - Other - end. - - - - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_subscr.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_subscr.erl deleted file mode 100644 index f077291bc6..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_subscr.erl +++ /dev/null @@ -1,492 +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_subscr.erl,v 1.1 2008/12/17 09:53:39 mikpe Exp $ -%% --module(mnesia_subscr). - --behaviour(gen_server). - --export([start/0, - set_debug_level/1, - subscribe/2, - unsubscribe/2, - unsubscribe_table/1, - subscribers/0, - report_table_event/4, - report_table_event/5, - report_table_event/6 - ]). - -%% gen_server callbacks --export([init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - terminate/2, - code_change/3 - ]). - --include("mnesia.hrl"). - --import(mnesia_lib, [error/2]). --record(state, {supervisor, pid_tab}). - -start() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [self()], - [{timeout, infinity}]). - -set_debug_level(Level) -> - OldEnv = application:get_env(mnesia, debug), - case mnesia_monitor:patch_env(debug, Level) of - {error, Reason} -> - {error, Reason}; - NewLevel -> - set_debug_level(NewLevel, OldEnv) - end. - -set_debug_level(Level, OldEnv) -> - case mnesia:system_info(is_running) of - no when OldEnv == undefined -> - none; - no -> - {ok, E} = OldEnv, - E; - _ -> - Old = mnesia_lib:val(debug), - Local = mnesia:system_info(local_tables), - E = whereis(mnesia_event), - Sub = fun(Tab) -> subscribe(E, {table, Tab}) end, - UnSub = fun(Tab) -> unsubscribe(E, {table, Tab}) end, - - case Level of - none -> - lists:foreach(UnSub, Local); - verbose -> - lists:foreach(UnSub, Local); - debug -> - lists:foreach(UnSub, Local -- [schema]), - Sub(schema); - trace -> - lists:foreach(Sub, Local) - end, - mnesia_lib:set(debug, Level), - Old - end. - -subscribe(ClientPid, system) -> - change_subscr(activate, ClientPid, system); -subscribe(ClientPid, {table, Tab}) -> - change_subscr(activate, ClientPid, {table, Tab, simple}); -subscribe(ClientPid, {table, Tab, simple}) -> - change_subscr(activate, ClientPid, {table, Tab, simple}); -subscribe(ClientPid, {table, Tab, detailed}) -> - change_subscr(activate, ClientPid, {table, Tab, detailed}); -subscribe(_ClientPid, What) -> - {error, {badarg, What}}. - -unsubscribe(ClientPid, system) -> - change_subscr(deactivate, ClientPid, system); -unsubscribe(ClientPid, {table, Tab}) -> - change_subscr(deactivate, ClientPid, {table, Tab, simple}); -unsubscribe(ClientPid, {table, Tab, simple}) -> - change_subscr(deactivate, ClientPid, {table, Tab, simple}); -unsubscribe(ClientPid, {table, Tab, detailed}) -> - change_subscr(deactivate, ClientPid, {table, Tab, detailed}); -unsubscribe(_ClientPid, What) -> - {error, {badarg, What}}. - -unsubscribe_table(Tab) -> - call({change, {deactivate_table, Tab}}). - -change_subscr(Kind, ClientPid, What) -> - call({change, {Kind, ClientPid, What}}). - -subscribers() -> - [whereis(mnesia_event) | mnesia_lib:val(subscribers)]. - -report_table_event(Tab, Tid, Obj, Op) -> - case ?catch_val({Tab, commit_work}) of - {'EXIT', _} -> ok; - Commit -> - case lists:keysearch(subscribers, 1, Commit) of - false -> ok; - {value, Subs} -> - report_table_event(Subs, Tab, Tid, Obj, Op, undefined) - end - end. - -%% Backwards compatible for the moment when mnesia_tm get's updated! -report_table_event(Subscr, Tab, Tid, Obj, Op) -> - report_table_event(Subscr, Tab, Tid, Obj, Op, undefined). - -report_table_event({subscribers, S1, S2}, Tab, Tid, _Obj, clear_table, _Old) -> - What = {delete, {schema, Tab}, Tid}, - deliver(S1, {mnesia_table_event, What}), - TabDef = mnesia_schema:cs2list(?catch_val({Tab, cstruct})), - What2 = {write, {schema, Tab, TabDef}, Tid}, - deliver(S1, {mnesia_table_event, What2}), - What3 = {delete, schema, {schema, Tab}, [{schema, Tab, TabDef}], Tid}, - deliver(S2, {mnesia_table_event, What3}), - What4 = {write, schema, {schema, Tab, TabDef}, [], Tid}, - deliver(S2, {mnesia_table_event, What4}); - -report_table_event({subscribers, Subscr, []}, Tab, Tid, Obj, Op, _Old) -> - What = {Op, patch_record(Tab, Obj), Tid}, - deliver(Subscr, {mnesia_table_event, What}); - -report_table_event({subscribers, S1, S2}, Tab, Tid, Obj, Op, Old) -> - Standard = {Op, patch_record(Tab, Obj), Tid}, - deliver(S1, {mnesia_table_event, Standard}), - Extended = what(Tab, Tid, Obj, Op, Old), - deliver(S2, Extended); - -%% Backwards compatible for the moment when mnesia_tm get's updated! -report_table_event({subscribers, Subscr}, Tab, Tid, Obj, Op, Old) -> - report_table_event({subscribers, Subscr, []}, Tab, Tid, Obj, Op, Old). - - -patch_record(Tab, Obj) -> - case Tab == element(1, Obj) of - true -> - Obj; - false -> - setelement(1, Obj, Tab) - end. - -what(Tab, Tid, {RecName, Key}, delete, undefined) -> - case catch mnesia_lib:db_get(Tab, Key) of - Old when list(Old) -> %% Op only allowed for set table. - {mnesia_table_event, {delete, Tab, {RecName, Key}, Old, Tid}}; - _ -> - %% Record just deleted by a dirty_op or - %% the whole table has been deleted - ignore - end; -what(Tab, Tid, Obj, delete, Old) -> - {mnesia_table_event, {delete, Tab, Obj, Old, Tid}}; -what(Tab, Tid, Obj, delete_object, _Old) -> - {mnesia_table_event, {delete, Tab, Obj, [Obj], Tid}}; -what(Tab, Tid, Obj, write, undefined) -> - case catch mnesia_lib:db_get(Tab, element(2, Obj)) of - Old when list(Old) -> - {mnesia_table_event, {write, Tab, Obj, Old, Tid}}; - {'EXIT', _} -> - ignore - end. - -deliver(_, ignore) -> - ok; -deliver([Pid | Pids], Msg) -> - Pid ! Msg, - deliver(Pids, Msg); -deliver([], _Msg) -> - ok. - -call(Msg) -> - Pid = whereis(?MODULE), - case Pid of - undefined -> - {error, {node_not_running, node()}}; - Pid -> - Res = gen_server:call(Pid, Msg, infinity), - %% We get an exit signal if server dies - receive - {'EXIT', _Pid, _Reason} -> - {error, {node_not_running, node()}} - after 0 -> - ignore - end, - Res - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%% Callback functions from gen_server - -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, State} | -%% {ok, State, Timeout} | -%% {stop, Reason} -%%---------------------------------------------------------------------- -init([Parent]) -> - process_flag(trap_exit, true), - ClientPid = whereis(mnesia_event), - link(ClientPid), - mnesia_lib:verbose("~p starting: ~p~n", [?MODULE, self()]), - Tab = ?ets_new_table(mnesia_subscr, [duplicate_bag, private]), - ?ets_insert(Tab, {ClientPid, system}), - {ok, #state{supervisor = Parent, pid_tab = Tab}}. - -%%---------------------------------------------------------------------- -%% Func: handle_call/3 -%% Returns: {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | (terminate/2 is called) -%%---------------------------------------------------------------------- -handle_call({change, How}, _From, State) -> - Reply = do_change(How, State#state.pid_tab), - {reply, Reply, State}; - -handle_call(Msg, _From, State) -> - error("~p got unexpected call: ~p~n", [?MODULE, Msg]), - {noreply, State}. - -%%---------------------------------------------------------------------- -%% Func: handle_cast/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- -handle_cast(Msg, State) -> - error("~p got unexpected cast: ~p~n", [?MODULE, Msg]), - {noreply, State}. - -%%---------------------------------------------------------------------- -%% Func: handle_info/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- - -handle_info({'EXIT', Pid, _R}, State) when Pid == State#state.supervisor -> - {stop, shutdown, State}; - -handle_info({'EXIT', Pid, _Reason}, State) -> - handle_exit(Pid, State#state.pid_tab), - {noreply, State}; - -handle_info(Msg, State) -> - error("~p got unexpected info: ~p~n", [?MODULE, Msg]), - {noreply, State}. - -%%---------------------------------------------------------------------- -%% Func: terminate/2 -%% Purpose: Shutdown the server -%% Returns: any (ignored by gen_server) -%%---------------------------------------------------------------------- -terminate(Reason, State) -> - prepare_stop(State#state.pid_tab), - mnesia_monitor:terminate_proc(?MODULE, Reason, State). - -%%---------------------------------------------------------------------- -%% Func: code_change/3 -%% Purpose: Upgrade process when its code is to be changed -%% Returns: {ok, NewState} -%%---------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%%---------------------------------------------------------------------- -%%% Internal functions -%%%---------------------------------------------------------------------- - -do_change({activate, ClientPid, system}, SubscrTab) when pid(ClientPid) -> - Var = subscribers, - activate(ClientPid, system, Var, subscribers(), SubscrTab); -do_change({activate, ClientPid, {table, Tab, How}}, SubscrTab) when pid(ClientPid) -> - case ?catch_val({Tab, where_to_read}) of - Node when Node == node() -> - Var = {Tab, commit_work}, - activate(ClientPid, {table, Tab, How}, Var, mnesia_lib:val(Var), SubscrTab); - {'EXIT', _} -> - {error, {no_exists, Tab}}; - _Node -> - {error, {not_active_local, Tab}} - end; -do_change({deactivate, ClientPid, system}, SubscrTab) -> - Var = subscribers, - deactivate(ClientPid, system, Var, SubscrTab); -do_change({deactivate, ClientPid, {table, Tab, How}}, SubscrTab) -> - Var = {Tab, commit_work}, - deactivate(ClientPid, {table, Tab, How}, Var, SubscrTab); -do_change({deactivate_table, Tab}, SubscrTab) -> - Var = {Tab, commit_work}, - case ?catch_val(Var) of - {'EXIT', _} -> - {error, {no_exists, Tab}}; - CommitWork -> - case lists:keysearch(subscribers, 1, CommitWork) of - false -> - ok; - {value, Subs} -> - Simple = {table, Tab, simple}, - Detailed = {table, Tab, detailed}, - Fs = fun(C) -> deactivate(C, Simple, Var, SubscrTab) end, - Fd = fun(C) -> deactivate(C, Detailed, Var, SubscrTab) end, - case Subs of - {subscribers, L1, L2} -> - lists:foreach(Fs, L1), - lists:foreach(Fd, L2); - {subscribers, L1} -> - lists:foreach(Fs, L1) - end - end, - {ok, node()} - end; -do_change(_, _) -> - {error, badarg}. - -activate(ClientPid, What, Var, OldSubscribers, SubscrTab) -> - Old = - if Var == subscribers -> - OldSubscribers; - true -> - case lists:keysearch(subscribers, 1, OldSubscribers) of - false -> []; - {value, Subs} -> - case Subs of - {subscribers, L1, L2} -> - L1 ++ L2; - {subscribers, L1} -> - L1 - end - end - end, - case lists:member(ClientPid, Old) of - false -> - %% Don't care about checking old links - case catch link(ClientPid) of - true -> - ?ets_insert(SubscrTab, {ClientPid, What}), - add_subscr(Var, What, ClientPid), - {ok, node()}; - {'EXIT', _Reason} -> - {error, {no_exists, ClientPid}} - end; - true -> - {error, {already_exists, What}} - end. - -%%-record(subscribers, {pids = []}). Old subscriber record removed -%% To solve backward compatibility, this code is a cludge.. -add_subscr(subscribers, _What, Pid) -> - mnesia_lib:add(subscribers, Pid), - {ok, node()}; -add_subscr({Tab, commit_work}, What, Pid) -> - Commit = mnesia_lib:val({Tab, commit_work}), - case lists:keysearch(subscribers, 1, Commit) of - false -> - Subscr = - case What of - {table, _, simple} -> - {subscribers, [Pid], []}; - {table, _, detailed} -> - {subscribers, [], [Pid]} - end, - mnesia_lib:add({Tab, subscribers}, Pid), - mnesia_lib:set({Tab, commit_work}, - mnesia_lib:sort_commit([Subscr | Commit])); - {value, Old} -> - {L1, L2} = - case Old of - {subscribers, L} -> %% Old Way - {L, []}; - {subscribers, SL1, SL2} -> - {SL1, SL2} - end, - Subscr = - case What of - {table, _, simple} -> - {subscribers, [Pid | L1], L2}; - {table, _, detailed} -> - {subscribers, L1, [Pid | L2]} - end, - NewC = lists:keyreplace(subscribers, 1, Commit, Subscr), - mnesia_lib:set({Tab, commit_work}, - mnesia_lib:sort_commit(NewC)), - mnesia_lib:add({Tab, subscribers}, Pid) - end. - -deactivate(ClientPid, What, Var, SubscrTab) -> - ?ets_match_delete(SubscrTab, {ClientPid, What}), - case catch ?ets_lookup_element(SubscrTab, ClientPid, 1) of - List when list(List) -> - ignore; - {'EXIT', _} -> - unlink(ClientPid) - end, - del_subscr(Var, What, ClientPid), - {ok, node()}. - -del_subscr(subscribers, _What, Pid) -> - mnesia_lib:del(subscribers, Pid); -del_subscr({Tab, commit_work}, What, Pid) -> - Commit = mnesia_lib:val({Tab, commit_work}), - case lists:keysearch(subscribers, 1, Commit) of - false -> - false; - {value, Old} -> - {L1, L2} = - case Old of - {subscribers, L} -> %% Old Way - {L, []}; - {subscribers, SL1, SL2} -> - {SL1, SL2} - end, - Subscr = - case What of %% Ignore user error delete subscr from any list - {table, _, simple} -> - NewL1 = lists:delete(Pid, L1), - NewL2 = lists:delete(Pid, L2), - {subscribers, NewL1, NewL2}; - {table, _, detailed} -> - NewL1 = lists:delete(Pid, L1), - NewL2 = lists:delete(Pid, L2), - {subscribers, NewL1, NewL2} - end, - case Subscr of - {subscribers, [], []} -> - NewC = lists:keydelete(subscribers, 1, Commit), - mnesia_lib:del({Tab, subscribers}, Pid), - mnesia_lib:set({Tab, commit_work}, - mnesia_lib:sort_commit(NewC)); - _ -> - NewC = lists:keyreplace(subscribers, 1, Commit, Subscr), - mnesia_lib:del({Tab, subscribers}, Pid), - mnesia_lib:set({Tab, commit_work}, - mnesia_lib:sort_commit(NewC)) - end - end. - -handle_exit(ClientPid, SubscrTab) -> - do_handle_exit(?ets_lookup(SubscrTab, ClientPid)), - ?ets_delete(SubscrTab, ClientPid). - -do_handle_exit([{ClientPid, What} | Tail]) -> - case What of - system -> - del_subscr(subscribers, What, ClientPid); - {_, Tab, _Level} -> - del_subscr({Tab, commit_work}, What, ClientPid) - end, - do_handle_exit(Tail); -do_handle_exit([]) -> - ok. - -prepare_stop(SubscrTab) -> - mnesia_lib:report_system_event({mnesia_down, node()}), - do_prepare_stop(?ets_first(SubscrTab), SubscrTab). - -do_prepare_stop('$end_of_table', _SubscrTab) -> - ok; -do_prepare_stop(ClientPid, SubscrTab) -> - Next = ?ets_next(SubscrTab, ClientPid), - handle_exit(ClientPid, SubscrTab), - unlink(ClientPid), - do_prepare_stop(Next, SubscrTab). - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_sup.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_sup.erl deleted file mode 100644 index a8a1df885f..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_sup.erl +++ /dev/null @@ -1,137 +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_sup.erl,v 1.1 2008/12/17 09:53:39 mikpe Exp $ -%% -%% Supervisor for the entire Mnesia application - --module(mnesia_sup). - --behaviour(application). --behaviour(supervisor). - --export([start/0, start/2, init/1, stop/1, start_event/0, kill/0]). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% application and suprvisor callback functions - -start(normal, Args) -> - SupName = {local,?MODULE}, - case supervisor:start_link(SupName, ?MODULE, [Args]) of - {ok, Pid} -> - {ok, Pid, {normal, Args}}; - Error -> - Error - end; -start(_, _) -> - {error, badarg}. - -start() -> - SupName = {local,?MODULE}, - supervisor:start_link(SupName, ?MODULE, []). - -stop(_StartArgs) -> - ok. - -init([]) -> % Supervisor - init(); -init([[]]) -> % Application - init(); -init(BadArg) -> - {error, {badarg, BadArg}}. - -init() -> - Flags = {one_for_all, 0, 3600}, % Should be rest_for_one policy - - Event = event_procs(), - Kernel = kernel_procs(), - Mnemosyne = mnemosyne_procs(), - - {ok, {Flags, Event ++ Kernel ++ Mnemosyne}}. - -event_procs() -> - KillAfter = timer:seconds(30), - KA = mnesia_kernel_sup:supervisor_timeout(KillAfter), - E = mnesia_event, - [{E, {?MODULE, start_event, []}, permanent, KA, worker, [E, gen_event]}]. - -kernel_procs() -> - K = mnesia_kernel_sup, - KA = infinity, - [{K, {K, start, []}, permanent, KA, supervisor, [K, supervisor]}]. - -mnemosyne_procs() -> - case mnesia_monitor:get_env(embedded_mnemosyne) of - true -> - Q = mnemosyne_sup, - KA = infinity, - [{Q, {Q, start, []}, permanent, KA, supervisor, [Q, supervisor]}]; - false -> - [] - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% event handler - -start_event() -> - case gen_event:start_link({local, mnesia_event}) of - {ok, Pid} -> - case add_event_handler() of - ok -> - {ok, Pid}; - Error -> - Error - end; - Error -> - Error - end. - -add_event_handler() -> - Handler = mnesia_monitor:get_env(event_module), - gen_event:add_handler(mnesia_event, Handler, []). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% debug functions - -kill() -> - Mnesia = [mnesia_fallback | mnesia:ms()], - Mnemosyne = mnemosyne_ms(), - Kill = fun(Name) -> catch exit(whereis(Name), kill) end, - lists:foreach(Kill, Mnemosyne), - lists:foreach(Kill, Mnesia), - lists:foreach(fun ensure_dead/1, Mnemosyne), - lists:foreach(fun ensure_dead/1, Mnesia), - timer:sleep(10), - case lists:keymember(mnesia, 1, application:which_applications()) of - true -> kill(); - false -> ok - end. - -ensure_dead(Name) -> - case whereis(Name) of - undefined -> - ok; - Pid when pid(Pid) -> - exit(Pid, kill), - timer:sleep(10), - ensure_dead(Name) - end. - -mnemosyne_ms() -> - case mnesia_monitor:get_env(embedded_mnemosyne) of - true -> mnemosyne:ms(); - false -> [] - end. - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_text.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_text.erl deleted file mode 100644 index e6084efbb1..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_text.erl +++ /dev/null @@ -1,191 +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_text.erl,v 1.2 2010/03/04 13:54:20 maria Exp $ -%% --module(mnesia_text). - --export([parse/1, file/1, load_textfile/1, dump_to_textfile/1]). - -load_textfile(File) -> - ensure_started(), - case parse(File) of - {ok, {Tabs, Data}} -> - Badtabs = make_tabs(lists:map(fun validate_tab/1, Tabs)), - load_data(del_data(Badtabs, Data, [])); - Other -> - Other - end. - -dump_to_textfile(File) -> - dump_to_textfile(mnesia_lib:is_running(), file:open(File, [write])). -dump_to_textfile(yes, {ok, F}) -> - Tabs = lists:delete(schema, mnesia_lib:local_active_tables()), - Defs = lists:map(fun(T) -> {T, [{record_name, mnesia_lib:val({T, record_name})}, - {attributes, mnesia_lib:val({T, attributes})}]} - end, - Tabs), - io:format(F, "~p.~n", [{tables, Defs}]), - lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs), - file:close(F); -dump_to_textfile(_,_) -> error. - - -dump_tab(F, T) -> - W = mnesia_lib:val({T, wild_pattern}), - {'atomic',All} = mnesia:transaction(fun() -> mnesia:match_object(T, W, read) end), - lists:foreach(fun(Term) -> io:format(F,"~p.~n", [setelement(1, Term, T)]) end, All). - - -ensure_started() -> - case mnesia_lib:is_running() of - yes -> - yes; - no -> - case mnesia_lib:exists(mnesia_lib:dir("schema.DAT")) of - true -> - mnesia:start(); - false -> - mnesia:create_schema([node()]), - mnesia:start() - end - end. - -del_data(Bad, [H|T], Ack) -> - case lists:member(element(1, H), Bad) of - true -> del_data(Bad, T, Ack); - false -> del_data(Bad, T, [H|Ack]) - end; -del_data(_Bad, [], Ack) -> - lists:reverse(Ack). - -%% Tis the place to call the validate func in mnesia_schema -validate_tab({Tabname, List}) -> - {Tabname, List}; -validate_tab({Tabname, RecName, List}) -> - {Tabname, RecName, List}; -validate_tab(_) -> error(badtab). - -make_tabs([{Tab, Def} | Tail]) -> - case catch mnesia:table_info(Tab, where_to_read) of - {'EXIT', _} -> %% non-existing table - case mnesia:create_table(Tab, Def) of - {aborted, Reason} -> - io:format("** Failed to create table ~w ~n" - "** Reason = ~w, Args = ~p~n", - [Tab, Reason, Def]), - [Tab | make_tabs(Tail)]; - _ -> - io:format("New table ~w~n", [Tab]), - make_tabs(Tail) - end; - Node -> - io:format("** Table ~w already exists on ~p, just entering data~n", - [Tab, Node]), - make_tabs(Tail) - end; - -make_tabs([]) -> - []. - -load_data(L) -> - mnesia:transaction(fun() -> - F = fun(X) -> - Tab = element(1, X), - RN = mnesia:table_info(Tab, record_name), - Rec = setelement(1, X, RN), - mnesia:write(Tab, Rec, write) end, - lists:foreach(F, L) - end). - -parse(File) -> - case file(File) of - {ok, Terms} -> - case catch collect(Terms) of - {error, X} -> - {error, X}; - Other -> - {ok, Other} - end; - Other -> - Other - end. - -collect([{_, {tables, Tabs}}|L]) -> - {Tabs, collect_data(Tabs, L)}; - -collect(_) -> - io:format("No tables found\n", []), - error(bad_header). - -collect_data(Tabs, [{Line, Term} | Tail]) when tuple(Term) -> - case lists:keysearch(element(1, Term), 1, Tabs) of - {value, _} -> - [Term | collect_data(Tabs, Tail)]; - _Other -> - io:format("Object:~p at line ~w unknown\n", [Term,Line]), - error(undefined_object) - end; -collect_data(_Tabs, []) -> []; -collect_data(_Tabs, [H|_T]) -> - io:format("Object:~p unknown\n", [H]), - error(undefined_object). - -error(What) -> throw({error, What}). - -file(File) -> - case file:open(File, [read]) of - {ok, Stream} -> - Res = read_terms(Stream, File, 1, []), - file:close(Stream), - Res; - _Other -> - {error, open} - end. - -read_terms(Stream, File, Line, L) -> - case read_term_from_stream(Stream, File, Line) of - {ok, Term, NextLine} -> - read_terms(Stream, File, NextLine, [Term|L]); - error -> - {error, read}; - eof -> - {ok, lists:reverse(L)} - end. - -read_term_from_stream(Stream, File, Line) -> - R = io:request(Stream, {get_until,'',erl_scan,tokens,[Line]}), - case R of - {ok,Toks,EndLine} -> - case erl_parse:parse_term(Toks) of - {ok, Term} -> - {ok, {Line, Term}, EndLine}; - {error, {NewLine,Mod,What}} -> - Str = Mod:format_error(What), - io:format("Error in line:~p of:~p ~s\n", - [NewLine, File, Str]), - error; - T -> - io:format("Error2 **~p~n",[T]), - error - end; - {eof,_EndLine} -> - eof; - Other -> - io:format("Error1 **~p~n",[Other]), - error - end. - - diff --git a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_tm.erl b/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_tm.erl deleted file mode 100644 index 7bee382a89..0000000000 --- a/lib/dialyzer/test/r9c_tests_SUITE_data/src/mnesia/mnesia_tm.erl +++ /dev/null @@ -1,2173 +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_tm.erl,v 1.2 2010/03/04 13:54:20 maria Exp $ -%% --module(mnesia_tm). - --export([ - start/0, - init/1, - non_transaction/5, - transaction/6, - commit_participant/5, - dirty/2, - display_info/2, - do_update_op/3, - get_info/1, - get_transactions/0, - info/1, - mnesia_down/1, - prepare_checkpoint/2, - prepare_checkpoint/1, % Internal - prepare_snmp/3, - do_snmp/2, - put_activity_id/1, - block_tab/1, - unblock_tab/1 - ]). - -%% sys callback functions --export([system_continue/3, - system_terminate/4, - system_code_change/4 - ]). - --include("mnesia.hrl"). --import(mnesia_lib, [set/2]). --import(mnesia_lib, [fatal/2, verbose/2, dbg_out/2]). - --record(state, {coordinators = [], participants = [], supervisor, - blocked_tabs = [], dirty_queue = []}). -%% Format on coordinators is [{Tid, EtsTabList} ..... - --record(prep, {protocol = sym_trans, - %% async_dirty | sync_dirty | sym_trans | sync_sym_trans | asym_trans - records = [], - prev_tab = [], % initiate to a non valid table name - prev_types, - prev_snmp, - types - }). - --record(participant, {tid, pid, commit, disc_nodes = [], - ram_nodes = [], protocol = sym_trans}). - -start() -> - mnesia_monitor:start_proc(?MODULE, ?MODULE, init, [self()]). - -init(Parent) -> - register(?MODULE, self()), - process_flag(trap_exit, true), - - %% Initialize the schema - IgnoreFallback = mnesia_monitor:get_env(ignore_fallback_at_startup), - mnesia_bup:tm_fallback_start(IgnoreFallback), - mnesia_schema:init(IgnoreFallback), - - %% Handshake and initialize transaction recovery - mnesia_recover:init(), - Early = mnesia_monitor:init(), - AllOthers = mnesia_lib:uniq(Early ++ mnesia_lib:all_nodes()) -- [node()], - set(original_nodes, AllOthers), - mnesia_recover:connect_nodes(AllOthers), - - %% Recover transactions, may wait for decision - case mnesia_monitor:use_dir() of - true -> - P = mnesia_dumper:opt_dump_log(startup), % previous log - L = mnesia_dumper:opt_dump_log(startup), % latest log - Msg = "Initial dump of log during startup: ~p~n", - mnesia_lib:verbose(Msg, [[P, L]]), - mnesia_log:init(); - false -> - ignore - end, - - mnesia_schema:purge_tmp_files(), - mnesia_recover:start_garb(), - - ?eval_debug_fun({?MODULE, init}, [{nodes, AllOthers}]), - - case val(debug) of - Debug when Debug /= debug, Debug /= trace -> - ignore; - _ -> - mnesia_subscr:subscribe(whereis(mnesia_event), {table, schema}) - end, - proc_lib:init_ack(Parent, {ok, self()}), - doit_loop(#state{supervisor = Parent}). - -val(Var) -> - case ?catch_val(Var) of - {'EXIT', _ReASoN_} -> mnesia_lib:other_val(Var, _ReASoN_); - _VaLuE_ -> _VaLuE_ - end. - -reply({From,Ref}, R) -> - From ! {?MODULE, Ref, R}; -reply(From, R) -> - From ! {?MODULE, node(), R}. - -reply(From, R, State) -> - reply(From, R), - doit_loop(State). - -req(R) -> - case whereis(?MODULE) of - undefined -> - {error, {node_not_running, node()}}; - Pid -> - Ref = make_ref(), - Pid ! {{self(), Ref}, R}, - rec(Pid, Ref) - end. - -rec() -> - rec(whereis(?MODULE)). - -rec(Pid) when pid(Pid) -> - receive - {?MODULE, _, Reply} -> - Reply; - - {'EXIT', Pid, _} -> - {error, {node_not_running, node()}} - end; -rec(undefined) -> - {error, {node_not_running, node()}}. - -rec(Pid, Ref) -> - receive - {?MODULE, Ref, Reply} -> - Reply; - {'EXIT', Pid, _} -> - {error, {node_not_running, node()}} - end. - -tmlink({From, Ref}) when reference(Ref) -> - link(From); -tmlink(From) -> - link(From). -tmpid({Pid, _Ref}) when pid(Pid) -> - Pid; -tmpid(Pid) -> - Pid. - -%% Returns a list of participant transaction Tid's -mnesia_down(Node) -> - %% Syncronously call needed in order to avoid - %% race with mnesia_tm's coordinator processes - %% that may restart and acquire new locks. - %% mnesia_monitor takes care of the sync - case whereis(?MODULE) of - undefined -> - mnesia_monitor:mnesia_down(?MODULE, {Node, []}); - Pid -> - Pid ! {mnesia_down, Node} - end. - -prepare_checkpoint(Nodes, Cp) -> - rpc:multicall(Nodes, ?MODULE, prepare_checkpoint, [Cp]). - -prepare_checkpoint(Cp) -> - req({prepare_checkpoint,Cp}). - -block_tab(Tab) -> - req({block_tab, Tab}). - -unblock_tab(Tab) -> - req({unblock_tab, Tab}). - -doit_loop(#state{coordinators = Coordinators, participants = Participants, supervisor = Sup} - = State) -> - receive - {_From, {async_dirty, Tid, Commit, Tab}} -> - case lists:member(Tab, State#state.blocked_tabs) of - false -> - do_async_dirty(Tid, Commit, Tab), - doit_loop(State); - true -> - Item = {async_dirty, Tid, Commit, Tab}, - State2 = State#state{dirty_queue = [Item | State#state.dirty_queue]}, - doit_loop(State2) - end; - - {From, {sync_dirty, Tid, Commit, Tab}} -> - case lists:member(Tab, State#state.blocked_tabs) of - false -> - do_sync_dirty(From, Tid, Commit, Tab), - doit_loop(State); - true -> - Item = {sync_dirty, From, Tid, Commit, Tab}, - State2 = State#state{dirty_queue = [Item | State#state.dirty_queue]}, - doit_loop(State2) - end; - - {From, start_outer} -> %% Create and associate ets_tab with Tid - case catch ?ets_new_table(mnesia_trans_store, [bag, public]) of - {'EXIT', Reason} -> %% system limit - Msg = "Cannot create an ets table for the " - "local transaction store", - reply(From, {error, {system_limit, Msg, Reason}}, State); - Etab -> - tmlink(From), - C = mnesia_recover:incr_trans_tid_serial(), - ?ets_insert(Etab, {nodes, node()}), - Tid = #tid{pid = tmpid(From), counter = C}, - A2 = [{Tid , [Etab]} | Coordinators], - S2 = State#state{coordinators = A2}, - reply(From, {new_tid, Tid, Etab}, S2) - end; - - {From, {ask_commit, Protocol, Tid, Commit, DiscNs, RamNs}} -> - ?eval_debug_fun({?MODULE, doit_ask_commit}, - [{tid, Tid}, {prot, Protocol}]), - mnesia_checkpoint:tm_enter_pending(Tid, DiscNs, RamNs), - Pid = - case Protocol of - asym_trans when node(Tid#tid.pid) /= node() -> - Args = [tmpid(From), Tid, Commit, DiscNs, RamNs], - spawn_link(?MODULE, commit_participant, Args); - _ when node(Tid#tid.pid) /= node() -> %% *_sym_trans - reply(From, {vote_yes, Tid}), - nopid - end, - P = #participant{tid = Tid, - pid = Pid, - commit = Commit, - disc_nodes = DiscNs, - ram_nodes = RamNs, - protocol = Protocol}, - State2 = State#state{participants = [P | Participants]}, - doit_loop(State2); - - {Tid, do_commit} -> - case mnesia_lib:key_search_delete(Tid, #participant.tid, Participants) of - {none, _} -> - verbose("Tried to commit a non participant transaction ~p~n", - [Tid]), - doit_loop(State); - {P, Participants2} -> - ?eval_debug_fun({?MODULE, do_commit, pre}, - [{tid, Tid}, {participant, P}]), - case P#participant.pid of - nopid -> - Commit = P#participant.commit, - Member = lists:member(node(), P#participant.disc_nodes), - if Member == false -> - ignore; - P#participant.protocol == sym_trans -> - mnesia_log:log(Commit); - P#participant.protocol == sync_sym_trans -> - mnesia_log:slog(Commit) - end, - mnesia_recover:note_decision(Tid, committed), - do_commit(Tid, Commit), - if - P#participant.protocol == sync_sym_trans -> - Tid#tid.pid ! {?MODULE, node(), {committed, Tid}}; - true -> - ignore - end, - mnesia_locker:release_tid(Tid), - transaction_terminated(Tid), - ?eval_debug_fun({?MODULE, do_commit, post}, [{tid, Tid}, {pid, nopid}]), - doit_loop(State#state{participants = Participants2}); - Pid when pid(Pid) -> - Pid ! {Tid, committed}, - ?eval_debug_fun({?MODULE, do_commit, post}, [{tid, Tid}, {pid, Pid}]), - doit_loop(State) - end - end; - - {Tid, simple_commit} -> - mnesia_recover:note_decision(Tid, committed), - mnesia_locker:release_tid(Tid), - transaction_terminated(Tid), - doit_loop(State); - - {Tid, {do_abort, Reason}} -> - ?eval_debug_fun({?MODULE, do_abort, pre}, [{tid, Tid}]), - mnesia_locker:release_tid(Tid), - case mnesia_lib:key_search_delete(Tid, #participant.tid, Participants) of - {none, _} -> - verbose("Tried to abort a non participant transaction ~p: ~p~n", - [Tid, Reason]), - doit_loop(State); - {P, Participants2} -> - case P#participant.pid of - nopid -> - Commit = P#participant.commit, - mnesia_recover:note_decision(Tid, aborted), - do_abort(Tid, Commit), - if - P#participant.protocol == sync_sym_trans -> - Tid#tid.pid ! {?MODULE, node(), {aborted, Tid}}; - true -> - ignore - end, - transaction_terminated(Tid), - ?eval_debug_fun({?MODULE, do_abort, post}, [{tid, Tid}, {pid, nopid}]), - doit_loop(State#state{participants = Participants2}); - Pid when pid(Pid) -> - Pid ! {Tid, {do_abort, Reason}}, - ?eval_debug_fun({?MODULE, do_abort, post}, - [{tid, Tid}, {pid, Pid}]), - doit_loop(State) - end - end; - - {From, {add_store, Tid}} -> %% new store for nested transaction - case catch ?ets_new_table(mnesia_trans_store, [bag, public]) of - {'EXIT', Reason} -> %% system limit - Msg = "Cannot create an ets table for a nested " - "local transaction store", - reply(From, {error, {system_limit, Msg, Reason}}, State); - Etab -> - A2 = add_coord_store(Coordinators, Tid, Etab), - reply(From, {new_store, Etab}, - State#state{coordinators = A2}) - end; - - {From, {del_store, Tid, Current, Obsolete, PropagateStore}} -> - opt_propagate_store(Current, Obsolete, PropagateStore), - A2 = del_coord_store(Coordinators, Tid, Current, Obsolete), - reply(From, store_erased, State#state{coordinators = A2}); - - {'EXIT', Pid, Reason} -> - handle_exit(Pid, Reason, State); - - {From, {restart, Tid, Store}} -> - A2 = restore_stores(Coordinators, Tid, Store), - ?ets_match_delete(Store, '_'), - ?ets_insert(Store, {nodes, node()}), - reply(From, {restarted, Tid}, State#state{coordinators = A2}); - - {delete_transaction, Tid} -> - %% used to clear transactions which are committed - %% in coordinator or participant processes - case mnesia_lib:key_search_delete(Tid, #participant.tid, Participants) of - {none, _} -> - case mnesia_lib:key_search_delete(Tid, 1, Coordinators) of - {none, _} -> - verbose("** ERROR ** Tried to delete a non transaction ~p~n", - [Tid]), - doit_loop(State); - {{_Tid, Etabs}, A2} -> - erase_ets_tabs(Etabs), - transaction_terminated(Tid), - doit_loop(State#state{coordinators = A2}) - end; - {_P, Participants2} -> - transaction_terminated(Tid), - State2 = State#state{participants = Participants2}, - doit_loop(State2) - end; - - {sync_trans_serial, Tid} -> - %% Do the Lamport thing here - mnesia_recover:sync_trans_tid_serial(Tid), - doit_loop(State); - - {From, info} -> - reply(From, {info, Participants, Coordinators}, State); - - {mnesia_down, N} -> - verbose("Got mnesia_down from ~p, reconfiguring...~n", [N]), - reconfigure_coordinators(N, Coordinators), - - Tids = [P#participant.tid || P <- Participants], - reconfigure_participants(N, Participants), - mnesia_monitor:mnesia_down(?MODULE, {N, Tids}), - doit_loop(State); - - {From, {unblock_me, Tab}} -> - case lists:member(Tab, State#state.blocked_tabs) of - false -> - verbose("Wrong dirty Op blocked on ~p ~p ~p", - [node(), Tab, From]), - reply(From, unblocked), - doit_loop(State); - true -> - Item = {Tab, unblock_me, From}, - State2 = State#state{dirty_queue = [Item | State#state.dirty_queue]}, - doit_loop(State2) - end; - - {From, {block_tab, Tab}} -> - State2 = State#state{blocked_tabs = [Tab | State#state.blocked_tabs]}, - reply(From, ok, State2); - - {From, {unblock_tab, Tab}} -> - BlockedTabs2 = State#state.blocked_tabs -- [Tab], - case lists:member(Tab, BlockedTabs2) of - false -> - mnesia_controller:unblock_table(Tab), - Queue = process_dirty_queue(Tab, State#state.dirty_queue), - State2 = State#state{blocked_tabs = BlockedTabs2, - dirty_queue = Queue}, - reply(From, ok, State2); - true -> - State2 = State#state{blocked_tabs = BlockedTabs2}, - reply(From, ok, State2) - end; - - {From, {prepare_checkpoint, Cp}} -> - Res = mnesia_checkpoint:tm_prepare(Cp), - case Res of - {ok, _Name, IgnoreNew, _Node} -> - prepare_pending_coordinators(Coordinators, IgnoreNew), - prepare_pending_participants(Participants, IgnoreNew); - {error, _Reason} -> - ignore - end, - reply(From, Res, State); - - {system, From, Msg} -> - dbg_out("~p got {system, ~p, ~p}~n", [?MODULE, From, Msg]), - sys:handle_system_msg(Msg, From, Sup, ?MODULE, [], State); - - Msg -> - verbose("** ERROR ** ~p got unexpected message: ~p~n", [?MODULE, Msg]), - doit_loop(State) - end. - -do_sync_dirty(From, Tid, Commit, _Tab) -> - ?eval_debug_fun({?MODULE, sync_dirty, pre}, [{tid, Tid}]), - Res = (catch do_dirty(Tid, Commit)), - ?eval_debug_fun({?MODULE, sync_dirty, post}, [{tid, Tid}]), - From ! {?MODULE, node(), {dirty_res, Res}}. - -do_async_dirty(Tid, Commit, _Tab) -> - ?eval_debug_fun({?MODULE, async_dirty, pre}, [{tid, Tid}]), - catch do_dirty(Tid, Commit), - ?eval_debug_fun({?MODULE, async_dirty, post}, [{tid, Tid}]). - -%% Process items in fifo order -process_dirty_queue(Tab, [Item | Queue]) -> - Queue2 = process_dirty_queue(Tab, Queue), - case Item of - {async_dirty, Tid, Commit, Tab} -> - do_async_dirty(Tid, Commit, Tab), - Queue2; - {sync_dirty, From, Tid, Commit, Tab} -> - do_sync_dirty(From, Tid, Commit, Tab), - Queue2; - {Tab, unblock_me, From} -> - reply(From, unblocked), - Queue2; - _ -> - [Item | Queue2] - end; -process_dirty_queue(_Tab, []) -> - []. - -prepare_pending_coordinators([{Tid, [Store | _Etabs]} | Coords], IgnoreNew) -> - case catch ?ets_lookup(Store, pending) of - [] -> - prepare_pending_coordinators(Coords, IgnoreNew); - [Pending] -> - case lists:member(Tid, IgnoreNew) of - false -> - mnesia_checkpoint:tm_enter_pending(Pending); - true -> - ignore - end, - prepare_pending_coordinators(Coords, IgnoreNew); - {'EXIT', _} -> - prepare_pending_coordinators(Coords, IgnoreNew) - end; -prepare_pending_coordinators([], _IgnoreNew) -> - ok. - -prepare_pending_participants([Part | Parts], IgnoreNew) -> - Tid = Part#participant.tid, - D = Part#participant.disc_nodes, - R = Part#participant.ram_nodes, - case lists:member(Tid, IgnoreNew) of - false -> - mnesia_checkpoint:tm_enter_pending(Tid, D, R); - true -> - ignore - end, - prepare_pending_participants(Parts, IgnoreNew); -prepare_pending_participants([], _IgnoreNew) -> - ok. - -handle_exit(Pid, Reason, State) when node(Pid) /= node() -> - %% We got exit from a remote fool - dbg_out("~p got remote EXIT from unknown ~p~n", - [?MODULE, {Pid, Reason}]), - doit_loop(State); - -handle_exit(Pid, _Reason, State) when Pid == State#state.supervisor -> - %% Our supervisor has died, time to stop - do_stop(State); - -handle_exit(Pid, Reason, State) -> - %% Check if it is a coordinator - case pid_search_delete(Pid, State#state.coordinators) of - {none, _} -> - %% Check if it is a participant - case mnesia_lib:key_search_delete(Pid, #participant.pid, State#state.participants) of - {none, _} -> - %% We got exit from a local fool - verbose("** ERROR ** ~p got local EXIT from unknown process: ~p~n", - [?MODULE, {Pid, Reason}]), - doit_loop(State); - - {P, RestP} when record(P, participant) -> - fatal("Participant ~p in transaction ~p died ~p~n", - [P#participant.pid, P#participant.tid, Reason]), - doit_loop(State#state{participants = RestP}) - end; - - {{Tid, Etabs}, RestC} -> - %% A local coordinator has died and - %% we must determine the outcome of the - %% transaction and tell mnesia_tm on the - %% other nodes about it and then recover - %% locally. - recover_coordinator(Tid, Etabs), - doit_loop(State#state{coordinators = RestC}) - end. - -recover_coordinator(Tid, Etabs) -> - verbose("Coordinator ~p in transaction ~p died.~n", [Tid#tid.pid, Tid]), - - Store = hd(Etabs), - CheckNodes = get_nodes(Store), - TellNodes = CheckNodes -- [node()], - case catch arrange(Tid, Store, async) of - {'EXIT', Reason} -> - dbg_out("Recovery of coordinator ~p failed:~n", [Tid, Reason]), - Protocol = asym_trans, - tell_outcome(Tid, Protocol, node(), CheckNodes, TellNodes); - {_N, Prep} -> - %% Tell the participants about the outcome - Protocol = Prep#prep.protocol, - Outcome = tell_outcome(Tid, Protocol, node(), CheckNodes, TellNodes), - - %% Recover locally - CR = Prep#prep.records, - {DiscNs, RamNs} = commit_nodes(CR, [], []), - {value, Local} = lists:keysearch(node(), #commit.node, CR), - - ?eval_debug_fun({?MODULE, recover_coordinator, pre}, - [{tid, Tid}, {outcome, Outcome}, {prot, Protocol}]), - recover_coordinator(Tid, Protocol, Outcome, Local, DiscNs, RamNs), - ?eval_debug_fun({?MODULE, recover_coordinator, post}, - [{tid, Tid}, {outcome, Outcome}, {prot, Protocol}]) - - end, - erase_ets_tabs(Etabs), - transaction_terminated(Tid), - mnesia_locker:release_tid(Tid). - -recover_coordinator(Tid, sym_trans, committed, Local, _, _) -> - mnesia_recover:note_decision(Tid, committed), - do_dirty(Tid, Local); -recover_coordinator(Tid, sym_trans, aborted, _Local, _, _) -> - mnesia_recover:note_decision(Tid, aborted); -recover_coordinator(Tid, sync_sym_trans, committed, Local, _, _) -> - mnesia_recover:note_decision(Tid, committed), - do_dirty(Tid, Local); -recover_coordinator(Tid, sync_sym_trans, aborted, _Local, _, _) -> - mnesia_recover:note_decision(Tid, aborted); - -recover_coordinator(Tid, asym_trans, committed, Local, DiscNs, RamNs) -> - D = #decision{tid = Tid, outcome = committed, - disc_nodes = DiscNs, ram_nodes = RamNs}, - mnesia_recover:log_decision(D), - do_commit(Tid, Local); -recover_coordinator(Tid, asym_trans, aborted, Local, DiscNs, RamNs) -> - D = #decision{tid = Tid, outcome = aborted, - disc_nodes = DiscNs, ram_nodes = RamNs}, - mnesia_recover:log_decision(D), - do_abort(Tid, Local). - -restore_stores([{Tid, Etstabs} | Tail], Tid, Store) -> - Remaining = lists:delete(Store, Etstabs), - erase_ets_tabs(Remaining), - [{Tid, [Store]} | Tail]; -restore_stores([H | T], Tid, Store) -> - [H | restore_stores(T, Tid, Store)]. -%% No NIL case on purpose - -add_coord_store([{Tid, Stores} | Coordinators], Tid, Etab) -> - [{Tid, [Etab | Stores]} | Coordinators]; -add_coord_store([H | T], Tid, Etab) -> - [H | add_coord_store(T, Tid, Etab)]. -%% no NIL case on purpose - -del_coord_store([{Tid, Stores} | Coordinators], Tid, Current, Obsolete) -> - Rest = - case Stores of - [Obsolete, Current | Tail] -> Tail; - [Current, Obsolete | Tail] -> Tail - end, - ?ets_delete_table(Obsolete), - [{Tid, [Current | Rest]} | Coordinators]; -del_coord_store([H | T], Tid, Current, Obsolete) -> - [H | del_coord_store(T, Tid, Current, Obsolete)]. -%% no NIL case on purpose - -erase_ets_tabs([H | T]) -> - ?ets_delete_table(H), - erase_ets_tabs(T); -erase_ets_tabs([]) -> - ok. - -%% Deletes a pid from a list of participants -%% or from a list of coordinators and returns -%% {none, All} or {Tr, Rest} -pid_search_delete(Pid, Trs) -> - pid_search_delete(Pid, Trs, none, []). -pid_search_delete(Pid, [Tr = {Tid, _Ts} | Trs], _Val, Ack) when Tid#tid.pid == Pid -> - pid_search_delete(Pid, Trs, Tr, Ack); -pid_search_delete(Pid, [Tr | Trs], Val, Ack) -> - pid_search_delete(Pid, Trs, Val, [Tr | Ack]); - -pid_search_delete(_Pid, [], Val, Ack) -> - {Val, Ack}. - -%% When TM gets an EXIT sig, we must also check to see -%% if the crashing transaction is in the Participant list -%% -%% search_participant_for_pid([Participant | Tail], Pid) -> -%% Tid = Participant#participant.tid, -%% if -%% Tid#tid.pid == Pid -> -%% {coordinator, Participant}; -%% Participant#participant.pid == Pid -> -%% {participant, Participant}; -%% true -> -%% search_participant_for_pid(Tail, Pid) -%% end; -%% search_participant_for_pid([], _) -> -%% fool. - -transaction_terminated(Tid) -> - mnesia_checkpoint:tm_exit_pending(Tid), - Pid = Tid#tid.pid, - if - node(Pid) == node() -> - unlink(Pid); - true -> %% Do the Lamport thing here - mnesia_recover:sync_trans_tid_serial(Tid) - end. - -non_transaction(OldState, Fun, Args, ActivityKind, Mod) -> - Id = {ActivityKind, self()}, - NewState = {Mod, Id, non_transaction}, - put(mnesia_activity_state, NewState), - %% I Want something uniqe here, references are expensive - Ref = mNeSia_nOn_TrAnSacTioN, - RefRes = (catch {Ref, apply(Fun, Args)}), - case OldState of - undefined -> erase(mnesia_activity_state); - _ -> put(mnesia_activity_state, OldState) - end, - case RefRes of - {Ref, Res} -> - case Res of - {'EXIT', Reason} -> exit(Reason); - {aborted, Reason} -> mnesia:abort(Reason); - _ -> Res - end; - {'EXIT', Reason} -> - exit(Reason); - Throw -> - throw(Throw) - end. - -transaction(OldTidTs, Fun, Args, Retries, Mod, Type) -> - Factor = 1, - case OldTidTs of - undefined -> % Outer - execute_outer(Mod, Fun, Args, Factor, Retries, Type); - {_OldMod, Tid, Ts} -> % Nested - execute_inner(Mod, Tid, Ts, Fun, Args, Factor, Retries, Type); - _ -> % Bad nesting - {aborted, nested_transaction} - end. - -execute_outer(Mod, Fun, Args, Factor, Retries, Type) -> - case req(start_outer) of - {error, Reason} -> - {aborted, Reason}; - {new_tid, Tid, Store} -> - Ts = #tidstore{store = Store}, - NewTidTs = {Mod, Tid, Ts}, - put(mnesia_activity_state, NewTidTs), - execute_transaction(Fun, Args, Factor, Retries, Type) - end. - -execute_inner(Mod, Tid, Ts, Fun, Args, Factor, Retries, Type) -> - case req({add_store, Tid}) of - {error, Reason} -> - {aborted, Reason}; - {new_store, Ets} -> - copy_ets(Ts#tidstore.store, Ets), - Up = [Ts#tidstore.store | Ts#tidstore.up_stores], - NewTs = Ts#tidstore{level = 1 + Ts#tidstore.level, - store = Ets, - up_stores = Up}, - NewTidTs = {Mod, Tid, NewTs}, - put(mnesia_activity_state, NewTidTs), - execute_transaction(Fun, Args, Factor, Retries, Type) - end. - -copy_ets(From, To) -> - do_copy_ets(?ets_first(From), From, To). -do_copy_ets('$end_of_table', _,_) -> - ok; -do_copy_ets(K, From, To) -> - Objs = ?ets_lookup(From, K), - insert_objs(Objs, To), - do_copy_ets(?ets_next(From, K), From, To). - -insert_objs([H|T], Tab) -> - ?ets_insert(Tab, H), - insert_objs(T, Tab); -insert_objs([], _Tab) -> - ok. - -execute_transaction(Fun, Args, Factor, Retries, Type) -> - case catch apply_fun(Fun, Args, Type) of - {'EXIT', Reason} -> - check_exit(Fun, Args, Factor, Retries, Reason, Type); - {'atomic', Value} -> - mnesia_lib:incr_counter(trans_commits), - erase(mnesia_activity_state), - %% no need to clear locks, already done by commit ... - %% Flush any un processed mnesia_down messages we might have - flush_downs(), - {'atomic', Value}; - {nested_atomic, Value} -> - mnesia_lib:incr_counter(trans_commits), - {'atomic', Value}; - Value -> %% User called throw - Reason = {aborted, {throw, Value}}, - return_abort(Fun, Args, Reason) - end. - -apply_fun(Fun, Args, Type) -> - Result = apply(Fun, Args), - case t_commit(Type) of - do_commit -> - {'atomic', Result}; - do_commit_nested -> - {nested_atomic, Result}; - {do_abort, {aborted, Reason}} -> - {'EXIT', {aborted, Reason}}; - {do_abort, Reason} -> - {'EXIT', {aborted, Reason}} - end. - -check_exit(Fun, Args, Factor, Retries, Reason, Type) -> - case Reason of - {aborted, C} when record(C, cyclic) -> - maybe_restart(Fun, Args, Factor, Retries, Type, C); - {aborted, {node_not_running, N}} -> - maybe_restart(Fun, Args, Factor, Retries, Type, {node_not_running, N}); - {aborted, {bad_commit, N}} -> - maybe_restart(Fun, Args, Factor, Retries, Type, {bad_commit, N}); - _ -> - return_abort(Fun, Args, Reason) - end. - -maybe_restart(Fun, Args, Factor, Retries, Type, Why) -> - {Mod, Tid, Ts} = get(mnesia_activity_state), - case try_again(Retries) of - yes when Ts#tidstore.level == 1 -> - restart(Mod, Tid, Ts, Fun, Args, Factor, Retries, Type, Why); - yes -> - return_abort(Fun, Args, Why); - no -> - return_abort(Fun, Args, {aborted, nomore}) - end. - -try_again(infinity) -> yes; -try_again(X) when number(X) , X > 1 -> yes; -try_again(_) -> no. - -%% We can only restart toplevel transactions. -%% If a deadlock situation occurs in a nested transaction -%% The whole thing including all nested transactions need to be -%% restarted. The stack is thus popped by a consequtive series of -%% exit({aborted, #cyclic{}}) calls - -restart(Mod, Tid, Ts, Fun, Args, Factor0, Retries0, Type, Why) -> - mnesia_lib:incr_counter(trans_restarts), - Retries = decr(Retries0), - case Why of - {bad_commit, _N} -> - return_abort(Fun, Args, Why), - Factor = 1, - SleepTime = mnesia_lib:random_time(Factor, Tid#tid.counter), - dbg_out("Restarting transaction ~w: in ~wms ~w~n", [Tid, SleepTime, Why]), - timer:sleep(SleepTime), - execute_outer(Mod, Fun, Args, Factor, Retries, Type); - {node_not_running, _N} -> %% Avoids hanging in receive_release_tid_ack - return_abort(Fun, Args, Why), - Factor = 1, - SleepTime = mnesia_lib:random_time(Factor, Tid#tid.counter), - dbg_out("Restarting transaction ~w: in ~wms ~w~n", [Tid, SleepTime, Why]), - timer:sleep(SleepTime), - execute_outer(Mod, Fun, Args, Factor, Retries, Type); - _ -> - SleepTime = mnesia_lib:random_time(Factor0, Tid#tid.counter), - dbg_out("Restarting transaction ~w: in ~wms ~w~n", [Tid, SleepTime, Why]), - - if - Factor0 /= 10 -> - ignore; - true -> - %% Our serial may be much larger than other nodes ditto - AllNodes = val({current, db_nodes}), - verbose("Sync serial ~p~n", [Tid]), - rpc:abcast(AllNodes, ?MODULE, {sync_trans_serial, Tid}) - end, - intercept_friends(Tid, Ts), - Store = Ts#tidstore.store, - Nodes = get_nodes(Store), - ?MODULE ! {self(), {restart, Tid, Store}}, - mnesia_locker:send_release_tid(Nodes, Tid), - timer:sleep(SleepTime), - mnesia_locker:receive_release_tid_acc(Nodes, Tid), - case rec() of - {restarted, Tid} -> - execute_transaction(Fun, Args, Factor0 + 1, - Retries, Type); - {error, Reason} -> - mnesia:abort(Reason) - end - end. - -decr(infinity) -> infinity; -decr(X) when integer(X), X > 1 -> X - 1; -decr(_X) -> 0. - -return_abort(Fun, Args, Reason) -> - {Mod, Tid, Ts} = get(mnesia_activity_state), - OldStore = Ts#tidstore.store, - Nodes = get_nodes(OldStore), - intercept_friends(Tid, Ts), - catch mnesia_lib:incr_counter(trans_failures), - Level = Ts#tidstore.level, - if - Level == 1 -> - mnesia_locker:async_release_tid(Nodes, Tid), - ?MODULE ! {delete_transaction, Tid}, - erase(mnesia_activity_state), - dbg_out("Transaction ~p calling ~p with ~p, failed ~p~n", - [Tid, Fun, Args, Reason]), - flush_downs(), - {aborted, mnesia_lib:fix_error(Reason)}; - true -> - %% Nested transaction - [NewStore | Tail] = Ts#tidstore.up_stores, - req({del_store, Tid, NewStore, OldStore, true}), - Ts2 = Ts#tidstore{store = NewStore, - up_stores = Tail, - level = Level - 1}, - NewTidTs = {Mod, Tid, Ts2}, - put(mnesia_activity_state, NewTidTs), - case Reason of - #cyclic{} -> - exit({aborted, Reason}); - {node_not_running, _N} -> - exit({aborted, Reason}); - {bad_commit, _N}-> - exit({aborted, Reason}); - _ -> - {aborted, mnesia_lib:fix_error(Reason)} - end - end. - -flush_downs() -> - receive - {?MODULE, _, _} -> flush_downs(); % Votes - {mnesia_down, _} -> flush_downs() - after 0 -> flushed - end. - -put_activity_id(undefined) -> - erase_activity_id(); -put_activity_id({Mod, Tid, Ts}) when record(Tid, tid), record(Ts, tidstore) -> - flush_downs(), - Store = Ts#tidstore.store, - ?ets_insert(Store, {friends, self()}), - NewTidTs = {Mod, Tid, Ts}, - put(mnesia_activity_state, NewTidTs); -put_activity_id(SimpleState) -> - put(mnesia_activity_state, SimpleState). - -erase_activity_id() -> - flush_downs(), - erase(mnesia_activity_state). - -get_nodes(Store) -> - case catch ?ets_lookup_element(Store, nodes, 2) of - {'EXIT', _} -> [node()]; - Nodes -> Nodes - end. - -get_friends(Store) -> - case catch ?ets_lookup_element(Store, friends, 2) of - {'EXIT', _} -> []; - Friends -> Friends - end. - -opt_propagate_store(_Current, _Obsolete, false) -> - ok; -opt_propagate_store(Current, Obsolete, true) -> - propagate_store(Current, nodes, get_nodes(Obsolete)), - propagate_store(Current, friends, get_friends(Obsolete)). - -propagate_store(Store, Var, [Val | Vals]) -> - ?ets_insert(Store, {Var, Val}), - propagate_store(Store, Var, Vals); -propagate_store(_Store, _Var, []) -> - ok. - -%% Tell all processes that are cooperating with the current transaction -intercept_friends(_Tid, Ts) -> - Friends = get_friends(Ts#tidstore.store), - Message = {activity_ended, undefined, self()}, - intercept_best_friend(Friends, Message). - -intercept_best_friend([], _Message) -> - ok; -intercept_best_friend([Pid | _], Message) -> - Pid ! Message, - wait_for_best_friend(Pid, 0). - -wait_for_best_friend(Pid, Timeout) -> - receive - {'EXIT', Pid, _} -> ok; - {activity_ended, _, Pid} -> ok - after Timeout -> - case my_process_is_alive(Pid) of - true -> wait_for_best_friend(Pid, 1000); - false -> ok - end - end. - -my_process_is_alive(Pid) -> - case catch erlang:is_process_alive(Pid) of % New BIF in R5 - true -> - true; - false -> - false; - {'EXIT', _} -> % Pre R5 backward compatibility - case process_info(Pid, message_queue_len) of - undefined -> false; - _ -> true - end - end. - -dirty(Protocol, Item) -> - {{Tab, Key}, _Val, _Op} = Item, - Tid = {dirty, self()}, - Prep = prepare_items(Tid, Tab, Key, [Item], #prep{protocol= Protocol}), - CR = Prep#prep.records, - case Protocol of - async_dirty -> - %% Send commit records to the other involved nodes, - %% but do only wait for one node to complete. - %% Preferrably, the local node if possible. - - ReadNode = val({Tab, where_to_read}), - {WaitFor, FirstRes} = async_send_dirty(Tid, CR, Tab, ReadNode), - rec_dirty(WaitFor, FirstRes); - - sync_dirty -> - %% Send commit records to the other involved nodes, - %% and wait for all nodes to complete - {WaitFor, FirstRes} = sync_send_dirty(Tid, CR, Tab, []), - rec_dirty(WaitFor, FirstRes); - _ -> - mnesia:abort({bad_activity, Protocol}) - end. - -%% This is the commit function, The first thing it does, -%% is to find out which nodes that have been participating -%% in this particular transaction, all of the mnesia_locker:lock* -%% functions insert the names of the nodes where it aquires locks -%% into the local shadow Store -%% This function exacutes in the context of the user process -t_commit(Type) -> - {Mod, Tid, Ts} = get(mnesia_activity_state), - Store = Ts#tidstore.store, - if - Ts#tidstore.level == 1 -> - intercept_friends(Tid, Ts), - %% N is number of updates - case arrange(Tid, Store, Type) of - {N, Prep} when N > 0 -> - multi_commit(Prep#prep.protocol, - Tid, Prep#prep.records, Store); - {0, Prep} -> - multi_commit(read_only, Tid, Prep#prep.records, Store) - end; - true -> - %% nested commit - Level = Ts#tidstore.level, - [Obsolete | Tail] = Ts#tidstore.up_stores, - req({del_store, Tid, Store, Obsolete, false}), - NewTs = Ts#tidstore{store = Store, - up_stores = Tail, - level = Level - 1}, - NewTidTs = {Mod, Tid, NewTs}, - put(mnesia_activity_state, NewTidTs), - do_commit_nested - end. - -%% This function arranges for all objects we shall write in S to be -%% in a list of {Node, CommitRecord} -%% Important function for the performance of mnesia. - -arrange(Tid, Store, Type) -> - %% The local node is always included - Nodes = get_nodes(Store), - Recs = prep_recs(Nodes, []), - Key = ?ets_first(Store), - N = 0, - Prep = - case Type of - async -> #prep{protocol = sym_trans, records = Recs}; - sync -> #prep{protocol = sync_sym_trans, records = Recs} - end, - case catch do_arrange(Tid, Store, Key, Prep, N) of - {'EXIT', Reason} -> - dbg_out("do_arrange failed ~p ~p~n", [Reason, Tid]), - case Reason of - {aborted, R} -> - mnesia:abort(R); - _ -> - mnesia:abort(Reason) - end; - {New, Prepared} -> - {New, Prepared#prep{records = reverse(Prepared#prep.records)}} - end. - -reverse([]) -> - []; -reverse([H|R]) when record(H, commit) -> - [ - H#commit{ - ram_copies = lists:reverse(H#commit.ram_copies), - disc_copies = lists:reverse(H#commit.disc_copies), - disc_only_copies = lists:reverse(H#commit.disc_only_copies), - snmp = lists:reverse(H#commit.snmp) - } - | reverse(R)]. - -prep_recs([N | Nodes], Recs) -> - prep_recs(Nodes, [#commit{decision = presume_commit, node = N} | Recs]); -prep_recs([], Recs) -> - Recs. - -%% storage_types is a list of {Node, Storage} tuples -%% where each tuple represents an active replica -do_arrange(Tid, Store, {Tab, Key}, Prep, N) -> - Oid = {Tab, Key}, - Items = ?ets_lookup(Store, Oid), %% Store is a bag - P2 = prepare_items(Tid, Tab, Key, Items, Prep), - do_arrange(Tid, Store, ?ets_next(Store, Oid), P2, N + 1); -do_arrange(Tid, Store, SchemaKey, Prep, N) when SchemaKey == op -> - Items = ?ets_lookup(Store, SchemaKey), %% Store is a bag - P2 = prepare_schema_items(Tid, Items, Prep), - do_arrange(Tid, Store, ?ets_next(Store, SchemaKey), P2, N + 1); -do_arrange(Tid, Store, RestoreKey, Prep, N) when RestoreKey == restore_op -> - [{restore_op, R}] = ?ets_lookup(Store, RestoreKey), - Fun = fun({Tab, Key}, CommitRecs, _RecName, Where, Snmp) -> - Item = [{{Tab, Key}, {Tab, Key}, delete}], - do_prepare_items(Tid, Tab, Key, Where, Snmp, Item, CommitRecs); - (BupRec, CommitRecs, RecName, Where, Snmp) -> - Tab = element(1, BupRec), - Key = element(2, BupRec), - Item = - if - Tab == RecName -> - [{{Tab, Key}, BupRec, write}]; - true -> - BupRec2 = setelement(1, BupRec, RecName), - [{{Tab, Key}, BupRec2, write}] - end, - do_prepare_items(Tid, Tab, Key, Where, Snmp, Item, CommitRecs) - end, - Recs2 = mnesia_schema:arrange_restore(R, Fun, Prep#prep.records), - P2 = Prep#prep{protocol = asym_trans, records = Recs2}, - do_arrange(Tid, Store, ?ets_next(Store, RestoreKey), P2, N + 1); -do_arrange(_Tid, _Store, '$end_of_table', Prep, N) -> - {N, Prep}; -do_arrange(Tid, Store, IgnoredKey, Prep, N) -> %% locks, nodes ... local atoms... - do_arrange(Tid, Store, ?ets_next(Store, IgnoredKey), Prep, N). - -%% Returns a prep record with all items in reverse order -prepare_schema_items(Tid, Items, Prep) -> - Types = [{N, schema_ops} || N <- val({current, db_nodes})], - Recs = prepare_nodes(Tid, Types, Items, Prep#prep.records, schema), - Prep#prep{protocol = asym_trans, records = Recs}. - -%% Returns a prep record with all items in reverse order -prepare_items(Tid, Tab, Key, Items, Prep) when Prep#prep.prev_tab == Tab -> - Types = Prep#prep.prev_types, - Snmp = Prep#prep.prev_snmp, - Recs = Prep#prep.records, - Recs2 = do_prepare_items(Tid, Tab, Key, Types, Snmp, Items, Recs), - Prep#prep{records = Recs2}; - -prepare_items(Tid, Tab, Key, Items, Prep) -> - Types = val({Tab, where_to_commit}), - case Types of - [] -> mnesia:abort({no_exists, Tab}); - {blocked, _} -> - unblocked = req({unblock_me, Tab}), - prepare_items(Tid, Tab, Key, Items, Prep); - _ -> - Snmp = val({Tab, snmp}), - Recs2 = do_prepare_items(Tid, Tab, Key, Types, - Snmp, Items, Prep#prep.records), - Prep2 = Prep#prep{records = Recs2, prev_tab = Tab, - prev_types = Types, prev_snmp = Snmp}, - check_prep(Prep2, Types) - end. - -do_prepare_items(Tid, Tab, Key, Types, Snmp, Items, Recs) -> - Recs2 = prepare_snmp(Tid, Tab, Key, Types, Snmp, Items, Recs), % May exit - prepare_nodes(Tid, Types, Items, Recs2, normal). - -prepare_snmp(Tab, Key, Items) -> - case val({Tab, snmp}) of - [] -> - []; - Ustruct when Key /= '_' -> - {_Oid, _Val, Op} = hd(Items), - %% Still making snmp oid (not used) because we want to catch errors here - %% And also it keeps backwards comp. with old nodes. - SnmpOid = mnesia_snmp_hook:key_to_oid(Tab, Key, Ustruct), % May exit - [{Op, Tab, Key, SnmpOid}]; - _ -> - [{clear_table, Tab}] - end. - -prepare_snmp(_Tid, _Tab, _Key, _Types, [], _Items, Recs) -> - Recs; - -prepare_snmp(Tid, Tab, Key, Types, Us, Items, Recs) -> - if Key /= '_' -> - {_Oid, _Val, Op} = hd(Items), - SnmpOid = mnesia_snmp_hook:key_to_oid(Tab, Key, Us), % May exit - prepare_nodes(Tid, Types, [{Op, Tab, Key, SnmpOid}], Recs, snmp); - Key == '_' -> - prepare_nodes(Tid, Types, [{clear_table, Tab}], Recs, snmp) - end. - -check_prep(Prep, Types) when Prep#prep.types == Types -> - Prep; -check_prep(Prep, Types) when Prep#prep.types == undefined -> - Prep#prep{types = Types}; -check_prep(Prep, _Types) -> - Prep#prep{protocol = asym_trans}. - -%% Returns a list of commit records -prepare_nodes(Tid, [{Node, Storage} | Rest], Items, C, Kind) -> - {Rec, C2} = pick_node(Tid, Node, C, []), - Rec2 = prepare_node(Node, Storage, Items, Rec, Kind), - [Rec2 | prepare_nodes(Tid, Rest, Items, C2, Kind)]; -prepare_nodes(_Tid, [], _Items, CommitRecords, _Kind) -> - CommitRecords. - -pick_node(Tid, Node, [Rec | Rest], Done) -> - if - Rec#commit.node == Node -> - {Rec, Done ++ Rest}; - true -> - pick_node(Tid, Node, Rest, [Rec | Done]) - end; -pick_node(_Tid, Node, [], Done) -> - {#commit{decision = presume_commit, node = Node}, Done}. - -prepare_node(Node, Storage, [Item | Items], Rec, Kind) when Kind == snmp -> - Rec2 = Rec#commit{snmp = [Item | Rec#commit.snmp]}, - prepare_node(Node, Storage, Items, Rec2, Kind); -prepare_node(Node, Storage, [Item | Items], Rec, Kind) when Kind /= schema -> - Rec2 = - case Storage of - ram_copies -> - Rec#commit{ram_copies = [Item | Rec#commit.ram_copies]}; - disc_copies -> - Rec#commit{disc_copies = [Item | Rec#commit.disc_copies]}; - disc_only_copies -> - Rec#commit{disc_only_copies = - [Item | Rec#commit.disc_only_copies]} - end, - prepare_node(Node, Storage, Items, Rec2, Kind); -prepare_node(_Node, _Storage, Items, Rec, Kind) - when Kind == schema, Rec#commit.schema_ops == [] -> - Rec#commit{schema_ops = Items}; -prepare_node(_Node, _Storage, [], Rec, _Kind) -> - Rec. - -%% multi_commit((Protocol, Tid, CommitRecords, Store) -%% Local work is always performed in users process -multi_commit(read_only, Tid, CR, _Store) -> - %% This featherweight commit protocol is used when no - %% updates has been performed in the transaction. - - {DiscNs, RamNs} = commit_nodes(CR, [], []), - Msg = {Tid, simple_commit}, - rpc:abcast(DiscNs -- [node()], ?MODULE, Msg), - rpc:abcast(RamNs -- [node()], ?MODULE, Msg), - mnesia_recover:note_decision(Tid, committed), - mnesia_locker:release_tid(Tid), - ?MODULE ! {delete_transaction, Tid}, - do_commit; - -multi_commit(sym_trans, Tid, CR, Store) -> - %% This lightweight commit protocol is used when all - %% the involved tables are replicated symetrically. - %% Their storage types must match on each node. - %% - %% 1 Ask the other involved nodes if they want to commit - %% All involved nodes votes yes if they are up - %% 2a Somebody has voted no - %% Tell all yes voters to do_abort - %% 2b Everybody has voted yes - %% Tell everybody to do_commit. I.e. that they should - %% prepare the commit, log the commit record and - %% perform the updates. - %% - %% The outcome is kept 3 minutes in the transient decision table. - %% - %% Recovery: - %% If somebody dies before the coordinator has - %% broadcasted do_commit, the transaction is aborted. - %% - %% If a participant dies, the table load algorithm - %% ensures that the contents of the involved tables - %% are picked from another node. - %% - %% If the coordinator dies, each participants checks - %% the outcome with all the others. If all are uncertain - %% about the outcome, the transaction is aborted. If - %% somebody knows the outcome the others will follow. - - {DiscNs, RamNs} = commit_nodes(CR, [], []), - Pending = mnesia_checkpoint:tm_enter_pending(Tid, DiscNs, RamNs), - ?ets_insert(Store, Pending), - - {WaitFor, Local} = ask_commit(sym_trans, Tid, CR, DiscNs, RamNs), - {Outcome, []} = rec_all(WaitFor, Tid, do_commit, []), - ?eval_debug_fun({?MODULE, multi_commit_sym}, - [{tid, Tid}, {outcome, Outcome}]), - rpc:abcast(DiscNs -- [node()], ?MODULE, {Tid, Outcome}), - rpc:abcast(RamNs -- [node()], ?MODULE, {Tid, Outcome}), - case Outcome of - do_commit -> - mnesia_recover:note_decision(Tid, committed), - do_dirty(Tid, Local), - mnesia_locker:release_tid(Tid), - ?MODULE ! {delete_transaction, Tid}; - {do_abort, _Reason} -> - mnesia_recover:note_decision(Tid, aborted) - end, - ?eval_debug_fun({?MODULE, multi_commit_sym, post}, - [{tid, Tid}, {outcome, Outcome}]), - Outcome; - -multi_commit(sync_sym_trans, Tid, CR, Store) -> - %% This protocol is the same as sym_trans except that it - %% uses syncronized calls to disk_log and syncronized commits - %% when several nodes are involved. - - {DiscNs, RamNs} = commit_nodes(CR, [], []), - Pending = mnesia_checkpoint:tm_enter_pending(Tid, DiscNs, RamNs), - ?ets_insert(Store, Pending), - - {WaitFor, Local} = ask_commit(sync_sym_trans, Tid, CR, DiscNs, RamNs), - {Outcome, []} = rec_all(WaitFor, Tid, do_commit, []), - ?eval_debug_fun({?MODULE, multi_commit_sym_sync}, - [{tid, Tid}, {outcome, Outcome}]), - rpc:abcast(DiscNs -- [node()], ?MODULE, {Tid, Outcome}), - rpc:abcast(RamNs -- [node()], ?MODULE, {Tid, Outcome}), - case Outcome of - do_commit -> - mnesia_recover:note_decision(Tid, committed), - mnesia_log:slog(Local), - do_commit(Tid, Local), - %% Just wait for completion result is ignore. - rec_all(WaitFor, Tid, ignore, []), - mnesia_locker:release_tid(Tid), - ?MODULE ! {delete_transaction, Tid}; - {do_abort, _Reason} -> - mnesia_recover:note_decision(Tid, aborted) - end, - ?eval_debug_fun({?MODULE, multi_commit_sym, post}, - [{tid, Tid}, {outcome, Outcome}]), - Outcome; - -multi_commit(asym_trans, Tid, CR, Store) -> - %% This more expensive commit protocol is used when - %% table definitions are changed (schema transactions). - %% It is also used when the involved tables are - %% replicated asymetrically. If the storage type differs - %% on at least one node this protocol is used. - %% - %% 1 Ask the other involved nodes if they want to commit. - %% All involved nodes prepares the commit, logs a presume_abort - %% commit record and votes yes or no depending of the - %% outcome of the prepare. The preparation is also performed - %% by the coordinator. - %% - %% 2a Somebody has died or voted no - %% Tell all yes voters to do_abort - %% 2b Everybody has voted yes - %% Put a unclear marker in the log. - %% Tell the others to pre_commit. I.e. that they should - %% put a unclear marker in the log and reply - %% acc_pre_commit when they are done. - %% - %% 3a Somebody died - %% Tell the remaining participants to do_abort - %% 3b Everybody has replied acc_pre_commit - %% Tell everybody to committed. I.e that they should - %% put a committed marker in the log, perform the updates - %% and reply done_commit when they are done. The coordinator - %% must wait with putting his committed marker inte the log - %% until the committed has been sent to all the others. - %% Then he performs local commit before collecting replies. - %% - %% 4 Everybody has either died or replied done_commit - %% Return to the caller. - %% - %% Recovery: - %% If the coordinator dies, the participants (and - %% the coordinator when he starts again) must do - %% the following: - %% - %% If we have no unclear marker in the log we may - %% safely abort, since we know that nobody may have - %% decided to commit yet. - %% - %% If we have a committed marker in the log we may - %% safely commit since we know that everybody else - %% also will come to this conclusion. - %% - %% If we have a unclear marker but no committed - %% in the log we are uncertain about the real outcome - %% of the transaction and must ask the others before - %% we can decide what to do. If someone knows the - %% outcome we will do the same. If nobody knows, we - %% will wait for the remaining involved nodes to come - %% up. When all involved nodes are up and uncertain, - %% we decide to commit (first put a committed marker - %% in the log, then do the updates). - - D = #decision{tid = Tid, outcome = presume_abort}, - {D2, CR2} = commit_decision(D, CR, [], []), - DiscNs = D2#decision.disc_nodes, - RamNs = D2#decision.ram_nodes, - Pending = mnesia_checkpoint:tm_enter_pending(Tid, DiscNs, RamNs), - ?ets_insert(Store, Pending), - {WaitFor, Local} = ask_commit(asym_trans, Tid, CR2, DiscNs, RamNs), - SchemaPrep = (catch mnesia_schema:prepare_commit(Tid, Local, {coord, WaitFor})), - {Votes, Pids} = rec_all(WaitFor, Tid, do_commit, []), - - ?eval_debug_fun({?MODULE, multi_commit_asym_got_votes}, - [{tid, Tid}, {votes, Votes}]), - case Votes of - do_commit -> - case SchemaPrep of - {_Modified, C, DumperMode} when record(C, commit) -> - mnesia_log:log(C), % C is not a binary - ?eval_debug_fun({?MODULE, multi_commit_asym_log_commit_rec}, - [{tid, Tid}]), - - D3 = C#commit.decision, - D4 = D3#decision{outcome = unclear}, - mnesia_recover:log_decision(D4), - ?eval_debug_fun({?MODULE, multi_commit_asym_log_commit_dec}, - [{tid, Tid}]), - tell_participants(Pids, {Tid, pre_commit}), - %% Now we are uncertain and we do not know - %% if all participants have logged that - %% they are uncertain or not - rec_acc_pre_commit(Pids, Tid, Store, C, - do_commit, DumperMode, [], []); - {'EXIT', Reason} -> - %% The others have logged the commit - %% record but they are not uncertain - mnesia_recover:note_decision(Tid, aborted), - ?eval_debug_fun({?MODULE, multi_commit_asym_prepare_exit}, - [{tid, Tid}]), - tell_participants(Pids, {Tid, {do_abort, Reason}}), - do_abort(Tid, Local), - {do_abort, Reason} - end; - - {do_abort, Reason} -> - %% The others have logged the commit - %% record but they are not uncertain - mnesia_recover:note_decision(Tid, aborted), - ?eval_debug_fun({?MODULE, multi_commit_asym_do_abort}, [{tid, Tid}]), - tell_participants(Pids, {Tid, {do_abort, Reason}}), - do_abort(Tid, Local), - {do_abort, Reason} - end. - -%% Returns do_commit or {do_abort, Reason} -rec_acc_pre_commit([Pid | Tail], Tid, Store, Commit, Res, DumperMode, - GoodPids, SchemaAckPids) -> - receive - {?MODULE, _, {acc_pre_commit, Tid, Pid, true}} -> - rec_acc_pre_commit(Tail, Tid, Store, Commit, Res, DumperMode, - [Pid | GoodPids], [Pid | SchemaAckPids]); - - {?MODULE, _, {acc_pre_commit, Tid, Pid, false}} -> - rec_acc_pre_commit(Tail, Tid, Store, Commit, Res, DumperMode, - [Pid | GoodPids], SchemaAckPids); - - {?MODULE, _, {acc_pre_commit, Tid, Pid}} -> - %% Kept for backwards compatibility. Remove after Mnesia 4.x - rec_acc_pre_commit(Tail, Tid, Store, Commit, Res, DumperMode, - [Pid | GoodPids], [Pid | SchemaAckPids]); - - {mnesia_down, Node} when Node == node(Pid) -> - AbortRes = {do_abort, {bad_commit, Node}}, - rec_acc_pre_commit(Tail, Tid, Store, Commit, AbortRes, DumperMode, - GoodPids, SchemaAckPids) - end; -rec_acc_pre_commit([], Tid, Store, Commit, Res, DumperMode, GoodPids, SchemaAckPids) -> - D = Commit#commit.decision, - case Res of - do_commit -> - %% Now everybody knows that the others - %% has voted yes. We also know that - %% everybody are uncertain. - prepare_sync_schema_commit(Store, SchemaAckPids), - tell_participants(GoodPids, {Tid, committed}), - D2 = D#decision{outcome = committed}, - mnesia_recover:log_decision(D2), - ?eval_debug_fun({?MODULE, rec_acc_pre_commit_log_commit}, - [{tid, Tid}]), - - %% Now we have safely logged committed - %% and we can recover without asking others - do_commit(Tid, Commit, DumperMode), - ?eval_debug_fun({?MODULE, rec_acc_pre_commit_done_commit}, - [{tid, Tid}]), - sync_schema_commit(Tid, Store, SchemaAckPids), - mnesia_locker:release_tid(Tid), - ?MODULE ! {delete_transaction, Tid}; - - {do_abort, Reason} -> - tell_participants(GoodPids, {Tid, {do_abort, Reason}}), - D2 = D#decision{outcome = aborted}, - mnesia_recover:log_decision(D2), - ?eval_debug_fun({?MODULE, rec_acc_pre_commit_log_abort}, - [{tid, Tid}]), - do_abort(Tid, Commit), - ?eval_debug_fun({?MODULE, rec_acc_pre_commit_done_abort}, - [{tid, Tid}]) - end, - Res. - -%% Note all nodes in case of mnesia_down mgt -prepare_sync_schema_commit(_Store, []) -> - ok; -prepare_sync_schema_commit(Store, [Pid | Pids]) -> - ?ets_insert(Store, {waiting_for_commit_ack, node(Pid)}), - prepare_sync_schema_commit(Store, Pids). - -sync_schema_commit(_Tid, _Store, []) -> - ok; -sync_schema_commit(Tid, Store, [Pid | Tail]) -> - receive - {?MODULE, _, {schema_commit, Tid, Pid}} -> - ?ets_match_delete(Store, {waiting_for_commit_ack, node(Pid)}), - sync_schema_commit(Tid, Store, Tail); - - {mnesia_down, Node} when Node == node(Pid) -> - ?ets_match_delete(Store, {waiting_for_commit_ack, Node}), - sync_schema_commit(Tid, Store, Tail) - end. - -tell_participants([Pid | Pids], Msg) -> - Pid ! Msg, - tell_participants(Pids, Msg); -tell_participants([], _Msg) -> - ok. - -%% No need for trapping exits. We are only linked -%% to mnesia_tm and if it dies we should also die. -%% The same goes for disk_log and dets. -commit_participant(Coord, Tid, Bin, DiscNs, RamNs) when binary(Bin) -> - Commit = binary_to_term(Bin), - commit_participant(Coord, Tid, Bin, Commit, DiscNs, RamNs); -commit_participant(Coord, Tid, C, DiscNs, RamNs) when record(C, commit) -> - commit_participant(Coord, Tid, C, C, DiscNs, RamNs). - -commit_participant(Coord, Tid, Bin, C0, DiscNs, _RamNs) -> - ?eval_debug_fun({?MODULE, commit_participant, pre}, [{tid, Tid}]), - case catch mnesia_schema:prepare_commit(Tid, C0, {part, Coord}) of - {Modified, C, DumperMode} when record(C, commit) -> - %% If we can not find any local unclear decision - %% we should presume abort at startup recovery - case lists:member(node(), DiscNs) of - false -> - ignore; - true -> - case Modified of - false -> mnesia_log:log(Bin); - true -> mnesia_log:log(C) - end - end, - ?eval_debug_fun({?MODULE, commit_participant, vote_yes}, - [{tid, Tid}]), - reply(Coord, {vote_yes, Tid, self()}), - - receive - {Tid, pre_commit} -> - D = C#commit.decision, - mnesia_recover:log_decision(D#decision{outcome = unclear}), - ?eval_debug_fun({?MODULE, commit_participant, pre_commit}, - [{tid, Tid}]), - Expect_schema_ack = C#commit.schema_ops /= [], - reply(Coord, {acc_pre_commit, Tid, self(), Expect_schema_ack}), - - %% Now we are vulnerable for failures, since - %% we cannot decide without asking others - receive - {Tid, committed} -> - mnesia_recover:log_decision(D#decision{outcome = committed}), - ?eval_debug_fun({?MODULE, commit_participant, log_commit}, - [{tid, Tid}]), - do_commit(Tid, C, DumperMode), - case Expect_schema_ack of - false -> ignore; - true -> reply(Coord, {schema_commit, Tid, self()}) - end, - ?eval_debug_fun({?MODULE, commit_participant, do_commit}, - [{tid, Tid}]); - - {Tid, {do_abort, _Reason}} -> - mnesia_recover:log_decision(D#decision{outcome = aborted}), - ?eval_debug_fun({?MODULE, commit_participant, log_abort}, - [{tid, Tid}]), - mnesia_schema:undo_prepare_commit(Tid, C), - ?eval_debug_fun({?MODULE, commit_participant, undo_prepare}, - [{tid, Tid}]); - - {'EXIT', _, _} -> - mnesia_recover:log_decision(D#decision{outcome = aborted}), - ?eval_debug_fun({?MODULE, commit_participant, exit_log_abort}, - [{tid, Tid}]), - mnesia_schema:undo_prepare_commit(Tid, C), - ?eval_debug_fun({?MODULE, commit_participant, exit_undo_prepare}, - [{tid, Tid}]); - - Msg -> - verbose("** ERROR ** commit_participant ~p, got unexpected msg: ~p~n", - [Tid, Msg]) - end; - {Tid, {do_abort, _Reason}} -> - mnesia_schema:undo_prepare_commit(Tid, C), - ?eval_debug_fun({?MODULE, commit_participant, pre_commit_undo_prepare}, - [{tid, Tid}]); - - {'EXIT', _, _} -> - mnesia_schema:undo_prepare_commit(Tid, C), - ?eval_debug_fun({?MODULE, commit_participant, pre_commit_undo_prepare}, [{tid, Tid}]); - - Msg -> - verbose("** ERROR ** commit_participant ~p, got unexpected msg: ~p~n", - [Tid, Msg]) - end; - - {'EXIT', Reason} -> - ?eval_debug_fun({?MODULE, commit_participant, vote_no}, - [{tid, Tid}]), - reply(Coord, {vote_no, Tid, Reason}), - mnesia_schema:undo_prepare_commit(Tid, C0) - end, - mnesia_locker:release_tid(Tid), - ?MODULE ! {delete_transaction, Tid}, - unlink(whereis(?MODULE)), - exit(normal). - -do_abort(Tid, Bin) when binary(Bin) -> - %% Possible optimization: - %% If we want we could pass arround a flag - %% that tells us whether the binary contains - %% schema ops or not. Only if the binary - %% contains schema ops there are meningful - %% unpack the binary and perform - %% mnesia_schema:undo_prepare_commit/1. - do_abort(Tid, binary_to_term(Bin)); -do_abort(Tid, Commit) -> - mnesia_schema:undo_prepare_commit(Tid, Commit), - Commit. - -do_dirty(Tid, Commit) when Commit#commit.schema_ops == [] -> - mnesia_log:log(Commit), - do_commit(Tid, Commit). - -%% do_commit(Tid, CommitRecord) -do_commit(Tid, Bin) when binary(Bin) -> - do_commit(Tid, binary_to_term(Bin)); -do_commit(Tid, C) -> - do_commit(Tid, C, optional). -do_commit(Tid, Bin, DumperMode) when binary(Bin) -> - do_commit(Tid, binary_to_term(Bin), DumperMode); -do_commit(Tid, C, DumperMode) -> - mnesia_dumper:update(Tid, C#commit.schema_ops, DumperMode), - R = do_snmp(Tid, C#commit.snmp), - R2 = do_update(Tid, ram_copies, C#commit.ram_copies, R), - R3 = do_update(Tid, disc_copies, C#commit.disc_copies, R2), - do_update(Tid, disc_only_copies, C#commit.disc_only_copies, R3). - -%% Update the items -do_update(Tid, Storage, [Op | Ops], OldRes) -> - case catch do_update_op(Tid, Storage, Op) of - ok -> - do_update(Tid, Storage, Ops, OldRes); - {'EXIT', Reason} -> - %% This may only happen when we recently have - %% deleted our local replica, changed storage_type - %% or transformed table - %% BUGBUG: Updates may be lost if storage_type is changed. - %% Determine actual storage type and try again. - %% BUGBUG: Updates may be lost if table is transformed. - - verbose("do_update in ~w failed: ~p -> {'EXIT', ~p}~n", - [Tid, Op, Reason]), - do_update(Tid, Storage, Ops, OldRes); - NewRes -> - do_update(Tid, Storage, Ops, NewRes) - end; -do_update(_Tid, _Storage, [], Res) -> - Res. - -do_update_op(Tid, Storage, {{Tab, K}, Obj, write}) -> - commit_write(?catch_val({Tab, commit_work}), Tid, - Tab, K, Obj, undefined), - mnesia_lib:db_put(Storage, Tab, Obj); - -do_update_op(Tid, Storage, {{Tab, K}, Val, delete}) -> - commit_delete(?catch_val({Tab, commit_work}), Tid, Tab, K, Val, undefined), - mnesia_lib:db_erase(Storage, Tab, K); - -do_update_op(Tid, Storage, {{Tab, K}, {RecName, Incr}, update_counter}) -> - {NewObj, OldObjs} = - case catch mnesia_lib:db_update_counter(Storage, Tab, K, Incr) of - NewVal when integer(NewVal), NewVal >= 0 -> - {{RecName, K, NewVal}, [{RecName, K, NewVal - Incr}]}; - _ -> - Zero = {RecName, K, 0}, - mnesia_lib:db_put(Storage, Tab, Zero), - {Zero, []} - end, - commit_update(?catch_val({Tab, commit_work}), Tid, Tab, - K, NewObj, OldObjs), - element(3, NewObj); - -do_update_op(Tid, Storage, {{Tab, Key}, Obj, delete_object}) -> - commit_del_object(?catch_val({Tab, commit_work}), - Tid, Tab, Key, Obj, undefined), - mnesia_lib:db_match_erase(Storage, Tab, Obj); - -do_update_op(Tid, Storage, {{Tab, Key}, Obj, clear_table}) -> - commit_clear(?catch_val({Tab, commit_work}), Tid, Tab, Key, Obj), - mnesia_lib:db_match_erase(Storage, Tab, Obj). - -commit_write([], _, _, _, _, _) -> ok; -commit_write([{checkpoints, CpList}|R], Tid, Tab, K, Obj, Old) -> - mnesia_checkpoint:tm_retain(Tid, Tab, K, write, CpList), - commit_write(R, Tid, Tab, K, Obj, Old); -commit_write([H|R], Tid, Tab, K, Obj, Old) - when element(1, H) == subscribers -> - mnesia_subscr:report_table_event(H, Tab, Tid, Obj, write, Old), - commit_write(R, Tid, Tab, K, Obj, Old); -commit_write([H|R], Tid, Tab, K, Obj, Old) - when element(1, H) == index -> - mnesia_index:add_index(H, Tab, K, Obj, Old), - commit_write(R, Tid, Tab, K, Obj, Old). - -commit_update([], _, _, _, _, _) -> ok; -commit_update([{checkpoints, CpList}|R], Tid, Tab, K, Obj, _) -> - Old = mnesia_checkpoint:tm_retain(Tid, Tab, K, write, CpList), - commit_update(R, Tid, Tab, K, Obj, Old); -commit_update([H|R], Tid, Tab, K, Obj, Old) - when element(1, H) == subscribers -> - mnesia_subscr:report_table_event(H, Tab, Tid, Obj, write, Old), - commit_update(R, Tid, Tab, K, Obj, Old); -commit_update([H|R], Tid, Tab, K, Obj, Old) - when element(1, H) == index -> - mnesia_index:add_index(H, Tab, K, Obj, Old), - commit_update(R, Tid, Tab, K, Obj, Old). - -commit_delete([], _, _, _, _, _) -> ok; -commit_delete([{checkpoints, CpList}|R], Tid, Tab, K, Obj, _) -> - Old = mnesia_checkpoint:tm_retain(Tid, Tab, K, delete, CpList), - commit_delete(R, Tid, Tab, K, Obj, Old); -commit_delete([H|R], Tid, Tab, K, Obj, Old) - when element(1, H) == subscribers -> - mnesia_subscr:report_table_event(H, Tab, Tid, Obj, delete, Old), - commit_delete(R, Tid, Tab, K, Obj, Old); -commit_delete([H|R], Tid, Tab, K, Obj, Old) - when element(1, H) == index -> - mnesia_index:delete_index(H, Tab, K), - commit_delete(R, Tid, Tab, K, Obj, Old). - -commit_del_object([], _, _, _, _, _) -> ok; -commit_del_object([{checkpoints, CpList}|R], Tid, Tab, K, Obj, _) -> - Old = mnesia_checkpoint:tm_retain(Tid, Tab, K, delete_object, CpList), - commit_del_object(R, Tid, Tab, K, Obj, Old); -commit_del_object([H|R], Tid, Tab, K, Obj, Old) - when element(1, H) == subscribers -> - mnesia_subscr:report_table_event(H, Tab, Tid, Obj, delete_object, Old), - commit_del_object(R, Tid, Tab, K, Obj, Old); -commit_del_object([H|R], Tid, Tab, K, Obj, Old) - when element(1, H) == index -> - mnesia_index:del_object_index(H, Tab, K, Obj, Old), - commit_del_object(R, Tid, Tab, K, Obj, Old). - -commit_clear([], _, _, _, _) -> ok; -commit_clear([{checkpoints, CpList}|R], Tid, Tab, K, Obj) -> - mnesia_checkpoint:tm_retain(Tid, Tab, K, clear_table, CpList), - commit_clear(R, Tid, Tab, K, Obj); -commit_clear([H|R], Tid, Tab, K, Obj) - when element(1, H) == subscribers -> - mnesia_subscr:report_table_event(H, Tab, Tid, Obj, clear_table, undefined), - commit_clear(R, Tid, Tab, K, Obj); -commit_clear([H|R], Tid, Tab, K, Obj) - when element(1, H) == index -> - mnesia_index:clear_index(H, Tab, K, Obj), - commit_clear(R, Tid, Tab, K, Obj). - -do_snmp(_, []) -> ok; -do_snmp(Tid, [Head | Tail]) -> - case catch mnesia_snmp_hook:update(Head) of - {'EXIT', Reason} -> - %% This should only happen when we recently have - %% deleted our local replica or recently deattached - %% the snmp table - - verbose("do_snmp in ~w failed: ~p -> {'EXIT', ~p}~n", - [Tid, Head, Reason]); - ok -> - ignore - end, - do_snmp(Tid, Tail). - -commit_nodes([C | Tail], AccD, AccR) - when C#commit.disc_copies == [], - C#commit.disc_only_copies == [], - C#commit.schema_ops == [] -> - commit_nodes(Tail, AccD, [C#commit.node | AccR]); -commit_nodes([C | Tail], AccD, AccR) -> - commit_nodes(Tail, [C#commit.node | AccD], AccR); -commit_nodes([], AccD, AccR) -> - {AccD, AccR}. - -commit_decision(D, [C | Tail], AccD, AccR) -> - N = C#commit.node, - {D2, Tail2} = - case C#commit.schema_ops of - [] when C#commit.disc_copies == [], - C#commit.disc_only_copies == [] -> - commit_decision(D, Tail, AccD, [N | AccR]); - [] -> - commit_decision(D, Tail, [N | AccD], AccR); - Ops -> - case ram_only_ops(N, Ops) of - true -> - commit_decision(D, Tail, AccD, [N | AccR]); - false -> - commit_decision(D, Tail, [N | AccD], AccR) - end - end, - {D2, [C#commit{decision = D2} | Tail2]}; -commit_decision(D, [], AccD, AccR) -> - {D#decision{disc_nodes = AccD, ram_nodes = AccR}, []}. - -ram_only_ops(N, [{op, change_table_copy_type, N, _FromS, _ToS, Cs} | _Ops ]) -> - case lists:member({name, schema}, Cs) of - true -> - %% We always use disk if change type of the schema - false; - false -> - not lists:member(N, val({schema, disc_copies})) - end; - -ram_only_ops(N, _Ops) -> - not lists:member(N, val({schema, disc_copies})). - -%% Returns {WaitFor, Res} -sync_send_dirty(Tid, [Head | Tail], Tab, WaitFor) -> - Node = Head#commit.node, - if - Node == node() -> - {WF, _} = sync_send_dirty(Tid, Tail, Tab, WaitFor), - Res = do_dirty(Tid, Head), - {WF, Res}; - true -> - {?MODULE, Node} ! {self(), {sync_dirty, Tid, Head, Tab}}, - sync_send_dirty(Tid, Tail, Tab, [Node | WaitFor]) - end; -sync_send_dirty(_Tid, [], _Tab, WaitFor) -> - {WaitFor, {'EXIT', {aborted, {node_not_running, WaitFor}}}}. - -%% Returns {WaitFor, Res} -async_send_dirty(_Tid, _Nodes, Tab, nowhere) -> - {[], {'EXIT', {aborted, {no_exists, Tab}}}}; -async_send_dirty(Tid, Nodes, Tab, ReadNode) -> - async_send_dirty(Tid, Nodes, Tab, ReadNode, [], ok). - -async_send_dirty(Tid, [Head | Tail], Tab, ReadNode, WaitFor, Res) -> - Node = Head#commit.node, - if - ReadNode == Node, Node == node() -> - NewRes = do_dirty(Tid, Head), - async_send_dirty(Tid, Tail, Tab, ReadNode, WaitFor, NewRes); - ReadNode == Node -> - {?MODULE, Node} ! {self(), {sync_dirty, Tid, Head, Tab}}, - NewRes = {'EXIT', {aborted, {node_not_running, Node}}}, - async_send_dirty(Tid, Tail, Tab, ReadNode, [Node | WaitFor], NewRes); - true -> - {?MODULE, Node} ! {self(), {async_dirty, Tid, Head, Tab}}, - async_send_dirty(Tid, Tail, Tab, ReadNode, WaitFor, Res) - end; -async_send_dirty(_Tid, [], _Tab, _ReadNode, WaitFor, Res) -> - {WaitFor, Res}. - -rec_dirty([Node | Tail], Res) when Node /= node() -> - NewRes = get_dirty_reply(Node, Res), - rec_dirty(Tail, NewRes); -rec_dirty([], Res) -> - Res. - -get_dirty_reply(Node, Res) -> - receive - {?MODULE, Node, {'EXIT', Reason}} -> - {'EXIT', {aborted, {badarg, Reason}}}; - {?MODULE, Node, {dirty_res, ok}} -> - case Res of - {'EXIT', {aborted, {node_not_running, _Node}}} -> - ok; - _ -> - %% Prioritize bad results, but node_not_running - Res - end; - {?MODULE, Node, {dirty_res, Reply}} -> - Reply; - {mnesia_down, Node} -> - %% It's ok to ignore mnesia_down's - %% since we will make the replicas - %% consistent again when Node is started - Res - after 1000 -> - case lists:member(Node, val({current, db_nodes})) of - true -> - get_dirty_reply(Node, Res); - false -> - Res - end - end. - -%% Assume that CommitRecord is no binary -%% Return {Res, Pids} -ask_commit(Protocol, Tid, CR, DiscNs, RamNs) -> - ask_commit(Protocol, Tid, CR, DiscNs, RamNs, [], no_local). - -ask_commit(Protocol, Tid, [Head | Tail], DiscNs, RamNs, WaitFor, Local) -> - Node = Head#commit.node, - if - Node == node() -> - ask_commit(Protocol, Tid, Tail, DiscNs, RamNs, WaitFor, Head); - true -> - Bin = opt_term_to_binary(Protocol, Head, DiscNs++RamNs), - Msg = {ask_commit, Protocol, Tid, Bin, DiscNs, RamNs}, - {?MODULE, Node} ! {self(), Msg}, - ask_commit(Protocol, Tid, Tail, DiscNs, RamNs, [Node | WaitFor], Local) - end; -ask_commit(_Protocol, _Tid, [], _DiscNs, _RamNs, WaitFor, Local) -> - {WaitFor, Local}. - -opt_term_to_binary(asym_trans, Head, Nodes) -> - opt_term_to_binary(Nodes, Head); -opt_term_to_binary(_Protocol, Head, _Nodes) -> - Head. - -opt_term_to_binary([], Head) -> - term_to_binary(Head); -opt_term_to_binary([H|R], Head) -> - case mnesia_monitor:needs_protocol_conversion(H) of - true -> Head; - false -> - opt_term_to_binary(R, Head) - end. - -rec_all([Node | Tail], Tid, Res, Pids) -> - receive - {?MODULE, Node, {vote_yes, Tid}} -> - rec_all(Tail, Tid, Res, Pids); - {?MODULE, Node, {vote_yes, Tid, Pid}} -> - rec_all(Tail, Tid, Res, [Pid | Pids]); - {?MODULE, Node, {vote_no, Tid, Reason}} -> - rec_all(Tail, Tid, {do_abort, Reason}, Pids); - {?MODULE, Node, {committed, Tid}} -> - rec_all(Tail, Tid, Res, Pids); - {?MODULE, Node, {aborted, Tid}} -> - rec_all(Tail, Tid, Res, Pids); - - {mnesia_down, Node} -> - rec_all(Tail, Tid, {do_abort, {bad_commit, Node}}, Pids) - end; -rec_all([], _Tid, Res, Pids) -> - {Res, Pids}. - -get_transactions() -> - {info, Participant, Coordinator} = req(info), - lists:map(fun({Tid, _Tabs}) -> - Status = tr_status(Tid,Participant), - {Tid#tid.counter, Tid#tid.pid, Status} - end,Coordinator). - -tr_status(Tid,Participant) -> - case lists:keymember(Tid, 1, Participant) of - true -> participant; - false -> coordinator - end. - -get_info(Timeout) -> - case whereis(?MODULE) of - undefined -> - {timeout, Timeout}; - Pid -> - Pid ! {self(), info}, - receive - {?MODULE, _, {info, Part, Coord}} -> - {info, Part, Coord} - after Timeout -> - {timeout, Timeout} - end - end. - -display_info(Stream, {timeout, T}) -> - io:format(Stream, "---> No info about coordinator and participant transactions, " - "timeout ~p <--- ~n", [T]); - -display_info(Stream, {info, Part, Coord}) -> - io:format(Stream, "---> Participant transactions <--- ~n", []), - lists:foreach(fun(P) -> pr_participant(Stream, P) end, Part), - io:format(Stream, "---> Coordinator transactions <---~n", []), - lists:foreach(fun({Tid, _Tabs}) -> pr_tid(Stream, Tid) end, Coord). - -pr_participant(Stream, P) -> - Commit0 = P#participant.commit, - Commit = - if - binary(Commit0) -> binary_to_term(Commit0); - true -> Commit0 - end, - pr_tid(Stream, P#participant.tid), - io:format(Stream, "with participant objects ~p~n", [Commit]). - - -pr_tid(Stream, Tid) -> - io:format(Stream, "Tid: ~p (owned by ~p) ~n", - [Tid#tid.counter, Tid#tid.pid]). - -info(Serial) -> - io:format( "Info about transaction with serial == ~p~n", [Serial]), - {info, Participant, Trs} = req(info), - search_pr_participant(Serial, Participant), - search_pr_coordinator(Serial, Trs). - - -search_pr_coordinator(_S, []) -> no; -search_pr_coordinator(S, [{Tid, _Ts}|Tail]) -> - case Tid#tid.counter of - S -> - io:format( "Tid is coordinator, owner == \n", []), - display_pid_info(Tid#tid.pid), - search_pr_coordinator(S, Tail); - _ -> - search_pr_coordinator(S, Tail) - end. - -search_pr_participant(_S, []) -> - false; -search_pr_participant(S, [ P | Tail]) -> - Tid = P#participant.tid, - Commit0 = P#participant.commit, - if - Tid#tid.counter == S -> - io:format( "Tid is participant to commit, owner == \n", []), - Pid = Tid#tid.pid, - display_pid_info(Pid), - io:format( "Tid wants to write objects \n",[]), - Commit = - if - binary(Commit0) -> binary_to_term(Commit0); - true -> Commit0 - end, - - io:format("~p~n", [Commit]), - search_pr_participant(S,Tail); %% !!!!! - true -> - search_pr_participant(S, Tail) - end. - -display_pid_info(Pid) -> - case rpc:pinfo(Pid) of - undefined -> - io:format( "Dead process \n"); - Info -> - Call = fetch(initial_call, Info), - Curr = case fetch(current_function, Info) of - {Mod,F,Args} when list(Args) -> - {Mod,F,length(Args)}; - Other -> - Other - end, - Reds = fetch(reductions, Info), - LM = length(fetch(messages, Info)), - pformat(io_lib:format("~p", [Pid]), - io_lib:format("~p", [Call]), - io_lib:format("~p", [Curr]), Reds, LM) - end. - -pformat(A1, A2, A3, A4, A5) -> - io:format( "~-12s ~-21s ~-21s ~9w ~4w~n", [A1,A2,A3,A4,A5]). - -fetch(Key, Info) -> - case lists:keysearch(Key, 1, Info) of - {value, {_, Val}} -> - Val; - _ -> - 0 - end. - - -%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%% reconfigure stuff comes here ...... -%%%%%%%%%%%%%%%%%%%%% - -reconfigure_coordinators(N, [{Tid, [Store | _]} | Coordinators]) -> - case mnesia_recover:outcome(Tid, unknown) of - committed -> - WaitingNodes = ?ets_lookup(Store, waiting_for_commit_ack), - case lists:keymember(N, 2, WaitingNodes) of - false -> - ignore; % avoid spurious mnesia_down messages - true -> - send_mnesia_down(Tid, Store, N) - end; - aborted -> - ignore; % avoid spurious mnesia_down messages - _ -> - %% Tell the coordinator about the mnesia_down - send_mnesia_down(Tid, Store, N) - end, - reconfigure_coordinators(N, Coordinators); -reconfigure_coordinators(_N, []) -> - ok. - -send_mnesia_down(Tid, Store, Node) -> - Msg = {mnesia_down, Node}, - send_to_pids([Tid#tid.pid | get_friends(Store)], Msg). - -send_to_pids([Pid | Pids], Msg) -> - Pid ! Msg, - send_to_pids(Pids, Msg); -send_to_pids([], _Msg) -> - ok. - -reconfigure_participants(N, [P | Tail]) -> - case lists:member(N, P#participant.disc_nodes) or - lists:member(N, P#participant.ram_nodes) of - false -> - %% Ignore, since we are not a participant - %% in the transaction. - reconfigure_participants(N, Tail); - - true -> - %% We are on a participant node, lets - %% check if the dead one was a - %% participant or a coordinator. - Tid = P#participant.tid, - if - node(Tid#tid.pid) /= N -> - %% Another participant node died. Ignore. - reconfigure_participants(N, Tail); - - true -> - %% The coordinator node has died and - %% we must determine the outcome of the - %% transaction and tell mnesia_tm on all - %% nodes (including the local node) about it - verbose("Coordinator ~p in transaction ~p died~n", - [Tid#tid.pid, Tid]), - - Nodes = P#participant.disc_nodes ++ - P#participant.ram_nodes, - AliveNodes = Nodes -- [N], - Protocol = P#participant.protocol, - tell_outcome(Tid, Protocol, N, AliveNodes, AliveNodes), - reconfigure_participants(N, Tail) - end - end; -reconfigure_participants(_, []) -> - []. - -%% We need to determine the outcome of the transaction and -%% tell mnesia_tm on all involved nodes (including the local node) -%% about the outcome. -tell_outcome(Tid, Protocol, Node, CheckNodes, TellNodes) -> - Outcome = mnesia_recover:what_happened(Tid, Protocol, CheckNodes), - case Outcome of - aborted -> - rpc:abcast(TellNodes, ?MODULE, {Tid,{do_abort, {mnesia_down, Node}}}); - committed -> - rpc:abcast(TellNodes, ?MODULE, {Tid, do_commit}) - end, - Outcome. - -do_stop(#state{coordinators = Coordinators}) -> - Msg = {mnesia_down, node()}, - lists:foreach(fun({Tid, _}) -> Tid#tid.pid ! Msg end, Coordinators), - mnesia_checkpoint:stop(), - mnesia_log:stop(), - exit(shutdown). - -%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% System upgrade - -system_continue(_Parent, _Debug, State) -> - doit_loop(State). - -system_terminate(_Reason, _Parent, _Debug, State) -> - do_stop(State). - -system_code_change(State, _Module, _OldVsn, _Extra) -> - {ok, State}. |