diff options
author | Paul J. Davis <paul.joseph.davis@gmail.com> | 2017-02-03 10:20:30 -0600 |
---|---|---|
committer | Paul J. Davis <paul.joseph.davis@gmail.com> | 2017-09-27 15:35:59 -0500 |
commit | 35fcd7ec986e6c3747284e66ff39593aae339a3e (patch) | |
tree | 6bc13ba6abefaa5dcc6184b95653312e1691511c | |
parent | 20a1021307030f199b1a8a8430aa201ea0a042cf (diff) | |
download | couchdb-35fcd7ec986e6c3747284e66ff39593aae339a3e.tar.gz |
Update couch_server to not use the db record
This removes introspection of the #db record by couch_server. While its
required for the pluggable storage engine upgrade, its also nice to
remove the hacky overloading of #db record fields for couch_server
logic.
COUCHDB-3288
-rw-r--r-- | src/couch/src/couch_db.erl | 17 | ||||
-rw-r--r-- | src/couch/src/couch_lru.erl | 9 | ||||
-rw-r--r-- | src/couch/src/couch_server.erl | 135 | ||||
-rw-r--r-- | src/couch/src/couch_server_int.hrl | 23 |
4 files changed, 115 insertions, 69 deletions
diff --git a/src/couch/src/couch_db.erl b/src/couch/src/couch_db.erl index 09d60eec7..183a6b45d 100644 --- a/src/couch/src/couch_db.erl +++ b/src/couch/src/couch_db.erl @@ -16,6 +16,7 @@ create/2, open/2, open_int/2, + incref/1, reopen/1, close/1, @@ -34,7 +35,9 @@ get_db_info/1, get_doc_count/1, get_epochs/1, + get_instance_start_time/1, get_last_purged/1, + get_pid/1, get_revs_limit/1, get_security/1, get_update_seq/1, @@ -46,6 +49,7 @@ increment_update_seq/1, set_revs_limit/2, set_security/2, + set_user_ctx/2, ensure_full_commit/1, ensure_full_commit/2, @@ -181,6 +185,10 @@ reopen(#db{main_pid = Pid, fd = Fd, fd_monitor = OldRef, user_ctx = UserCtx}) -> {ok, NewDb#db{user_ctx = UserCtx, fd_monitor = NewRef}} end. +incref(#db{fd = Fd} = Db) -> + Ref = erlang:monitor(process, Fd), + {ok, Db#db{fd_monitor = Ref}}. + is_system_db(#db{options = Options}) -> lists:member(sys_db, Options). @@ -381,6 +389,9 @@ get_last_purged(#db{}=Db) -> couch_file:pread_term(Db#db.fd, Pointer) end. +get_pid(#db{main_pid = Pid}) -> + Pid. + get_doc_count(Db) -> {ok, {Count, _, _}} = couch_btree:full_reduce(Db#db.id_tree), {ok, Count}. @@ -393,6 +404,9 @@ get_epochs(#db{}=Db) -> validate_epochs(Epochs), Epochs. +get_instance_start_time(#db{instance_start_time = IST}) -> + IST. + get_compacted_seq(#db{}=Db) -> couch_db_header:compacted_seq(Db#db.header). @@ -585,6 +599,9 @@ set_security(#db{main_pid=Pid}=Db, {NewSecProps}) when is_list(NewSecProps) -> set_security(_, _) -> throw(bad_request). +set_user_ctx(#db{} = Db, UserCtx) -> + {ok, Db#db{user_ctx = UserCtx}}. + validate_security_object(SecProps) -> Admins = couch_util:get_value(<<"admins">>, SecProps, {[]}), % we fallback to readers here for backwards compatibility diff --git a/src/couch/src/couch_lru.erl b/src/couch/src/couch_lru.erl index b58a623d6..023515e7c 100644 --- a/src/couch/src/couch_lru.erl +++ b/src/couch/src/couch_lru.erl @@ -13,7 +13,7 @@ -module(couch_lru). -export([new/0, insert/2, update/2, close/1]). --include_lib("couch/include/couch_db.hrl"). +-include("couch_server_int.hrl"). new() -> {gb_trees:empty(), dict:new()}. @@ -43,16 +43,17 @@ close({Tree, _} = Cache) -> close_int(none, _) -> false; close_int({Lru, DbName, Iter}, {Tree, Dict} = Cache) -> - case ets:update_element(couch_dbs, DbName, {#db.fd_monitor, locked}) of + case ets:update_element(couch_dbs, DbName, {#entry.lock, locked}) of true -> - [#db{main_pid = Pid} = Db] = ets:lookup(couch_dbs, DbName), + [#entry{db = Db, pid = Pid}] = ets:lookup(couch_dbs, DbName), case couch_db:is_idle(Db) of true -> true = ets:delete(couch_dbs, DbName), true = ets:delete(couch_dbs_pid_to_name, Pid), exit(Pid, kill), {true, {gb_trees:delete(Lru, Tree), dict:erase(DbName, Dict)}}; false -> - true = ets:update_element(couch_dbs, DbName, {#db.fd_monitor, nil}), + ElemSpec = {#entry.lock, unlocked}, + true = ets:update_element(couch_dbs, DbName, ElemSpec), couch_stats:increment_counter([couchdb, couch_server, lru_skip]), close_int(gb_trees:next(Iter), update(DbName, Cache)) end; diff --git a/src/couch/src/couch_server.erl b/src/couch/src/couch_server.erl index 24016e05c..efcef714e 100644 --- a/src/couch/src/couch_server.erl +++ b/src/couch/src/couch_server.erl @@ -27,6 +27,7 @@ -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). @@ -74,16 +75,18 @@ sup_start_link() -> open(DbName, Options0) -> Ctx = couch_util:get_value(user_ctx, Options0, #user_ctx{}), case ets:lookup(couch_dbs, DbName) of - [#db{fd=Fd, fd_monitor=Lock, options=Options} = Db] when Lock =/= locked -> - update_lru(DbName, Options), - {ok, Db#db{user_ctx=Ctx, fd_monitor=erlang:monitor(process,Fd)}}; + [#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, #db{fd=Fd} = Db} -> - {ok, Db#db{user_ctx=Ctx, fd_monitor=erlang:monitor(process,Fd)}}; + {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); @@ -104,9 +107,10 @@ close_lru() -> create(DbName, Options0) -> Options = maybe_add_sys_db_callbacks(DbName, Options0), case gen_server:call(couch_server, {create, DbName, Options}, infinity) of - {ok, #db{fd=Fd} = Db} -> + {ok, Db0} -> Ctx = couch_util:get_value(user_ctx, Options, #user_ctx{}), - {ok, Db#db{user_ctx=Ctx, fd_monitor=erlang:monitor(process,Fd)}}; + {ok, Db1} = couch_db:incref(Db0), + couch_db:set_user_ctx(Db1, Ctx); Error -> Error end. @@ -176,9 +180,9 @@ hash_admin_passwords(Persist) -> close_db_if_idle(DbName) -> case ets:lookup(couch_dbs, DbName) of - [#db{}] -> + [#entry{}] -> gen_server:cast(couch_server, {close_db_if_idle, DbName}); - _ -> + [] -> ok end. @@ -197,7 +201,7 @@ init([]) -> 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, #db.name}]), + ets:new(couch_dbs, [set, protected, named_table, {keypos, #entry.name}]), ets:new(couch_dbs_pid_to_name, [set, protected, named_table]), process_flag(trap_exit, true), {ok, #server{root_dir=RootDir, @@ -209,8 +213,9 @@ terminate(Reason, Srv) -> couch_log:error("couch_server terminating with ~p, state ~2048p", [Reason, Srv#server{lru = redacted}]), - ets:foldl(fun(#db{main_pid=Pid}, _) -> couch_util:shutdown_sync(Pid) end, - nil, couch_dbs), + ets:foldl(fun(#entry{db = Db}, _) -> + couch_util:shutdown_sync(couch_db:get_pid(Db)) + end, nil, couch_dbs), ok. handle_config_change("couchdb", "database_dir", _, _, _) -> @@ -316,15 +321,13 @@ open_async(Server, From, DbName, Filepath, Options) -> true -> create; false -> open end, - % icky hack of field values - compactor_pid used to store clients - % and fd used for opening request info - true = ets:insert(couch_dbs, #db{ + true = ets:insert(couch_dbs, #entry{ name = DbName, - fd = ReqType, - main_pid = Opener, - compactor_pid = [From], - fd_monitor = locked, - options = Options + 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). @@ -348,16 +351,15 @@ handle_call({open_result, T0, DbName, {ok, Db}}, {FromPid, _Tag}, Server) -> true = ets:delete(couch_dbs_pid_to_name, FromPid), OpenTime = timer:now_diff(os:timestamp(), T0) / 1000, couch_stats:update_histogram([couchdb, db_open_time], OpenTime), - % icky hack of field values - compactor_pid used to store clients - % and fd used to possibly store a creation request + DbPid = couch_db:get_pid(Db), case ets:lookup(couch_dbs, DbName) of [] -> % db was deleted during async open - exit(Db#db.main_pid, kill), + exit(DbPid, kill), {reply, ok, Server}; - [#db{fd=ReqType, compactor_pid=Froms}] -> - link(Db#db.main_pid), - [gen_server:reply(From, {ok, Db}) || From <- Froms], + [#entry{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, _Filepath, _Options, CrFrom} -> @@ -365,8 +367,15 @@ handle_call({open_result, T0, DbName, {ok, Db}}, {FromPid, _Tag}, Server) -> _ -> ok end, - true = ets:insert(couch_dbs, Db), - true = ets:insert(couch_dbs_pid_to_name, {Db#db.main_pid, DbName}), + 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); @@ -378,13 +387,12 @@ handle_call({open_result, T0, DbName, {ok, Db}}, {FromPid, _Tag}, Server) -> handle_call({open_result, T0, DbName, {error, eexist}}, From, Server) -> handle_call({open_result, T0, DbName, file_exists}, From, Server); handle_call({open_result, _T0, DbName, Error}, {FromPid, _Tag}, Server) -> - % icky hack of field values - compactor_pid used to store clients case ets:lookup(couch_dbs, DbName) of [] -> % db was deleted during async open {reply, ok, Server}; - [#db{fd=ReqType, compactor_pid=Froms}=Db] -> - [gen_server:reply(From, Error) || From <- Froms], + [#entry{req_type = ReqType, waiters = Waiters} = Entry] -> + [gen_server:reply(Waiter, Error) || Waiter <- Waiters], couch_log:info("open_result error ~p for ~s", [Error, DbName]), true = ets:delete(couch_dbs, DbName), true = ets:delete(couch_dbs_pid_to_name, FromPid), @@ -394,7 +402,7 @@ handle_call({open_result, _T0, DbName, Error}, {FromPid, _Tag}, Server) -> _ -> Server end, - {reply, ok, db_closed(NewServer, Db#db.options)} + {reply, ok, db_closed(NewServer, Entry#entry.db_options)} end; handle_call({open, DbName, Options}, From, Server) -> case ets:lookup(couch_dbs, DbName) of @@ -412,15 +420,14 @@ handle_call({open, DbName, Options}, From, Server) -> Error -> {reply, Error, Server} end; - [#db{compactor_pid = Froms} = Db] when is_list(Froms) -> - % icky hack of field values - compactor_pid used to store clients - true = ets:insert(couch_dbs, Db#db{compactor_pid = [From|Froms]}), - if length(Froms) =< 10 -> ok; true -> + [#entry{waiters = Waiters} = Entry] when is_list(Waiters) -> + true = ets:insert(couch_dbs, Entry#entry{waiters = [From | Waiters]}), + if length(Waiters) =< 10 -> ok; true -> Fmt = "~b clients waiting to open db ~s", - couch_log:info(Fmt, [length(Froms), DbName]) + couch_log:info(Fmt, [length(Waiters), DbName]) end, {noreply, Server}; - [#db{} = Db] -> + [#entry{db = Db}] -> {reply, {ok, Db}, Server} end; handle_call({create, DbName, Options}, From, Server) -> @@ -437,14 +444,13 @@ handle_call({create, DbName, Options}, From, Server) -> CloseError -> {reply, CloseError, Server} end; - [#db{fd=open}=Db] -> + [#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. - % icky hack of field values - fd used to store create request CrOptions = [create | Options], - NewDb = Db#db{fd={create, DbName, Filepath, CrOptions, From}}, - true = ets:insert(couch_dbs, NewDb), + Req = {create, DbName, Filepath, CrOptions, From}, + true = ets:insert(couch_dbs, Entry#entry{req_type = Req}), {noreply, Server}; [_AlreadyRunningDb] -> {reply, file_exists, Server} @@ -460,18 +466,17 @@ handle_call({delete, DbName, Options}, _From, Server) -> Server2 = case ets:lookup(couch_dbs, DbName) of [] -> Server; - [#db{main_pid=Pid, compactor_pid=Froms} = Db] when is_list(Froms) -> - % icky hack of field values - compactor_pid used to store clients + [#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(F, not_found) || F <- Froms], - db_closed(Server, Db#db.options); - [#db{main_pid=Pid} = Db] -> + [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, Db#db.options) + db_closed(Server, Entry#entry.db_options) end, %% Delete any leftover compaction files. If we don't do this a @@ -497,11 +502,12 @@ handle_call({delete, DbName, Options}, _From, Server) -> Error -> {reply, Error, Server} end; -handle_call({db_updated, #db{}=Db}, _From, Server0) -> - #db{name = DbName, instance_start_time = StartTime} = Db, - Server = try ets:lookup_element(couch_dbs, DbName, #db.instance_start_time) of +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:insert(couch_dbs, Db), + 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 @@ -519,17 +525,19 @@ handle_cast({update_lru, DbName}, #server{lru = Lru, update_lru_on_read=true} = handle_cast({update_lru, _DbName}, Server) -> {noreply, Server}; handle_cast({close_db_if_idle, DbName}, Server) -> - case ets:update_element(couch_dbs, DbName, {#db.fd_monitor, locked}) of + case ets:update_element(couch_dbs, DbName, {#entry.lock, locked}) of true -> - [#db{main_pid = Pid} = Db] = ets:lookup(couch_dbs, DbName), + [#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, Pid), - exit(Pid, kill), - {noreply, db_closed(Server, Db#db.options)}; + 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, {#db.fd_monitor, nil}), + true = ets:update_element( + couch_dbs, DbName, {#entry.lock, unlocked}), {noreply, Server} end; false -> @@ -547,22 +555,19 @@ handle_info({'EXIT', _Pid, config_change}, Server) -> handle_info({'EXIT', Pid, Reason}, Server) -> case ets:lookup(couch_dbs_pid_to_name, Pid) of [{Pid, DbName}] -> - [#db{compactor_pid=Froms}=Db] = ets:lookup(couch_dbs, 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, couch_log:info("db ~s died with reason ~p", [DbName, Reason]), - % icky hack of field values - compactor_pid used to store clients - if is_list(Froms) -> - [gen_server:reply(From, Reason) || From <- Froms]; - true -> - ok + 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, Db#db.options)}; + {noreply, db_closed(Server, Entry#entry.db_options)}; [] -> {noreply, Server} end; diff --git a/src/couch/src/couch_server_int.hrl b/src/couch/src/couch_server_int.hrl new file mode 100644 index 000000000..537a6abb9 --- /dev/null +++ b/src/couch/src/couch_server_int.hrl @@ -0,0 +1,23 @@ +% 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. + + +-record(entry, { + name, + db, + pid, + lock, + waiters, + req_type, + db_options, + start_time +}). |