diff options
author | Damien F. Katz <damien@apache.org> | 2010-06-23 19:11:50 +0000 |
---|---|---|
committer | Damien F. Katz <damien@apache.org> | 2010-06-23 19:11:50 +0000 |
commit | feb18bd6efc40126bdb4af390419223e10ef134d (patch) | |
tree | 0833e401ab28327acf5f377bb929fe8d351502c8 | |
parent | 918f8bf54a4aeffc3c0ef4ad1fa31ce93bbc1346 (diff) |
Authentication caching, to avoid repeated opening and closing of the users database for each request requiring authentication. COUCHDB-807
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@957314 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | etc/couchdb/default.ini.tpl.in | 2 | ||||
-rw-r--r-- | share/Makefile.am | 1 | ||||
-rw-r--r-- | share/www/script/couch_tests.js | 1 | ||||
-rw-r--r-- | share/www/script/test/users_db.js | 6 | ||||
-rw-r--r-- | src/couchdb/Makefile.am | 8 | ||||
-rw-r--r-- | src/couchdb/couch_db.erl | 24 | ||||
-rw-r--r-- | src/couchdb/couch_db.hrl | 3 | ||||
-rw-r--r-- | src/couchdb/couch_db_updater.erl | 2 | ||||
-rw-r--r-- | src/couchdb/couch_file.erl | 15 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_auth.erl | 150 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 33 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_oauth.erl | 2 | ||||
-rw-r--r-- | src/couchdb/couch_server.erl | 92 | ||||
-rw-r--r-- | src/couchdb/priv/stat_descriptions.cfg.in | 2 |
14 files changed, 131 insertions, 210 deletions
diff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/default.ini.tpl.in index a4bf96f2..7b9ac37c 100644 --- a/etc/couchdb/default.ini.tpl.in +++ b/etc/couchdb/default.ini.tpl.in @@ -31,6 +31,7 @@ include_sasl = true authentication_db = _users require_valid_user = false timeout = 600 ; number of seconds before automatic logout +auth_cache_size = 50 ; size is number of cache entries [query_servers] javascript = %bindir%/%couchjs_command_name% %localbuilddatadir%/server/main.js @@ -55,6 +56,7 @@ httpd={couch_httpd, start_link, []} stats_aggregator={couch_stats_aggregator, start, []} stats_collector={couch_stats_collector, start, []} uuids={couch_uuids, start, []} +auth_cache={couch_auth_cache, start_link, []} [httpd_global_handlers] / = {couch_httpd_misc_handlers, handle_welcome_req, <<"Welcome">>} diff --git a/share/Makefile.am b/share/Makefile.am index 935815a2..3c4b5da5 100644 --- a/share/Makefile.am +++ b/share/Makefile.am @@ -110,6 +110,7 @@ nobase_dist_localdata_DATA = \ www/script/test/attachment_names.js \ www/script/test/attachment_paths.js \ www/script/test/attachment_views.js \ + www/script/test/auth_cache.js \ www/script/test/basics.js \ www/script/test/batch_save.js \ www/script/test/bulk_docs.js \ diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index 4225ce7a..383d2ee6 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -35,6 +35,7 @@ loadTest("attachments_multipart.js"); loadTest("attachment_names.js"); loadTest("attachment_paths.js"); loadTest("attachment_views.js"); +loadTest("auth_cache.js"); loadTest("batch_save.js"); loadTest("bulk_docs.js"); loadTest("changes.js"); diff --git a/share/www/script/test/users_db.js b/share/www/script/test/users_db.js index 77069c98..b85adf08 100644 --- a/share/www/script/test/users_db.js +++ b/share/www/script/test/users_db.js @@ -24,8 +24,6 @@ couchTests.users_db = function(debug) { // to determine the actual users db name. function testFun() { - usersDb.deleteDb(); - // test that the validation function is installed var ddoc = usersDb.open("_design/_auth"); T(ddoc.validate_doc_update); @@ -89,10 +87,12 @@ couchTests.users_db = function(debug) { }; + usersDb.deleteDb(); run_on_modified_server( [{section: "couch_httpd_auth", - key: "authentication_db", value: "test_suite_users"}], + key: "authentication_db", value: usersDb.name}], testFun ); + usersDb.deleteDb(); // cleanup }
\ No newline at end of file diff --git a/src/couchdb/Makefile.am b/src/couchdb/Makefile.am index a5818703..308a3837 100644 --- a/src/couchdb/Makefile.am +++ b/src/couchdb/Makefile.am @@ -17,7 +17,7 @@ couchlibdir = $(localerlanglibdir)/couch-$(version) couchincludedir = $(couchlibdir)/include couchebindir = $(couchlibdir)/ebin -couchinclude_DATA = couch_db.hrl +couchinclude_DATA = couch_db.hrl couch_js_functions.hrl couchebin_DATA = $(compiled_files) # dist_devdoc_DATA = $(doc_base) $(doc_modules) @@ -29,6 +29,7 @@ CLEANFILES = $(compiled_files) $(doc_base) source_files = \ couch.erl \ couch_app.erl \ + couch_auth_cache.erl \ couch_btree.erl \ couch_changes.erl \ couch_config.erl \ @@ -80,12 +81,13 @@ source_files = \ couch_db_updater.erl \ couch_work_queue.erl -EXTRA_DIST = $(source_files) couch_db.hrl +EXTRA_DIST = $(source_files) couch_db.hrl couch_js_functions.hrl compiled_files = \ couch.app \ couch.beam \ couch_app.beam \ + couch_auth_cache.beam \ couch_btree.beam \ couch_changes.beam \ couch_config.beam \ @@ -194,6 +196,6 @@ endif # $(ERL) -noshell -run edoc_run files [\"$<\"] -%.beam: %.erl couch_db.hrl +%.beam: %.erl couch_db.hrl couch_js_functions.hrl $(ERLC) $(ERLC_FLAGS) ${TEST} $<; diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl index 63ada164..cf25dc62 100644 --- a/src/couchdb/couch_db.erl +++ b/src/couchdb/couch_db.erl @@ -114,7 +114,7 @@ open_doc(Db, IdOrDocInfo) -> open_doc(Db, IdOrDocInfo, []). open_doc(Db, Id, Options) -> - couch_stats_collector:increment({couchdb, database_reads}), + increment_stat(Db, {couchdb, database_reads}), case open_doc_int(Db, Id, Options) of {ok, #doc{deleted=true}=Doc} -> case lists:member(deleted, Options) of @@ -157,7 +157,7 @@ find_ancestor_rev_pos({RevPos, [RevId|Rest]}, AttsSinceRevs) -> end. open_doc_revs(Db, Id, Revs, Options) -> - couch_stats_collector:increment({couchdb, database_reads}), + increment_stat(Db, {couchdb, database_reads}), [{ok, Results}] = open_doc_revs_int(Db, [{Id, Revs}], Options), {ok, [apply_open_options(Result, Options) || Result <- Results]}. @@ -621,7 +621,7 @@ check_dup_atts2(_) -> update_docs(Db, Docs, Options, replicated_changes) -> - couch_stats_collector:increment({couchdb, database_writes}), + increment_stat(Db, {couchdb, database_writes}), DocBuckets = group_alike_docs(Docs), case (Db#db.validate_doc_funs /= []) orelse @@ -647,7 +647,7 @@ update_docs(Db, Docs, Options, replicated_changes) -> {ok, DocErrors}; update_docs(Db, Docs, Options, interactive_edit) -> - couch_stats_collector:increment({couchdb, database_writes}), + increment_stat(Db, {couchdb, database_writes}), AllOrNothing = lists:member(all_or_nothing, Options), % go ahead and generate the new revision ids for the documents. % separate out the NonRep documents from the rest of the documents @@ -959,7 +959,12 @@ init({DbName, Filepath, Fd, Options}) -> {ok, UpdaterPid} = gen_server:start_link(couch_db_updater, {self(), DbName, Filepath, Fd, Options}, []), {ok, #db{fd_ref_counter=RefCntr}=Db} = gen_server:call(UpdaterPid, get_db), couch_ref_counter:add(RefCntr), - couch_stats_collector:track_process_count({couchdb, open_databases}), + case lists:member(sys_db, Options) of + true -> + ok; + false -> + couch_stats_collector:track_process_count({couchdb, open_databases}) + end, process_flag(trap_exit, true), {ok, Db}. @@ -983,7 +988,9 @@ handle_call({db_updated, NewDb}, _From, #db{fd_ref_counter=OldRefCntr}) -> couch_ref_counter:add(NewRefCntr), couch_ref_counter:drop(OldRefCntr) end, - {reply, ok, NewDb}. + {reply, ok, NewDb}; +handle_call(get_db, _From, Db) -> + {reply, {ok, Db}, Db}. handle_cast(Msg, Db) -> @@ -1178,4 +1185,7 @@ make_doc(#db{fd=Fd}=Db, Id, Deleted, Bp, RevisionPath) -> }. - +increment_stat(#db{is_sys_db = true}, _Stat) -> + ok; +increment_stat(#db{}, Stat) -> + couch_stats_collector:increment(Stat). diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl index f1b88a05..4497dc8f 100644 --- a/src/couchdb/couch_db.hrl +++ b/src/couchdb/couch_db.hrl @@ -173,7 +173,8 @@ user_ctx = #user_ctx{}, waiting_delayed_commit = nil, revs_limit = 1000, - fsync_options = [] + fsync_options = [], + is_sys_db = false }). diff --git a/src/couchdb/couch_db_updater.erl b/src/couchdb/couch_db_updater.erl index c7c78dd2..e4eb72cc 100644 --- a/src/couchdb/couch_db_updater.erl +++ b/src/couchdb/couch_db_updater.erl @@ -35,7 +35,7 @@ init({MainPid, DbName, Filepath, Fd, Options}) -> Db = init_db(DbName, Filepath, Fd, Header), Db2 = refresh_validate_doc_funs(Db), - {ok, Db2#db{main_pid=MainPid}}. + {ok, Db2#db{main_pid = MainPid, is_sys_db = lists:member(sys_db, Options)}}. terminate(_Reason, Db) -> diff --git a/src/couchdb/couch_file.erl b/src/couchdb/couch_file.erl index fe524e24..8c82272d 100644 --- a/src/couchdb/couch_file.erl +++ b/src/couchdb/couch_file.erl @@ -231,16 +231,14 @@ init({Filepath, Options, ReturnPid, Ref}) -> {ok, 0} = file:position(Fd, 0), ok = file:truncate(Fd), ok = file:sync(Fd), - couch_stats_collector:track_process_count( - {couchdb, open_os_files}), + maybe_track_open_os_files(Options), {ok, #file{fd=Fd}}; false -> ok = file:close(Fd), init_status_error(ReturnPid, Ref, file_exists) end; false -> - couch_stats_collector:track_process_count( - {couchdb, open_os_files}), + maybe_track_open_os_files(Options), {ok, #file{fd=Fd}} end; Error -> @@ -252,7 +250,7 @@ init({Filepath, Options, ReturnPid, Ref}) -> {ok, Fd_Read} -> {ok, Fd} = file:open(Filepath, [read, append, raw, binary]), ok = file:close(Fd_Read), - couch_stats_collector:track_process_count({couchdb, open_os_files}), + maybe_track_open_os_files(Options), {ok, Length} = file:position(Fd, eof), {ok, #file{fd=Fd, eof=Length}}; Error -> @@ -260,6 +258,13 @@ init({Filepath, Options, ReturnPid, Ref}) -> end end. +maybe_track_open_os_files(FileOptions) -> + case lists:member(sys_db, FileOptions) of + true -> + ok; + false -> + couch_stats_collector:track_process_count({couchdb, open_os_files}) + end. terminate(_Reason, _Fd) -> ok. diff --git a/src/couchdb/couch_httpd_auth.erl b/src/couchdb/couch_httpd_auth.erl index 572edbcc..f8c9a346 100644 --- a/src/couchdb/couch_httpd_auth.erl +++ b/src/couchdb/couch_httpd_auth.erl @@ -19,7 +19,6 @@ -export([proxy_authentification_handler/1]). -export([cookie_auth_header/2]). -export([handle_session_req/1]). --export([ensure_users_db_exists/1, get_user/1]). -import(couch_httpd, [header_value/2, send_json/2,send_json/4, send_method_not_allowed/2]). @@ -66,7 +65,7 @@ basic_name_pw(Req) -> default_authentication_handler(Req) -> case basic_name_pw(Req) of {User, Pass} -> - case get_user(?l2b(User)) of + case couch_auth_cache:get_user_creds(User) of nil -> throw({unauthorized, <<"Name or password is incorrect.">>}); UserProps -> @@ -156,149 +155,6 @@ proxy_auth_user(Req) -> end end. -% maybe we can use hovercraft to simplify running this view query -% rename to get_user_from_users_db -get_user(UserName) -> - case couch_config:get("admins", ?b2l(UserName)) of - "-hashed-" ++ HashedPwdAndSalt -> - % the name is an admin, now check to see if there is a user doc - % which has a matching name, salt, and password_sha - [HashedPwd, Salt] = string:tokens(HashedPwdAndSalt, ","), - case get_user_props_from_db(UserName) of - nil -> - [{<<"roles">>, [<<"_admin">>]}, - {<<"salt">>, ?l2b(Salt)}, - {<<"password_sha">>, ?l2b(HashedPwd)}]; - UserProps when is_list(UserProps) -> - DocRoles = couch_util:get_value(<<"roles">>, UserProps), - [{<<"roles">>, [<<"_admin">> | DocRoles]}, - {<<"salt">>, ?l2b(Salt)}, - {<<"password_sha">>, ?l2b(HashedPwd)}] - end; - _Else -> - get_user_props_from_db(UserName) - end. - -get_user_props_from_db(UserName) -> - DbName = couch_config:get("couch_httpd_auth", "authentication_db"), - {ok, Db} = ensure_users_db_exists(?l2b(DbName)), - DocId = <<"org.couchdb.user:", UserName/binary>>, - try couch_httpd_db:couch_doc_open(Db, DocId, nil, [conflicts]) of - #doc{meta=Meta}=Doc -> - % check here for conflict state and throw error if conflicted - case couch_util:get_value(conflicts,Meta,[]) of - [] -> - {DocProps} = couch_query_servers:json_doc(Doc), - case couch_util:get_value(<<"type">>, DocProps) of - <<"user">> -> - DocProps; - _Else -> - ?LOG_ERROR("Invalid user doc. Id: ~p",[DocId]), - nil - end; - _Else -> - throw({unauthorized, <<"User document conflict must be resolved before login.">>}) - end - catch - throw:_Throw -> - nil - after - couch_db:close(Db) - end. - -% this should handle creating the ddoc -ensure_users_db_exists(DbName) -> - case couch_db:open(DbName, [{user_ctx, #user_ctx{roles=[<<"_admin">>]}}]) of - {ok, Db} -> - ensure_auth_ddoc_exists(Db, <<"_design/_auth">>), - {ok, Db}; - _Error -> - {ok, Db} = couch_db:create(DbName, [{user_ctx, #user_ctx{roles=[<<"_admin">>]}}]), - ensure_auth_ddoc_exists(Db, <<"_design/_auth">>), - {ok, Db} - end. - -ensure_auth_ddoc_exists(Db, DDocId) -> - try couch_httpd_db:couch_doc_open(Db, DDocId, nil, []) of - _Foo -> ok - catch - _:_Error -> - % create the design document - {ok, AuthDesign} = auth_design_doc(DDocId), - {ok, _Rev} = couch_db:update_doc(Db, AuthDesign, []), - ok - end. - -% add the validation function here -auth_design_doc(DocId) -> - DocProps = [ - {<<"_id">>, DocId}, - {<<"language">>,<<"javascript">>}, - { - <<"validate_doc_update">>, - <<"function(newDoc, oldDoc, userCtx) { - if ((oldDoc || newDoc).type != 'user') { - throw({forbidden : 'doc.type must be user'}); - } // we only validate user docs for now - if (newDoc._deleted === true) { - // allow deletes by admins and matching users - // without checking the other fields - if ((userCtx.roles.indexOf('_admin') != -1) || (userCtx.name == oldDoc.name)) { - return; - } else { - throw({forbidden : 'Only admins may delete other user docs.'}); - } - } - if (!newDoc.name) { - throw({forbidden : 'doc.name is required'}); - } - if (!(newDoc.roles && (typeof newDoc.roles.length != 'undefined') )) { - throw({forbidden : 'doc.roles must be an array'}); - } - if (newDoc._id != 'org.couchdb.user:'+newDoc.name) { - throw({forbidden : 'Docid must be of the form org.couchdb.user:name'}); - } - if (oldDoc) { // validate all updates - if (oldDoc.name != newDoc.name) { - throw({forbidden : 'Usernames may not be changed.'}); - } - } - if (newDoc.password_sha && !newDoc.salt) { - throw({forbidden : 'Users with password_sha must have a salt. See /_utils/script/couch.js for example code.'}); - } - if (userCtx.roles.indexOf('_admin') == -1) { // not an admin - if (oldDoc) { // validate non-admin updates - if (userCtx.name != newDoc.name) { - throw({forbidden : 'You may only update your own user document.'}); - } - // validate role updates - var oldRoles = oldDoc.roles.sort(); - var newRoles = newDoc.roles.sort(); - if (oldRoles.length != newRoles.length) { - throw({forbidden : 'Only _admin may edit roles'}); - } - for (var i=0; i < oldRoles.length; i++) { - if (oldRoles[i] != newRoles[i]) { - throw({forbidden : 'Only _admin may edit roles'}); - } - }; - } else if (newDoc.roles.length > 0) { - throw({forbidden : 'Only _admin may set roles'}); - } - } - // no system roles in users db - for (var i=0; i < newDoc.roles.length; i++) { - if (newDoc.roles[i][0] == '_') { - throw({forbidden : 'No system roles (starting with underscore) in users db.'}); - } - }; - // no system names as names - if (newDoc.name[0] == '_') { - throw({forbidden : 'Username may not start with underscore.'}); - } - }">> - }], - {ok, couch_doc:from_json_obj({DocProps})}. cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req) -> case MochiReq:get_cookie_value("AuthSession") of @@ -321,7 +177,7 @@ cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req) -> Req; SecretStr -> Secret = ?l2b(SecretStr), - case get_user(?l2b(User)) of + case couch_auth_cache:get_user_creds(User) of nil -> Req; UserProps -> UserSalt = couch_util:get_value(<<"salt">>, UserProps, <<"">>), @@ -403,7 +259,7 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) -> UserName = ?l2b(couch_util:get_value("name", Form, "")), Password = ?l2b(couch_util:get_value("password", Form, "")), ?LOG_DEBUG("Attempt Login: ~s",[UserName]), - User = case get_user(UserName) of + User = case couch_auth_cache:get_user_creds(UserName) of nil -> []; Result -> Result end, diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 87fc15d8..06d56c7b 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -160,23 +160,13 @@ handle_design_info_req(Req, _Db, _DDoc) -> create_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) -> ok = couch_httpd:verify_is_server_admin(Req), - LDbName = ?b2l(DbName), - case couch_config:get("couch_httpd_auth", "authentication_db") of - LDbName -> - % make sure user's db always has the auth ddoc - {ok, Db} = couch_httpd_auth:ensure_users_db_exists(DbName), - couch_db:close(Db), - DbUrl = absolute_uri(Req, "/" ++ couch_util:url_encode(DbName)), - send_json(Req, 201, [{"Location", DbUrl}], {[{ok, true}]}); - _Else -> - case couch_server:create(DbName, [{user_ctx, UserCtx}]) of - {ok, Db} -> - couch_db:close(Db), - DbUrl = absolute_uri(Req, "/" ++ couch_util:url_encode(DbName)), - send_json(Req, 201, [{"Location", DbUrl}], {[{ok, true}]}); - Error -> - throw(Error) - end + case couch_server:create(DbName, [{user_ctx, UserCtx}]) of + {ok, Db} -> + couch_db:close(Db), + DbUrl = absolute_uri(Req, "/" ++ couch_util:url_encode(DbName)), + send_json(Req, 201, [{"Location", DbUrl}], {[{ok, true}]}); + Error -> + throw(Error) end. delete_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) -> @@ -189,15 +179,6 @@ delete_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) -> end. do_db_req(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Fun) -> - LDbName = ?b2l(DbName), - % I hope this lookup is cheap. - case couch_config:get("couch_httpd_auth", "authentication_db") of - LDbName -> - % make sure user's db always has the auth ddoc - {ok, ADb} = couch_httpd_auth:ensure_users_db_exists(DbName), - couch_db:close(ADb); - _Else -> ok - end, case couch_db:open(DbName, [{user_ctx, UserCtx}]) of {ok, Db} -> try diff --git a/src/couchdb/couch_httpd_oauth.erl b/src/couchdb/couch_httpd_oauth.erl index accdfaca..05ee10e2 100644 --- a/src/couchdb/couch_httpd_oauth.erl +++ b/src/couchdb/couch_httpd_oauth.erl @@ -41,7 +41,7 @@ set_user_ctx(Req, AccessToken) -> undefined -> throw({bad_request, unknown_oauth_token}); Value -> ?l2b(Value) end, - case couch_httpd_auth:get_user(Name) of + case couch_auth_cache:get_user_creds(Name) of nil -> Req; User -> Roles = couch_util:get_value(<<"roles">>, User, []), diff --git a/src/couchdb/couch_server.erl b/src/couchdb/couch_server.erl index c2313aef..65e4b7a0 100644 --- a/src/couchdb/couch_server.erl +++ b/src/couchdb/couch_server.erl @@ -141,6 +141,7 @@ init([]) -> ets:new(couch_dbs_by_name, [set, private, named_table]), ets:new(couch_dbs_by_pid, [set, private, named_table]), ets:new(couch_dbs_by_lru, [ordered_set, private, named_table]), + ets:new(couch_sys_dbs, [set, private, named_table]), process_flag(trap_exit, true), {ok, #server{root_dir=RootDir, dbname_regexp=RegExp, @@ -180,7 +181,7 @@ maybe_close_lru_db(#server{dbs_open=NumOpen}=Server) -> end. try_close_lru(StartTime) -> - LruTime = ets:first(couch_dbs_by_lru), + LruTime = get_lru(), if LruTime > StartTime -> % this means we've looped through all our opened dbs and found them % all in use. @@ -194,6 +195,7 @@ try_close_lru(StartTime) -> true = ets:delete(couch_dbs_by_lru, LruTime), true = ets:delete(couch_dbs_by_name, DbName), true = ets:delete(couch_dbs_by_pid, MainPid), + true = ets:delete(couch_sys_dbs, DbName), ok; false -> % this still has referrers. Go ahead and give it a current lru time @@ -207,11 +209,31 @@ try_close_lru(StartTime) -> end end. +get_lru() -> + get_lru(ets:first(couch_dbs_by_lru)). + +get_lru(LruTime) -> + [{LruTime, DbName}] = ets:lookup(couch_dbs_by_lru, LruTime), + case ets:member(couch_sys_dbs, DbName) of + false -> + LruTime; + true -> + [{_, {opened, MainPid, _}}] = ets:lookup(couch_dbs_by_name, DbName), + case couch_db:is_idle(MainPid) of + true -> + LruTime; + false -> + get_lru(ets:next(couch_dbs_by_lru, LruTime)) + end + end. + open_async(Server, From, DbName, Filepath, Options) -> Parent = self(), Opener = spawn_link(fun() -> Res = couch_db:start_link(DbName, Filepath, Options), - gen_server:call(Parent, {open_result, DbName, Res}, infinity), + gen_server:call( + Parent, {open_result, DbName, Res, Options}, infinity + ), unlink(Parent), case Res of {ok, DbReader} -> @@ -222,13 +244,20 @@ open_async(Server, From, DbName, Filepath, Options) -> end), true = ets:insert(couch_dbs_by_name, {DbName, {opening, Opener, [From]}}), true = ets:insert(couch_dbs_by_pid, {Opener, DbName}), - Server#server{dbs_open=Server#server.dbs_open + 1}. + DbsOpen = case lists:member(sys_db, Options) of + true -> + true = ets:insert(couch_sys_dbs, {DbName, true}), + Server#server.dbs_open; + false -> + Server#server.dbs_open + 1 + end, + Server#server{dbs_open = DbsOpen}. handle_call({set_max_dbs_open, Max}, _From, Server) -> {reply, ok, Server#server{max_dbs_open=Max}}; handle_call(get_server, _From, Server) -> {reply, {ok, Server}, Server}; -handle_call({open_result, DbName, {ok, OpenedDbPid}}, _From, Server) -> +handle_call({open_result, DbName, {ok, OpenedDbPid}, Options}, _From, Server) -> link(OpenedDbPid), [{DbName, {opening,Opener,Froms}}] = ets:lookup(couch_dbs_by_name, DbName), lists:foreach(fun({FromPid,_}=From) -> @@ -241,15 +270,28 @@ handle_call({open_result, DbName, {ok, OpenedDbPid}}, _From, Server) -> true = ets:delete(couch_dbs_by_pid, Opener), true = ets:insert(couch_dbs_by_pid, {OpenedDbPid, DbName}), true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}), + case lists:member(create, Options) of + true -> + couch_db_update_notifier:notify({created, DbName}); + false -> + ok + end, {reply, ok, Server}; -handle_call({open_result, DbName, Error}, _From, Server) -> +handle_call({open_result, DbName, Error, Options}, _From, Server) -> [{DbName, {opening,Opener,Froms}}] = ets:lookup(couch_dbs_by_name, DbName), lists:foreach(fun(From) -> gen_server:reply(From, Error) end, Froms), true = ets:delete(couch_dbs_by_name, DbName), true = ets:delete(couch_dbs_by_pid, Opener), - {reply, ok, Server#server{dbs_open=Server#server.dbs_open - 1}}; + DbsOpen = case lists:member(sys_db, Options) of + true -> + true = ets:delete(couch_sys_dbs, DbName), + Server#server.dbs_open; + false -> + Server#server.dbs_open - 1 + end, + {reply, ok, Server#server{dbs_open = DbsOpen}}; handle_call({open, DbName, Options}, {FromPid,_}=From, Server) -> LruTime = now(), case ets:lookup(couch_dbs_by_name, DbName) of @@ -257,12 +299,17 @@ handle_call({open, DbName, Options}, {FromPid,_}=From, Server) -> DbNameList = binary_to_list(DbName), case check_dbname(Server, DbNameList) of ok -> - case maybe_close_lru_db(Server) of - {ok, Server2} -> - Filepath = get_full_filename(Server, DbNameList), - {noreply, open_async(Server2, From, DbName, Filepath, Options)}; - CloseError -> - {reply, CloseError, Server} + Path = get_full_filename(Server, DbNameList), + case lists:member(sys_db, Options) of + true -> + {noreply, open_async(Server, From, DbName, Path, Options)}; + false -> + case maybe_close_lru_db(Server) of + {ok, Server2} -> + {noreply, open_async(Server2, From, DbName, Path, Options)}; + CloseError -> + {reply, CloseError, Server} + end end; Error -> {reply, Error, Server} @@ -301,21 +348,34 @@ handle_call({delete, DbName, _Options}, _From, Server) -> case check_dbname(Server, DbNameList) of ok -> FullFilepath = get_full_filename(Server, DbNameList), - Server2 = + UpdateState = case ets:lookup(couch_dbs_by_name, DbName) of - [] -> Server; + [] -> false; [{_, {opening, Pid, Froms}}] -> couch_util:shutdown_sync(Pid), true = ets:delete(couch_dbs_by_name, DbName), true = ets:delete(couch_dbs_by_pid, Pid), [gen_server:send_result(F, not_found) || F <- Froms], - Server#server{dbs_open=Server#server.dbs_open - 1}; + true; [{_, {opened, Pid, LruTime}}] -> couch_util:shutdown_sync(Pid), true = ets:delete(couch_dbs_by_name, DbName), true = ets:delete(couch_dbs_by_pid, Pid), true = ets:delete(couch_dbs_by_lru, LruTime), - Server#server{dbs_open=Server#server.dbs_open - 1} + true + end, + Server2 = case UpdateState of + true -> + DbsOpen = case ets:member(couch_sys_dbs, DbName) of + true -> + true = ets:delete(couch_sys_dbs, DbName), + Server#server.dbs_open; + false -> + Server#server.dbs_open - 1 + end, + Server#server{dbs_open = DbsOpen}; + false -> + Server end, %% Delete any leftover .compact files. If we don't do this a subsequent diff --git a/src/couchdb/priv/stat_descriptions.cfg.in b/src/couchdb/priv/stat_descriptions.cfg.in index f146f8b8..5c972ddf 100644 --- a/src/couchdb/priv/stat_descriptions.cfg.in +++ b/src/couchdb/priv/stat_descriptions.cfg.in @@ -19,6 +19,8 @@ {couchdb, open_databases, "number of open databases"}. {couchdb, open_os_files, "number of file descriptors CouchDB has open"}. {couchdb, request_time, "length of a request inside CouchDB without MochiWeb"}. +{couchdb, auth_cache_hits, "number of authentication cache hits"}. +{couchdb, auth_cache_misses, "number of authentication cache misses"}. {httpd, bulk_requests, "number of bulk requests"}. {httpd, requests, "number of HTTP requests"}. |