Skip to content

Commit a058a37

Browse files
Merge branch 'dragonflydb:main' into tls_info
2 parents b36de44 + 44c6497 commit a058a37

29 files changed

+757
-301
lines changed

src/core/collection_entry.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ struct CollectionEntry {
2121
explicit CollectionEntry(long long longval) : value_{nullptr}, longval_{longval} {
2222
}
2323

24+
CollectionEntry& operator=(const CollectionEntry&) = default;
25+
2426
std::string ToString() const {
2527
if (value_)
2628
return {value_, length_};

src/core/detail/listpack.cc

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,11 @@ bool ListPack::Insert(string_view pivot, string_view elem, QList::InsertOpt inse
8888
return false;
8989
}
9090

91-
unsigned ListPack::Remove(string_view elem, unsigned count, QList::Where where) {
91+
unsigned ListPack::Remove(const CollectionEntry& elem, unsigned count, QList::Where where) {
9292
unsigned removed = 0;
93-
int64_t ival;
94-
95-
// try parsing the element into an integer.
96-
int is_int = lpStringToInt64(elem.data(), elem.size(), &ival);
9793

9894
auto is_match = [&](const QList::Entry& entry) {
99-
return is_int ? entry.is_int() && entry.ival() == ival : entry == elem;
95+
return elem.is_int() ? entry.is_int() && entry.ival() == elem.ival() : entry == elem.view();
10096
};
10197

10298
uint8_t* p = GetFirst(where);

src/core/detail/listpack.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class ListPack {
5050
bool Insert(std::string_view pivot, std::string_view elem, QList::InsertOpt insert_opt);
5151

5252
// Removes up to count occurrences of elem from the specified direction.
53-
unsigned Remove(std::string_view elem, unsigned count, QList::Where where);
53+
unsigned Remove(const CollectionEntry& elem, unsigned count, QList::Where where);
5454

5555
// Replaces the element at the specified index with a new value.
5656
bool Replace(long index, std::string_view elem);
@@ -66,7 +66,7 @@ class ListPack {
6666
}
6767

6868
private:
69-
static QList::Entry GetEntry(uint8_t* pos);
69+
static CollectionEntry GetEntry(uint8_t* pos);
7070

7171
uint8_t* GetFirst(QList::Where where) const {
7272
return (where == QList::HEAD) ? lpFirst(lp_) : lpLast(lp_);

src/core/listpack_test.cc

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class ListPackTest : public ::testing::Test {
3939
EXPECT_EQ(zmalloc_used_memory_tl, 0);
4040
}
4141

42+
unsigned Remove(string_view elem, unsigned count, QList::Where where) {
43+
return lp_.Remove(CollectionEntry{elem.data(), elem.size()}, count, where);
44+
}
45+
4246
ListPack lp_;
4347
uint8_t* ptr_ = nullptr;
4448
};
@@ -59,7 +63,7 @@ TEST_F(ListPackTest, RemoveIntegerFromHead) {
5963
lp_.Push("3", QList::TAIL);
6064

6165
// Remove integer value "1" from head
62-
unsigned removed = lp_.Remove("1", 0, QList::HEAD);
66+
unsigned removed = Remove("1", 0, QList::HEAD);
6367
EXPECT_EQ(2, removed);
6468
EXPECT_EQ(2, lp_.Size());
6569

@@ -76,7 +80,7 @@ TEST_F(ListPackTest, RemoveFromTailAll) {
7680
lp_.Push("a", QList::TAIL);
7781

7882
// Remove all "a" from tail direction
79-
unsigned removed = lp_.Remove("a", 0, QList::TAIL);
83+
unsigned removed = Remove("a", 0, QList::TAIL);
8084
EXPECT_EQ(3, removed);
8185
EXPECT_EQ(2, lp_.Size());
8286

@@ -94,7 +98,7 @@ TEST_F(ListPackTest, RemoveFromTailWithCount) {
9498
lp_.Push("a", QList::TAIL);
9599

96100
// Remove only 2 occurrences of "a" from tail (removes indices 4 and 2)
97-
unsigned removed = lp_.Remove("a", 2, QList::TAIL);
101+
unsigned removed = Remove("a", 2, QList::TAIL);
98102
EXPECT_EQ(2, removed);
99103
EXPECT_EQ(3, lp_.Size());
100104

@@ -113,7 +117,7 @@ TEST_F(ListPackTest, RemoveFromTailConsecutive) {
113117
lp_.Push("target", QList::TAIL);
114118
lp_.Push("target", QList::TAIL);
115119

116-
unsigned removed = lp_.Remove("target", 0, QList::TAIL);
120+
unsigned removed = Remove("target", 0, QList::TAIL);
117121
EXPECT_EQ(3, removed);
118122
EXPECT_EQ(1, lp_.Size());
119123
EXPECT_EQ("x", lp_.At(0));
@@ -129,7 +133,7 @@ TEST_F(ListPackTest, RemoveFromTailDeletesHead) {
129133
lp_.Push("b", QList::TAIL);
130134
lp_.Push("c", QList::TAIL);
131135

132-
unsigned removed = lp_.Remove("a", 0, QList::TAIL);
136+
unsigned removed = Remove("a", 0, QList::TAIL);
133137
EXPECT_EQ(1, removed);
134138
EXPECT_EQ(2, lp_.Size());
135139

src/core/qlist.cc

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -611,19 +611,21 @@ void QList::AppendPlain(unsigned char* data, size_t sz) {
611611
bool QList::Insert(std::string_view pivot, std::string_view elem, InsertOpt opt) {
612612
Iterator it = GetIterator(HEAD);
613613

614-
while (it.Next()) {
615-
if (it.Get() == pivot) {
616-
Insert(it, elem, opt);
617-
return true;
618-
}
614+
if (it.Valid()) {
615+
do {
616+
if (it.Get() == pivot) {
617+
Insert(it, elem, opt);
618+
return true;
619+
}
620+
} while (it.Next());
619621
}
620622

621623
return false;
622624
}
623625

624626
bool QList::Replace(long index, std::string_view elem) {
625627
Iterator it = GetIterator(index);
626-
if (it.Next()) {
628+
if (it.Valid()) {
627629
Replace(it, elem);
628630
return true;
629631
}
@@ -650,10 +652,12 @@ void QList::Iterate(IterateFunc cb, long start, long end) const {
650652
if (end < 0 || end >= long(Size()))
651653
end = Size() - 1;
652654
Iterator it = GetIterator(start);
653-
while (start <= end && it.Next()) {
654-
if (!cb(it.Get()))
655-
break;
656-
start++;
655+
if (it.Valid()) {
656+
do {
657+
if (start > end || !cb(it.Get()))
658+
break;
659+
start++;
660+
} while (it.Next());
657661
}
658662
}
659663

@@ -1053,6 +1057,16 @@ bool QList::DelPackedIndex(Node* node, uint8_t* p) {
10531057
return false;
10541058
}
10551059

1060+
void QList::InitIteratorEntry(Iterator* it) const {
1061+
DCHECK(it->current_);
1062+
const_cast<QList*>(this)->malloc_size_ += DecompressNodeIfNeeded(true, it->current_);
1063+
if (QL_NODE_IS_PLAIN(it->current_)) {
1064+
it->zi_ = it->current_->entry;
1065+
} else {
1066+
it->zi_ = lpSeek(it->current_->entry, it->offset_);
1067+
}
1068+
}
1069+
10561070
auto QList::GetIterator(Where where) const -> Iterator {
10571071
Iterator it;
10581072
it.owner_ = this;
@@ -1067,6 +1081,10 @@ auto QList::GetIterator(Where where) const -> Iterator {
10671081
it.direction_ = REV;
10681082
}
10691083

1084+
if (it.current_) {
1085+
InitIteratorEntry(&it);
1086+
}
1087+
10701088
return it;
10711089
}
10721090

@@ -1119,6 +1137,8 @@ auto QList::GetIterator(long idx) const -> Iterator {
11191137
iter.offset_ = (-index) - 1 + accum;
11201138
}
11211139

1140+
InitIteratorEntry(&iter);
1141+
11221142
return iter;
11231143
}
11241144

@@ -1137,10 +1157,9 @@ auto QList::Erase(Iterator it) -> Iterator {
11371157
deleted_node = DelPackedIndex(node, it.zi_);
11381158
}
11391159

1140-
/* after delete, the zi is now invalid for any future usage. */
1141-
it.zi_ = NULL;
1160+
it.zi_ = NULL; // Reset current entry pointer
11421161

1143-
/* If current node is deleted, we must update iterator node and offset. */
1162+
// If current node is deleted, we must update iterator node and offset.
11441163
if (deleted_node) {
11451164
if (it.direction_ == FWD) {
11461165
it.current_ = next;
@@ -1151,6 +1170,10 @@ auto QList::Erase(Iterator it) -> Iterator {
11511170
}
11521171
}
11531172

1173+
if (it.current_) {
1174+
InitIteratorEntry(&it);
1175+
}
1176+
11541177
// Sanity, should be noop in release mode.
11551178
if (len_ == 1) {
11561179
DCHECK_EQ(count_, head_->count);
@@ -1241,29 +1264,35 @@ bool QList::Erase(const long start, unsigned count) {
12411264
return true;
12421265
}
12431266

1267+
uint8_t* QList::TryExtractListpack() {
1268+
// TODO: enable this once we support listpack encoding for lists.
1269+
return nullptr;
1270+
1271+
if (len_ != 1 || QL_NODE_IS_PLAIN(head_) || !ShouldStoreAsListPack(head_->sz) ||
1272+
head_->IsCompressed()) {
1273+
return nullptr;
1274+
}
1275+
1276+
uint8_t* res = std::exchange(head_->entry, nullptr);
1277+
DelNode(head_);
1278+
1279+
return res;
1280+
}
1281+
12441282
bool QList::Iterator::Next() {
12451283
if (!current_)
12461284
return false;
12471285

12481286
int plain = QL_NODE_IS_PLAIN(current_);
1249-
if (!zi_) {
1250-
/* If !zi, use current index. */
1251-
const_cast<QList*>(owner_)->malloc_size_ += DecompressNodeIfNeeded(true, current_);
1252-
if (ABSL_PREDICT_FALSE(plain))
1253-
zi_ = current_->entry;
1254-
else
1255-
zi_ = lpSeek(current_->entry, offset_);
1256-
} else if (ABSL_PREDICT_FALSE(plain)) {
1287+
1288+
// Advance to the next element in the current node.
1289+
if (ABSL_PREDICT_FALSE(plain)) {
12571290
zi_ = NULL;
12581291
} else {
1259-
unsigned char* (*nextFn)(unsigned char*, unsigned char*) = NULL;
1260-
int offset_update = 0;
1292+
unsigned char* (*nextFn)(unsigned char*, unsigned char*) = lpNext;
1293+
int offset_update = 1;
12611294

1262-
/* else, use existing iterator offset and get prev/next as necessary. */
1263-
if (direction_ == FWD) {
1264-
nextFn = lpNext;
1265-
offset_update = 1;
1266-
} else {
1295+
if (direction_ == REV) {
12671296
DCHECK_EQ(REV, direction_);
12681297
nextFn = lpPrev;
12691298
offset_update = -1;
@@ -1275,7 +1304,7 @@ bool QList::Iterator::Next() {
12751304
if (zi_)
12761305
return true;
12771306

1278-
// Retry again with the next node.
1307+
// Move to the next node.
12791308
const_cast<QList*>(owner_)->Compress(current_);
12801309

12811310
if (direction_ == FWD) {
@@ -1289,7 +1318,11 @@ bool QList::Iterator::Next() {
12891318
current_ = (current_ == owner_->head_) ? nullptr : current_->prev;
12901319
}
12911320

1292-
return current_ ? Next() : false;
1321+
if (!current_)
1322+
return false;
1323+
1324+
owner_->InitIteratorEntry(this);
1325+
return zi_ != nullptr;
12931326
}
12941327

12951328
auto QList::Iterator::Get() const -> Entry {

src/core/qlist.h

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <absl/functional/function_ref.h>
88

9+
#include <cstdint>
910
#include <string>
1011

1112
#include "core/collection_entry.h"
@@ -27,6 +28,14 @@ namespace dfly {
2728

2829
class PageUsage;
2930

31+
// Heuristic: for values smaller than 2 KiB we prefer the compact listpack
32+
// representation. 2048 was chosen as a conservative threshold that matches
33+
// common quicklist usage patterns and avoids creating very large listpacks
34+
// that are costly to reallocate or compress.
35+
inline bool ShouldStoreAsListPack(size_t size) {
36+
return size < 2048;
37+
}
38+
3039
class QList {
3140
public:
3241
enum Where : uint8_t { TAIL, HEAD };
@@ -69,9 +78,14 @@ class QList {
6978
using Entry = CollectionEntry;
7079
class Iterator {
7180
public:
81+
// Returns true if the iterator is valid (points to an element).
82+
bool Valid() const {
83+
return zi_ != nullptr;
84+
}
85+
7286
Entry Get() const;
7387

74-
// Returns false if no more entries.
88+
// Advances to the next/prev element. Returns false if no more entries.
7589
bool Next();
7690

7791
private:
@@ -138,8 +152,8 @@ class QList {
138152
// Returns the popped value. Precondition: list is not empty.
139153
std::string Pop(Where where);
140154

141-
void AppendListpack(unsigned char* zl);
142-
void AppendPlain(unsigned char* zl, size_t sz);
155+
void AppendListpack(uint8_t* zl);
156+
void AppendPlain(uint8_t* zl, size_t sz);
143157

144158
// Returns true if pivot found and elem inserted, false otherwise.
145159
bool Insert(std::string_view pivot, std::string_view elem, InsertOpt opt);
@@ -155,14 +169,13 @@ class QList {
155169
void Iterate(IterateFunc cb, long start, long end) const;
156170

157171
// Returns an iterator to tail or the head of the list.
158-
// To mirror the quicklist interface, the iterator is not valid until Next() is called.
159-
// TODO: to fix this.
172+
// result.Valid() is true if the list is not empty.
160173
Iterator GetIterator(Where where) const;
161174

162175
// Returns an iterator at a specific index 'idx',
163176
// or Invalid iterator if index is out of range.
164177
// negative index - means counting from the tail.
165-
// Requires calling subsequent Next() to initialize the iterator.
178+
// result.Valid() is true if the index is within range.
166179
Iterator GetIterator(long idx) const;
167180

168181
uint32_t node_count() const {
@@ -188,6 +201,11 @@ class QList {
188201
return _Tail();
189202
}
190203

204+
// Returns nullptr if quicklist does not fit the necessary requirements
205+
// to be converted to listpack, and listpack otherwise. The ownership over the listpack
206+
// blob is moved to the caller.
207+
uint8_t* TryExtractListpack();
208+
191209
void set_fill(int fill) {
192210
fill_ = fill;
193211
}
@@ -244,6 +262,10 @@ class QList {
244262
void DelNode(Node* node);
245263
bool DelPackedIndex(Node* node, uint8_t* p);
246264

265+
// Initializes iterator's zi_ to point to the element at offset_.
266+
// Decompresses the node if needed. Assumes current_ is not null.
267+
void InitIteratorEntry(Iterator* it) const;
268+
247269
Node* head_ = nullptr;
248270
size_t malloc_size_ = 0; // size of the quicklist struct
249271
uint32_t count_ = 0; /* total count of all entries in all listpacks */

0 commit comments

Comments
 (0)