Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 13F44200CA9 for ; Fri, 16 Jun 2017 23:40:32 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 1288E160BEC; Fri, 16 Jun 2017 21:40:32 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id DF4D1160BEB for ; Fri, 16 Jun 2017 23:40:30 +0200 (CEST) Received: (qmail 96282 invoked by uid 500); 16 Jun 2017 21:40:30 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 96265 invoked by uid 99); 16 Jun 2017 21:40:30 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 16 Jun 2017 21:40:30 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id D5A5A8360E; Fri, 16 Jun 2017 21:40:26 +0000 (UTC) Date: Fri, 16 Jun 2017 21:40:27 +0000 To: "commits@couchdb.apache.org" Subject: [couchdb] 01/03: Remove LRU from ddoc_cache MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit From: davisp@apache.org Reply-To: "commits@couchdb.apache.org" In-Reply-To: <149764922617.21686.6226112941736214692@gitbox.apache.org> References: <149764922617.21686.6226112941736214692@gitbox.apache.org> X-Git-Host: gitbox.apache.org X-Git-Repo: couchdb X-Git-Refname: refs/heads/optimize-ddoc-cache X-Git-Reftype: branch X-Git-Rev: 834844601b131716beb343fc504e1d166e8348c2 X-Git-NotificationType: diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated Message-Id: <20170616214027.D5A5A8360E@gitbox.apache.org> archived-at: Fri, 16 Jun 2017 21:40:32 -0000 This is an automated email from the ASF dual-hosted git repository. davisp pushed a commit to branch optimize-ddoc-cache in repository https://gitbox.apache.org/repos/asf/couchdb.git commit 834844601b131716beb343fc504e1d166e8348c2 Author: Paul J. Davis AuthorDate: Fri Jun 16 13:29:47 2017 -0500 Remove LRU from ddoc_cache This is only a part of the necessary work to optimize the ddoc cache. This will not work in production because the ets_lru max_age is not implemented so once a ddoc is in the cache it won't be removed unless everything stops using it (which for a busy database will be never because of the match_newest code). --- src/ddoc_cache/src/ddoc_cache.app.src | 7 +- src/ddoc_cache/src/ddoc_cache.erl | 2 +- src/ddoc_cache/src/ddoc_cache.hrl | 32 +++++ src/ddoc_cache/src/ddoc_cache_lru.erl | 221 +++++++++++++++++++++++++++++++ src/ddoc_cache/src/ddoc_cache_opener.erl | 123 ++++++----------- src/ddoc_cache/src/ddoc_cache_sup.erl | 35 ++--- src/ddoc_cache/src/ddoc_cache_tables.erl | 64 +++++++++ 7 files changed, 370 insertions(+), 114 deletions(-) diff --git a/src/ddoc_cache/src/ddoc_cache.app.src b/src/ddoc_cache/src/ddoc_cache.app.src index a64b2f5..084895e 100644 --- a/src/ddoc_cache/src/ddoc_cache.app.src +++ b/src/ddoc_cache/src/ddoc_cache.app.src @@ -21,6 +21,7 @@ ddoc_cache_util ]}, {registered, [ + ddoc_cache_tables, ddoc_cache_lru, ddoc_cache_opener ]}, @@ -29,16 +30,10 @@ stdlib, crypto, couch_event, - ets_lru, mem3, fabric, couch_log, couch_stats ]}, {mod, {ddoc_cache_app, []}}, - {env, [ - {max_objects, unlimited}, - {max_size, 104857600}, % 100M - {max_lifetime, 60000} % 1m - ]} ]}. diff --git a/src/ddoc_cache/src/ddoc_cache.erl b/src/ddoc_cache/src/ddoc_cache.erl index ed93309..07d89ac 100644 --- a/src/ddoc_cache/src/ddoc_cache.erl +++ b/src/ddoc_cache/src/ddoc_cache.erl @@ -91,7 +91,7 @@ open_custom(DbName, Mod) -> evict(ShardDbName, DDocIds) -> DbName = mem3:dbname(ShardDbName), - ddoc_cache_opener:evict_docs(DbName, DDocIds). + ddoc_cache_lru:evict(DbName, DDocIds). open(DbName, validation_funs) -> open_validation_funs(DbName); diff --git a/src/ddoc_cache/src/ddoc_cache.hrl b/src/ddoc_cache/src/ddoc_cache.hrl new file mode 100644 index 0000000..8545914 --- /dev/null +++ b/src/ddoc_cache/src/ddoc_cache.hrl @@ -0,0 +1,32 @@ +% Licensed 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. + +-type dbname() :: iodata(). +-type docid() :: iodata(). +-type doc_hash() :: <<_:128>>. +-type revision() :: {pos_integer(), doc_hash()}. + +-define(CACHE, ddoc_cache_entries). +-define(ATIMES, ddoc_cache_atimes). +-define(OPENERS, ddoc_cache_openers). + + +-record(entry, { + key, + val +}). + +-record(opener, { + key, + pid, + clients +}). diff --git a/src/ddoc_cache/src/ddoc_cache_lru.erl b/src/ddoc_cache/src/ddoc_cache_lru.erl new file mode 100644 index 0000000..9d8c397 --- /dev/null +++ b/src/ddoc_cache/src/ddoc_cache_lru.erl @@ -0,0 +1,221 @@ +% Licensed 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(ddoc_cache_lru). +-behaviour(gen_server). +-vsn(1). + + +-export([ + start_link/0, + + insert/2, + accessed/1, + evict/1, + evict/2 +]). + +-export([ + init/1, + terminate/2, + handle_call/3, + handle_cast/2, + handle_info/2, + code_change/3 +]). + +-export([ + handle_db_event/3 +]). + + +-include("ddoc_cache.hrl"). + + +-record(st, { + keys, + time, + max_size, + evictor +}). + + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + + +insert(Key, Val) -> + gen_server:call(?MODULE, {insert, Key}). + + +accessed(Key) -> + gen_server:cast(?MODULE, {accessed, Key}). + + +-spec evict(dbname(), [docid()]) -> ok. +evict(DbName, DDocIds) -> + gen_server:cast(?MODULE, {evict, DbName, DDocIds}). + + +init(_) -> + {ok, Keys} = khash:new(), + {ok, Evictor} = couch_event:link_listener( + ?MODULE, handle_db_event, nil, [all_dbs] + ), + MaxSize = config:get_integer("ddoc_cache", "max_size", 1000), + {ok, #st{ + keys = Keys, + time = 0, + max_size = MaxSize, + evictor = Evictor + }}. + + +terminate(_Reason, St) -> + case is_pid(St#st.evictor) of + true -> exit(St#st.evictor, kill); + false -> ok + end, + ok. + + +handle_call({insert, Key, Val}, _From, St) -> + #st{ + keys = Keys, + time = Time + } = St, + NewTime = Time + 1, + true = ets:insert(?CACHE, #entry{key = Key, val = Val}), + true = ets:insert(?ATIMES, {NewTime, Key}), + ok = khash:put(Keys, NewTime), + {reply, ok, trim(St#st{time = NewTime})}; + +handle_call(Msg, _From, St) -> + {stop, {invalid_call, Msg}, {invalid_call, Msg}, St}. + + +handle_cast({accessed, Key}, _St) -> + #st{ + keys = Keys, + time = Time + } = St, + NewTime = Time + 1, + case khash:lookup(Keys, Key) of + {value, OldTime} -> + true = ets:delete(?ATIMES, OldTime), + true = ets:insert(?ATIMES, {NewTime, Key}), + ok = khash:put(Keys, NewTime); + not_found -> + % Likely a client read from the cache while an + % eviction message was in our mailbox + ok + end, + {noreply, St}; + +handle_cast({evict, _} = Msg, St) -> + gen_server:abcast(mem3:nodes(), ?MODULE, Msg), + {noreply, St}; + +handle_cast({evict, _, _} = Msg, St) -> + gen_server:abcast(mem3:nodes(), ?MODULE, Msg), + {noreply, St}; + +handle_cast({do_evict, DbName} = Msg, St) -> + Pattern = #entry{ + key = {DbName, '$1', '_'}, + val = '_', + _ = '_' + }, + DDocIds = lists:flatten(ets:match(?CACHE, Pattern)), + handle_cast({do_evict, DbName, DDocIds}); + +handle_cast({do_evict, DbName, DDocIds}, St) -> + Pattern = #entry{ + key = {DbName, '$1'}, + val = '_', + _ = '_' + }, + CustomKeys = lists:flatten(ets:match(?CACHE, Pattern)), + lists:foreach(fun(Mod) -> + ets:delete(?CACHE, {DbName, Mod}) + end, CustomKeys), + lists:foreach(fun(DDocId) -> + RevPattern = #entry{ + key = {DbName, DDocId, '$1'}, + val = '_', + _ = '_' + }, + Revs = lists:flatten(ets:match(?CACHE, RevPattern)), + lists:foreach(fun(Rev) -> + ets:delete(?CACHE, {DbName, DDocId, Rev}) + end, Revs) + end, DDocIds), + {noreply, St}; + +handle_cast(Msg, St) -> + {stop, {invalid_cast, Msg}, St}. + + +handle_info({'EXIT', Pid, Reason}, #st{evictor=Pid}=St) -> + couch_log:error("ddoc_cache_opener evictor died ~w", [Reason]), + {ok, Evictor} = couch_event:link_listener( + ?MODULE, handle_db_event, nil, [all_dbs] + ), + {noreply, St#st{evictor=Evictor}}; + +handle_info(Msg, St) -> + {stop, {invalid_info, Msg}, St}. + + +code_change(_OldVsn, St, _Extra) -> + {ok, St}. + + +handle_db_event(ShardDbName, created, St) -> + gen_server:cast(?MODULE, {evict, mem3:dbname(ShardDbName)}), + {ok, St}; + +handle_db_event(ShardDbName, deleted, St) -> + gen_server:cast(?MODULE, {evict, mem3:dbname(ShardDbName)}), + {ok, St}; + +handle_db_event(_DbName, _Event, St) -> + {ok, St}. + + +trim(St) -> + #st{ + keys = Keys, + max_size = MaxSize + } = St, + case khash:size(Keys) > MaxSize of + true -> + case ets:first(?ATIMES) of + '$end_of_table' -> + St; + ATime -> + trim(remove(St, ATime)) + end; + false -> + St + end. + + +remove(St, ATime) -> + #st{ + keys = Keys + } = St, + {value, Key} = khash:lookup(Keys, ATime), + true = ets:delete(?CACHE, Key), + true = ets:delete(?ATIMES, ATime), + ok = khash:del(Keys, Key), + St. diff --git a/src/ddoc_cache/src/ddoc_cache_opener.erl b/src/ddoc_cache/src/ddoc_cache_opener.erl index b76a228..a4adffc 100644 --- a/src/ddoc_cache/src/ddoc_cache_opener.erl +++ b/src/ddoc_cache/src/ddoc_cache_opener.erl @@ -35,7 +35,6 @@ open_doc/2, open_doc/3, open_validation_funs/1, - evict_docs/2, lookup/1, match_newest/1, recover_doc/2, @@ -43,29 +42,17 @@ recover_validation_funs/1 ]). -export([ - handle_db_event/3 -]). --export([ fetch_doc_data/1 ]). --define(CACHE, ddoc_cache_lru). --define(OPENING, ddoc_cache_opening). +-include("ddoc_cache.hrl"). --type dbname() :: iodata(). --type docid() :: iodata(). --type doc_hash() :: <<_:128>>. --type revision() :: {pos_integer(), doc_hash()}. --record(opener, { - key, - pid, - clients -}). +-define(LRU, ddoc_cache_lru). + -record(st, { - db_ddocs, - evictor + db_ddocs }). start_link() -> @@ -86,14 +73,12 @@ open_validation_funs(DbName) -> Resp = gen_server:call(?MODULE, {open, {DbName, validation_funs}}, infinity), handle_open_response(Resp). --spec evict_docs(dbname(), [docid()]) -> ok. -evict_docs(DbName, DocIds) -> - gen_server:cast(?MODULE, {evict, DbName, DocIds}). lookup(Key) -> - try ets_lru:lookup_d(?CACHE, Key) of - {ok, _} = Resp -> - Resp; + try ets:lookup(?CACHE, Key) of + [#entry{key = Key, val = Val}] -> + ddoc_cache_lru:accessed(Key), + {ok, Val}; _ -> missing catch @@ -102,10 +87,19 @@ lookup(Key) -> end. match_newest(Key) -> - try ets_lru:match_object(?CACHE, Key, '_') of + Pattern = #entry{ + key = Key, + val = '_', + _ = '_' + }, + try ets:match_object(?CACHE, Pattern) of [] -> missing; - Docs -> + Entries -> + Docs = lists:map(fun(#entry{key = K, val = V}) -> + ddoc_cache_lru:accessed(K), + V + end, Entries), Sorted = lists:sort( fun (#doc{deleted=DelL, revs=L}, #doc{deleted=DelR, revs=R}) -> {not DelL, L} > {not DelR, R} @@ -133,40 +127,22 @@ recover_validation_funs(DbName) -> end, DDocs), {ok, Funs}. -handle_db_event(ShardDbName, created, St) -> - gen_server:cast(?MODULE, {evict, mem3:dbname(ShardDbName)}), - {ok, St}; -handle_db_event(ShardDbName, deleted, St) -> - gen_server:cast(?MODULE, {evict, mem3:dbname(ShardDbName)}), - {ok, St}; -handle_db_event(_DbName, _Event, St) -> - {ok, St}. init(_) -> process_flag(trap_exit, true), - _ = ets:new(?OPENING, [set, protected, named_table, {keypos, #opener.key}]), - {ok, Evictor} = couch_event:link_listener( - ?MODULE, handle_db_event, nil, [all_dbs] - ), - {ok, #st{ - evictor = Evictor - }}. - -terminate(_Reason, St) -> - case is_pid(St#st.evictor) of - true -> exit(St#st.evictor, kill); - false -> ok - end, + {ok, #st{}}. + +terminate(_Reason, _St) -> ok. handle_call({open, OpenerKey}, From, St) -> - case ets:lookup(?OPENING, OpenerKey) of + case ets:lookup(?OPENERS, OpenerKey) of [#opener{clients=Clients}=O] -> - ets:insert(?OPENING, O#opener{clients=[From | Clients]}), + ets:insert(?OPENERS, O#opener{clients=[From | Clients]}), {noreply, St}; [] -> Pid = spawn_link(?MODULE, fetch_doc_data, [OpenerKey]), - ets:insert(?OPENING, #opener{key=OpenerKey, pid=Pid, clients=[From]}), + ets:insert(?OPENERS, #opener{key=OpenerKey, pid=Pid, clients=[From]}), {noreply, St} end; @@ -174,38 +150,19 @@ handle_call(Msg, _From, St) -> {stop, {invalid_call, Msg}, {invalid_call, Msg}, St}. -handle_cast({evict, DbName}, St) -> - gen_server:abcast(mem3:nodes(), ?MODULE, {do_evict, DbName}), - {noreply, St}; - -handle_cast({evict, DbName, DDocIds}, St) -> - gen_server:abcast(mem3:nodes(), ?MODULE, {do_evict, DbName, DDocIds}), +% The do_evict clauses are upgrades while we're +% in a rolling reboot. +handle_cast({do_evict, _} = Msg, St) -> + gen_server:cast(?LRU, Msg), {noreply, St}; -handle_cast({do_evict, DbName}, St) -> - DDocIds = lists:flatten(ets_lru:match(?CACHE, {DbName, '$1', '_'}, '_')), - handle_cast({do_evict, DbName, DDocIds}, St); - -handle_cast({do_evict, DbName, DDocIds}, St) -> - CustomKeys = lists:flatten(ets_lru:match(?CACHE, {DbName, '$1'}, '_')), - lists:foreach(fun(Mod) -> - ets_lru:remove(?CACHE, {DbName, Mod}) - end, CustomKeys), - lists:foreach(fun(DDocId) -> - Revs = ets_lru:match(?CACHE, {DbName, DDocId, '$1'}, '_'), - lists:foreach(fun([Rev]) -> - ets_lru:remove(?CACHE, {DbName, DDocId, Rev}) - end, Revs) - end, DDocIds), +handle_cast({do_evict, _, _}, St) -> + gen_server:cast(?LRU, Msg) {noreply, St}; handle_cast(Msg, St) -> {stop, {invalid_cast, Msg}, St}. -handle_info({'EXIT', Pid, Reason}, #st{evictor=Pid}=St) -> - couch_log:error("ddoc_cache_opener evictor died ~w", [Reason]), - {ok, Evictor} = couch_event:link_listener(?MODULE, handle_db_event, nil, [all_dbs]), - {noreply, St#st{evictor=Evictor}}; handle_info({'EXIT', _Pid, {open_ok, OpenerKey, Resp}}, St) -> respond(OpenerKey, {open_ok, Resp}), @@ -217,10 +174,10 @@ handle_info({'EXIT', _Pid, {open_error, OpenerKey, Type, Error}}, St) -> handle_info({'EXIT', Pid, Reason}, St) -> Pattern = #opener{pid=Pid, _='_'}, - case ets:match_object(?OPENING, Pattern) of + case ets:match_object(?OPENERS, Pattern) of [#opener{key=OpenerKey, clients=Clients}] -> - _ = [gen_server:reply(C, {error, Reason}) || C <- Clients], - ets:delete(?OPENING, OpenerKey), + [gen_server:reply(C, {error, Reason}) || C <- Clients], + ets:delete(?OPENERS, OpenerKey), {noreply, St}; [] -> {stop, {unknown_pid_died, {Pid, Reason}}, St} @@ -238,14 +195,14 @@ code_change(_OldVsn, State, _Extra) -> ({dbname(), docid(), revision()}) -> no_return(). fetch_doc_data({DbName, validation_funs}=OpenerKey) -> {ok, Funs} = recover_validation_funs(DbName), - ok = ets_lru:insert(?CACHE, OpenerKey, Funs), + ok = ddoc_cache_lru:insert(OpenerKey, Funs), exit({open_ok, OpenerKey, {ok, Funs}}); fetch_doc_data({DbName, Mod}=OpenerKey) when is_atom(Mod) -> % This is not actually a docid but rather a custom cache key. % Treat the argument as a code module and invoke its recover function. try Mod:recover(DbName) of {ok, Result} -> - ok = ets_lru:insert(?CACHE, OpenerKey, Result), + ok = ddoc_cache_lru:insert(OpenerKey, Result), exit({open_ok, OpenerKey, {ok, Result}}); Else -> exit({open_ok, OpenerKey, Else}) @@ -258,7 +215,7 @@ fetch_doc_data({DbName, DocId}=OpenerKey) -> {ok, Doc} -> {RevDepth, [RevHash| _]} = Doc#doc.revs, Rev = {RevDepth, RevHash}, - ok = ets_lru:insert(?CACHE, {DbName, DocId, Rev}, Doc), + ok = ddoc_cache_lru:insert({DbName, DocId, Rev}, Doc), exit({open_ok, OpenerKey, {ok, Doc}}); Else -> exit({open_ok, OpenerKey, Else}) @@ -269,7 +226,7 @@ fetch_doc_data({DbName, DocId}=OpenerKey) -> fetch_doc_data({DbName, DocId, Rev}=OpenerKey) -> try recover_doc(DbName, DocId, Rev) of {ok, Doc} -> - ok = ets_lru:insert(?CACHE, {DbName, DocId, Rev}, Doc), + ok = ddoc_cache_lru:insert({DbName, DocId, Rev}, Doc), exit({open_ok, OpenerKey, {ok, Doc}}); Else -> exit({open_ok, OpenerKey, Else}) @@ -287,6 +244,6 @@ handle_open_response(Resp) -> end. respond(OpenerKey, Resp) -> - [#opener{clients=Clients}] = ets:lookup(?OPENING, OpenerKey), + [#opener{clients=Clients}] = ets:lookup(?OPENERS, OpenerKey), _ = [gen_server:reply(C, Resp) || C <- Clients], - ets:delete(?OPENING, OpenerKey). + ets:delete(?OPENERS, OpenerKey). diff --git a/src/ddoc_cache/src/ddoc_cache_sup.erl b/src/ddoc_cache/src/ddoc_cache_sup.erl index 85e90b3..ddb1232 100644 --- a/src/ddoc_cache/src/ddoc_cache_sup.erl +++ b/src/ddoc_cache/src/ddoc_cache_sup.erl @@ -27,12 +27,20 @@ start_link() -> init([]) -> Children = [ { + ddoc_cache_tables, + {ddoc_cache_tables, start_link, []}, + permanent, + 5000, + worker, + [ddoc_cache_tables] + }, + { ddoc_cache_lru, - {ets_lru, start_link, [ddoc_cache_lru, lru_opts()]}, + {ddoc_cache_lru, start_link, []}, permanent, 5000, worker, - [ets_lru] + [ddoc_cache_lru] }, { ddoc_cache_opener, @@ -43,25 +51,4 @@ init([]) -> [ddoc_cache_opener] } ], - {ok, {{one_for_one, 5, 10}, Children}}. - - -lru_opts() -> - case application:get_env(ddoc_cache, max_objects) of - {ok, MxObjs} when is_integer(MxObjs), MxObjs >= 0 -> - [{max_objects, MxObjs}]; - _ -> - [] - end ++ - case application:get_env(ddoc_cache, max_size) of - {ok, MxSize} when is_integer(MxSize), MxSize >= 0 -> - [{max_size, MxSize}]; - _ -> - [] - end ++ - case application:get_env(ddoc_cache, max_lifetime) of - {ok, MxLT} when is_integer(MxLT), MxLT >= 0 -> - [{max_lifetime, MxLT}]; - _ -> - [] - end. + {ok, {{one_for_all, 5, 10}, Children}}. diff --git a/src/ddoc_cache/src/ddoc_cache_tables.erl b/src/ddoc_cache/src/ddoc_cache_tables.erl new file mode 100644 index 0000000..9b35943 --- /dev/null +++ b/src/ddoc_cache/src/ddoc_cache_tables.erl @@ -0,0 +1,64 @@ +% Licensed 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(ddoc_cache_tables). +-behaviour(gen_server). +-vsn(1). + + +-export([ + start_link/0 +]). + +-export([ + init/1, + terminate/2, + handle_call/3, + handle_cast/2, + handle_info/2, + code_change/3 +]). + + +-include("ddoc_cache.hrl"). + + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + + +init(_) -> + BaseOpts = [public, named_table], + ets:new(?CACHE, [set, {read_concurrency, true}] ++ BaseOpts), + ets:new(?ATIMES, [sorted_set] ++ BaseOpts), + ets:new(?OPENING, [set, {keypos, #opener.key}] ++ BaseOpts), + {ok, nil}. + + +terminate(_Reason, _St) -> + ok. + + +handle_call(Msg, _From, St) -> + {stop, {invalid_call, Msg}, {invalid_call, Msg}, St}. + + +handle_cast(Msg, St) -> + {stop, {invalid_cast, Msg}, St}. + + +handle_info(Msg, St) -> + {stop, {invalid_info, Msg}, St}. + + +code_change(_OldVsn, St, _Extra) -> + {ok, St}. -- To stop receiving notification emails like this one, please contact "commits@couchdb.apache.org" .