diff --git a/src/wterl_conn.erl b/src/wterl_conn.erl index c78dbdd..3ed3b43 100644 --- a/src/wterl_conn.erl +++ b/src/wterl_conn.erl @@ -36,8 +36,9 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, {conn :: wterl:connection(), - monitors :: ets:tid()}). +-record(state, { + conn :: wterl:connection() + }). %% ==================================================================== %% API @@ -72,23 +73,23 @@ close(_Conn) -> %% ==================================================================== init([]) -> + true = wterl_ets:table_ready(), {ok, #state{}}. handle_call({open, Dir, Caller}, _From, #state{conn=undefined}=State) -> Opts = [{create, true}, {cache_size, "100MB"}, {session_max, 100}], {Reply, NState} = case wterl:conn_open(Dir, wterl:config_to_bin(Opts)) of {ok, ConnRef}=OK -> - Monitors = ets:new(?MODULE, []), Monitor = erlang:monitor(process, Caller), - true = ets:insert(Monitors, {Monitor, Caller}), - {OK, State#state{conn = ConnRef, monitors=Monitors}}; + true = ets:insert(wterl_ets, {Monitor, Caller}), + {OK, State#state{conn = ConnRef}}; Error -> {Error, State} end, {reply, Reply, NState}; -handle_call({open, _Dir, Caller}, _From,#state{conn=ConnRef, monitors=Monitors}=State) -> +handle_call({open, _Dir, Caller}, _From,#state{conn=ConnRef}=State) -> Monitor = erlang:monitor(process, Caller), - true = ets:insert(Monitors, {Monitor, Caller}), + true = ets:insert(wterl_ets, {Monitor, Caller}), {reply, {ok, ConnRef}, State}; handle_call(is_open, _From, #state{conn=ConnRef}=State) -> @@ -99,40 +100,41 @@ handle_call(get, _From, #state{conn=undefined}=State) -> handle_call(get, _From, #state{conn=ConnRef}=State) -> {reply, {ok, ConnRef}, State}; -handle_call({close, Caller}, _From, #state{conn=ConnRef, monitors=Monitors}=State) -> - {[{Monitor, Caller}], _} = ets:match_object(Monitors, {'_', Caller}, 1), +handle_call({close, Caller}, _From, #state{conn=ConnRef}=State) -> + {[{Monitor, Caller}], _} = ets:match_object(wterl_ets, {'_', Caller}, 1), true = erlang:demonitor(Monitor, [flush]), - true = ets:delete(Monitors, Monitor), - NState = case ets:info(Monitors, size) of + true = ets:delete(wterl_ets, Monitor), + NState = case ets:info(wterl_ets, size) of 0 -> do_close(ConnRef), - ets:delete(Monitors), - State#state{conn=undefined, monitors=undefined}; + State#state{conn=undefined}; _ -> State end, - {reply, ok, NState}. + {reply, ok, NState}; +handle_call(_Msg, _From, State) -> + {reply, ok, State}. handle_cast(stop, #state{conn=undefined}=State) -> - {noreply, State}; -handle_cast(stop, #state{conn=ConnRef, monitors=Monitors}=State) -> + {stop, normal, State}; +handle_cast(stop, #state{conn=ConnRef}=State) -> do_close(ConnRef), ets:foldl(fun({Monitor, _}, _) -> - true = erl:demonitor(Monitor, [flush]) - end, true, Monitors), - {noreply, State#state{conn=undefined, monitors=undefined}}; + true = erl:demonitor(Monitor, [flush]), + ets:delete(wterl_ets, Monitor) + end, true, wterl_ets), + {stop, normal, State#state{conn=undefined}}; handle_cast(_Msg, State) -> {noreply, State}. -handle_info({'DOWN', Monitor, _, _, _}, #state{conn=ConnRef, monitors=Monitors}=State) -> - NState = case ets:lookup(Monitors, Monitor) of +handle_info({'DOWN', Monitor, _, _, _}, #state{conn=ConnRef}=State) -> + NState = case ets:lookup(wterl_ets, Monitor) of [{Monitor, _}] -> - true = ets:delete(Monitors, Monitor), - case ets:info(Monitors, size) of + true = ets:delete(wterl_ets, Monitor), + case ets:info(wterl_ets, size) of 0 -> do_close(ConnRef), - ets:delete(Monitors), - State#state{conn=undefined, monitors=undefined}; + State#state{conn=undefined}; _ -> State end; @@ -170,15 +172,23 @@ simple_test_() -> fun() -> ?assertCmd("rm -rf " ++ ?DATADIR), ?assertMatch(ok, filelib:ensure_dir(filename:join(?DATADIR, "x"))), - case start_link() of - {ok, Pid} -> - Pid; - {error, {already_started, Pid}} -> - Pid - end + EtsPid = case wterl_ets:start_link() of + {ok, Pid1} -> + Pid1; + {error, {already_started, Pid1}} -> + Pid1 + end, + MyPid = case start_link() of + {ok, Pid2} -> + Pid2; + {error, {already_started, Pid2}} -> + Pid2 + end, + {EtsPid, MyPid} end, fun(_) -> - stop() + stop(), + wterl_ets:stop() end, fun(_) -> {inorder, diff --git a/src/wterl_ets.erl b/src/wterl_ets.erl new file mode 100644 index 0000000..5ecb34f --- /dev/null +++ b/src/wterl_ets.erl @@ -0,0 +1,91 @@ +%% ------------------------------------------------------------------- +%% +%% wterl_ets: ets table owner for wterl_conn +%% +%% Copyright (c) 2012 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- +-module(wterl_ets). +-author('Steve Vinoski '). + +-behaviour(gen_server). + +%% ==================================================================== +%% The sole purpose of this module is to own the ets table used by the +%% wterl_conn module. Holding the ets table in an otherwise do-nothing +%% server avoids losing the table and its contents should an unexpected +%% error occur in wterl_conn if it were the owner instead. This module +%% is unit-tested as part of the wterl_conn module. +%% ==================================================================== + +%% API +-export([start_link/0, stop/0, + table_ready/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, { + monitors :: ets:tid() + }). + +%% ==================================================================== +%% API +%% ==================================================================== + +-spec start_link() -> {ok, pid()} | {error, term()}. +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + + +-spec stop() -> ok. +stop() -> + gen_server:cast(?MODULE, stop). + +-spec table_ready() -> boolean(). +table_ready() -> + gen_server:call(?MODULE, table_ready). + +%% ==================================================================== +%% gen_server callbacks +%% ==================================================================== + +init([]) -> + Monitors = ets:new(?MODULE, [named_table,public]), + {ok, #state{monitors = Monitors}}. + +handle_call(table_ready, _From, State) -> + %% Always return true since the table is prepared in init. + {reply, true, State}; +handle_call(_Msg, _From, State) -> + {reply, ok, State}. + +handle_cast(stop, State) -> + {stop, normal, State}; +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, #state{monitors=Monitors}) -> + ets:delete(Monitors), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/src/wterl_sup.erl b/src/wterl_sup.erl index 0ead62a..07bc6cd 100644 --- a/src/wterl_sup.erl +++ b/src/wterl_sup.erl @@ -45,4 +45,5 @@ start_link() -> %% =================================================================== init([]) -> - {ok, {{one_for_one, 5, 10}, [?CHILD(wterl_conn, worker)]}}. + {ok, {{one_for_one, 5, 10}, [?CHILD(wterl_ets, worker), + ?CHILD(wterl_conn, worker)]}}.