Commit 720683ed authored by Vladimír Čunát's avatar Vladimír Čunát

cache.clear(): more flexibility via parameters

parent a1fa7d82
...@@ -860,34 +860,42 @@ daemons or manipulated from other processes, making for example synchronised loa ...@@ -860,34 +860,42 @@ daemons or manipulated from other processes, making for example synchronised loa
[AAAA] => true [AAAA] => true
} }
.. function:: cache.clear([domain]) .. function:: cache.clear([name], [exact_name], [rr_type], [maxcount], [callback])
:return: ``bool`` or ``int``
Purge cache records. Purge cache records.
If the domain isn't provided, whole cache is purged and ``bool`` is returned (denoting success).
If you provide a name, only records in that subtree are purged, :return: ``bool`` (success of removing all in one go)
and the number of removed records is returned.
:param string name: if the name isn't provided, whole cache is purged
(and any other parameters are disregarded).
Otherwise only records in that subtree are removed.
:param bool exact_name: if set to ``true``, only records with *the same* name are removed.
:param kres.type rr_type: you may additionally specify the type to remove,
but that is only supported with ``exact_name == true``.
:param integer maxcount: the number of records to remove at one go, default: 100.
The purpose is not to block the resolver for long;
the ``callback`` parameter by default handles this by asynchronous repetition.
:param function callback: custom code to handle result of the underlying C call.
As the first parameter it gets the return code from :func:`kr_cache_remove_subtree()`,
and the following parameters are copies of those passed to `cache.clear()`.
The default callback repeats the command after one millisecond (if successful).
Examples: Examples:
.. code-block:: lua .. code-block:: lua
-- Clear records at and below 'bad.cz'
cache.clear('bad.cz')
-- Clear whole cache -- Clear whole cache
cache.clear() cache.clear()
-- Clear records at and below 'bad.cz'
cache.clear('bad.cz')
.. attention:: In case you provide a name: .. attention::
- The number of removed records is limited to 1000. To minimize surprises with partial cache removal,
The purpose is not to block the resolver for long; if you need larger purges, you may prefer to specify names that have NS/SOA records,
you may e.g. repeat the command with a 1ms timer until it returns a lower value. e.g. ``example.com``. Details: validated NSEC and NSEC3 records
- To minimize surprises, you may prefer to specify names that have NS/SOA records, (which are used for aggressive non-existence proofs)
e.g. ``example.com``. Details: validated NSEC and NSEC3 records will be removed only for zones whose **apex** is at or below the specified name.
(which are used for aggressive non-existence proofs)
will be removed only for zones whose **apex** is at or below the specified name.
Timers and events Timers and events
......
...@@ -1076,52 +1076,16 @@ static int cache_close(lua_State *L) ...@@ -1076,52 +1076,16 @@ static int cache_close(lua_State *L)
} }
/** @internal Prefix walk. */ /** @internal Prefix walk. */
static int cache_prefixed(struct kr_cache *cache, const char *args, knot_db_val_t *results, int maxresults) static int cache_prefixed(struct kr_cache *cache, const char *prefix, bool exact_name,
knot_db_val_t keyval[][2], int maxcount)
{ {
/* Convert to domain name */ /* Convert to domain name */
uint8_t buf[KNOT_DNAME_MAXLEN]; uint8_t buf[KNOT_DNAME_MAXLEN];
if (!knot_dname_from_str(buf, args, sizeof(buf))) { if (!knot_dname_from_str(buf, prefix, sizeof(buf))) {
return kr_error(EINVAL); return kr_error(EINVAL);
} }
/* Start prefix search */ /* Start prefix search */
int ret = kr_cache_match(cache, buf, results, maxresults); return kr_cache_match(cache, buf, exact_name, keyval, maxcount);
kr_cache_sync(cache);
return ret;
}
/** @internal Delete iterated key. */
static int cache_remove_prefix(struct kr_cache *cache, const char *args)
{
/* Check if we can remove */
if (!cache || !cache->api || !cache->api->remove) {
return kr_error(ENOSYS);
}
knot_db_val_t result_set[1000];
int ret = cache_prefixed(cache, args, result_set, 1000);
if (ret < 0) {
return ret;
}
/* Duplicate result set as we're going to remove it
* which will invalidate result set. */
int i;
for (i = 0; i < ret; ++i) {
void *dst = malloc(result_set[i].len);
if (!dst) {
ret = kr_error(ENOMEM);
goto cleanup;
}
memcpy(dst, result_set[i].data, result_set[i].len);
result_set[i].data = dst;
}
cache->api->remove(cache->db, result_set, ret);
kr_cache_sync(cache);
cleanup:
/* Free keys */
while (--i >= 0) {
free(result_set[i].data);
}
return ret;
} }
/** Prune expired/invalid records. */ /** Prune expired/invalid records. */
...@@ -1149,30 +1113,12 @@ static int cache_prune(lua_State *L) ...@@ -1149,30 +1113,12 @@ static int cache_prune(lua_State *L)
return 1; return 1;
} }
/** Clear all records. */ /** Clear everything. */
static int cache_clear(lua_State *L) static int cache_clear_everything(lua_State *L)
{ {
struct kr_cache *cache = cache_assert_open(L); struct kr_cache *cache = cache_assert_open(L);
/* Check parameters */ /* Clear records and packets. */
const char *args = NULL;
int n = lua_gettop(L);
if (n >= 1 && lua_isstring(L, 1)) {
args = lua_tostring(L, 1);
}
/* Clear a sub-tree in cache. */
if (args && strlen(args) > 0) {
int ret = cache_remove_prefix(cache, args);
if (ret < 0) {
format_error(L, kr_strerror(ret));
lua_error(L);
}
lua_pushinteger(L, ret);
return 1;
}
/* Clear cache. */
int ret = kr_cache_clear(cache); int ret = kr_cache_clear(cache);
if (ret < 0) { if (ret < 0) {
format_error(L, kr_strerror(ret)); format_error(L, kr_strerror(ret));
...@@ -1189,13 +1135,13 @@ static int cache_clear(lua_State *L) ...@@ -1189,13 +1135,13 @@ static int cache_clear(lua_State *L)
} }
/** @internal Dump cache key into table on Lua stack. */ /** @internal Dump cache key into table on Lua stack. */
static void cache_dump_key(lua_State *L, knot_db_val_t key) static void cache_dump(lua_State *L, knot_db_val_t keyval[])
{ {
knot_dname_t dname[KNOT_DNAME_MAXLEN]; knot_dname_t dname[KNOT_DNAME_MAXLEN];
char name[KNOT_DNAME_TXT_MAXLEN]; char name[KNOT_DNAME_TXT_MAXLEN];
uint16_t type; uint16_t type;
int ret = kr_unpack_cache_key(key, dname, &type); int ret = kr_unpack_cache_key(keyval[0], dname, &type);
if (ret < 0) { if (ret < 0) {
return; return;
} }
...@@ -1232,9 +1178,9 @@ static int cache_get(lua_State *L) ...@@ -1232,9 +1178,9 @@ static int cache_get(lua_State *L)
} }
/* Retrieve set of keys */ /* Retrieve set of keys */
const char *args = lua_tostring(L, 1); const char *prefix = lua_tostring(L, 1);
knot_db_val_t result_set[100]; knot_db_val_t keyval[100][2];
int ret = cache_prefixed(cache, args, result_set, 100); int ret = cache_prefixed(cache, prefix, false/*FIXME*/, keyval, 100);
if (ret < 0) { if (ret < 0) {
format_error(L, kr_strerror(ret)); format_error(L, kr_strerror(ret));
lua_error(L); lua_error(L);
...@@ -1242,7 +1188,7 @@ static int cache_get(lua_State *L) ...@@ -1242,7 +1188,7 @@ static int cache_get(lua_State *L)
/* Format output */ /* Format output */
lua_newtable(L); lua_newtable(L);
for (int i = 0; i < ret; ++i) { for (int i = 0; i < ret; ++i) {
cache_dump_key(L, result_set[i]); cache_dump(L, keyval[i]);
} }
return 1; return 1;
} }
...@@ -1365,8 +1311,8 @@ int lib_cache(lua_State *L) ...@@ -1365,8 +1311,8 @@ int lib_cache(lua_State *L)
{ "open", cache_open }, { "open", cache_open },
{ "close", cache_close }, { "close", cache_close },
{ "prune", cache_prune }, { "prune", cache_prune },
{ "clear", cache_clear }, { "clear_everything", cache_clear_everything },
{ "get", cache_get }, { "get", cache_get },
{ "max_ttl", cache_max_ttl }, { "max_ttl", cache_max_ttl },
{ "min_ttl", cache_min_ttl }, { "min_ttl", cache_min_ttl },
{ "ns_tout", cache_ns_tout }, { "ns_tout", cache_ns_tout },
......
...@@ -322,7 +322,10 @@ _Bool kr_dnssec_key_ksk(const uint8_t *); ...@@ -322,7 +322,10 @@ _Bool kr_dnssec_key_ksk(const uint8_t *);
_Bool kr_dnssec_key_revoked(const uint8_t *); _Bool kr_dnssec_key_revoked(const uint8_t *);
int kr_dnssec_key_tag(uint16_t, const uint8_t *, size_t); int kr_dnssec_key_tag(uint16_t, const uint8_t *, size_t);
int kr_dnssec_key_match(const uint8_t *, size_t, const uint8_t *, size_t); int kr_dnssec_key_match(const uint8_t *, size_t, const uint8_t *, size_t);
int kr_cache_closest_apex(struct kr_cache *, const knot_dname_t *, _Bool);
int kr_cache_insert_rr(struct kr_cache *, const knot_rrset_t *, const knot_rrset_t *, uint8_t, uint32_t); int kr_cache_insert_rr(struct kr_cache *, const knot_rrset_t *, const knot_rrset_t *, uint8_t, uint32_t);
int kr_cache_remove(struct kr_cache *, const knot_dname_t *, uint16_t);
int kr_cache_remove_subtree(struct kr_cache *, const knot_dname_t *, _Bool, int);
int kr_cache_sync(struct kr_cache *); int kr_cache_sync(struct kr_cache *);
typedef struct { typedef struct {
uint8_t bitmap[32]; uint8_t bitmap[32];
......
...@@ -183,6 +183,8 @@ EOF ...@@ -183,6 +183,8 @@ EOF
# Cache # Cache
kr_cache_insert_rr kr_cache_insert_rr
kr_cache_sync kr_cache_sync
kr_cache_remove
kr_cache_remove_subtree
EOF EOF
......
...@@ -157,6 +157,44 @@ setmetatable(modules, { ...@@ -157,6 +157,44 @@ setmetatable(modules, {
end end
}) })
cache.clear = function (name, exact_name, rr_type, maxcount, callback)
if name == nil then return cache.clear_everything() end
-- Check parameters, in order, and set defaults if missing.
local dname = kres.str2dname(name)
if not dname then error('cache.clear(): incorrect name passed') end
if exact_name == nil then exact_name = false end
if type(exact_name) ~= 'boolean'
then error('cache.clear(): incorrect exact_name passed') end
if rr_type ~= nil then
-- Special case, without any subtree searching.
if not exact_name
then error('cache.clear(): specifying rr_type only supported with exact_name') end
if maxcount or callback
then error('cache.clear(): maxcount and callback parameters not supported with rr_type') end
local ret = ffi.C.kr_cache_remove(kres.context().cache, dname, rr_type)
if ret ~= 0 then error(ffi.string(ffi.C.knot_strerror(ret))) end
return true
end
if maxcount == nil then maxcount = 100 end
if type(maxcount) ~= 'number' or maxcount <= 0
then error('cache.clear(): maxcount has to be a positive integer') end
-- Default callback function: repeat after 1ms
if callback == nil then callback =
function (ret, name, exact_name, rr_type, maxcount, callback)
if ret < 0 then error(ffi.string(ffi.C.knot_strerror(ret))) end
if (ret < maxcount) then return true end
event.after(1, function ()
cache.clear(name, exact_name, rr_type, maxcount, callback)
end)
return false
end
end
-- Do the C call and callback.
local ret = ffi.C.kr_cache_remove_subtree(kres.context().cache, dname, exact_name, maxcount)
return callback(ret, name, exact_name, rr_type, maxcount, callback)
end
-- Syntactic sugar for cache -- Syntactic sugar for cache
-- `cache[x] -> cache.get(x)` -- `cache[x] -> cache.get(x)`
-- `cache.{size|storage} = value` -- `cache.{size|storage} = value`
...@@ -372,4 +410,4 @@ else ...@@ -372,4 +410,4 @@ else
worker.sleep = disabled worker.sleep = disabled
worker.map = disabled worker.map = disabled
worker.coroutine = disabled worker.coroutine = disabled
end end
\ No newline at end of file
...@@ -779,7 +779,7 @@ int kr_cache_remove(struct kr_cache *cache, const knot_dname_t *name, uint16_t t ...@@ -779,7 +779,7 @@ int kr_cache_remove(struct kr_cache *cache, const knot_dname_t *name, uint16_t t
} }
int kr_cache_match(struct kr_cache *cache, const knot_dname_t *name, int kr_cache_match(struct kr_cache *cache, const knot_dname_t *name,
knot_db_val_t *keys, int max) bool exact_name, knot_db_val_t keyval[][2], int maxcount)
{ {
if (!cache_isvalid(cache)) { if (!cache_isvalid(cache)) {
return kr_error(EINVAL); return kr_error(EINVAL);
...@@ -795,9 +795,13 @@ int kr_cache_match(struct kr_cache *cache, const knot_dname_t *name, ...@@ -795,9 +795,13 @@ int kr_cache_match(struct kr_cache *cache, const knot_dname_t *name,
// use a mock type // use a mock type
knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_A); knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_A);
key.len -= 2 /* '\0' 'E' */ + sizeof(uint16_t); /* CACHE_KEY_DEF */ /* CACHE_KEY_DEF */
if (name[0] == '\0') ++key.len; /* the root name is special ATM */ key.len -= sizeof(uint16_t); /* the type */
return cache_op(cache, match, &key, keys, max); if (!exact_name) {
key.len -= 2; /* '\0' 'E' */
if (name[0] == '\0') ++key.len; /* the root name is special ATM */
}
return cache_op(cache, match, &key, keyval, maxcount);
} }
int kr_unpack_cache_key(knot_db_val_t key, knot_dname_t *buf, uint16_t *type) int kr_unpack_cache_key(knot_db_val_t key, knot_dname_t *buf, uint16_t *type)
...@@ -831,3 +835,39 @@ int kr_unpack_cache_key(knot_db_val_t key, knot_dname_t *buf, uint16_t *type) ...@@ -831,3 +835,39 @@ int kr_unpack_cache_key(knot_db_val_t key, knot_dname_t *buf, uint16_t *type)
return kr_ok(); return kr_ok();
} }
int kr_cache_remove_subtree(struct kr_cache *cache, const knot_dname_t *name,
bool exact_name, int maxcount)
{
if (!cache_isvalid(cache)) {
return kr_error(EINVAL);
}
knot_db_val_t keyval[maxcount][2], keys[maxcount];
int ret = kr_cache_match(cache, name, exact_name, keyval, maxcount);
if (ret < 0) {
return ret;
}
const int count = ret;
/* Duplicate the key strings, as deletion may invalidate the pointers. */
int i;
for (i = 0; i < count; ++i) {
keys[i].len = keyval[i][0].len;
keys[i].data = malloc(keys[i].len);
if (!keys[i].data) {
ret = kr_error(ENOMEM);
goto cleanup;
}
memcpy(keys[i].data, keyval[i][0].data, keys[i].len);
}
ret = cache->api->remove(cache->db, keys, count);
cleanup:
kr_cache_sync(cache); /* Sync even after just kr_cache_match(). */
/* Free keys */
while (--i >= 0) {
free(keys[i].data);
}
return ret ? ret : count;
}
...@@ -153,14 +153,23 @@ int kr_cache_remove(struct kr_cache *cache, const knot_dname_t *name, uint16_t t ...@@ -153,14 +153,23 @@ int kr_cache_remove(struct kr_cache *cache, const knot_dname_t *name, uint16_t t
* Get keys matching a dname lf prefix * Get keys matching a dname lf prefix
* @param cache cache structure * @param cache cache structure
* @param name dname * @param name dname
* @param keys matched keys * @param exact_name whether to only consider exact name matches
* @param keyval matched key-value pairs
* @param maxcount limit on the number of returned key-value pairs
* @return result count or an errcode * @return result count or an errcode
* @note the cache keys are matched by prefix, i.e. it very much depends * @note the cache keys are matched by prefix, i.e. it very much depends
* on their structure; CACHE_KEY_DEF. * on their structure; CACHE_KEY_DEF.
*/ */
KR_EXPORT KR_EXPORT
int kr_cache_match(struct kr_cache *cache, const knot_dname_t *name, int kr_cache_match(struct kr_cache *cache, const knot_dname_t *name,
knot_db_val_t *keys, int max); bool exact_name, knot_db_val_t keyval[][2], int maxcount);
/**
* Remove a subtree in cache. It's like _match but removing them instead of returning.
*/
KR_EXPORT
int kr_cache_remove_subtree(struct kr_cache *cache, const knot_dname_t *name,
bool exact_name, int maxcount);
/** /**
* Unpack dname and type from db key * Unpack dname and type from db key
......
...@@ -47,11 +47,15 @@ struct kr_cdb_api { ...@@ -47,11 +47,15 @@ struct kr_cdb_api {
int maxcount); int maxcount);
int (*write)(knot_db_t *db, const knot_db_val_t *key, knot_db_val_t *val, int (*write)(knot_db_t *db, const knot_db_val_t *key, knot_db_val_t *val,
int maxcount); int maxcount);
int (*remove)(knot_db_t *db, knot_db_val_t *key, int maxcount); /** Remove maxcount keys. Return error code. (Returns on first error.) */
int (*remove)(knot_db_t *db, knot_db_val_t keys[], int maxcount);
/* Specialised operations */ /* Specialised operations */
int (*match)(knot_db_t *db, knot_db_val_t *key, knot_db_val_t *val, int maxcount); /** Find key-value pairs that are prefixed by the given key, limited by maxcount.
* \return the number of pairs or negative error. */
int (*match)(knot_db_t *db, knot_db_val_t *key, knot_db_val_t keyval[][2], int maxcount);
/** Not implemented ATM. */
int (*prune)(knot_db_t *db, int maxcount); int (*prune)(knot_db_t *db, int maxcount);
/** Less-or-equal search (lexicographic ordering). /** Less-or-equal search (lexicographic ordering).
......
...@@ -547,14 +547,14 @@ static int cdb_writev(knot_db_t *db, const knot_db_val_t *key, knot_db_val_t *va ...@@ -547,14 +547,14 @@ static int cdb_writev(knot_db_t *db, const knot_db_val_t *key, knot_db_val_t *va
return ret; return ret;
} }
static int cdb_remove(knot_db_t *db, knot_db_val_t *key, int maxcount) static int cdb_remove(knot_db_t *db, knot_db_val_t keys[], int maxcount)
{ {
struct lmdb_env *env = db; struct lmdb_env *env = db;
MDB_txn *txn = NULL; MDB_txn *txn = NULL;
int ret = txn_get(env, &txn, false); int ret = txn_get(env, &txn, false);
for (int i = 0; ret == kr_ok() && i < maxcount; ++i) { for (int i = 0; ret == kr_ok() && i < maxcount; ++i) {
MDB_val _key = val_knot2mdb(key[i]); MDB_val _key = val_knot2mdb(keys[i]);
MDB_val val = { 0, NULL }; MDB_val val = { 0, NULL };
ret = lmdb_error(mdb_del(txn, env->dbi, &_key, &val)); ret = lmdb_error(mdb_del(txn, env->dbi, &_key, &val));
} }
...@@ -562,7 +562,7 @@ static int cdb_remove(knot_db_t *db, knot_db_val_t *key, int maxcount) ...@@ -562,7 +562,7 @@ static int cdb_remove(knot_db_t *db, knot_db_val_t *key, int maxcount)
return ret; return ret;
} }
static int cdb_match(knot_db_t *db, knot_db_val_t *key, knot_db_val_t *val, int maxcount) static int cdb_match(knot_db_t *db, knot_db_val_t *key, knot_db_val_t keyval[][2], int maxcount)
{ {
struct lmdb_env *env = db; struct lmdb_env *env = db;
MDB_txn *txn = NULL; MDB_txn *txn = NULL;
...@@ -594,7 +594,8 @@ static int cdb_match(knot_db_t *db, knot_db_val_t *key, knot_db_val_t *val, int ...@@ -594,7 +594,8 @@ static int cdb_match(knot_db_t *db, knot_db_val_t *key, knot_db_val_t *val, int
} }
/* Add to result set */ /* Add to result set */
if (results < maxcount) { if (results < maxcount) {
val[results] = val_mdb2knot(cur_key); keyval[results][0] = val_mdb2knot(cur_key);
keyval[results][1] = val_mdb2knot(cur_val);
++results; ++results;
} else { } else {
break; break;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment