diff options
author | Nick Vatamaniuc <vatamane@apache.org> | 2020-04-14 17:43:04 -0400 |
---|---|---|
committer | Nick Vatamaniuc <nickva@users.noreply.github.com> | 2020-04-16 19:00:38 -0400 |
commit | f71c4c0a2a9b8979508487cef8d7ec9bca222c78 (patch) | |
tree | 6196517d80fe0fde1ca737234207d2b7a6e936b6 | |
parent | a527ad1a8da059e6ff202fb976a4e70da216dc93 (diff) | |
download | couchdb-f71c4c0a2a9b8979508487cef8d7ec9bca222c78.tar.gz |
Allow using cached security and revs_limit properties
By default, transactions are used to check metadata, and possibly
reopen the db, to get a current db handle. However, if a `max_age`
option is provided and db handle was checked less than `max_age`
milliseconds ago, use properties from that cached handle instead.
The main use of this feature be in pluggable authorization handlers
where it might be necessary to inspect the security doc multiple times
for the same request before a final decision is made.
`revs_limit/1` was updated as well, mainly for consistency since it is
almost identical to `get_security/1`.
-rw-r--r-- | src/fabric/src/fabric2_db.erl | 36 | ||||
-rw-r--r-- | src/fabric/src/fabric2_fdb.erl | 8 | ||||
-rw-r--r-- | src/fabric/test/fabric2_db_misc_tests.erl | 34 |
3 files changed, 67 insertions, 11 deletions
diff --git a/src/fabric/src/fabric2_db.erl b/src/fabric/src/fabric2_db.erl index 9b9efdac2..b94226190 100644 --- a/src/fabric/src/fabric2_db.erl +++ b/src/fabric/src/fabric2_db.erl @@ -50,7 +50,9 @@ get_instance_start_time/1, get_pid/1, get_revs_limit/1, + get_revs_limit/2, get_security/1, + get_security/2, get_update_seq/1, get_user_ctx/1, get_uuid/1, @@ -500,17 +502,21 @@ get_pid(#{}) -> get_revs_limit(#{} = Db) -> - #{revs_limit := RevsLimit} = fabric2_fdb:transactional(Db, fun(TxDb) -> - fabric2_fdb:ensure_current(TxDb) - end), - RevsLimit. + get_revs_limit(Db, []). + + +get_revs_limit(#{} = Db, Opts) -> + CurrentDb = get_cached_db(Db, Opts), + maps:get(revs_limit, CurrentDb). get_security(#{} = Db) -> - #{security_doc := SecDoc} = fabric2_fdb:transactional(Db, fun(TxDb) -> - fabric2_fdb:ensure_current(TxDb) - end), - SecDoc. + get_security(Db, []). + + +get_security(#{} = Db, Opts) -> + CurrentDb = get_cached_db(Db, Opts), + maps:get(security_doc, CurrentDb). get_update_seq(#{} = Db) -> @@ -2037,3 +2043,17 @@ open_json_doc(Db, DocId, OpenOpts, DocOpts) -> {ok, #doc{} = Doc} -> [{doc, couch_doc:to_json_obj(Doc, DocOpts)}] end. + + +get_cached_db(#{} = Db, Opts) when is_list(Opts) -> + MaxAge = fabric2_util:get_value(max_age, Opts, 0), + Now = erlang:monotonic_time(millisecond), + Age = Now - maps:get(check_current_ts, Db), + case Age < MaxAge of + true -> + Db; + false -> + fabric2_fdb:transactional(Db, fun(TxDb) -> + fabric2_fdb:ensure_current(TxDb) + end) + end. diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl index 53102d6e9..03f3bad82 100644 --- a/src/fabric/src/fabric2_fdb.erl +++ b/src/fabric/src/fabric2_fdb.erl @@ -231,6 +231,7 @@ create(#{} = Db0, Options) -> revs_limit => 1000, security_doc => {[]}, user_ctx => UserCtx, + check_current_ts => erlang:monotonic_time(millisecond), validate_doc_update_funs => [], before_doc_update => undefined, @@ -272,6 +273,7 @@ open(#{} = Db0, Options) -> security_doc => {[]}, user_ctx => UserCtx, + check_current_ts => erlang:monotonic_time(millisecond), % Place holders until we implement these % bits. @@ -1273,8 +1275,10 @@ check_db_version(#{} = Db, CheckDbVersion) -> case erlfdb:wait(erlfdb:get(Tx, DbVersionKey)) of DbVersion -> put(?PDICT_CHECKED_DB_IS_CURRENT, true), - on_commit(Tx, fun() -> fabric2_server:store(Db) end), - Db; + Now = erlang:monotonic_time(millisecond), + Db1 = Db#{check_current_ts := Now}, + on_commit(Tx, fun() -> fabric2_server:store(Db1) end), + Db1; _NewDBVersion -> fabric2_server:remove(maps:get(name, Db)), throw({?MODULE, reopen}) diff --git a/src/fabric/test/fabric2_db_misc_tests.erl b/src/fabric/test/fabric2_db_misc_tests.erl index 19599823e..9c95ca565 100644 --- a/src/fabric/test/fabric2_db_misc_tests.erl +++ b/src/fabric/test/fabric2_db_misc_tests.erl @@ -38,6 +38,7 @@ misc_test_() -> ?TDEF(accessors), ?TDEF(set_revs_limit), ?TDEF(set_security), + ?TDEF(get_security_cached), ?TDEF(is_system_db), ?TDEF(validate_dbname), ?TDEF(validate_doc_ids), @@ -113,6 +114,30 @@ set_security({DbName, Db, _}) -> ?assertEqual(SecObj, fabric2_db:get_security(Db2)). +get_security_cached({DbName, Db, _}) -> + OldSecObj = fabric2_db:get_security(Db), + SecObj = {[ + {<<"admins">>, {[ + {<<"names">>, [<<"foo1">>]}, + {<<"roles">>, []} + ]}} + ]}, + + % Set directly so we don't auto-update the local cache + {ok, Db1} = fabric2_db:open(DbName, [?ADMIN_CTX]), + ?assertMatch({ok, #{}}, fabric2_fdb:transactional(Db1, fun(TxDb) -> + fabric2_fdb:set_config(TxDb, security_doc, SecObj) + end)), + + {ok, Db2} = fabric2_db:open(DbName, [?ADMIN_CTX]), + ?assertEqual(OldSecObj, fabric2_db:get_security(Db2, [{max_age, 1000}])), + + timer:sleep(100), + ?assertEqual(SecObj, fabric2_db:get_security(Db2, [{max_age, 50}])), + + ?assertEqual(ok, fabric2_db:set_security(Db2, OldSecObj)). + + is_system_db({DbName, Db, _}) -> ?assertEqual(false, fabric2_db:is_system_db(Db)), ?assertEqual(false, fabric2_db:is_system_db_name("foo")), @@ -305,12 +330,19 @@ metadata_bump({DbName, _, _}) -> erlfdb:wait(erlfdb:get(Tx, ?METADATA_VERSION_KEY)) end), + % Save timetamp before ensure_current/1 is called + TsBeforeEnsureCurrent = erlang:monotonic_time(millisecond), + % Perform a random operation which calls ensure_current {ok, _} = fabric2_db:get_db_info(Db), % Check that db handle in the cache got the new metadata version + % and that check_current_ts was updated CachedDb = fabric2_server:fetch(DbName, undefined), - ?assertMatch(#{md_version := NewMDVersion}, CachedDb). + ?assertMatch(#{ + md_version := NewMDVersion, + check_current_ts := Ts + } when Ts >= TsBeforeEnsureCurrent, CachedDb). db_version_bump({DbName, _, _}) -> |