summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@apache.org>2020-04-14 17:43:04 -0400
committerNick Vatamaniuc <nickva@users.noreply.github.com>2020-04-16 19:00:38 -0400
commitf71c4c0a2a9b8979508487cef8d7ec9bca222c78 (patch)
tree6196517d80fe0fde1ca737234207d2b7a6e936b6
parenta527ad1a8da059e6ff202fb976a4e70da216dc93 (diff)
downloadcouchdb-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.erl36
-rw-r--r--src/fabric/src/fabric2_fdb.erl8
-rw-r--r--src/fabric/test/fabric2_db_misc_tests.erl34
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, _, _}) ->