summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@gmail.com>2021-04-14 00:46:31 -0400
committerNick Vatamaniuc <nickva@users.noreply.github.com>2021-04-16 17:44:43 -0400
commit9e4fc195efd9b1e65ca30744a1e2cf168bf58fbf (patch)
treed941ab978737a6c4af61f9b24632700b009b0785
parente31ae8d99173bc95801a06903764980b8c6ae7a0 (diff)
downloadcouchdb-9e4fc195efd9b1e65ca30744a1e2cf168bf58fbf.tar.gz
Remove most of the functionality from couch_server
Remove all the code related to opening and caching of databases. However, couch_server did a few other things such as: * Parse and maintain the CouchDB version * Return the server "uuid" value * The gen_server was monitoring config updates and hashing admin passwords when they were updated It was a 50/50 decision to move that functionality out to other modules completely or keep it where it is. Since it wasn't just a single thing, and the overall PR was getting rather large, opted to pair the exisiting code to the minimum, and then later we can do another round of cleanup and find a better place for those features.
-rw-r--r--src/couch/src/couch_server.erl872
1 files changed, 16 insertions, 856 deletions
diff --git a/src/couch/src/couch_server.erl b/src/couch/src/couch_server.erl
index 42eab738c..8fd074a78 100644
--- a/src/couch/src/couch_server.erl
+++ b/src/couch/src/couch_server.erl
@@ -15,43 +15,18 @@
-behaviour(config_listener).
-vsn(3).
--export([open/2,create/2,delete/2,get_version/0,get_version/1,get_git_sha/0,get_uuid/0]).
--export([all_databases/0, all_databases/2]).
+-export([get_version/0,get_version/1,get_git_sha/0,get_uuid/0]).
-export([init/1, handle_call/3,sup_start_link/0]).
--export([handle_cast/2,code_change/3,handle_info/2,terminate/2,format_status/2]).
--export([dev_start/0,is_admin/2,has_admins/0,get_stats/0]).
--export([close_lru/0]).
--export([close_db_if_idle/1]).
--export([delete_compaction_files/1]).
--export([exists/1]).
--export([get_engine_extensions/0]).
--export([get_engine_path/2]).
--export([lock/2, unlock/1]).
+-export([handle_cast/2,code_change/3,handle_info/2,terminate/2]).
+-export([is_admin/2,has_admins/0]).
% config_listener api
-export([handle_config_change/5, handle_config_terminate/3]).
-include_lib("couch/include/couch_db.hrl").
--include("couch_server_int.hrl").
--define(MAX_DBS_OPEN, 500).
-define(RELISTEN_DELAY, 5000).
--record(server,{
- root_dir = [],
- engines = [],
- max_dbs_open=?MAX_DBS_OPEN,
- dbs_open=0,
- start_time="",
- update_lru_on_read=true,
- lru = couch_lru:new()
- }).
-
-dev_start() ->
- couch:stop(),
- up_to_date = make:all([load, debug_info]),
- couch:start().
-
get_version() ->
?COUCHDB_VERSION. %% Defined in rebar.config.script
get_version(short) ->
@@ -70,138 +45,9 @@ get_uuid() ->
UUID -> ?l2b(UUID)
end.
-get_stats() ->
- {ok, #server{start_time=Time,dbs_open=Open}} =
- gen_server:call(couch_server, get_server),
- [{start_time, ?l2b(Time)}, {dbs_open, Open}].
-
sup_start_link() ->
gen_server:start_link({local, couch_server}, couch_server, [], []).
-open(DbName, Options) ->
- try
- validate_open_or_create(DbName, Options),
- open_int(DbName, Options)
- catch throw:{?MODULE, Error} ->
- Error
- end.
-
-open_int(DbName, Options0) ->
- Ctx = couch_util:get_value(user_ctx, Options0, #user_ctx{}),
- case ets:lookup(couch_dbs, DbName) of
- [#entry{db = Db0, lock = Lock} = Entry] when Lock =/= locked ->
- update_lru(DbName, Entry#entry.db_options),
- {ok, Db1} = couch_db:incref(Db0),
- couch_db:set_user_ctx(Db1, Ctx);
- _ ->
- Options = maybe_add_sys_db_callbacks(DbName, Options0),
- Timeout = couch_util:get_value(timeout, Options, infinity),
- Create = couch_util:get_value(create_if_missing, Options, false),
- case gen_server:call(couch_server, {open, DbName, Options}, Timeout) of
- {ok, Db0} ->
- {ok, Db1} = couch_db:incref(Db0),
- couch_db:set_user_ctx(Db1, Ctx);
- {not_found, no_db_file} when Create ->
- couch_log:warning("creating missing database: ~s", [DbName]),
- couch_server:create(DbName, Options);
- Error ->
- Error
- end
- end.
-
-update_lru(DbName, Options) ->
- case config:get_boolean("couchdb", "update_lru_on_read", false) of
- true ->
- case lists:member(sys_db, Options) of
- false -> gen_server:cast(couch_server, {update_lru, DbName});
- true -> ok
- end;
- false ->
- ok
- end.
-
-close_lru() ->
- gen_server:call(couch_server, close_lru).
-
-create(DbName, Options) ->
- try
- validate_open_or_create(DbName, Options),
- create_int(DbName, Options)
- catch throw:{?MODULE, Error} ->
- Error
- end.
-
-create_int(DbName, Options0) ->
- Options = maybe_add_sys_db_callbacks(DbName, Options0),
- couch_partition:validate_dbname(DbName, Options),
- case gen_server:call(couch_server, {create, DbName, Options}, infinity) of
- {ok, Db0} ->
- Ctx = couch_util:get_value(user_ctx, Options, #user_ctx{}),
- {ok, Db1} = couch_db:incref(Db0),
- couch_db:set_user_ctx(Db1, Ctx);
- Error ->
- Error
- end.
-
-delete(DbName, Options) ->
- gen_server:call(couch_server, {delete, DbName, Options}, infinity).
-
-
-exists(DbName) ->
- RootDir = config:get("couchdb", "database_dir", "."),
- Engines = get_configured_engines(),
- Possible = get_possible_engines(DbName, RootDir, Engines),
- Possible /= [].
-
-
-delete_compaction_files(DbName) ->
- delete_compaction_files(DbName, []).
-
-delete_compaction_files(DbName, DelOpts) when is_list(DbName) ->
- RootDir = config:get("couchdb", "database_dir", "."),
- lists:foreach(fun({Ext, Engine}) ->
- FPath = make_filepath(RootDir, DbName, Ext),
- couch_db_engine:delete_compaction_files(Engine, RootDir, FPath, DelOpts)
- end, get_configured_engines()),
- ok;
-delete_compaction_files(DbName, DelOpts) when is_binary(DbName) ->
- delete_compaction_files(?b2l(DbName), DelOpts).
-
-maybe_add_sys_db_callbacks(DbName, Options) when is_binary(DbName) ->
- maybe_add_sys_db_callbacks(?b2l(DbName), Options);
-maybe_add_sys_db_callbacks(DbName, Options) ->
- DbsDbName = config:get("mem3", "shards_db", "_dbs"),
- NodesDbName = config:get("mem3", "nodes_db", "_nodes"),
-
- IsReplicatorDb = path_ends_with(DbName, "_replicator"),
- UsersDbSuffix = config:get("couchdb", "users_db_suffix", "_users"),
- IsUsersDb = path_ends_with(DbName, "_users")
- orelse path_ends_with(DbName, UsersDbSuffix),
- if
- DbName == DbsDbName ->
- [sys_db | Options];
- DbName == NodesDbName ->
- [sys_db | Options];
- IsReplicatorDb ->
- [{before_doc_update, fun couch_replicator_docs:before_doc_update/3},
- {after_doc_read, fun couch_replicator_docs:after_doc_read/2},
- sys_db | Options];
- IsUsersDb ->
- [{before_doc_update, fun couch_users_db:before_doc_update/3},
- {after_doc_read, fun couch_users_db:after_doc_read/2},
- sys_db | Options];
- true ->
- Options
- end.
-
-path_ends_with(Path, Suffix) when is_binary(Suffix) ->
- Suffix =:= couch_db:dbname_suffix(Path);
-path_ends_with(Path, Suffix) when is_list(Suffix) ->
- path_ends_with(Path, ?l2b(Suffix)).
-
-check_dbname(DbName) ->
- couch_db:validate_dbname(DbName).
-
is_admin(User, ClearPwd) ->
case config:get("admins", User) of
"-hashed-" ++ HashedPwdAndSalt ->
@@ -224,22 +70,9 @@ hash_admin_passwords(Persist) ->
config:set("admins", User, ?b2l(HashedPassword), Persist)
end, couch_passwords:get_unhashed_admins()).
-close_db_if_idle(DbName) ->
- case ets:lookup(couch_dbs, DbName) of
- [#entry{}] ->
- gen_server:cast(couch_server, {close_db_if_idle, DbName});
- [] ->
- ok
- end.
-
-
init([]) ->
- couch_util:set_mqd_off_heap(?MODULE),
- couch_util:set_process_priority(?MODULE, high),
-
% Mark being able to receive documents with an _access property as a supported feature
config:enable_feature('access-ready'),
-
% Mark if fips is enabled
case
erlang:function_exported(crypto, info_fips, 0) andalso
@@ -249,83 +82,28 @@ init([]) ->
false ->
ok
end,
+ ok = config:listen_for_changes(?MODULE, nil),
+ hash_admin_passwords(),
+ {ok, nil}.
- % read config and register for configuration changes
+handle_call(Msg, _From, Srv) ->
+ {stop, {bad_call, Msg}, Srv}.
- % just stop if one of the config settings change. couch_server_sup
- % will restart us and then we will pick up the new settings.
+handle_cast(Msg, Srv) ->
+ {stop, {bad_cast, Msg}, Srv}.
- RootDir = config:get("couchdb", "database_dir", "."),
- Engines = get_configured_engines(),
- MaxDbsOpen = list_to_integer(
- config:get("couchdb", "max_dbs_open", integer_to_list(?MAX_DBS_OPEN))),
- UpdateLruOnRead =
- config:get("couchdb", "update_lru_on_read", "false") =:= "true",
- ok = config:listen_for_changes(?MODULE, nil),
- ok = couch_file:init_delete_dir(RootDir),
- hash_admin_passwords(),
- ets:new(couch_dbs, [
- set,
- protected,
- named_table,
- {keypos, #entry.name},
- {read_concurrency, true}
- ]),
- ets:new(couch_dbs_pid_to_name, [set, protected, named_table]),
- ets:new(couch_dbs_locks, [
- set,
- public,
- named_table,
- {read_concurrency, true}
- ]),
- process_flag(trap_exit, true),
- {ok, #server{root_dir=RootDir,
- engines = Engines,
- max_dbs_open=MaxDbsOpen,
- update_lru_on_read=UpdateLruOnRead,
- start_time=couch_util:rfc1123_date()}}.
+handle_info(Msg, Srv) ->
+ {stop, {unknown_message, Msg}, Srv}.
-terminate(Reason, Srv) ->
- couch_log:error("couch_server terminating with ~p, state ~2048p",
- [Reason,
- Srv#server{lru = redacted}]),
- ets:foldl(fun(#entry{db = Db}, _) ->
- % Filter out any entry records for open_async
- % processes that haven't finished.
- if Db == undefined -> ok; true ->
- couch_util:shutdown_sync(couch_db:get_pid(Db))
- end
- end, nil, couch_dbs),
- ok.
+code_change(_OldVsn, Srv, _Extra) ->
+ {ok, Srv}.
-format_status(_Opt, [_PDict, Srv]) ->
- Scrubbed = Srv#server{lru=couch_lru:sizes(Srv#server.lru)},
- [{data, [{"State", ?record_to_keyval(server, Scrubbed)}]}].
+terminate(_Reason, _Srv) ->
+ ok.
-handle_config_change("couchdb", "database_dir", _, _, _) ->
- exit(whereis(couch_server), config_change),
- remove_handler;
-handle_config_change("couchdb", "update_lru_on_read", "true", _, _) ->
- {ok, gen_server:call(couch_server,{set_update_lru_on_read,true})};
-handle_config_change("couchdb", "update_lru_on_read", _, _, _) ->
- {ok, gen_server:call(couch_server,{set_update_lru_on_read,false})};
-handle_config_change("couchdb", "max_dbs_open", Max, _, _) when is_list(Max) ->
- {ok, gen_server:call(couch_server,{set_max_dbs_open,list_to_integer(Max)})};
-handle_config_change("couchdb", "max_dbs_open", _, _, _) ->
- {ok, gen_server:call(couch_server,{set_max_dbs_open,?MAX_DBS_OPEN})};
-handle_config_change("couchdb_engines", _, _, _, _) ->
- {ok, gen_server:call(couch_server, reload_engines)};
handle_config_change("admins", _, _, Persist, _) ->
% spawn here so couch event manager doesn't deadlock
{ok, spawn(fun() -> hash_admin_passwords(Persist) end)};
-handle_config_change("httpd", "authentication_handlers", _, _, _) ->
- {ok, couch_httpd:stop()};
-handle_config_change("httpd", "bind_address", _, _, _) ->
- {ok, couch_httpd:stop()};
-handle_config_change("httpd", "port", _, _, _) ->
- {ok, couch_httpd:stop()};
-handle_config_change("httpd", "max_connections", _, _, _) ->
- {ok, couch_httpd:stop()};
handle_config_change(_, _, _, _, _) ->
{ok, nil}.
@@ -333,621 +111,3 @@ handle_config_terminate(_, stop, _) ->
ok;
handle_config_terminate(_Server, _Reason, _State) ->
erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
-
-
-all_databases() ->
- {ok, DbList} = all_databases(
- fun(DbName, Acc) -> {ok, [DbName | Acc]} end, []),
- {ok, lists:usort(DbList)}.
-
-all_databases(Fun, Acc0) ->
- {ok, #server{root_dir=Root}} = gen_server:call(couch_server, get_server),
- NormRoot = couch_util:normpath(Root),
- Extensions = get_engine_extensions(),
- ExtRegExp = "(" ++ string:join(Extensions, "|") ++ ")",
- RegExp =
- "^[a-z0-9\\_\\$()\\+\\-]*" % stock CouchDB name regex
- "(\\.[0-9]{10,})?" % optional shard timestamp
- "\\." ++ ExtRegExp ++ "$", % filename extension
- FinalAcc = try
- couch_util:fold_files(Root,
- RegExp,
- true,
- fun(Filename, AccIn) ->
- NormFilename = couch_util:normpath(Filename),
- case NormFilename -- NormRoot of
- [$/ | RelativeFilename] -> ok;
- RelativeFilename -> ok
- end,
- Ext = filename:extension(RelativeFilename),
- case Fun(?l2b(filename:rootname(RelativeFilename, Ext)), AccIn) of
- {ok, NewAcc} -> NewAcc;
- {stop, NewAcc} -> throw({stop, Fun, NewAcc})
- end
- end, Acc0)
- catch throw:{stop, Fun, Acc1} ->
- Acc1
- end,
- {ok, FinalAcc}.
-
-
-make_room(Server, Options) ->
- case lists:member(sys_db, Options) of
- false -> maybe_close_lru_db(Server);
- true -> {ok, Server}
- end.
-
-maybe_close_lru_db(#server{dbs_open=NumOpen, max_dbs_open=MaxOpen}=Server)
- when NumOpen < MaxOpen ->
- {ok, Server};
-maybe_close_lru_db(#server{lru=Lru}=Server) ->
- case couch_lru:close(Lru) of
- {true, NewLru} ->
- {ok, db_closed(Server#server{lru = NewLru}, [])};
- false ->
- {error, all_dbs_active}
- end.
-
-open_async(Server, From, DbName, Options) ->
- NoLRUServer = Server#server{
- lru = redacted
- },
- Parent = self(),
- T0 = os:timestamp(),
- Opener = spawn_link(fun() ->
- Res = open_async_int(NoLRUServer, DbName, Options),
- IsSuccess = case Res of
- {ok, _} -> true;
- _ -> false
- end,
- case IsSuccess andalso lists:member(create, Options) of
- true ->
- couch_event:notify(DbName, created);
- false ->
- ok
- end,
- gen_server:call(Parent, {open_result, DbName, Res}, infinity),
- unlink(Parent),
- case IsSuccess of
- true ->
- % Track latency times for successful opens
- Diff = timer:now_diff(os:timestamp(), T0) / 1000,
- couch_stats:update_histogram([couchdb, db_open_time], Diff);
- false ->
- % Log unsuccessful open results
- couch_log:info("open_result error ~p for ~s", [Res, DbName])
- end
- end),
- ReqType = case lists:member(create, Options) of
- true -> create;
- false -> open
- end,
- true = ets:insert(couch_dbs, #entry{
- name = DbName,
- pid = Opener,
- lock = locked,
- waiters = [From],
- req_type = ReqType,
- db_options = Options
- }),
- true = ets:insert(couch_dbs_pid_to_name, {Opener, DbName}),
- db_opened(Server, Options).
-
-open_async_int(Server, DbName, Options) ->
- DbNameList = binary_to_list(DbName),
- case check_dbname(DbNameList) of
- ok ->
- case get_engine(Server, DbNameList, Options) of
- {ok, {Module, FilePath}} ->
- couch_db:start_link(Module, DbName, FilePath, Options);
- Error2 ->
- Error2
- end;
- Error1 ->
- Error1
- end.
-
-handle_call(close_lru, _From, #server{lru=Lru} = Server) ->
- case couch_lru:close(Lru) of
- {true, NewLru} ->
- {reply, ok, db_closed(Server#server{lru = NewLru}, [])};
- false ->
- {reply, {error, all_dbs_active}, Server}
- end;
-handle_call(open_dbs_count, _From, Server) ->
- {reply, Server#server.dbs_open, Server};
-handle_call({set_update_lru_on_read, UpdateOnRead}, _From, Server) ->
- {reply, ok, Server#server{update_lru_on_read=UpdateOnRead}};
-handle_call({set_max_dbs_open, Max}, _From, Server) ->
- {reply, ok, Server#server{max_dbs_open=Max}};
-handle_call(reload_engines, _From, Server) ->
- {reply, ok, Server#server{engines = get_configured_engines()}};
-handle_call(get_server, _From, Server) ->
- {reply, {ok, Server}, Server};
-handle_call({open_result, DbName, {ok, Db}}, {Opener, _}, Server) ->
- true = ets:delete(couch_dbs_pid_to_name, Opener),
- DbPid = couch_db:get_pid(Db),
- case ets:lookup(couch_dbs, DbName) of
- [] ->
- % db was deleted during async open
- exit(DbPid, kill),
- {reply, ok, Server};
- [#entry{pid = Opener, req_type = ReqType, waiters = Waiters} = Entry] ->
- link(DbPid),
- [gen_server:reply(Waiter, {ok, Db}) || Waiter <- Waiters],
- % Cancel the creation request if it exists.
- case ReqType of
- {create, DbName, _Options, CrFrom} ->
- gen_server:reply(CrFrom, file_exists);
- _ ->
- ok
- end,
- true = ets:insert(couch_dbs, #entry{
- name = DbName,
- db = Db,
- pid = DbPid,
- lock = unlocked,
- db_options = Entry#entry.db_options,
- start_time = couch_db:get_instance_start_time(Db)
- }),
- true = ets:insert(couch_dbs_pid_to_name, {DbPid, DbName}),
- Lru = case couch_db:is_system_db(Db) of
- false ->
- couch_lru:insert(DbName, Server#server.lru);
- true ->
- Server#server.lru
- end,
- {reply, ok, Server#server{lru = Lru}};
- [#entry{}] ->
- % A mismatched opener pid means that this open_result message
- % was in our mailbox but is now stale. Mostly ignore
- % it except to ensure that the db pid is super dead.
- exit(couch_db:get_pid(Db), kill),
- {reply, ok, Server}
- end;
-handle_call({open_result, DbName, {error, eexist}}, From, Server) ->
- handle_call({open_result, DbName, file_exists}, From, Server);
-handle_call({open_result, DbName, Error}, {Opener, _}, Server) ->
- case ets:lookup(couch_dbs, DbName) of
- [] ->
- % db was deleted during async open
- {reply, ok, Server};
- [#entry{pid = Opener, req_type = ReqType, waiters = Waiters} = Entry] ->
- [gen_server:reply(Waiter, Error) || Waiter <- Waiters],
- true = ets:delete(couch_dbs, DbName),
- true = ets:delete(couch_dbs_pid_to_name, Opener),
- NewServer = case ReqType of
- {create, DbName, Options, CrFrom} ->
- open_async(Server, CrFrom, DbName, Options);
- _ ->
- Server
- end,
- {reply, ok, db_closed(NewServer, Entry#entry.db_options)};
- [#entry{}] ->
- % A mismatched pid means that this open_result message
- % was in our mailbox and is now stale. Ignore it.
- {reply, ok, Server}
- end;
-handle_call({open, DbName, Options}, From, Server) ->
- case ets:lookup(couch_dbs, DbName) of
- [] ->
- case make_room(Server, Options) of
- {ok, Server2} ->
- {noreply, open_async(Server2, From, DbName, Options)};
- CloseError ->
- {reply, CloseError, Server}
- end;
- [#entry{waiters = Waiters} = Entry] when is_list(Waiters) ->
- true = ets:insert(couch_dbs, Entry#entry{waiters = [From | Waiters]}),
- NumWaiters = length(Waiters),
- if NumWaiters =< 10 orelse NumWaiters rem 10 /= 0 -> ok; true ->
- Fmt = "~b clients waiting to open db ~s",
- couch_log:info(Fmt, [length(Waiters), DbName])
- end,
- {noreply, Server};
- [#entry{db = Db}] ->
- {reply, {ok, Db}, Server}
- end;
-handle_call({create, DbName, Options}, From, Server) ->
- case ets:lookup(couch_dbs, DbName) of
- [] ->
- case make_room(Server, Options) of
- {ok, Server2} ->
- CrOptions = [create | Options],
- {noreply, open_async(Server2, From, DbName, CrOptions)};
- CloseError ->
- {reply, CloseError, Server}
- end;
- [#entry{req_type = open} = Entry] ->
- % We're trying to create a database while someone is in
- % the middle of trying to open it. We allow one creator
- % to wait while we figure out if it'll succeed.
- CrOptions = [create | Options],
- Req = {create, DbName, CrOptions, From},
- true = ets:insert(couch_dbs, Entry#entry{req_type = Req}),
- {noreply, Server};
- [_AlreadyRunningDb] ->
- {reply, file_exists, Server}
- end;
-handle_call({delete, DbName, Options}, _From, Server) ->
- DbNameList = binary_to_list(DbName),
- case check_dbname(DbNameList) of
- ok ->
- Server2 =
- case ets:lookup(couch_dbs, DbName) of
- [] -> Server;
- [#entry{pid = Pid, waiters = Waiters} = Entry] when is_list(Waiters) ->
- true = ets:delete(couch_dbs, DbName),
- true = ets:delete(couch_dbs_pid_to_name, Pid),
- exit(Pid, kill),
- [gen_server:reply(Waiter, not_found) || Waiter <- Waiters],
- db_closed(Server, Entry#entry.db_options);
- [#entry{pid = Pid} = Entry] ->
- true = ets:delete(couch_dbs, DbName),
- true = ets:delete(couch_dbs_pid_to_name, Pid),
- exit(Pid, kill),
- db_closed(Server, Entry#entry.db_options)
- end,
-
- couch_db_plugin:on_delete(DbName, Options),
-
- DelOpt = [{context, delete} | Options],
-
- % Make sure and remove all compaction data
- delete_compaction_files(DbNameList, Options),
-
- {ok, {Engine, FilePath}} = get_engine(Server, DbNameList),
- RootDir = Server#server.root_dir,
- case couch_db_engine:delete(Engine, RootDir, FilePath, DelOpt) of
- ok ->
- couch_event:notify(DbName, deleted),
- {reply, ok, Server2};
- {error, enoent} ->
- {reply, not_found, Server2};
- Else ->
- {reply, Else, Server2}
- end;
- Error ->
- {reply, Error, Server}
- end;
-handle_call({db_updated, Db}, _From, Server0) ->
- DbName = couch_db:name(Db),
- StartTime = couch_db:get_instance_start_time(Db),
- Server = try ets:lookup_element(couch_dbs, DbName, #entry.start_time) of
- StartTime ->
- true = ets:update_element(couch_dbs, DbName, {#entry.db, Db}),
- Lru = case couch_db:is_system_db(Db) of
- false -> couch_lru:update(DbName, Server0#server.lru);
- true -> Server0#server.lru
- end,
- Server0#server{lru = Lru};
- _ ->
- Server0
- catch _:_ ->
- Server0
- end,
- {reply, ok, Server}.
-
-handle_cast({update_lru, DbName}, #server{lru = Lru, update_lru_on_read=true} = Server) ->
- {noreply, Server#server{lru = couch_lru:update(DbName, Lru)}};
-handle_cast({update_lru, _DbName}, Server) ->
- {noreply, Server};
-handle_cast({close_db_if_idle, DbName}, Server) ->
- case ets:update_element(couch_dbs, DbName, {#entry.lock, locked}) of
- true ->
- [#entry{db = Db, db_options = DbOpts}] = ets:lookup(couch_dbs, DbName),
- case couch_db:is_idle(Db) of
- true ->
- DbPid = couch_db:get_pid(Db),
- true = ets:delete(couch_dbs, DbName),
- true = ets:delete(couch_dbs_pid_to_name, DbPid),
- exit(DbPid, kill),
- {noreply, db_closed(Server, DbOpts)};
- false ->
- true = ets:update_element(
- couch_dbs, DbName, {#entry.lock, unlocked}),
- {noreply, Server}
- end;
- false ->
- {noreply, Server}
- end;
-
-handle_cast(Msg, Server) ->
- {stop, {unknown_cast_message, Msg}, Server}.
-
-code_change(_OldVsn, #server{}=State, _Extra) ->
- {ok, State}.
-
-handle_info({'EXIT', _Pid, config_change}, Server) ->
- {stop, config_change, Server};
-handle_info({'EXIT', Pid, Reason}, Server) ->
- case ets:lookup(couch_dbs_pid_to_name, Pid) of
- [{Pid, DbName}] ->
- [#entry{waiters = Waiters} = Entry] = ets:lookup(couch_dbs, DbName),
- if Reason /= snappy_nif_not_loaded -> ok; true ->
- Msg = io_lib:format("To open the database `~s`, Apache CouchDB "
- "must be built with Erlang OTP R13B04 or higher.", [DbName]),
- couch_log:error(Msg, [])
- end,
- % We kill databases on purpose so there's no reason
- % to log that fact. So we restrict logging to "interesting"
- % reasons.
- if Reason == normal orelse Reason == killed -> ok; true ->
- couch_log:info("db ~s died with reason ~p", [DbName, Reason])
- end,
- if not is_list(Waiters) -> ok; true ->
- [gen_server:reply(Waiter, Reason) || Waiter <- Waiters]
- end,
- true = ets:delete(couch_dbs, DbName),
- true = ets:delete(couch_dbs_pid_to_name, Pid),
- {noreply, db_closed(Server, Entry#entry.db_options)};
- [] ->
- {noreply, Server}
- end;
-handle_info(restart_config_listener, State) ->
- ok = config:listen_for_changes(?MODULE, nil),
- {noreply, State};
-handle_info(Info, Server) ->
- {stop, {unknown_message, Info}, Server}.
-
-db_opened(Server, Options) ->
- case lists:member(sys_db, Options) of
- false -> Server#server{dbs_open=Server#server.dbs_open + 1};
- true -> Server
- end.
-
-db_closed(Server, Options) ->
- case lists:member(sys_db, Options) of
- false -> Server#server{dbs_open=Server#server.dbs_open - 1};
- true -> Server
- end.
-
-validate_open_or_create(DbName, Options) ->
- case check_dbname(DbName) of
- ok ->
- ok;
- DbNameError ->
- throw({?MODULE, DbNameError})
- end,
-
- case check_engine(Options) of
- ok ->
- ok;
- EngineError ->
- throw({?MODULE, EngineError})
- end,
-
- case ets:lookup(couch_dbs_locks, DbName) of
- [] ->
- ok;
- [{DbName, Reason}] ->
- throw({?MODULE, {error, {locked, Reason}}})
- end.
-
-get_configured_engines() ->
- ConfigEntries = config:get("couchdb_engines"),
- Engines = lists:flatmap(fun({Extension, ModuleStr}) ->
- try
- [{Extension, list_to_atom(ModuleStr)}]
- catch _T:_R ->
- []
- end
- end, ConfigEntries),
- case Engines of
- [] ->
- [{"couch", couch_bt_engine}];
- Else ->
- Else
- end.
-
-
-get_engine(Server, DbName, Options) ->
- #server{
- root_dir = RootDir,
- engines = Engines
- } = Server,
- case couch_util:get_value(engine, Options) of
- Ext when is_binary(Ext) ->
- ExtStr = binary_to_list(Ext),
- case lists:keyfind(ExtStr, 1, Engines) of
- {ExtStr, Engine} ->
- Path = make_filepath(RootDir, DbName, ExtStr),
- {ok, {Engine, Path}};
- false ->
- {error, {invalid_engine_extension, Ext}}
- end;
- _ ->
- get_engine(Server, DbName)
- end.
-
-
-get_engine(Server, DbName) ->
- #server{
- root_dir = RootDir,
- engines = Engines
- } = Server,
- Possible = get_possible_engines(DbName, RootDir, Engines),
- case Possible of
- [] ->
- get_default_engine(Server, DbName);
- [Engine] ->
- {ok, Engine};
- _ ->
- erlang:error(engine_conflict)
- end.
-
-
-get_possible_engines(DbName, RootDir, Engines) ->
- lists:foldl(fun({Extension, Engine}, Acc) ->
- Path = make_filepath(RootDir, DbName, Extension),
- case couch_db_engine:exists(Engine, Path) of
- true ->
- [{Engine, Path} | Acc];
- false ->
- Acc
- end
- end, [], Engines).
-
-
-get_default_engine(Server, DbName) ->
- #server{
- root_dir = RootDir,
- engines = Engines
- } = Server,
- Default = {couch_bt_engine, make_filepath(RootDir, DbName, "couch")},
- case config:get("couchdb", "default_engine") of
- Extension when is_list(Extension) ->
- case lists:keyfind(Extension, 1, Engines) of
- {Extension, Module} ->
- {ok, {Module, make_filepath(RootDir, DbName, Extension)}};
- false ->
- Fmt = "Invalid storage engine extension ~s,"
- " configured engine extensions are: ~s",
- Exts = [E || {E, _} <- Engines],
- Args = [Extension, string:join(Exts, ", ")],
- couch_log:error(Fmt, Args),
- {ok, Default}
- end;
- _ ->
- {ok, Default}
- end.
-
-
-make_filepath(RootDir, DbName, Extension) when is_binary(RootDir) ->
- make_filepath(binary_to_list(RootDir), DbName, Extension);
-make_filepath(RootDir, DbName, Extension) when is_binary(DbName) ->
- make_filepath(RootDir, binary_to_list(DbName), Extension);
-make_filepath(RootDir, DbName, Extension) when is_binary(Extension) ->
- make_filepath(RootDir, DbName, binary_to_list(Extension));
-make_filepath(RootDir, DbName, Extension) ->
- filename:join([RootDir, "./" ++ DbName ++ "." ++ Extension]).
-
-
-get_engine_extensions() ->
- case config:get("couchdb_engines") of
- [] ->
- ["couch"];
- Entries ->
- [Ext || {Ext, _Mod} <- Entries]
- end.
-
-
-check_engine(Options) ->
- case couch_util:get_value(engine, Options) of
- Ext when is_binary(Ext) ->
- ExtStr = binary_to_list(Ext),
- Extensions = get_engine_extensions(),
- case lists:member(ExtStr, Extensions) of
- true ->
- ok;
- false ->
- {error, {invalid_engine_extension, Ext}}
- end;
- _ ->
- ok
- end.
-
-
-get_engine_path(DbName, Engine) when is_binary(DbName), is_atom(Engine) ->
- RootDir = config:get("couchdb", "database_dir", "."),
- case lists:keyfind(Engine, 2, get_configured_engines()) of
- {Ext, Engine} ->
- {ok, make_filepath(RootDir, DbName, Ext)};
- false ->
- {error, {invalid_engine, Engine}}
- end.
-
-lock(DbName, Reason) when is_binary(DbName), is_binary(Reason) ->
- case ets:lookup(couch_dbs, DbName) of
- [] ->
- true = ets:insert(couch_dbs_locks, {DbName, Reason}),
- ok;
- [#entry{}] ->
- {error, already_opened}
- end.
-
-unlock(DbName) when is_binary(DbName) ->
- true = ets:delete(couch_dbs_locks, DbName),
- ok.
-
-
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
-
-setup_all() ->
- ok = meck:new(config, [passthrough]),
- ok = meck:expect(config, get, fun config_get/3),
- ok.
-
-teardown_all(_) ->
- meck:unload().
-
-config_get("couchdb", "users_db_suffix", _) -> "users_db";
-config_get(_, _, _) -> undefined.
-
-maybe_add_sys_db_callbacks_pass_test_() ->
- {
- setup,
- fun setup_all/0,
- fun teardown_all/1,
- [
- fun should_add_sys_db_callbacks/0,
- fun should_not_add_sys_db_callbacks/0
- ]
- }.
-
-should_add_sys_db_callbacks() ->
- Cases = [
- "shards/00000000-3fffffff/foo/users_db.1415960794.couch",
- "shards/00000000-3fffffff/foo/users_db.1415960794",
- "shards/00000000-3fffffff/foo/users_db",
- "shards/00000000-3fffffff/users_db.1415960794.couch",
- "shards/00000000-3fffffff/users_db.1415960794",
- "shards/00000000-3fffffff/users_db",
-
- "shards/00000000-3fffffff/_users.1415960794.couch",
- "shards/00000000-3fffffff/_users.1415960794",
- "shards/00000000-3fffffff/_users",
-
- "foo/users_db.couch",
- "foo/users_db",
- "users_db.couch",
- "users_db",
- "foo/_users.couch",
- "foo/_users",
- "_users.couch",
- "_users",
-
- "shards/00000000-3fffffff/foo/_replicator.1415960794.couch",
- "shards/00000000-3fffffff/foo/_replicator.1415960794",
- "shards/00000000-3fffffff/_replicator",
- "foo/_replicator.couch",
- "foo/_replicator",
- "_replicator.couch",
- "_replicator"
- ],
- lists:foreach(fun(DbName) ->
- check_case(DbName, true),
- check_case(?l2b(DbName), true)
- end, Cases).
-
-should_not_add_sys_db_callbacks() ->
- Cases = [
- "shards/00000000-3fffffff/foo/mydb.1415960794.couch",
- "shards/00000000-3fffffff/foo/mydb.1415960794",
- "shards/00000000-3fffffff/mydb",
- "foo/mydb.couch",
- "foo/mydb",
- "mydb.couch",
- "mydb"
- ],
- lists:foreach(fun(DbName) ->
- check_case(DbName, false),
- check_case(?l2b(DbName), false)
- end, Cases).
-
-check_case(DbName, IsAdded) ->
- Options = maybe_add_sys_db_callbacks(DbName, [other_options]),
- ?assertEqual(IsAdded, lists:member(sys_db, Options)).
-
--endif.