From 87df33fd477bd8d08c03d72670ca0faa3546d2e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20P=C3=A5lsson?= Date: Thu, 24 Feb 2011 22:22:32 +0100 Subject: [PATCH] Now we can crash ggs_coordinator without it affecting the clients. --- src/ggs_coordinator.erl | 29 +++++++++++---- src/ggs_coordinator_backup.erl | 65 ++++++++++++++++++++++++++++++++++ src/ggs_sup.erl | 9 ++++- src/ggs_table.erl | 9 +++-- 4 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 src/ggs_coordinator_backup.erl diff --git a/src/ggs_coordinator.erl b/src/ggs_coordinator.erl index 535b9b0..ba65c5a 100644 --- a/src/ggs_coordinator.erl +++ b/src/ggs_coordinator.erl @@ -53,13 +53,26 @@ remove_player(_From, _Player) -> %gen_server:cast(ggs_coordinator, {remove_player, Player}). ggs_logger:not_implemented(). +%% Just to shorten the name +back_up(State) -> + ggs_coordinator_backup:back_up(State), + State. + %% gen_server callbacks init([]) -> - {ok, #co_state{}}. + % Restore old state from backup if there is old state stored there + case ggs_coordinator_backup:retrieve() of + no_state_stored -> + io:format("No old state stored.. Creating new!~n"), + {ok, #co_state{}}; + State -> + {ok, State} + end. handle_call(join_lobby, _From, State) -> Token = helpers:get_new_token(), + back_up(State), {reply, {ok, Token}, State}; handle_call({join_table, Table}, From, State) -> @@ -68,19 +81,23 @@ handle_call({join_table, Table}, From, State) -> case lists:keyfind(Table, 1, Tables) of {_TableID, TablePID} -> ggs_table:add_player(TablePID, FromPlayer), + back_up(State), {reply, {ok, TablePID}, State}; false -> + back_up(State), {reply, {error, no_such_table}, State} end; handle_call({create_table, {force, TableID}}, From, State) -> TableIDMap = State#co_state.player_table_map, Tables = State#co_state.tables, - NewTableProc = ggs_table:start_link(), - {reply, {ok, TableID}, State#co_state{ - player_table_map = [{From, TableID} | TableIDMap], - tables = [{TableID, NewTableProc} | Tables] - }}; + NewTableProc = ggs_table:start(), % With start_link, the table dies with the coordinator + NewState = State#co_state{ + player_table_map = [{From, TableID} | TableIDMap], + tables = [{TableID, NewTableProc} | Tables] + }, + back_up(NewState), + {reply, {ok, TableID}, NewState}; handle_call(_Message, _From, State) -> {noreply, State}. diff --git a/src/ggs_coordinator_backup.erl b/src/ggs_coordinator_backup.erl new file mode 100644 index 0000000..e42fe48 --- /dev/null +++ b/src/ggs_coordinator_backup.erl @@ -0,0 +1,65 @@ +-module(ggs_coordinator_backup). + +-behaviour(gen_server). + +%% API Exports +-export([start_link/0, stop/1]). +-export([back_up/1, retrieve/0]). + +%% gen_server callback exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-define(SERVER, ?MODULE). + +%% @doc This module is repsponsible for keeping a backup of the coodinator +%% at all times. At any point in time a backup can be restored from this +%% module. +%% This module is started by the root supervisor, and is restarted when it +%% crashes. Upon a crash, the backup state is lost in this module, and must +%% be filled in from the ggs_coordinator. + +%% @doc Start a new ggs_coordinator backup instance, and register it under +%% this name. This means that there can only be one instance of this module +%% running. +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%% @doc Stops the server with the specified reason. +%% @spec stop(Reason) -> ok. +%% Reason = String +stop(_Reason) -> ggs_logger:not_implemented(). + +%% API +back_up(State) -> + gen_server:cast(?SERVER, State). + +%% @doc Retrieve the state stored in this server. If there is a state stored +%% here, it is returned to the caller. If the backup server does not have a +%% state stored, it will return the no_state_stored atom. +retrieve() -> + gen_server:call(?SERVER, retrieve). + +%% gen_server callbacks + +%% @doc Initiate the server. This is called from gen_server +init([]) -> + {ok, no_state_stored}. + +handle_call(retrieve, _From, State) -> + {reply, State, State}. + +handle_cast(NewState, _State) -> + {noreply, NewState}. + +handle_info(Msg, State) -> + io:format("Received out of bounds message! "), + erlang:display(Msg), + io:format("~n"), + {noreply, State}. + +terminate(normal, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/src/ggs_sup.erl b/src/ggs_sup.erl index 05fab65..2e5c2ef 100644 --- a/src/ggs_sup.erl +++ b/src/ggs_sup.erl @@ -26,7 +26,14 @@ init([Port]) -> worker, [ggs_coordinator] }, - Children = [Dispatcher, Coordinator], + Coordinator_backup = {ggs_coordinator_backup, + {ggs_coordinator_backup, start_link, []}, + permanent, + 2000, + worker, + [ggs_coordinator_backup] + }, + Children = [Dispatcher, Coordinator_backup, Coordinator], RestartStrategy = { one_for_one, % Restart only crashing child 10, % Allow ten crashes per.. diff --git a/src/ggs_table.erl b/src/ggs_table.erl index d6d2988..dbd16f7 100644 --- a/src/ggs_table.erl +++ b/src/ggs_table.erl @@ -11,7 +11,7 @@ -record(state, { players, game_vm } ). %% API --export([start_link/0, +-export([start/0, add_player/2, remove_player/2, stop/1, @@ -24,8 +24,8 @@ % API implementation % @doc returns a new table -start_link() -> - {ok, Pid} = gen_server:start_link(?MODULE, [], []), +start() -> + {ok, Pid} = gen_server:start(?MODULE, [], []), Pid. %% @private @@ -56,14 +56,13 @@ notify_all_players(Table, Message) -> gen_server:cast(Table, {notify_all_players, Message}). notify_game(Table, From, Message) -> - erlang:display(Table), - io:format("~n"), gen_server:cast(Table, {notify_game, Message, From}). %% ---------------------------------------------------------------------- %% @private init([]) -> + process_flag(trap_exit, true), GameVM = ggs_gamevm_e:start_link(self()), %% @TODO: Temporary erlang gamevm {ok, #state { game_vm = GameVM,