#pragma once /* * INVALID_EXPIRE is the value we set the expireEntry's m_when value to when the main key is not expired and the value we return when we try to get the expire time of a key or subkey that is not expired * Want this value to be LLONG_MAX however we use the most significant bit of m_when as a flag to see if the expireEntry is Fat or not so we want to ensure that it is unset hence the (& ~((1LL) << (sizeof(long long)*CHAR_BIT - 1))) * Eventually this number might actually end up being a valid expire time, this could cause bugs so at that time it might be a good idea to use a larger data type. */ #define INVALID_EXPIRE (LLONG_MAX & ~((1LL) << (sizeof(long long)*CHAR_BIT - 1))) class expireEntryFat { friend class expireEntry; static const int INDEX_THRESHOLD = 16; public: struct subexpireEntry { long long when; std::unique_ptr spsubkey; subexpireEntry(long long when, const char *subkey) : when(when), spsubkey(subkey, sdsfree) {} subexpireEntry(const subexpireEntry &other) : spsubkey(nullptr, sdsfree) { when = other.when; if (other.spsubkey != nullptr) spsubkey = std::unique_ptr((const char*)sdsdupshared(other.spsubkey.get()), sdsfree); } subexpireEntry(subexpireEntry &&) = default; subexpireEntry& operator=(subexpireEntry&&) = default; subexpireEntry& operator=(const subexpireEntry &src) { when = src.when; spsubkey = std::unique_ptr((const char*)sdsdupshared(src.spsubkey.get()), sdsfree); return *this; } bool operator<(long long when) const noexcept { return this->when < when; } bool operator<(const subexpireEntry &se) { return this->when < se.when; } }; private: std::vector m_vecexpireEntries; // Note a NULL for the sds portion means the expire is for the primary key dict *m_dictIndex = nullptr; long long m_whenPrimary = LLONG_MAX; void createIndex(); public: expireEntryFat() = default; expireEntryFat(const expireEntryFat &); ~expireEntryFat(); long long when() const noexcept { return m_vecexpireEntries.front().when; } bool operator<(long long when) const noexcept { return this->when() < when; } void expireSubKey(const char *szSubkey, long long when); bool FGetPrimaryExpire(long long *pwhen) const { if (m_whenPrimary != LLONG_MAX) { *pwhen = m_whenPrimary; return true; } return false; } bool FEmpty() const noexcept { return m_vecexpireEntries.empty(); } const subexpireEntry &nextExpireEntry() const noexcept { return m_vecexpireEntries.front(); } void popfrontExpireEntry(); const subexpireEntry &operator[](size_t idx) const { return m_vecexpireEntries[idx]; } size_t size() const noexcept { return m_vecexpireEntries.size(); } }; class expireEntry { struct { uint64_t m_whenAndPtrUnion : 63, fFat : 1; } s; static_assert(sizeof(expireEntryFat*) <= sizeof(int64_t), "The pointer must fit in the union"); public: class iter { friend class expireEntry; const expireEntry *m_pentry = nullptr; size_t m_idx = 0; public: iter(const expireEntry *pentry, size_t idx) : m_pentry(pentry), m_idx(idx) {} iter &operator++() { ++m_idx; return *this; } const char *subkey() const { if (m_pentry->FFat()) return (*m_pentry->pfatentry())[m_idx].spsubkey.get(); return nullptr; } long long when() const { if (m_pentry->FFat()) return (*m_pentry->pfatentry())[m_idx].when; return m_pentry->when(); } bool operator!=(const iter &other) { return m_idx != other.m_idx; } const iter &operator*() const { return *this; } }; expireEntry() { s.fFat = 0; s.m_whenAndPtrUnion = 0; } expireEntry(const char *subkey, long long when) { if (subkey != nullptr) { auto pfatentry = new (MALLOC_LOCAL) expireEntryFat(); pfatentry->expireSubKey(subkey, when); s.m_whenAndPtrUnion = reinterpret_cast(pfatentry); s.fFat = true; } else { s.m_whenAndPtrUnion = when; s.fFat = false; } } expireEntry(expireEntryFat *pfatentry) { assert(pfatentry != nullptr); s.m_whenAndPtrUnion = reinterpret_cast(pfatentry); s.fFat = true; } expireEntry(const expireEntry &e) { if (e.FFat()) { s.m_whenAndPtrUnion = reinterpret_cast(new expireEntryFat(*e.pfatentry())); s.fFat = true; } else { s = e.s; } } expireEntry(expireEntry &&e) { s = e.s; } expireEntry &operator=(expireEntry &&e) { s = e.s; e.s.m_whenAndPtrUnion = 0; e.s.fFat = false; return *this; } expireEntry &operator=(expireEntry &e) { if (e.FFat()) { s.m_whenAndPtrUnion = reinterpret_cast(new expireEntryFat(*e.pfatentry())); s.fFat = true; } else { s = e.s; } return *this; } // Duplicate the expire, note this is intended to be passed directly to setExpire expireEntry duplicate() const { expireEntry dst; if (FFat()) { auto pfatentry = new expireEntryFat(*expireEntry::pfatentry()); dst.s.m_whenAndPtrUnion = reinterpret_cast(pfatentry); dst.s.fFat = true; } else { dst.s.m_whenAndPtrUnion = s.m_whenAndPtrUnion; dst.s.fFat = false; } return dst; } void reset() { if (FFat()) delete pfatentry(); s.fFat = false; s.m_whenAndPtrUnion = 0; } ~expireEntry() { if (FFat()) delete pfatentry(); } inline bool FFat() const noexcept { return s.fFat; } expireEntryFat *pfatentry() { assert(FFat()); return reinterpret_cast(s.m_whenAndPtrUnion); } const expireEntryFat *pfatentry() const { return const_cast(this)->pfatentry(); } bool operator<(const expireEntry &e) const noexcept { return when() < e.when(); } bool operator<(long long when) const noexcept { return this->when() < when; } long long when() const noexcept { if (FFat()) return pfatentry()->when(); return s.m_whenAndPtrUnion; } void update(const char *subkey, long long when) { if (!FFat()) { if (subkey == nullptr) { s.m_whenAndPtrUnion = when; return; } else { // we have to upgrade to a fat entry auto pfatentry = new (MALLOC_LOCAL) expireEntryFat(); pfatentry->expireSubKey(nullptr, s.m_whenAndPtrUnion); s.m_whenAndPtrUnion = reinterpret_cast(pfatentry); s.fFat = true; // at this point we're fat so fall through } } pfatentry()->expireSubKey(subkey, when); } iter begin() const { return iter(this, 0); } iter end() const { if (FFat()) return iter(this, pfatentry()->size()); return iter(this, 1); } void erase(iter &itr) { if (!FFat()) throw -1; // assert pfatentry()->m_vecexpireEntries.erase( pfatentry()->m_vecexpireEntries.begin() + itr.m_idx); } size_t size() const { if (FFat()) return pfatentry()->size(); return 1; } bool FGetPrimaryExpire(long long *pwhen) const noexcept { if (FFat()) { return pfatentry()->FGetPrimaryExpire(pwhen); } else { *pwhen = s.m_whenAndPtrUnion; return true; } } void *release_as_void() { uint64_t whenT = s.m_whenAndPtrUnion; whenT |= static_cast(s.fFat) << 63; s.m_whenAndPtrUnion = 0; s.fFat = 0; return reinterpret_cast(whenT); } static expireEntry *from_void(void **src) { uintptr_t llV = reinterpret_cast(src); return reinterpret_cast(llV); } static const expireEntry *from_void(void *const*src) { uintptr_t llV = reinterpret_cast(src); return reinterpret_cast(llV); } explicit operator long long() const noexcept { return when(); } }; static_assert(sizeof(expireEntry) == sizeof(long long), "This must fit in a long long so it can be put in a dictEntry");