diff --git a/LICENSE.md b/LICENSE.md
index fddf6bb54..b6367f96e 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -6,7 +6,7 @@
Licensed Work: Dragonfly including the software components, or any portion of them, and any modification.
-Change Date: September 1, 2028
+Change Date: March 1, 2029
Change License: [Apache License, Version
2.0](https://www.apache.org/licenses/LICENSE-2.0), as published by the
diff --git a/src/core/qlist.cc b/src/core/qlist.cc
index 8f3c218de..5302c586c 100644
--- a/src/core/qlist.cc
+++ b/src/core/qlist.cc
@@ -60,7 +60,7 @@ namespace dfly {
namespace {
-static_assert(sizeof(QList) == 32);
+static_assert(sizeof(QList) == 24);
enum IterDir : uint8_t { FWD = 1, REV = 0 };
@@ -305,13 +305,12 @@ QList::QList(int fill, int compress) : fill_(fill), compress_(compress), bookmar
QList::QList(QList&& other)
: head_(other.head_),
- tail_(other.tail_),
count_(other.count_),
len_(other.len_),
fill_(other.fill_),
compress_(other.compress_),
bookmark_count_(other.bookmark_count_) {
- other.head_ = other.tail_ = nullptr;
+ other.head_ = nullptr;
other.len_ = other.count_ = 0;
}
@@ -323,14 +322,13 @@ QList& QList::operator=(QList&& other) {
if (this != &other) {
Clear();
head_ = other.head_;
- tail_ = other.tail_;
len_ = other.len_;
count_ = other.count_;
fill_ = other.fill_;
compress_ = other.compress_;
bookmark_count_ = other.bookmark_count_;
- other.head_ = other.tail_ = nullptr;
+ other.head_ = nullptr;
other.len_ = other.count_ = 0;
}
return *this;
@@ -348,28 +346,24 @@ void QList::Clear() {
len_--;
current = next;
}
- head_ = tail_ = nullptr;
+ head_ = nullptr;
count_ = 0;
}
void QList::Push(string_view value, Where where) {
/* The head and tail should never be compressed (we don't attempt to decompress them) */
- if (head_)
+ if (head_) {
DCHECK(head_->encoding != QUICKLIST_NODE_ENCODING_LZF);
- if (tail_)
- DCHECK(tail_->encoding != QUICKLIST_NODE_ENCODING_LZF);
-
+ DCHECK(head_->prev->encoding != QUICKLIST_NODE_ENCODING_LZF);
+ }
PushSentinel(value, where);
}
string QList::Pop(Where where) {
DCHECK_GT(count_, 0u);
- quicklistNode* node;
- if (where == HEAD) {
- node = head_;
- } else {
- DCHECK_EQ(TAIL, where);
- node = tail_;
+ quicklistNode* node = head_;
+ if (where == TAIL) {
+ node = head_->prev;
}
/* The head and tail should never be compressed */
@@ -402,7 +396,7 @@ void QList::AppendListpack(unsigned char* zl) {
node->count = lpLength(node->entry);
node->sz = lpBytes(zl);
- InsertNode(tail_, node, AFTER);
+ InsertNode(_Tail(), node, AFTER);
count_ += node->count;
}
@@ -414,7 +408,7 @@ void QList::AppendPlain(unsigned char* data, size_t sz) {
node->sz = sz;
node->container = QUICKLIST_NODE_CONTAINER_PLAIN;
- InsertNode(tail_, node, AFTER);
+ InsertNode(_Tail(), node, AFTER);
++count_;
}
@@ -469,7 +463,11 @@ void QList::Iterate(IterateFunc cb, long start, long end) const {
}
bool QList::PushSentinel(string_view value, Where where) {
- quicklistNode* orig = where == HEAD ? head_ : tail_;
+ quicklistNode* orig = head_;
+ if (where == TAIL && orig) {
+ orig = orig->prev;
+ }
+
InsertOpt opt = where == HEAD ? BEFORE : AFTER;
size_t sz = value.size();
@@ -507,23 +505,27 @@ void QList::InsertNode(quicklistNode* old_node, quicklistNode* new_node, InsertO
if (old_node->next)
old_node->next->prev = new_node;
old_node->next = new_node;
+ if (head_->prev == old_node) // if old_node is tail, update the tail to the new node.
+ head_->prev = new_node;
}
- if (tail_ == old_node)
- tail_ = new_node;
} else {
new_node->next = old_node;
if (old_node) {
new_node->prev = old_node->prev;
- if (old_node->prev)
+ // if old_node is not head, link its prev to the new node.
+ // head->prev is tail, so we don't need to update it.
+ if (old_node != head_)
old_node->prev->next = new_node;
old_node->prev = new_node;
}
if (head_ == old_node)
head_ = new_node;
}
+
/* If this insert creates the only element so far, initialize head/tail. */
if (len_ == 0) {
- head_ = tail_ = new_node;
+ head_ = new_node;
+ head_->prev = new_node;
}
/* Update len first, so in Compress we know exactly len */
@@ -698,7 +700,7 @@ void QList::Compress(quicklistNode* node) {
return;
/* The head and tail should never be compressed (we should not attempt to recompress them) */
- assert(head_->recompress == 0 && tail_->recompress == 0);
+ DCHECK(head_->recompress == 0 && head_->prev->recompress == 0);
/* If length is less than our compress depth (from both sides),
* we can't compress anything. */
@@ -709,7 +711,7 @@ void QList::Compress(quicklistNode* node) {
* Note: because we do length checks at the *top* of this function,
* we can skip explicit null checks below. Everything exists. */
quicklistNode* forward = head_;
- quicklistNode* reverse = tail_;
+ quicklistNode* reverse = head_->prev;
int depth = 0;
int in_depth = 0;
while (depth++ < compress_) {
@@ -837,12 +839,10 @@ void QList::DelNode(quicklistNode* node) {
if (node->prev)
node->prev->next = node->next;
- if (node == tail_) {
- tail_ = node->prev;
- }
-
if (node == head_) {
head_ = node->next;
+ } else if (node == head_->prev) { // tail
+ head_->prev = node->prev;
}
/* Update len first, so in Compress we know exactly len */
@@ -892,7 +892,7 @@ auto QList::GetIterator(Where where) const -> Iterator {
it.offset_ = 0;
it.direction_ = FWD;
} else {
- it.current_ = tail_;
+ it.current_ = _Tail();
it.offset_ = -1;
it.direction_ = REV;
}
@@ -918,7 +918,7 @@ auto QList::GetIterator(long idx) const -> Iterator {
seek_index = count_ - 1 - index;
}
- n = seek_forward ? head_ : tail_;
+ n = seek_forward ? head_ : head_->prev;
while (ABSL_PREDICT_TRUE(n)) {
if ((accum + n->count) > seek_index) {
break;
@@ -1119,9 +1119,8 @@ bool QList::Iterator::Next() {
} else {
/* Reverse traversal, Jumping to end of previous node */
DCHECK_EQ(REV, direction_);
-
- current_ = current_->prev;
offset_ = -1;
+ current_ = (current_ == owner_->head_) ? nullptr : current_->prev;
}
return current_ ? Next() : false;
diff --git a/src/core/qlist.h b/src/core/qlist.h
index e59ed9b56..c39377b53 100644
--- a/src/core/qlist.h
+++ b/src/core/qlist.h
@@ -144,7 +144,7 @@ class QList {
}
const quicklistNode* Tail() const {
- return tail_;
+ return _Tail();
}
void set_fill(int fill) {
@@ -158,6 +158,10 @@ class QList {
return compress_ != 0;
}
+ quicklistNode* _Tail() const {
+ return head_ ? head_->prev : nullptr;
+ }
+
// Returns false if used existing sentinel, true if a new sentinel was created.
bool PushSentinel(std::string_view value, Where where);
@@ -176,7 +180,7 @@ class QList {
bool DelPackedIndex(quicklistNode* node, uint8_t** p);
quicklistNode* head_ = nullptr;
- quicklistNode* tail_ = nullptr;
+
uint32_t count_ = 0; /* total count of all entries in all listpacks */
uint32_t len_ = 0; /* number of quicklistNodes */
signed int fill_ : QL_FILL_BITS; /* fill factor for individual nodes */
diff --git a/src/core/qlist_test.cc b/src/core/qlist_test.cc
index 3623bd759..fdf7185e8 100644
--- a/src/core/qlist_test.cc
+++ b/src/core/qlist_test.cc
@@ -90,7 +90,7 @@ static int ql_verify(const QList& ql, uint32_t nc, uint32_t count, uint32_t head
node_size = 0;
while (node) {
node_size += node->count;
- node = node->prev;
+ node = (node == ql.Head()) ? nullptr : node->prev;
}
if (node_size != ql.Size()) {
LOG(ERROR) << "has different forward count than reverse count! "
@@ -165,7 +165,6 @@ TEST_F(QListTest, Basic) {
EXPECT_EQ(0, ql_.Size());
ql_.Push("abc", QList::HEAD);
EXPECT_EQ(1, ql_.Size());
- EXPECT_TRUE(ql_.Head()->prev == nullptr);
EXPECT_TRUE(ql_.Tail() == ql_.Head());
auto it = ql_.GetIterator(QList::HEAD);
@@ -517,6 +516,10 @@ TEST_P(OptionsTest, DelNeg100From500) {
for (int i = 0; i < 500; i++)
ql_.Push(StrCat("hello", i + 1), QList::TAIL);
ql_.Erase(-100, 100);
+
+ QList::Iterator it = ql_.GetIterator(QList::TAIL);
+ ASSERT_TRUE(it.Next());
+ ASSERT_EQ("hello400", it.Get());
ASSERT_EQ(0, ql_verify(ql_, 13, 400, 32, 16));
}