summaryrefslogtreecommitdiff
path: root/src/couch/src/couch_lru.erl
blob: 618a0144fac36367a12a77da424246d7fde51560 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
% 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(couch_lru).
-export([new/0, insert/2, update/2, close/1]).

-include("couch_server_int.hrl").

new() ->
    {gb_trees:empty(), dict:new()}.

insert(DbName, {Tree0, Dict0}) ->
    Lru = couch_util:unique_monotonic_integer(),
    {gb_trees:insert(Lru, DbName, Tree0), dict:store(DbName, Lru, Dict0)}.

update(DbName, {Tree0, Dict0}) ->
    case dict:find(DbName, Dict0) of
    {ok, Old} ->
        New = couch_util:unique_monotonic_integer(),
        Tree = gb_trees:insert(New, DbName, gb_trees:delete(Old, Tree0)),
        Dict = dict:store(DbName, New, Dict0),
        {Tree, Dict};
    error ->
        % We closed this database before processing the update.  Ignore
        {Tree0, Dict0}
    end.

%% Attempt to close the oldest idle database.
close({Tree, _} = Cache) ->
    close_int(gb_trees:next(gb_trees:iterator(Tree)), Cache).

%% internals

close_int(none, _) ->
    false;
close_int({Lru, DbName, Iter}, {Tree, Dict} = Cache) ->
    CouchDbs = couch_server:couch_dbs(DbName),
    CouchDbsPidToName = couch_server:couch_dbs_pid_to_name(DbName),

    case ets:update_element(CouchDbs, DbName, {#entry.lock, locked}) of
    true ->
        [#entry{db = Db, pid = Pid}] = ets:lookup(CouchDbs, DbName),
        case couch_db:is_idle(Db) of true ->
            true = ets:delete(CouchDbs, DbName),
            true = ets:delete(CouchDbsPidToName, Pid),
            exit(Pid, kill),
            {true, {gb_trees:delete(Lru, Tree), dict:erase(DbName, Dict)}};
        false ->
            ElemSpec = {#entry.lock, unlocked},
            true = ets:update_element(CouchDbs, DbName, ElemSpec),
            couch_stats:increment_counter([couchdb, couch_server, lru_skip]),
            close_int(gb_trees:next(Iter), update(DbName, Cache))
        end;
    false ->
        NewTree = gb_trees:delete(Lru, Tree),
        NewIter = gb_trees:iterator(NewTree),
        close_int(gb_trees:next(NewIter), {NewTree, dict:erase(DbName, Dict)})
end.