@@ -169,6 +169,10 @@ class IntrusiveStringList {
169169 return res;
170170 }
171171
172+ bool Empty () {
173+ return start_;
174+ }
175+
172176 ISLEntry Emplace (std::string_view key, uint32_t ttl_sec = UINT32_MAX) {
173177 return Insert (ISLEntry::Create (key, ttl_sec));
174178 }
@@ -181,19 +185,24 @@ class IntrusiveStringList {
181185 }
182186
183187 bool Erase (std::string_view str) {
188+ auto cond = [str](const ISLEntry e) { return str == e.Key (); };
189+ return Erase (cond);
190+ }
191+
192+ template <class T , std::enable_if_t <std::is_invocable_v<T, ISLEntry>>* = nullptr >
193+ bool Erase (const T& cond) {
184194 if (!start_) {
185195 return false ;
186196 }
187- auto it = start_;
188- if (it. Key () == str ) {
197+
198+ if (auto it = start_; cond (it) ) {
189199 start_ = it.Next ();
190200 ISLEntry::Destroy (it);
191201 return true ;
192202 }
193203
194- auto prev = it;
195- for (it = it.Next (); it; prev = it, it = it.Next ()) {
196- if (it.Key () == str) {
204+ for (auto prev = start_, it = start_.Next (); it; prev = it, it = it.Next ()) {
205+ if (cond (it)) {
197206 prev.SetNext (it.Next ());
198207 ISLEntry::Destroy (it);
199208 return true ;
@@ -202,6 +211,25 @@ class IntrusiveStringList {
202211 return false ;
203212 }
204213
214+ template <class T , std::enable_if_t <std::is_invocable_v<T, std::string_view>>* = nullptr >
215+ bool Scan (const T& cb, uint32_t curr_time) {
216+ for (auto it = start_; it && it.ExpiryTime () < curr_time; it = start_) {
217+ start_ = it.Next ();
218+ ISLEntry::Destroy (it);
219+ }
220+
221+ for (auto curr = start_, next = start_; curr; curr = next) {
222+ cb (curr.Key ());
223+ next = curr.Next ();
224+ for (auto tmp = next; tmp && tmp.ExpiryTime () < curr_time; tmp = next) {
225+ next = next.Next ();
226+ ISLEntry::Destroy (tmp);
227+ }
228+ curr.SetNext (next);
229+ }
230+ return start_;
231+ }
232+
205233 private:
206234 ISLEntry start_;
207235};
@@ -260,6 +288,40 @@ class IntrusiveStringSet {
260288 return res;
261289 }
262290
291+ /* *
292+ * stable scanning api. has the same guarantees as redis scan command.
293+ * we avoid doing bit-reverse by using a different function to derive a bucket id
294+ * from hash values. By using msb part of hash we make it "stable" with respect to
295+ * rehashes. For example, with table log size 4 (size 16), entries in bucket id
296+ * 1110 come from hashes 1110XXXXX.... When a table grows to log size 5,
297+ * these entries can move either to 11100 or 11101. So if we traversed with our cursor
298+ * range [0000-1110], it's guaranteed that in grown table we do not need to cover again
299+ * [00000-11100]. Similarly with shrinkage, if a table is shrunk to log size 3,
300+ * keys from 1110 and 1111 will move to bucket 111. Again, it's guaranteed that we
301+ * covered the range [000-111] (all keys in that case).
302+ * Returns: next cursor or 0 if reached the end of scan.
303+ * cursor = 0 - initiates a new scan.
304+ */
305+
306+ using ItemCb = std::function<void (std::string_view)>;
307+
308+ uint32_t Scan (uint32_t cursor, const ItemCb& cb) {
309+ uint32_t entries_idx = cursor >> (32 - capacity_log_);
310+
311+ // First find the bucket to scan, skip empty buckets.
312+ for (; entries_idx < entries_.size (); ++entries_idx) {
313+ if (entries_[entries_idx].Scan (cb, time_now_)) {
314+ break ;
315+ }
316+ }
317+
318+ if (++entries_idx >= entries_.size ()) {
319+ return 0 ;
320+ }
321+
322+ return entries_idx << (32 - capacity_log_);
323+ }
324+
263325 bool Erase (std::string_view str) {
264326 if (entries_.empty ())
265327 return false ;
@@ -329,7 +391,7 @@ class IntrusiveStringSet {
329391 }
330392
331393 private:
332- std::uint32_t capacity_log_ = 1 ;
394+ std::uint32_t capacity_log_ = 0 ;
333395 std::uint32_t size_ = 0 ; // number of elements in the set.
334396 std::uint32_t time_now_ = 0 ;
335397
0 commit comments