summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@apache.org>2020-04-14 17:43:04 -0400
committerNick Vatamaniuc <vatamane@apache.org>2020-04-15 11:49:40 -0400
commit444920e2be3d7714c7a8a4c29c28313e97deef8f (patch)
treed80f3438f6d160d62e1443e69b348aa83f2e6ed6
parent6bc6f9c285acba015614e08a62edffbd9a2f3699 (diff)
downloadcouchdb-allow-using-cached-security-docs.tar.gz
Allow using cached security and revs_limit propertiesallow-using-cached-security-docs
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 d96c3ae60..bc0a8458c 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, _, _}) ->