diff options
Diffstat (limited to 'examples/redis-unstable/src/keymeta.c')
| -rw-r--r-- | examples/redis-unstable/src/keymeta.c | 935 |
1 files changed, 0 insertions, 935 deletions
diff --git a/examples/redis-unstable/src/keymeta.c b/examples/redis-unstable/src/keymeta.c deleted file mode 100644 index 59a8be8..0000000 --- a/examples/redis-unstable/src/keymeta.c +++ /dev/null @@ -1,935 +0,0 @@ -/* Read keymeta.h for high-level overview. */ - -#include "server.h" -#include <string.h> - -/* Encoding constants for metadata class names and serialization */ -#define KM_NAME_LEN 4 /* Short name length (e.g., "KMT1") */ -#define KM_PREFIX "META-" -#define KM_PREFIX_LEN 5 /* Length of "META-" prefix */ -#define KM_FULLNAME_LEN 9 /* Full name length: "META-xxxx" */ -#define KM_ENC_CHAR_BITS 6 /* Bits per character in encoding */ -#define KM_CHARSET_SIZE 64 /* Size of character set (2^6) */ -#define KM_VER_BITS 5 /* Bits for version in 32-bit class spec */ -#define KM_VER_MAX 31 /* Max version value (2^5 - 1) */ -#define KM_FLAGS_BITS 3 /* Bits for flags in 32-bit class spec */ -#define KM_FLAGS_MASK 0x7 /* Mask for 3-bit flags */ -#define KM_VER_MASK 0x1F /* Mask for 5-bit version */ -#define KM_CHAR_MASK 0x3F /* Mask for 6-bit character */ -#define KM_ENTITY_VER_BITS 10 /* Bits for version in 64-bit entity ID */ -#define KM_CLASS_SPEC_SIZE 4 /* Size of 32-bit class spec in bytes */ -#define KM_EXPIRE_RESET_VALUE ((uint64_t)-1) /* Sentinel: no expiration */ - -/* Cast const away only for initialization */ -#define KM_SET_CONST_CONF(conf) (*((KeyMetaClassConf *) (&conf))) - -/* Character set for metadata class names (same as module types). */ -static const char *keyMetaCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789_-"; - -typedef enum KeyMetaClassState { - CLASS_STATE_FREE = 0, /* Free must be 0. */ - CLASS_STATE_INUSE = 1, - CLASS_STATE_RELEASED = 2, -} KeyMetaClassState; - -static_assert(CLASS_STATE_FREE == 0, "CLASS_STATE_FREE must be 0 for memset initialization"); - -/* Key metadata class */ -typedef struct KeyMetaClass { - char name[5]; /* 4-char name of the class */ - ModuleEntityId entity; /* module key metadata name and ID. */ - const KeyMetaClassConf conf; /* copy of config */ - KeyMetaClassState state; /* FREE/INUSE/RELEASED */ - uint32_t classSpecEncoded; /* See keyMetaClassEncode() */ -} KeyMetaClass; -static KeyMetaClass keyMetaClass[KEY_META_ID_MAX]; - -/* Add metadata to keymeta spec, handling out-of-order metaid */ -static void keyMetaSpecAddUnordered(KeyMetaSpec *keymeta, int metaid, uint64_t metaval); - - -/* Encode 64b For module entity encode. Encode 32b class spec for RDB. - * - * Takes a 4-character name (e.g., "KMT1"), version (0-31), and flags, validates - * 4-char name uses valid character set. Version is 5 bits (0-31). - * - * >> ENCODING 32-BIT CLASS SPEC - * Encodes compact 32-bit class Spec for RDB/DUMP serialization: - * 31 8 7 3 2 0 - * ┌───────────────────────────────┬───────┬─────┐ - * │ 4-char name "xxxx"(24 bits) │ ver │flags│ - * │ (6 bits per char) │(5 bit)│(3b) │ - * └───────────────────────────────┴───────┴─────┘ - * - * >> ENCODING MODULE-STYLE ID - * Generates 9-char entity name with "META-" prefix (e.g., "META-KMT1"), 54 bits - * in total, plus 10 bits version (values 0-31). Compatible with moduleTypeEncodeId: - * 63 10 9 0 - * ┌───────────────────────────────────────┬─────────────┐ - * │ 9-char name (56 bits) "META-xxxx" │ ver (0-31) │ - * │ (6 bits per char) │ (10 bit) │ - * └───────────────────────────────────────┴─────────────┘ - * - */ -static uint64_t keyMetaClassEncode(const char *name, int metaver, uint64_t flags, - char *fullname, uint32_t *rdbEncodedValue) { - /* Validate name is exactly 4 characters */ - if (strlen(name) != KM_NAME_LEN) return 0; - - /* Validate version range (5 bits = 0-31 for metadata classes) */ - if (metaver < 0 || metaver > KM_VER_MAX) return 0; - - /* Generate 9-char name with "META-" prefix */ - memcpy(fullname, KM_PREFIX, KM_PREFIX_LEN); - memcpy(fullname + KM_PREFIX_LEN, name, KM_NAME_LEN); - fullname[KM_FULLNAME_LEN] = '\0'; - - /* Encode 9-char name into 64-bit entityId (module-style ID, 54 bits name - * plus 10 bits version) */ - uint64_t encName9Chars = 0; - /* Encode last 4-char into 32-bit serialized class ID (24b name + 5b version + 3b flags) */ - uint32_t encName4chars = 0; - for (int j = 0; j < KM_FULLNAME_LEN; j++) { - char *p = strchr(keyMetaCharSet, fullname[j]); - if (!p) return 0; /* Invalid character in name */ - unsigned long pos = p - keyMetaCharSet; - encName9Chars = (encName9Chars << KM_ENC_CHAR_BITS) | pos; - if (j >= KM_PREFIX_LEN) encName4chars = (encName4chars << KM_ENC_CHAR_BITS) | pos; - } - - /* Encodes compact 32-bit RDB/DUMP serialized class Spec */ - *rdbEncodedValue = ((encName4chars << KM_VER_BITS) | metaver) << KM_FLAGS_BITS | (flags & KM_FLAGS_MASK); - - /* Encodes the 9-char name into 64-bit ID (compatible with moduleTypeEncodeId) */ - uint64_t entityId = (encName9Chars << KM_ENTITY_VER_BITS) | metaver; - return entityId; -} - -/* Decode 32-bit class spec from RDB/DUMP format - * - * Takes a 32-bit keyMetaClassSer and extracts: - * - 4-character name (24 bits, 6 bits per char) - * - version (5 bits, 0-31) - * - flags (3 bits) - * - * This is the reverse of the encoding done in keyMetaClassEncode(). - * - * Cannot fail: all 32-bit values are valid (6-bit char mask ensures valid charset - * indices, and all 32 bits are consumed by design: 3 + 5 + 24 = 32). - */ -void keyMetaClassDecode(uint32_t value, char *name, int *metaver, uint8_t *flags) { - debugServerAssert(name && metaver && flags); - - /* Extract flags (lowest 3 bits) */ - *flags = value & KM_FLAGS_MASK; - value >>= KM_FLAGS_BITS; - - /* Extract version (next 5 bits) */ - *metaver = value & KM_VER_MASK; - value >>= KM_VER_BITS; - - /* Extract 4-char name (24 bits, 6 bits per char, big-endian) */ - for (int i = KM_NAME_LEN - 1; i >= 0; i--) { - unsigned int pos = value & KM_CHAR_MASK; - debugServerAssert(pos < KM_CHARSET_SIZE); /* 6-bit value always < 64 */ - name[i] = keyMetaCharSet[pos]; - value >>= KM_ENC_CHAR_BITS; - } - name[KM_NAME_LEN] = '\0'; - - /* All 32 bits should be consumed (3 + 5 + 24 = 32) */ - debugServerAssert(value == 0); -} - -/* Return -1 if not found, 1..7 for slot if INUSE, alreadyReleased if found but released */ -static int keyMetaClassLookupByName(const char *name, int *alreadyReleased) { - *alreadyReleased = 0; - if (!name) return -1; - - for (int i = KEY_META_ID_MODULE_FIRST; i <= KEY_META_ID_MODULE_LAST; i++) { - if (keyMetaClass[i].state == CLASS_STATE_FREE) - continue; - if (memcmp(keyMetaClass[i].name, name, KM_NAME_LEN) != 0) - continue; - if (keyMetaClass[i].state == CLASS_STATE_INUSE) - return i; - if (keyMetaClass[i].state == CLASS_STATE_RELEASED) { - *alreadyReleased = 1; - return i; - } - } - return -1; -} - -/* Initialize server.keyMeta with defaults and reserve built-in classes. */ -void keyMetaInit(void) { - memset(keyMetaClass, 0, sizeof(KeyMetaClass) * KEY_META_ID_MAX); - - /* Slot 0 is EXPIRE, built-in and always active. */ - keyMetaClass[KEY_META_ID_EXPIRE].state = CLASS_STATE_INUSE; - KM_SET_CONST_CONF(keyMetaClass[KEY_META_ID_EXPIRE].conf).flags = 0; - KM_SET_CONST_CONF(keyMetaClass[KEY_META_ID_EXPIRE].conf).reset_value = KM_EXPIRE_RESET_VALUE; -} - -/* Prepare key metadata spec for copy of `srcKv` */ -void keyMetaOnCopy(kvobj *kv, robj *srcKey, robj *dstKey, int srcDbId, int dstDbId, - KeyMetaSpec *keymeta) -{ - uint64_t *pMeta = ((uint64_t *)kv) - 1; - if (kv->metabits & KEY_META_MASK_EXPIRE) { - if (*pMeta != KM_EXPIRE_RESET_VALUE) - keyMetaSpecAdd(keymeta, KEY_META_ID_EXPIRE, *pMeta); - pMeta--; - } - - uint32_t mbits = kv->metabits >> KEY_META_ID_MODULE_FIRST; - if (likely(mbits == 0)) return; - - int keyMetaId = KEY_META_ID_MODULE_FIRST; - struct RedisModuleKeyOptCtx ctx = {srcKey, dstKey, srcDbId, dstDbId }; - do { - if (mbits & 1) { - serverAssert(keyMetaClass[keyMetaId].state == CLASS_STATE_INUSE); - /* Copy metadata from kv to temporary storage keymeta */ - uint64_t tmpMeta = *pMeta--; - if (tmpMeta != keyMetaClass[keyMetaId].conf.reset_value && - keyMetaClass[keyMetaId].conf.copy && - keyMetaClass[keyMetaId].conf.copy(&ctx, &tmpMeta)) - keyMetaSpecAdd(keymeta, keyMetaId, tmpMeta); - } - mbits >>= 1; - keyMetaId++; - } while (mbits != 0); -} - -/* Prepare metadata spec for rename of `kv` */ -void keyMetaOnRename(struct redisDb *db, kvobj *kv, robj *oldKey, robj *newKey, KeyMetaSpec *kms) { - uint64_t *pMeta = ((uint64_t *)kv) - 1; - - /* Handle builtin expire: add only if set and value != -1, but always advance - * the pointer when the expire bit is set since the slot exists either way. */ - if (kv->metabits & KEY_META_MASK_EXPIRE) { - if (*pMeta != KM_EXPIRE_RESET_VALUE) - keyMetaSpecAdd(kms, KEY_META_ID_EXPIRE, *pMeta); - pMeta--; /* skip expire slot */ - } - - /* Process module metadata. Default on rename: keep if no callback. */ - uint32_t mbits = kv->metabits >> KEY_META_ID_MODULE_FIRST; - if (likely(mbits == 0)) return; - - int keyMetaId = KEY_META_ID_MODULE_FIRST; - struct RedisModuleKeyOptCtx ctx = { oldKey, newKey, db ? db->id : -1, db ? db->id : -1 }; - do { - if (mbits & 1) { - serverAssert(keyMetaClass[keyMetaId].state == CLASS_STATE_INUSE); - uint64_t tmpMeta = *pMeta; /* read current module slot */ - if (tmpMeta != keyMetaClass[keyMetaId].conf.reset_value && - (!keyMetaClass[keyMetaId].conf.rename || - keyMetaClass[keyMetaId].conf.rename(&ctx, &tmpMeta))) - { - keyMetaSpecAdd(kms, keyMetaId, tmpMeta); - /* Set old metadata slot to reset_value to prevent free callback */ - *pMeta = keyMetaClass[keyMetaId].conf.reset_value; - } - pMeta--; /* advance to next module slot */ - } - mbits >>= 1; - keyMetaId++; - } while (mbits != 0); -} - -/* Prepare metadata spec for move of `kv` from srcDbId to dstDbId */ -void keyMetaOnMove(kvobj *kv, robj *key, int srcDbId, int dstDbId, KeyMetaSpec *kms) { - uint64_t *pMeta = ((uint64_t *)kv) - 1; - - /* Handle builtin expire: add only if set and value != -1, but always advance - * the pointer when the expire bit is set since the slot exists either way. */ - if (kv->metabits & KEY_META_MASK_EXPIRE) { - if (*pMeta != KM_EXPIRE_RESET_VALUE) - keyMetaSpecAdd(kms, KEY_META_ID_EXPIRE, *pMeta); - pMeta--; /* skip expire slot */ - } - - /* Process module metadata. Default on move: keep if no callback. */ - uint32_t mbits = kv->metabits >> KEY_META_ID_MODULE_FIRST; - if (likely(mbits == 0)) return; - - int keyMetaId = KEY_META_ID_MODULE_FIRST; - struct RedisModuleKeyOptCtx ctx = { key, NULL, srcDbId, dstDbId}; - do { - if (mbits & 1) { - serverAssert(keyMetaClass[keyMetaId].state == CLASS_STATE_INUSE); - uint64_t tmpMeta = *pMeta; /* read current module slot */ - if (tmpMeta != keyMetaClass[keyMetaId].conf.reset_value && - (!keyMetaClass[keyMetaId].conf.move || - keyMetaClass[keyMetaId].conf.move(&ctx, &tmpMeta))) - { - keyMetaSpecAdd(kms, keyMetaId, tmpMeta); - /* If keep, set old metadata to reset_value to prevent free callback */ - *pMeta = keyMetaClass[keyMetaId].conf.reset_value; - } - pMeta--; /* advance to next module slot */ - } - mbits >>= 1; - keyMetaId++; - } while (mbits != 0); -} - -/* - * keyMetaOnUnlink() - when a key is logically overwritten/removed from the DB - * - * - Runs before the value object is actually freed (see keyMetaOnFree()). - * - Runs on the main thread (same timing as moduleNotifyKeyUnlink()). - * - Allows modules to detach per-key metadata from external structures, update - * auxiliary indexes, stats, etc. - * - Skips the built-in EXPIRE slot (handled by caller). - * - Iterates over module metadata bits and, for every set bit, invokes the - * class-specific unlink callback if provided. - */ -void keyMetaOnUnlink(redisDb *db, robj *key, kvobj *kv) { - /* Skip builtin expire slot if present; no action for expire itself here. */ - uint64_t *pMeta = ((uint64_t *)kv) - 1; - if (kv->metabits & KEY_META_MASK_EXPIRE) - pMeta--; - - /* Iterate module metadata and invoke per-class unlink if provided. */ - uint32_t mbits = kv->metabits >> KEY_META_ID_MODULE_FIRST; - if (likely(mbits == 0)) return; - - /* Build operation context for modules: from_key = key name, to_key = NULL. */ - struct RedisModuleKeyOptCtx ctx = { key, NULL, db ? db->id : -1, -1 }; - - int keyMetaId = KEY_META_ID_MODULE_FIRST; - do { - if (mbits & 1) { - serverAssert(keyMetaClass[keyMetaId].state == CLASS_STATE_INUSE); - - if (*pMeta != keyMetaClass[keyMetaId].conf.reset_value && - keyMetaClass[keyMetaId].conf.unlink) - { - keyMetaClass[keyMetaId].conf.unlink(&ctx, pMeta); - } - pMeta--; - } - mbits >>= 1; - keyMetaId++; - } while (mbits != 0); -} - -/* - * keyMetaOnFree() - when kvobj's metadata is actually being freed - * - * - Called after the key has been logically unlinked (see keyMetaOnUnlink()) - * - This is the place to reclaim resources associated with per-key metadata (e.g., - * free external allocations referenced by the 8-byte metadata value). - * - May run in a background thread; therefore module code invoked here must NOT - * access Redis keyspace or perform operations that require the main thread. - * Only perform thread-safe memory cleanup pertinent to the metadata. - * - For each attached metadata invokes class-specific 'free' callback if given, - */ -void keyMetaOnFree(kvobj *kv) { - /* Skip builtin expire slot if present; no action needed for expire itself. */ - uint64_t *pMeta = ((uint64_t *)kv) - 1; - if (kv->metabits & KEY_META_MASK_EXPIRE) - pMeta--; - - /* Iterate module metadata and invoke per-class free if provided. */ - uint32_t mbits = kv->metabits >> KEY_META_ID_MODULE_FIRST; - if (likely(mbits == 0)) return; - - int keyMetaId = KEY_META_ID_MODULE_FIRST; - const char *keyname = kvobjGetKey(kv); - do { - if (mbits & 1) { - serverAssert(keyMetaClass[keyMetaId].state == CLASS_STATE_INUSE); - uint64_t meta = *pMeta--; /* consume this module's metadata slot */ - if (meta != keyMetaClass[keyMetaId].conf.reset_value && - keyMetaClass[keyMetaId].conf.free) - keyMetaClass[keyMetaId].conf.free(keyname, meta); - } - mbits >>= 1; - keyMetaId++; - } while (mbits != 0); -} - -/* Free any metadata stored in a KeyMetaSpec. This is called when RDB load fails - * after some metadata has been loaded. It invokes the free cb for each metadata - * class that was already loaded, preventing memory leaks from partially-loaded metadata. - * - * Note: - * - We pass NULL for keyname since the key doesn't exist yet. - * - The kms->meta[] array is stored in reverse order: smallest metaid at the end. - */ -void keyMetaSpecCleanup(KeyMetaSpec *kms) { - if (kms->numMeta == 0) return; - - /* Iterate through the metadata array in reverse order (largest to smallest ID) */ - int startIdx = KEY_META_ID_MAX - kms->numMeta; - uint32_t mbits = kms->metabits; - - for (int i = startIdx ; mbits != 0 ; i++) { - /* Find the highest metaid remaining in mbits */ - int metaid = 31 - __builtin_clz((unsigned)mbits); - - /* Get the metadata value for this slot */ - uint64_t meta = kms->meta[i]; - - /* Call free callback if metadata is not reset value */ - KeyMetaClass *pClass = &keyMetaClass[metaid]; - if (pClass->state == CLASS_STATE_INUSE && - meta != pClass->conf.reset_value && - pClass->conf.free) - { - pClass->conf.free(NULL, meta); - } - - /* Clear this bit and continue to next slot */ - mbits &= ~(1 << metaid); - } - kms->numMeta = 0; - kms->metabits = 0; -} - -int rdbLoadSkipMetaIfAllowed(rio *rdb, char *cname, int flags) { - static int countDownNotice = 0; - static rio *lastRdb = NULL; - if (lastRdb != rdb) { - countDownNotice = 10; - lastRdb = rdb; - } - - /* Check ALLOW_IGNORE flag */ - if (flags & (1 << KEY_META_FLAG_ALLOW_IGNORE)) { - if (countDownNotice-- > 0) { - /* Skip this metadata gracefully */ - serverLog(LL_NOTICE, "Skipping metadata for class '%s' (not registered or missing rdb_load)", cname); - } - - /* Skip the metadata value by loading and discarding it. - * The metadata format is: VALUE (variable length) + EOF marker. - * - * The VALUE is saved using RedisModule_Save* functions which use module opcodes - * (RDB_MODULE_OPCODE_SINT, etc.), so we use rdbLoadCheckModuleValue() to skip it. - * - * Note: rdbLoadCheckModuleValue() reads opcodes until it finds RDB_MODULE_OPCODE_EOF, - * so it consumes the EOF marker as well. We don't need to read it separately. */ - robj *dummy = rdbLoadCheckModuleValue(rdb, cname); - if (dummy == NULL) { - serverLog(LL_WARNING, "Corrupted metadata value for class '%s'", cname); - return -1; - } - - decrRefCount(dummy); - return 0; - } else { - serverLog(LL_WARNING, "RDB load key metadata failed: Class '%s' not registered or missing rdb_load().", cname); - return -1; - } -} - -/* Load module metadata from RDB. - * Returns 0 on success, -1 on error. - * Stores loaded metadata in the provided KeyMetaSpec structure. - * - * Format (same as save): - * 1B: NUM_CLASSES (already read by caller) - * For each class: - * 4B: CLASS_SPEC (32-bit classSpecEncoded) - * ?B: VALUE (from rdb_load callback) - * 1B: RDB_MODULE_OPCODE_EOF - */ -int rdbLoadKeyMetadata(rio *rdb, int dbid, int numClasses, KeyMetaSpec *kms) { - if (numClasses > KEY_META_MAX_NUM_MODULES) { - serverLog(LL_WARNING, "Too many metadata classes: %d (max %d)", - numClasses, KEY_META_MAX_NUM_MODULES); - return -1; - } - - for (int i = 0; i < numClasses; i++) { - /* Read 32-bit encoded class spec */ - uint32_t encClassSpec; - if (rioRead(rdb, &encClassSpec, KM_CLASS_SPEC_SIZE) == 0) goto error; - - /* Deserialize to get name, version, flags */ - char name[5]; - int metaver; - uint8_t flags; - keyMetaClassDecode(encClassSpec, name, &metaver, &flags); - - /* Lookup class by name */ - int alreadyReleased = 0; - KeyMetaClassId classId = keyMetaClassLookupByName(name, &alreadyReleased); - - /* If class not found or released, check ALLOW_IGNORE flag */ - if (classId == -1 || alreadyReleased) { - int rc = rdbLoadSkipMetaIfAllowed(rdb, name, flags); - if (rc == -1) goto error; - continue; - } - - /* Verify version matches */ - KeyMetaClass *pClass = &keyMetaClass[classId]; - debugServerAssert(pClass->state == CLASS_STATE_INUSE); - - /* If no rdb_load callback, check ALLOW_IGNORE flag */ - if (pClass->conf.rdb_load == NULL) { - /* No rdb_load callback - check ALLOW_IGNORE flag */ - int rc = rdbLoadSkipMetaIfAllowed(rdb, name, flags); - if (rc == -1) goto error; - continue; - } - - RedisModuleIO io; - /* We don't have the key yet, so pass NULL for now */ - moduleInitIOContext(&io, &pClass->entity, rdb, NULL, dbid); - - uint64_t meta = 0; - int rc = pClass->conf.rdb_load(&io, &meta, metaver); - - /* Read EOF marker */ - uint64_t eof = rdbLoadLen(rdb, NULL); - if (eof != RDB_MODULE_OPCODE_EOF) { - serverLog(LL_WARNING, "Missing EOF after key metadata '%s' (got 0x%llx)", - name, (unsigned long long)eof); - io.error = 1; - } - - if (io.ctx) { - moduleFreeContext(io.ctx); - zfree(io.ctx); - } - - if (io.error) { - /* rdb_load succeeded but loading EOF failed */ - if (rc == 1) keyMetaSpecAddUnordered(kms, classId, meta); - goto error; - } - - /* Handle rdb_load return value: - * 1: Attach metadata to key (success) - * 0: Ignore/skip metadata (not an error) - * -1: Error - abort RDB load (module should clean up before returning -1) */ - if (rc == 1) { - /* Add metadata, handling out-of-order classIds that may occur when - * modules register in different order at load time vs save time */ - keyMetaSpecAddUnordered(kms, classId, meta); - } else if (rc == 0) { - /* Ignore/skip - don't attach metadata, continue loading */ - } else if (rc == -1) { - /* Error - abort RDB load */ - serverLog(LL_WARNING, - "RDB load failed: rdb_load callback for metadata class '%s' returned error", name); - goto error; - } else { - /* Invalid return value */ - serverLog(LL_WARNING, - "RDB load failed: rdb_load callback for metadata class '%s' " - "returned invalid value %d (expected -1, 0, or 1)", - name, rc); - goto error; - } - } - - return 0; /* Success */ - -error: - /* Clean up any metadata that was successfully loaded before the error */ - keyMetaSpecCleanup(kms); - return -1; -} - -/* Save all key metadata to RDB using lazy header writing. - * We accumulate class data (CLASS_SPEC + VALUE + EOF) in a temporary buffer, - * counting classes that actually write data. Only if count > 0, we write the - * opcode and NUM_CLASSES to RDB, followed by the accumulated payload. - * This avoids writing RDB_OPCODE_KEY_META when no module writes any data. - * - * Format: - * 1B: RDB_OPCODE_KEY_META - * ?B: NUM_CLASSES (count of classes that wrote data) - * For each class: - * 4B: CLASS_SPEC (32-bit classSpecEncoded) - * ?B: VALUE (from rdb_save callback) - * 1B: RDB_MODULE_OPCODE_EOF - * - * Returns -1 on error, 0 on success. - */ -int rdbSaveKeyMetadata(rio *rdb, robj *key, kvobj *kv, int dbid) { - - /* Check if there are any module metadata bits set */ - uint32_t mbits = kv->metabits >> KEY_META_ID_MODULE_FIRST; - if (likely(mbits == 0)) return 0; /* No module metadata */ - - /* Skip builtin expire slot if present */ - uint64_t *pMeta = ((uint64_t *)kv) - 1; - if (kv->metabits & KEY_META_MASK_EXPIRE) - pMeta--; - - /* Create temporary buffer for payload (class data only, no headers) */ - rio payload_rio; - rioInitWithBuffer(&payload_rio, sdsempty()); - - /* Iterate through classes and accumulate payload */ - int numClasses = 0; - int keyMetaId = KEY_META_ID_MODULE_FIRST; - uint32_t mbits_copy = mbits; - - do { - /* Check if metadata is attached for this class */ - if (mbits_copy & 1) { - KeyMetaClass *pClass = &keyMetaClass[keyMetaId]; - serverAssert(pClass->state == CLASS_STATE_INUSE); - - if (*pMeta != pClass->conf.reset_value && pClass->conf.rdb_save) { - /* Write 32-bit class spec to payload buffer */ - uint32_t classSpec = pClass->classSpecEncoded; - if (rdbWriteRaw(&payload_rio, &classSpec, KM_CLASS_SPEC_SIZE) == -1) goto error; - - size_t bytes_before = sdslen(payload_rio.io.buffer.ptr); - - /* Call module's rdb_save callback */ - RedisModuleIO io; - moduleInitIOContext(&io, &pClass->entity, &payload_rio, key, dbid); - pClass->conf.rdb_save(&io, kv, pMeta); - - if (io.ctx) { - moduleFreeContext(io.ctx); - zfree(io.ctx); - } - - if (io.error) goto error; - - size_t bytes_after = sdslen(payload_rio.io.buffer.ptr); - - /* Check if module actually wrote any data */ - if (bytes_after > bytes_before) { - /* Module wrote data - add EOF marker and count it */ - if (rdbSaveLen(&payload_rio, RDB_MODULE_OPCODE_EOF) == -1) goto error; - numClasses++; - } else { - /* Module didn't write data - remove the class spec we wrote. - * bytes_before is the length after writing the class spec, so we want - * to keep bytes_before - KM_CLASS_SPEC_SIZE bytes. We also need to update the RIO's pos to match. */ - sdssubstr(payload_rio.io.buffer.ptr, 0, bytes_before - KM_CLASS_SPEC_SIZE); - payload_rio.io.buffer.pos = bytes_before - KM_CLASS_SPEC_SIZE; - } - } - - pMeta--; /* Move to next metadata slot */ - } - keyMetaId++; - mbits_copy >>= 1; - } while (mbits_copy); - - /* If no classes wrote data, discard everything */ - if (numClasses == 0) { - sdsfree(payload_rio.io.buffer.ptr); - return 0; - } - - /* Now write: [RDB_OPCODE_KEY_META][numClasses][payload] */ - if ((rdbSaveType(rdb, RDB_OPCODE_KEY_META) == -1) || - (rdbSaveLen(rdb, numClasses) == -1) || - (rdbWriteRaw(rdb, payload_rio.io.buffer.ptr, sdslen(payload_rio.io.buffer.ptr)) == -1)) - { - goto error; - } - - sdsfree(payload_rio.io.buffer.ptr); - return 0; - -error: - sdsfree(payload_rio.io.buffer.ptr); - return -1; -} - -/* returns 0 on error, 1 on success. */ -int keyMetaOnAof(rio *r, robj *key, kvobj *kv, int dbid) { - /* Skip builtin expire slot if present; no action needed for expire itself. */ - uint64_t *pMeta = ((uint64_t *)kv) - 1; - if (kv->metabits & KEY_META_MASK_EXPIRE) - pMeta--; - - /* Iterate module metadata and invoke per-class aof_rewrite if provided */ - uint32_t mbits = kv->metabits >> KEY_META_ID_MODULE_FIRST; - if (likely(mbits == 0)) return 1; - - int keyMetaId = KEY_META_ID_MODULE_FIRST; - do { - if (mbits & 1) { - serverAssert(keyMetaClass[keyMetaId].state == CLASS_STATE_INUSE); - - uint64_t meta = *pMeta; - if (meta != keyMetaClass[keyMetaId].conf.reset_value && - keyMetaClass[keyMetaId].conf.aof_rewrite) - { - RedisModuleIO io; - moduleInitIOContext(&io, &keyMetaClass[keyMetaId].entity, r, key, dbid); - keyMetaClass[keyMetaId].conf.aof_rewrite(&io, kv, meta); - if (io.ctx) { - moduleFreeContext(io.ctx); - zfree(io.ctx); - } - if (io.error) return 0; - } - pMeta--; - } - mbits >>= 1; - keyMetaId++; - } while (mbits != 0); - - return 1; -} - -/* Move entire metadata from old to new kvobj as is */ -void keyMetaTransition(kvobj *kvOld, kvobj *kvNew) { - /* Precondition: */ - debugServerAssert(kvOld->metabits>>KEY_META_ID_MODULE_FIRST); - - /* Skip builtin expire slot if present; no action needed for expire itself. */ - uint64_t *pMetaOld = ((uint64_t *)kvOld) - 1; - if (kvOld->metabits & KEY_META_MASK_EXPIRE) pMetaOld--; - uint64_t *pMetaNew = ((uint64_t *)kvNew) - 1; - if (kvNew->metabits & KEY_META_MASK_EXPIRE) pMetaNew--; - - uint32_t mbitsOld = kvOld->metabits >> KEY_META_ID_MODULE_FIRST; - uint32_t mbitsNew = kvNew->metabits >> KEY_META_ID_MODULE_FIRST; - if (likely(mbitsOld == 0)) return; - int keyMetaId = KEY_META_ID_MODULE_FIRST; - do { - if (mbitsOld & 1) { - if (mbitsNew & 1) { - /* Transition metadata from old to new */ - *pMetaNew-- = *pMetaOld; - /* Reset old metadata value to prevent double-free */ - *pMetaOld-- = keyMetaClass[keyMetaId].conf.reset_value; - } else { - /* Leave metadata in old key as is */ - pMetaOld--; - } - } else { - /* Update pMetaNew if needed (No need to reset value in new key, - * assuming it was initialized earlier). */ - pMetaNew -= mbitsNew & 1; - } - - mbitsOld >>= 1; - mbitsNew >>= 1; - keyMetaId++; - } while (mbitsOld); -} - -/* Create a new metadata class. Returns class ID (1-7) on success, 0 on failure. - * - * context - In case of a module, pass the module pointer. Otherwise NULL. - */ -KeyMetaClassId keyMetaClassCreate(RedisModule *context, const char *name, - int metaver, KeyMetaClassConf *conf) { - if (!conf) return 0; - - /* Validate and encode ID. This also validates 4-char name and generates "META-" prefix. */ - char fullname[KM_FULLNAME_LEN+1]; - uint32_t classSpecEncoded; - /* Resolve: entityId, fullname, keyMetaClassSer */ - uint64_t entityId = keyMetaClassEncode(name, - metaver, - conf->flags & KEY_META_FLAGS_RDB_MASK, - fullname, - &classSpecEncoded); - if (entityId == 0) return 0; - - /* Check for name conflicts using 4-char name. Allow reuse of RELEASED; forbid if INUSE. */ - int alreayReleased; - int slot = keyMetaClassLookupByName(name, &alreayReleased); - - if (alreayReleased) { - /* If already released, then reuse the slot. */ - } else { - /* Assert class is registered for first time */ - serverAssert(slot == -1); - - /* Find free slot */ - for (int i = KEY_META_ID_MODULE_FIRST; i <= KEY_META_ID_MODULE_LAST; i++) { - if (keyMetaClass[i].state == CLASS_STATE_FREE) { - slot = i; - break; - } - } - if (slot == -1) return 0; /* no free slots */ - } - - KeyMetaClass *pKeyMetaClass = &keyMetaClass[slot]; - - /* Store 4-char short name */ - memcpy(pKeyMetaClass->name, name, KM_NAME_LEN); - pKeyMetaClass->name[KM_NAME_LEN] = '\0'; - - /* Store 9-char full name with "META-" prefix */ - memcpy(pKeyMetaClass->entity.name, fullname, KM_FULLNAME_LEN+1); - pKeyMetaClass->entity.id = entityId; - pKeyMetaClass->entity.module = context; - pKeyMetaClass->state = CLASS_STATE_INUSE; - pKeyMetaClass->classSpecEncoded = classSpecEncoded; - KM_SET_CONST_CONF(pKeyMetaClass->conf) = *conf; /* Copy config as is. */ - return slot; /* Return handle (1..7). */ -} - -/* Destroy (release) a class by its ID. Returns 1 on success, 0 on failure. */ -int keyMetaClassRelease(KeyMetaClassId id) { - if (!(id >= KEY_META_ID_MODULE_FIRST && id <= KEY_META_ID_MODULE_LAST)) - return 0; - - if (keyMetaClass[id].state != CLASS_STATE_INUSE) - return 0; - - keyMetaClass[id].state = CLASS_STATE_RELEASED; - return 1; -} - -/* Set a module metadata value on an opened key. Returns the new kvobj pointer (may be reallocated). - * Returns NULL on failure. The caller must update any references to the old kv pointer. */ -kvobj *keyMetaSetMetadata(redisDb *db, kvobj *kv, KeyMetaClassId id, uint64_t metadata) { - serverAssert(id >= KEY_META_ID_MODULE_FIRST && id <= KEY_META_ID_MODULE_LAST); - - /* Class must be active */ - if (keyMetaClass[id].state != CLASS_STATE_INUSE) - return NULL; - - /* If metadata already attached, just update it in place. */ - if (kv->metabits & (1u << id)) { - *kvobjMetaRef(kv, id) = metadata; - return kv; - } - - /* We need to grow kv to add a new 8-byte metadata slot. This may reallocate - * the object, so we must carefully preserve and restore: - * - The key's expires dictionary entry (if TTL is set) - * - The global Hash Field Expires (HFE) registration for hash objects - * - All existing metadata values (including expire value) - */ - - sds key = kvobjGetKey(kv); - int slot = getKeySlot(key); - - /* Preserve HFE registration for hash objects (embedded in object memory). */ - uint64_t subexpiry = EB_EXPIRE_TIME_INVALID; - if (kv->type == OBJ_HASH) - subexpiry = estoreRemove(db->subexpires, slot, kv); - - /* Preserve existing expire value (and whether an expires entry exists). */ - long long old_expire_val = kvobjGetExpire(kv); - - /* We'll need the key's link in the main dictionary to update pointer if reallocated. */ - dictEntryLink keyLink = kvstoreDictFindLink(db->keys, slot, key, NULL); - serverAssert(keyLink != NULL); - - /* If the key has an actual TTL (expire != -1), also preserve the expires dict link. */ - dictEntryLink exLink = NULL; - if (old_expire_val != -1) { - exLink = kvstoreDictFindLink(db->expires, slot, key, NULL); - serverAssert(exLink != NULL); - } - - /* Reallocate kv with the new metadata bit enabled. kvobjSet may return a new - * ptr. Takes care to transition existing metadata as needed. */ - kv = kvobjSet(key, kv, kv->metabits | (1u << id)); - kvstoreDictSetAtLink(db->keys, slot, kv, &keyLink, 0); - - /* Set new metadata */ - *kvobjMetaRef(kv, id) = metadata; - - /* If there was an expires entry (expire != -1), update its kv pointer. */ - if (exLink) { - ((uint64_t *)kv)[-1] = old_expire_val; /* expiry must be first meta */ - kvstoreDictSetAtLink(db->expires, slot, kv, &exLink, 0); - } - - /* Re-register in HFE if needed. */ - if (subexpiry != EB_EXPIRE_TIME_INVALID) - estoreAdd(db->subexpires, slot, kv, subexpiry); - - return kv; -} - -/* Retrieve a module metadata value from an opened key. Returns 1 on success, 0 otherwise. */ -int keyMetaGetMetadata(KeyMetaClassId kmcId, kvobj *kv, uint64_t *metadata) { - serverAssert(kmcId >= KEY_META_ID_MODULE_FIRST && kmcId <= KEY_META_ID_MODULE_LAST); - - if (keyMetaClass[kmcId].state != CLASS_STATE_INUSE) - return 0; - - if (!(kv->metabits & (1u << kmcId))) - return 0; /* metadata not attached */ - - *metadata = *kvobjMetaRef(kv, kmcId); - return 1; -} - -/* Add metadata to keymeta spec. Must be in range 0..7 and in order! */ -void keyMetaSpecAdd(KeyMetaSpec *keymeta, int metaid, uint64_t metaval) { - /* Verify added in order and for the first time */ - debugServerAssert(keymeta->metabits == 0 || (1<<metaid) > keymeta->metabits); - keymeta->metabits |= 1 << metaid ; - keymeta->numMeta++; - /* populated in reverse order */ - keymeta->meta[KEY_META_ID_MAX - keymeta->numMeta] = metaval; -} - -/* Add metadata to keymeta spec, handling out-of-order metaid addition. - * This is useful when metadata may arrive in different order than class IDs - * (e.g., RDB load with different module registration order). - * The function maintains the sorted order of the reverse-populated array. */ -static void keyMetaSpecAddUnordered(KeyMetaSpec *keymeta, int metaid, uint64_t metaval) { - debugServerAssert(metaid >= 0 && metaid < KEY_META_ID_MAX); - debugServerAssert((keymeta->metabits & (1 << metaid)) == 0); /* Not already added */ - - /* The meta array is populated in reverse order from the end backward. smallest - * metaid is at the end. Iterate through array slots upward, but find metaids - * by scanning downward (highest to lowest) to match the reverse-order layout. */ - int startIdx = KEY_META_ID_MAX - keymeta->numMeta; - uint16_t tmpBits = keymeta->metabits; - int slot = startIdx; - - while (tmpBits) { - /* Find highest metaid in tmpBits (scanning downward from highest bit) */ - int id = 31 - __builtin_clz((unsigned)tmpBits); - - /* break if we found the slot for the new metaid */ - if (id < metaid) break; - - /* This id is bigger, shift it down */ - keymeta->meta[slot - 1] = keymeta->meta[slot]; - tmpBits &= ~(1 << id); - slot++; - } - - /* Insert new metaid at position slot - 1 */ - keymeta->meta[slot - 1] = metaval; - keymeta->metabits |= 1 << metaid; - keymeta->numMeta++; -} - -/* Blindly reset modules metadata values to reset_value */ -void keyMetaResetModuleValues(kvobj *kv) { - /* Precondition: only called for module metadata (bits 1-7) */ - debugServerAssert(kv->metabits & KEY_META_MASK_MODULES); - - /* Skip expire slot (bit 0) if present, start directly at module metadata */ - uint64_t *pMeta = ((uint64_t *)kv) - 1; - if (kv->metabits & KEY_META_MASK_EXPIRE) - pMeta--; - - /* Process only module metadata bits (1-7) */ - uint32_t mbits = kv->metabits >> KEY_META_ID_MODULE_FIRST; - int keyMetaId = KEY_META_ID_MODULE_FIRST; - do { - if (mbits & 1) - *pMeta-- = keyMetaClass[keyMetaId].conf.reset_value; - - mbits >>= 1; - keyMetaId++; - } while (mbits != 0); -} |
