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
[AAAA] => true
}
.. function:: cache.clear([domain])
:return: ``bool`` or ``int``
.. function:: cache.clear([name], [exact_name], [rr_type], [maxcount], [callback])
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,
and the number of removed records is returned.
:return: ``bool`` (success of removing all in one go)
: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:
.. code-block:: lua
-- Clear records at and below 'bad.cz'
cache.clear('bad.cz')
-- Clear whole cache
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.
The purpose is not to block the resolver for long; if you need larger purges,
you may e.g. repeat the command with a 1ms timer until it returns a lower value.
- To minimize surprises, you may prefer to specify names that have NS/SOA records,
e.g. ``example.com``. Details: validated NSEC and NSEC3 records
(which are used for aggressive non-existence proofs)
will be removed only for zones whose **apex** is at or below the specified name.
To minimize surprises with partial cache removal,
you may prefer to specify names that have NS/SOA records,
e.g. ``example.com``. Details: validated NSEC and NSEC3 records
(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
......
......@@ -1076,52 +1076,16 @@ static int cache_close(lua_State *L)
}
/** @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 */
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);
}
/* Start prefix search */
int ret = kr_cache_match(cache, buf, results, maxresults);
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;
return kr_cache_match(cache, buf, exact_name, keyval, maxcount);
}
/** Prune expired/invalid records. */
......@@ -1149,30 +1113,12 @@ static int cache_prune(lua_State *L)
return 1;
}
/** Clear all records. */
static int cache_clear(lua_State *L)
/** Clear everything. */
static int cache_clear_everything(lua_State *L)
{
struct kr_cache *cache = cache_assert_open(L);
/* Check parameters */
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. */
/* Clear records and packets. */
int ret = kr_cache_clear(cache);
if (ret < 0) {
format_error(L, kr_strerror(ret));
......@@ -1189,13 +1135,13 @@ static int cache_clear(lua_State *L)
}
/** @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];
char name[KNOT_DNAME_TXT_MAXLEN];
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) {
return;
}
......@@ -1232,9 +1178,9 @@ static int cache_get(lua_State *L)
}
/* Retrieve set of keys */
const char *args = lua_tostring(L, 1);
knot_db_val_t result_set[100];
int ret = cache_prefixed(cache, args, result_set, 100);
const char *prefix = lua_tostring(L, 1);
knot_db_val_t keyval[100][2];
int ret = cache_prefixed(cache, prefix, false/*FIXME*/, keyval, 100);
if (ret < 0) {
format_error(L, kr_strerror(ret));
lua_error(L);
......@@ -1242,7 +1188,7 @@ static int cache_get(lua_State *L)
/* Format output */
lua_newtable(L);
for (int i = 0; i < ret; ++i) {
cache_dump_key(L, result_set[i]);
cache_dump(L, keyval[i]);
}
return 1;
}
......@@ -1365,8 +1311,8 @@ int lib_cache(lua_State *L)
{ "open", cache_open },
{ "close", cache_close },
{ "prune", cache_prune },
{ "clear", cache_clear },
{ "get", cache_get },
{ "clear_everything", cache_clear_everything },
{ "get", cache_get },
{ "max_ttl", cache_max_ttl },
{ "min_ttl", cache_min_ttl },
{ "ns_tout", cache_ns_tout },
......
......@@ -322,7 +322,10 @@ _Bool kr_dnssec_key_ksk(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_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_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 *);
typedef struct {
uint8_t bitmap[32];
......
......@@ -183,6 +183,8 @@ EOF
# Cache
kr_cache_insert_rr
kr_cache_sync
kr_cache_remove
kr_cache_remove_subtree
EOF
......
......@@ -157,6 +157,44 @@ setmetatable(modules, {
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
-- `cache[x] -> cache.get(x)`
-- `cache.{size|storage} = value`
......@@ -372,4 +410,4 @@ else
worker.sleep = disabled
worker.map = disabled
worker.coroutine = disabled
end
\ No newline at end of file
end
......@@ -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,
knot_db_val_t *keys, int max)
bool exact_name, knot_db_val_t keyval[][2], int maxcount)
{
if (!cache_isvalid(cache)) {
return kr_error(EINVAL);
......@@ -795,9 +795,13 @@ int kr_cache_match(struct kr_cache *cache, const knot_dname_t *name,
// use a mock type
knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_A);
key.len -= 2 /* '\0' 'E' */ + sizeof(uint16_t); /* CACHE_KEY_DEF */
if (name[0] == '\0') ++key.len; /* the root name is special ATM */
return cache_op(cache, match, &key, keys, max);
/* CACHE_KEY_DEF */
key.len -= sizeof(uint16_t); /* the type */
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)
......@@ -831,3 +835,39 @@ int kr_unpack_cache_key(knot_db_val_t key, knot_dname_t *buf, uint16_t *type)
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
* Get keys matching a dname lf prefix
* @param cache cache structure
* @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
* @note the cache keys are matched by prefix, i.e. it very much depends
* on their structure; CACHE_KEY_DEF.
*/
KR_EXPORT
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
......
......@@ -47,11 +47,15 @@ struct kr_cdb_api {
int maxcount);
int (*write)(knot_db_t *db, const knot_db_val_t *key, knot_db_val_t *val,
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 */
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);
/** 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
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;
MDB_txn *txn = NULL;
int ret = txn_get(env, &txn, false);
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 };
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)
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;
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
}
/* Add to result set */
if (results < maxcount) {
val[results] = val_mdb2knot(cur_key);
keyval[results][0] = val_mdb2knot(cur_key);
keyval[results][1] = val_mdb2knot(cur_val);
++results;
} else {
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