Skip to main content

yo_kv/
list.rs

1//! A list, in whichever of the two representations currently fits it.
2//!
3//! A list is one packed blob while it is small and a ring of [`Chunk`]s once it
4//! is not, which is the same two bands Redis has and the same two names
5//! `OBJECT ENCODING` answers, `listpack` and `quicklist`.
6//!
7//! ```text
8//!   under eight kilobytes            everything else
9//! +--------------------------+   +------------------------------------+
10//! | one listpack             |-->| chunk | chunk | ... | chunk        |
11//! | walked from either end   |   | head cursor ...... tail cursor     |
12//! +--------------------------+   +------------------------------------+
13//! ```
14//!
15//! # Why a chunk and not a listpack per node
16//!
17//! Redis's quicklist node is a listpack, and taking the first element out of a
18//! listpack moves every byte behind it left. On a list that is being used as a
19//! queue, which is what a list is for, that memmove is on the hot path of every
20//! single `LPOP`. [`Chunk`] holds the same entries in the same encoding with a
21//! cursor at each end, so a pop is a cursor step and a push at the other end
22//! does not touch it. That is `04` section 6's independent head and tail, and it
23//! is the answer to the row aki lost.
24//!
25//! # The band boundary is Redis's, and it goes both ways
26//!
27//! `list-max-listpack-size` defaults to `-2`, which means eight kilobytes rather
28//! than a count, so a list of a thousand short strings is still one blob and a
29//! list of two hundred long ones is not. That was read off `t_list.c` in the
30//! 7.4.5 tarball rather than assumed, and so was the part that surprised: a list
31//! converts **back** when it shrinks, which no other collection here does. Redis
32//! only converts back once the list is under **half** the limit, so that a
33//! workload sitting exactly on the boundary does not rebuild itself on every
34//! other command, and `List::shrunk` is that rule.
35//!
36//! # Elements
37//!
38//! An element comes back as a [`Member`](crate::set::Member), which is
39//! [`listpack::Entry`](crate::listpack::Entry) under another name, so a value
40//! stored as an integer is handed over as one and formatted once, into the reply
41//! buffer, at the moment the reply is built. That is Y18 again.
42
43use std::cell::RefCell;
44use std::collections::VecDeque;
45
46use yo_common::Small;
47
48use crate::chunk::{CHUNK_BYTES, Chunk};
49use crate::frozen::{self, Broken};
50use crate::listpack::{Entry, Listpack};
51
52/// How many `LREM` hits fit without the allocator.
53///
54/// `LREM key 1 value` and `LREM key -1 value` are what this command is for, and
55/// a count past a handful is somebody clearing every copy out of a long list,
56/// where one allocation is not what it is paying for.
57const HITS: usize = 8;
58
59/// One packed blob, which is Redis's `LIST_QUICKLIST` of a single listpack.
60const FORM_PACKED: u8 = 1;
61/// The ring, written chunk by chunk.
62const FORM_CHUNKS: u8 = 2;
63
64/// A list element: bytes as they lie, or an integer not yet formatted.
65pub type Element<'a> = Entry<'a>;
66
67/// Where a list changes representation.
68///
69/// One number, because Redis has one: `list-max-listpack-size`. A negative value
70/// there is a size in kilobytes and a positive one is a count of elements, and
71/// both arrive here already turned into the two fields the bands actually ask
72/// about.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct Limits {
75    /// The most bytes a packed list holds before it becomes chunks.
76    pub max_packed_bytes: usize,
77    /// The most elements a packed list holds, or none for no limit.
78    ///
79    /// The default configuration has no count limit at all, because `-2` is a
80    /// size. A server configured with a positive `list-max-listpack-size` has
81    /// one and no size limit, which is why these are two fields and not an enum
82    /// of one or the other: the halving rule for shrinking applies to whichever
83    /// is set and the code below should not have to know which that was.
84    pub max_packed_entries: Option<usize>,
85}
86
87impl Default for Limits {
88    /// What a server with no configuration file uses.
89    ///
90    /// `list-max-listpack-size -2`, which is eight kilobytes and no count.
91    fn default() -> Limits {
92        Limits {
93            max_packed_bytes: CHUNK_BYTES,
94            max_packed_entries: None,
95        }
96    }
97}
98
99impl Limits {
100    /// The limits a `list-max-listpack-size` of `fill` describes.
101    ///
102    /// This is `quicklistNodeLimit`. A positive fill is a count and the size is
103    /// left at the safety limit, a negative one is an index into Redis's five
104    /// sizes, and zero means one element per node, which is a setting nobody
105    /// uses and which still has to mean something.
106    #[must_use]
107    pub fn of(fill: i32) -> Limits {
108        if fill >= 0 {
109            return Limits {
110                max_packed_bytes: CHUNK_BYTES,
111                max_packed_entries: Some((fill as usize).max(1)),
112            };
113        }
114        // Redis's `optimization_level`, which is 4 KiB, 8, 16, 32 and 64.
115        const SIZES: [usize; 5] = [4096, 8192, 16384, 32768, 65536];
116        let at = ((-fill) as usize - 1).min(SIZES.len() - 1);
117        Limits {
118            max_packed_bytes: SIZES[at],
119            max_packed_entries: None,
120        }
121    }
122
123    /// Whether a packed list of these dimensions is past what the band holds.
124    #[must_use]
125    fn exceeded(&self, bytes: usize, entries: usize) -> bool {
126        match self.max_packed_entries {
127            Some(cap) => bytes > CHUNK_BYTES || entries > cap,
128            None => bytes > self.max_packed_bytes,
129        }
130    }
131
132    /// The same question with both limits halved, which is the shrinking rule.
133    #[must_use]
134    fn exceeded_halved(&self, bytes: usize, entries: usize) -> bool {
135        match self.max_packed_entries {
136            Some(cap) => bytes > CHUNK_BYTES / 2 || entries > cap / 2,
137            None => bytes > self.max_packed_bytes / 2,
138        }
139    }
140}
141
142/// What `OBJECT ENCODING` calls a list.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum Encoding {
145    /// One packed blob.
146    Listpack,
147    /// A ring of chunks.
148    Quicklist,
149}
150
151impl Encoding {
152    /// The string `OBJECT ENCODING` returns.
153    #[must_use]
154    pub const fn name(self) -> &'static str {
155        match self {
156            Encoding::Listpack => "listpack",
157            Encoding::Quicklist => "quicklist",
158        }
159    }
160}
161
162/// Which representation the elements are in.
163#[derive(Debug, Clone, PartialEq, Eq)]
164enum Body {
165    Packed(Listpack),
166    Chunks(Deque),
167}
168
169/// A list of elements, in order, reachable from both ends.
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct List {
172    body: Body,
173}
174
175impl Default for List {
176    fn default() -> List {
177        List::new()
178    }
179}
180
181impl List {
182    /// An empty list, in the band every list starts in.
183    #[must_use]
184    pub fn new() -> List {
185        List {
186            body: Body::Packed(Listpack::new()),
187        }
188    }
189
190    /// How many elements it holds.
191    #[must_use]
192    #[inline]
193    pub fn len(&self) -> usize {
194        match &self.body {
195            Body::Packed(lp) => lp.len(),
196            Body::Chunks(d) => d.len(),
197        }
198    }
199
200    /// Whether it holds nothing.
201    ///
202    /// A list that reaches zero is deleted by the keyspace, the same as a set
203    /// that does, so this is a question about the moment between the last pop
204    /// and that delete rather than a state a client can observe.
205    #[must_use]
206    #[inline]
207    pub fn is_empty(&self) -> bool {
208        self.len() == 0
209    }
210
211    /// Write this list out as the bytes it comes back from.
212    ///
213    /// What a demotion turns the body into. The packed band goes out as the
214    /// listpack bytes it already is, and the ring goes out chunk by chunk, each
215    /// one its element count and its live bytes.
216    ///
217    /// Chunk by chunk and not element by element, because a chunk's bytes are
218    /// already in the encoding [`Chunk::adopt`] takes, so a list of a million
219    /// elements is a couple of thousand copies out and the same number back
220    /// rather than two million encodes. The ring also comes back with the same
221    /// chunk boundaries it left with, which keeps `MEMORY USAGE` and the walk
222    /// cost of an index the same on both sides of a trip to the device.
223    ///
224    /// The dead space at either end of a chunk is not written. A chunk that had
225    /// room to push into comes back full, and pushing into it again allocates a
226    /// new chunk where the old one would have grown in place. That is a list
227    /// which was quiet long enough to be demoted paying one allocation on the
228    /// write that wakes it, and it is worth the bytes it saves on the device.
229    pub fn freeze(&self, out: &mut Vec<u8>) {
230        match &self.body {
231            Body::Packed(lp) => {
232                out.push(FORM_PACKED);
233                out.extend_from_slice(lp.as_bytes());
234            }
235            Body::Chunks(d) => {
236                out.push(FORM_CHUNKS);
237                frozen::put_uint(out, d.chunks.len() as u64);
238                for c in &d.chunks {
239                    frozen::put_uint(out, c.len() as u64);
240                    frozen::put_bytes(out, c.entries());
241                }
242            }
243        }
244    }
245
246    /// Read a list back out of what [`List::freeze`] wrote.
247    ///
248    /// # Errors
249    ///
250    /// [`Broken`] for bytes that are not the shape freeze wrote, which is a read
251    /// that came back torn rather than anything a caller did.
252    pub fn thaw(bytes: &[u8]) -> Result<List, Broken> {
253        let mut cut = frozen::Cut::new(bytes);
254        match cut.byte()? {
255            FORM_PACKED => Ok(List {
256                body: Body::Packed(Listpack::from_bytes(cut.rest()).map_err(|_| Broken::Body)?),
257            }),
258            FORM_CHUNKS => {
259                let n = usize::try_from(cut.uint()?).map_err(|_| Broken::Short)?;
260                // A chunk is at least a count and a length, so two bytes, and a
261                // number larger than what is left is not worth an allocation.
262                if n > cut.rest().len() {
263                    return Err(Broken::Body);
264                }
265                let mut d = Deque::new();
266                d.chunks.reserve(n);
267                for _ in 0..n {
268                    let count = usize::try_from(cut.uint()?).map_err(|_| Broken::Short)?;
269                    let entries = cut.bytes()?;
270                    if count > entries.len() {
271                        // An entry is at least one byte, so this cannot be a
272                        // chunk anything wrote.
273                        return Err(Broken::Body);
274                    }
275                    d.len += count;
276                    d.chunks.push_back(Chunk::adopt(entries, count));
277                }
278                Ok(List {
279                    body: Body::Chunks(d),
280                })
281            }
282            _ => Err(Broken::Form),
283        }
284    }
285
286    /// What `OBJECT ENCODING` says about it.
287    #[must_use]
288    pub const fn encoding(&self) -> Encoding {
289        match &self.body {
290            Body::Packed(_) => Encoding::Listpack,
291            Body::Chunks(_) => Encoding::Quicklist,
292        }
293    }
294
295    /// What it costs, not counting anything a caller is holding.
296    #[must_use]
297    pub fn memory_bytes(&self) -> usize {
298        match &self.body {
299            Body::Packed(lp) => lp.byte_len(),
300            Body::Chunks(d) => d.memory_bytes(),
301        }
302    }
303
304    /// The element at `index` from the front.
305    #[must_use]
306    pub fn get(&self, index: usize) -> Option<Element<'_>> {
307        match &self.body {
308            Body::Packed(lp) => lp.get(index),
309            Body::Chunks(d) => d.get(index),
310        }
311    }
312
313    /// The first element.
314    #[must_use]
315    pub fn front(&self) -> Option<Element<'_>> {
316        match &self.body {
317            Body::Packed(lp) => lp.get(0),
318            Body::Chunks(d) => d.front(),
319        }
320    }
321
322    /// The last element.
323    ///
324    /// Reads the back length rather than walking, in both bands, which is what
325    /// makes `RPOP` on a long list cost the same as `LPOP` on one.
326    #[must_use]
327    pub fn back(&self) -> Option<Element<'_>> {
328        match &self.body {
329            Body::Packed(lp) => lp.get_back(0),
330            Body::Chunks(d) => d.back(),
331        }
332    }
333
334    /// A forward walk over every element.
335    pub fn iter(&self) -> impl Iterator<Item = Element<'_>> {
336        // Two shapes with one type, because a caller that only wants the
337        // elements should not have to know which band it is standing on.
338        let (packed, chunks) = match &self.body {
339            Body::Packed(lp) => (Some(lp.iter()), None),
340            Body::Chunks(d) => (None, Some(d.iter())),
341        };
342        packed
343            .into_iter()
344            .flatten()
345            .chain(chunks.into_iter().flatten())
346    }
347
348    /// The same walk the other way.
349    ///
350    /// Both bands keep a length behind every element, so this costs what the
351    /// forward walk costs. `LPOS` with a negative rank is what wants it.
352    pub fn iter_back(&self) -> impl Iterator<Item = Element<'_>> {
353        let (packed, chunks) = match &self.body {
354            Body::Packed(lp) => (Some(lp.iter_back()), None),
355            Body::Chunks(d) => (None, Some(d.iter_back())),
356        };
357        packed
358            .into_iter()
359            .flatten()
360            .chain(chunks.into_iter().flatten())
361    }
362
363    /// `count` elements starting at `start`, which is `LRANGE`.
364    ///
365    /// Both ends are already normalised by the caller, because the wire's start
366    /// and stop can be negative, can be the wrong way round and can hang off
367    /// either end, and every one of those turns into an empty reply rather than
368    /// into an error.
369    ///
370    /// A window in the middle does not walk to its start. The packed band skips
371    /// entries because that is all a hundred and twenty eight of them costs, and
372    /// the chunked band steps over whole chunks and only decodes the ones it is
373    /// going to hand back. `LRANGE mylist 500000 500099` on a million element
374    /// list reads a hundred elements and not five hundred thousand.
375    pub fn range(&self, start: usize, count: usize) -> impl Iterator<Item = Element<'_>> {
376        let (packed, chunks) = match &self.body {
377            Body::Packed(lp) => (Some(lp.iter_from(start).take(count)), None),
378            Body::Chunks(d) => (None, Some(d.range(start, count))),
379        };
380        packed
381            .into_iter()
382            .flatten()
383            .chain(chunks.into_iter().flatten())
384    }
385
386    /// Put `value` at the front.
387    pub fn push_front(&mut self, value: &[u8], limits: &Limits) {
388        self.grow_by(value, limits);
389        match &mut self.body {
390            Body::Packed(lp) => lp.insert(0, value),
391            Body::Chunks(d) => d.push_front(value),
392        }
393    }
394
395    /// Put `value` at the back.
396    pub fn push_back(&mut self, value: &[u8], limits: &Limits) {
397        self.grow_by(value, limits);
398        match &mut self.body {
399            Body::Packed(lp) => lp.push(value),
400            Body::Chunks(d) => d.push_back(value),
401        }
402    }
403
404    /// Put `value` in at `index`, pushing what was there along, which is the
405    /// half of `LINSERT` that already knows where the pivot was.
406    ///
407    /// An index equal to the length appends. Anything past that is nothing.
408    pub fn insert(&mut self, index: usize, value: &[u8], limits: &Limits) -> bool {
409        if index > self.len() {
410            return false;
411        }
412        self.grow_by(value, limits);
413        match &mut self.body {
414            Body::Packed(lp) => {
415                lp.insert(index, value);
416                true
417            }
418            Body::Chunks(d) => d.insert_at(index, value),
419        }
420    }
421
422    /// Put `value` next to the first `pivot` in the list, which is `LINSERT`.
423    ///
424    /// Gives back the new length, or nothing when the pivot is not there, which
425    /// is the difference between the reply being a length and being `-1`.
426    pub fn insert_at_pivot(
427        &mut self,
428        pivot: &[u8],
429        value: &[u8],
430        before: bool,
431        limits: &Limits,
432    ) -> Option<usize> {
433        let at = self.find(pivot)?;
434        let at = if before { at } else { at + 1 };
435        self.insert(at, value, limits).then(|| self.len())
436    }
437
438    /// Put `value` where the element at `index` is, which is `LSET`.
439    pub fn set(&mut self, index: usize, value: &[u8], limits: &Limits) -> bool {
440        if index >= self.len() {
441            return false;
442        }
443        // What the blob would weigh after the swap, which is not what it weighs
444        // now plus the new element: the old one is going away. Getting this
445        // wrong would promote a list that still fits and then keep it promoted,
446        // because a list only converts back under half the limit.
447        if let Body::Packed(lp) = &self.body {
448            let old = lp.get(index).map_or(0, |e| e.byte_len());
449            let after = lp.byte_len() + crate::listpack::entry_len(value) - old;
450            self.grow_to(after, self.len(), limits);
451        }
452        match &mut self.body {
453            Body::Packed(lp) => lp.replace(index, value),
454            Body::Chunks(d) => d.replace_at(index, value),
455        }
456    }
457
458    /// Where the first `value` is, front to back.
459    ///
460    /// This is what `LINSERT` spends its time in, and on a long list it is
461    /// essentially all of it: the insert itself is a couple of hundred
462    /// nanoseconds and the pivot search in front of it is however long the list
463    /// is. So it goes to the band rather than through the element walk, and the
464    /// band reads entry headers instead of decoding elements.
465    #[must_use]
466    pub fn find(&self, value: &[u8]) -> Option<usize> {
467        let as_int = yo_common::num::parse_i64(value);
468        match &self.body {
469            Body::Packed(lp) => lp.find_parsed(value, as_int, 1),
470            Body::Chunks(d) => d.find(value, as_int),
471        }
472    }
473
474    /// Where `value` is, as many times as asked, which is `LPOS`.
475    ///
476    /// `rank` is which match to start at and which way to look: 1 is the first
477    /// from the front, -1 the first from the back, 2 the second from the front.
478    /// `count` is how many to give back with 0 meaning all of them, and `maxlen`
479    /// is how many elements may be compared before giving up, with 0 meaning no
480    /// limit. The indexes handed back are always from the front, whichever way
481    /// the walk went, because that is what the client can use.
482    ///
483    /// Each answer is handed to `found` as it is discovered, and the number of
484    /// them comes back, because this runs on a shard thread and a shard thread
485    /// that allocates aborts. The wire writes each position straight into the
486    /// reply buffer and never holds a list of them at all.
487    ///
488    /// `found` is a `dyn` call rather than a generic, so that the two walks
489    /// below stay one body. Monomorphising this over the sink would double a
490    /// function whose whole cost is the comparison inside it.
491    ///
492    /// Like [`List::find`] this goes to the band rather than through the element
493    /// walk, and for the same reason: an `LPOS` that is not answered by the
494    /// first few elements reads the list, and reading the list one decoded
495    /// [`Element`] at a time costs about three times what reading it as entry
496    /// headers does. The walk carries the `MAXLEN` budget itself rather than
497    /// counting elements out here, because counting them out here means the
498    /// budget is only checked between calls into the band, which on a ring is
499    /// once a chunk.
500    pub fn positions(
501        &self,
502        value: &[u8],
503        rank: i64,
504        count: usize,
505        maxlen: usize,
506        found: &mut dyn FnMut(usize),
507    ) -> usize {
508        if rank == 0 {
509            return 0;
510        }
511        let as_int = yo_common::num::parse_i64(value);
512        let len = self.len();
513        let mut skip = rank.unsigned_abs() as usize - 1;
514        let mut hits = 0usize;
515        {
516            // What to do with a match, wherever the walk found it. A rank past
517            // the first drops matches on the floor until it has dropped enough,
518            // which is why this cannot be the walk's own counter.
519            let mut take = |at: usize| -> bool {
520                if skip > 0 {
521                    skip -= 1;
522                    return true;
523                }
524                found(at);
525                hits += 1;
526                count == 0 || hits < count
527            };
528            if rank > 0 {
529                match &self.body {
530                    Body::Packed(lp) => lp.find_each(value, as_int, maxlen, &mut take),
531                    Body::Chunks(d) => d.find_each(value, as_int, maxlen, &mut take),
532                };
533            } else {
534                // The backward walk counts from the last element and the client
535                // wants indexes from the first, so they are turned round here
536                // and nowhere below.
537                let mut back = |at: usize| take(len - at - 1);
538                match &self.body {
539                    Body::Packed(lp) => lp.find_each_back(value, as_int, maxlen, &mut back),
540                    Body::Chunks(d) => d.find_each_back(value, as_int, maxlen, &mut back),
541                };
542            }
543        }
544        hits
545    }
546
547    /// Take out up to `count` elements equal to `value`, which is `LREM`.
548    ///
549    /// A positive count works from the front, a negative one from the back, and
550    /// zero means every one of them. Gives back how many went.
551    pub fn remove(&mut self, count: i64, value: &[u8], limits: &Limits) -> usize {
552        let as_int = yo_common::num::parse_i64(value);
553        let want = if count == 0 {
554            usize::MAX
555        } else {
556            count.unsigned_abs() as usize
557        };
558        // Collected first and removed after, because removing during the walk
559        // moves the elements the walk has not reached yet. Highest index first
560        // so that the ones still to go do not move either.
561        //
562        // On the stack up to `HITS`, because the count `LREM` is given is one
563        // or two in almost every use of it and a `Vec` for one `usize` is a
564        // malloc and a free on a command path. `LREM key 0 value` on a list
565        // holding many copies spills, which is the right answer for it.
566        let mut hits: Small<usize, HITS> = Small::new();
567        if count >= 0 {
568            match &self.body {
569                Body::Packed(lp) => lp.find_each(value, as_int, 0, &mut |at| {
570                    hits.push(at);
571                    hits.len() < want
572                }),
573                Body::Chunks(d) => d.find_each(value, as_int, 0, &mut |at| {
574                    hits.push(at);
575                    hits.len() < want
576                }),
577            };
578            hits.reverse();
579        } else {
580            let len = self.len();
581            match &self.body {
582                Body::Packed(lp) => lp.find_each_back(value, as_int, 0, &mut |at| {
583                    hits.push(len - at - 1);
584                    hits.len() < want
585                }),
586                Body::Chunks(d) => d.find_each_back(value, as_int, 0, &mut |at| {
587                    hits.push(len - at - 1);
588                    hits.len() < want
589                }),
590            };
591        }
592        for at in &hits {
593            self.remove_at(*at);
594        }
595        self.shrunk(limits);
596        hits.len()
597    }
598
599    /// Take out the element at `index`.
600    ///
601    /// The band is left alone, because a caller taking several out in a row
602    /// would otherwise convert between them. Everything public that removes
603    /// finishes with [`List::shrunk`].
604    fn remove_at(&mut self, index: usize) -> bool {
605        match &mut self.body {
606            Body::Packed(lp) => lp.delete(index, 1),
607            Body::Chunks(d) => d.remove_at(index),
608        }
609    }
610
611    /// Keep `count` elements starting at `start` and drop the rest, which is
612    /// `LTRIM`.
613    ///
614    /// Both ends are normalised by the caller, the same as [`List::range`], and
615    /// a count of zero empties the list, which on the wire deletes the key.
616    pub fn trim(&mut self, start: usize, count: usize, limits: &Limits) {
617        let len = self.len();
618        let start = start.min(len);
619        let keep = count.min(len - start);
620        match &mut self.body {
621            Body::Packed(lp) => {
622                lp.delete(start + keep, len - start - keep);
623                lp.delete(0, start);
624            }
625            Body::Chunks(d) => d.trim(start, keep),
626        }
627        self.shrunk(limits);
628    }
629
630    /// Drop the first element, and say whether there was one.
631    ///
632    /// The read and the removal are separate so that `LPOP` on the wire can
633    /// write the element straight into the reply buffer and then drop it,
634    /// which is the same split [`crate::set::Set::drop_at`] exists for.
635    pub fn drop_front(&mut self, limits: &Limits) -> bool {
636        let gone = match &mut self.body {
637            Body::Packed(lp) => lp.delete(0, 1),
638            Body::Chunks(d) => d.drop_front(),
639        };
640        self.shrunk(limits);
641        gone
642    }
643
644    /// Drop the last element, and say whether there was one.
645    pub fn drop_back(&mut self, limits: &Limits) -> bool {
646        let gone = match &mut self.body {
647            Body::Packed(lp) => {
648                let last = lp.len().checked_sub(1);
649                last.is_some_and(|at| lp.delete(at, 1))
650            }
651            Body::Chunks(d) => d.drop_back(),
652        };
653        self.shrunk(limits);
654        gone
655    }
656
657    /// Take the first element out and hand it back.
658    ///
659    /// The embedded API's `LPOP`, where the caller wants the bytes and has
660    /// nowhere to put them.
661    pub fn pop_front(&mut self, limits: &Limits) -> Option<Vec<u8>> {
662        let out = self.front()?.to_vec();
663        self.drop_front(limits);
664        Some(out)
665    }
666
667    /// Take the last element out and hand it back.
668    pub fn pop_back(&mut self, limits: &Limits) -> Option<Vec<u8>> {
669        let out = self.back()?.to_vec();
670        self.drop_back(limits);
671        Some(out)
672    }
673
674    /// Go back to one blob if the list has shrunk far enough to deserve it.
675    ///
676    /// Called by everything here that removes elements. Redis converts back only
677    /// below half the limit, so that a list sitting on the boundary does not
678    /// rebuild itself on every other command, and it only converts a quicklist
679    /// that is down to one node. Both of those are here.
680    fn shrunk(&mut self, limits: &Limits) {
681        let Body::Chunks(d) = &self.body else {
682            return;
683        };
684        if d.chunks.len() != 1 {
685            return;
686        }
687        let only = &d.chunks[0];
688        if limits.exceeded_halved(only.live_bytes(), only.len()) {
689            return;
690        }
691        let mut lp = Listpack::new();
692        for e in only.iter() {
693            match e {
694                Entry::Int(n) => {
695                    let mut digits = Vec::new();
696                    Entry::Int(n).write_to(&mut digits);
697                    lp.push(&digits);
698                }
699                Entry::Str(s) => lp.push(s),
700            }
701        }
702        self.body = Body::Packed(lp);
703    }
704
705    /// Promote out of the packed band if one more `value` would not fit in it.
706    ///
707    /// Asked before the write rather than after, because a listpack that has
708    /// already been grown past the limit and is then converted has done the
709    /// work twice.
710    fn grow_by(&mut self, value: &[u8], limits: &Limits) {
711        let Body::Packed(lp) = &self.body else {
712            return;
713        };
714        let after = lp.byte_len() + crate::listpack::entry_len(value);
715        self.grow_to(after, lp.len() + 1, limits);
716    }
717
718    /// Promote out of the packed band if a list of this size does not fit it.
719    fn grow_to(&mut self, bytes: usize, entries: usize, limits: &Limits) {
720        if !matches!(self.body, Body::Packed(_)) || !limits.exceeded(bytes, entries) {
721            return;
722        }
723        let Body::Packed(lp) = std::mem::replace(&mut self.body, Body::Chunks(Deque::new())) else {
724            unreachable!("just matched a packed body");
725        };
726        let Body::Chunks(d) = &mut self.body else {
727            unreachable!("just put a chunked body there");
728        };
729        d.adopt(&lp);
730    }
731}
732
733/// A chunk of its own holding nothing but `value`, at the end asked for.
734///
735/// An element too big for an ordinary chunk gets one sized to it, which is what
736/// Redis calls a plain node. Without this a value over eight kilobytes would be
737/// refused by a chunk that had just been made for it and the list would count an
738/// element it does not hold.
739fn lone(value: &[u8], front: bool) -> Chunk {
740    if crate::listpack::entry_len(value) > CHUNK_BYTES {
741        return Chunk::plain(value);
742    }
743    let mut c = if front {
744        Chunk::for_front()
745    } else {
746        Chunk::for_back()
747    };
748    let put = if front {
749        c.push_front(value)
750    } else {
751        c.push_back(value)
752    };
753    debug_assert!(put, "an empty chunk refused the only element in it");
754    c
755}
756
757/// A ring of chunks, with the list's length kept beside it.
758///
759/// The length is carried rather than summed because `LLEN` is a command and
760/// summing a thousand chunk counts to answer it would be a walk of the whole
761/// list to say how long it is.
762#[derive(Debug, Clone)]
763struct Deque {
764    chunks: VecDeque<Chunk>,
765    len: usize,
766    /// Where each chunk starts, so that finding an index is a binary search
767    /// over the ring rather than a walk along it. See [`Deque::locate`].
768    ///
769    /// Behind a cell because it is filled in by reads, and the reads that want
770    /// it take `&self`. Nothing outside this thread can see it: a shard owns
771    /// its keyspace and `yo-shard` has a test that the type system says so.
772    starts: RefCell<VecDeque<i64>>,
773}
774
775/// Two rings are the same when they hold the same elements in the same chunks.
776///
777/// Written out rather than derived because the start index is a cache, and a
778/// list that has been read is not a different list from one that has not.
779impl PartialEq for Deque {
780    fn eq(&self, other: &Deque) -> bool {
781        self.len == other.len && self.chunks == other.chunks
782    }
783}
784
785impl Eq for Deque {}
786
787impl Deque {
788    /// An empty ring.
789    fn new() -> Deque {
790        Deque {
791            chunks: VecDeque::new(),
792            len: 0,
793            starts: RefCell::new(VecDeque::new()),
794        }
795    }
796
797    /// How many elements are in the whole ring.
798    #[inline]
799    const fn len(&self) -> usize {
800        self.len
801    }
802
803    /// Take the entries of a listpack as this ring's first chunk.
804    fn adopt(&mut self, lp: &Listpack) {
805        self.len = lp.len();
806        self.chunks.push_back(Chunk::adopt(lp.entries(), lp.len()));
807        self.tail_added();
808    }
809
810    /// What the whole ring costs.
811    ///
812    /// A chunk counts its own header, because it is sitting in the ring's own
813    /// allocation, so what is left to add is the slots the ring is holding empty
814    /// for the chunks it does not have yet. The start index goes in as well,
815    /// because it is eight bytes a chunk that the list would not otherwise be
816    /// holding, and a structure that hides part of itself from `MEMORY USAGE`
817    /// is worse than one that costs a little more.
818    fn memory_bytes(&self) -> usize {
819        let spare = self.chunks.capacity() - self.chunks.len();
820        self.chunks.iter().map(Chunk::memory_bytes).sum::<usize>()
821            + spare * size_of::<Chunk>()
822            + self.starts.borrow().capacity() * size_of::<i64>()
823    }
824
825    /// The first element.
826    fn front(&self) -> Option<Element<'_>> {
827        self.chunks.front()?.front()
828    }
829
830    /// The last element.
831    fn back(&self) -> Option<Element<'_>> {
832        self.chunks.back()?.back()
833    }
834
835    /// The element at `index`, chunks first and elements second.
836    fn get(&self, index: usize) -> Option<Element<'_>> {
837        let (i, within) = self.locate(index)?;
838        self.chunks[i].get(within)
839    }
840
841    /// A forward walk over every chunk in turn.
842    fn iter(&self) -> impl Iterator<Item = Element<'_>> {
843        self.chunks.iter().flat_map(Chunk::iter)
844    }
845
846    /// Where the first `value` is, counting from the front of the ring.
847    ///
848    /// Chunk by chunk, with a running base, rather than element by element. A
849    /// chunk that does not hold the value costs one call and one walk of its own
850    /// bytes, and the ring never builds an element for anything it is only
851    /// stepping over.
852    fn find(&self, value: &[u8], as_int: Option<i64>) -> Option<usize> {
853        let mut base = 0usize;
854        for c in &self.chunks {
855            if let Some(at) = c.find(value, as_int) {
856                return Some(base + at);
857            }
858            base += c.len();
859        }
860        None
861    }
862
863    /// Every place `value` is, front to back, with indexes from the front.
864    ///
865    /// One `limit` is spent across the whole ring rather than per chunk, which
866    /// is what makes `LPOS`'s `MAXLEN` mean the same thing here as it does on a
867    /// list small enough to still be one blob.
868    fn find_each(
869        &self,
870        value: &[u8],
871        as_int: Option<i64>,
872        limit: usize,
873        hit: &mut dyn FnMut(usize) -> bool,
874    ) -> usize {
875        let mut base = 0usize;
876        let mut looked = 0usize;
877        for c in &self.chunks {
878            if limit != 0 && looked >= limit {
879                break;
880            }
881            let at = base;
882            let mut on = true;
883            looked += c.find_each(value, as_int, limit.saturating_sub(looked), &mut |i| {
884                on = hit(at + i);
885                on
886            });
887            base += c.len();
888            if !on {
889                break;
890            }
891        }
892        looked
893    }
894
895    /// The same from the back, with indexes counted from the last element.
896    fn find_each_back(
897        &self,
898        value: &[u8],
899        as_int: Option<i64>,
900        limit: usize,
901        hit: &mut dyn FnMut(usize) -> bool,
902    ) -> usize {
903        let mut base = 0usize;
904        let mut looked = 0usize;
905        for c in self.chunks.iter().rev() {
906            if limit != 0 && looked >= limit {
907                break;
908            }
909            let at = base;
910            let mut on = true;
911            looked += c.find_each_back(value, as_int, limit.saturating_sub(looked), &mut |i| {
912                on = hit(at + i);
913                on
914            });
915            base += c.len();
916            if !on {
917                break;
918            }
919        }
920        looked
921    }
922
923    /// `count` elements from `start`, without walking to `start`.
924    ///
925    /// The chunks before the one holding `start` are stepped over as chunks, so
926    /// the only elements this decodes are the ones inside the chunk it lands in
927    /// and the ones it is going to return. A `skip` on the element walk decodes
928    /// every entry it passes, which turned a hundred element window in the
929    /// middle of a million element list into two milliseconds of reading
930    /// listpack headers nobody asked for.
931    fn range(&self, start: usize, count: usize) -> impl Iterator<Item = Element<'_>> {
932        let (chunk, within) = self.locate(start).unwrap_or((self.chunks.len(), 0));
933        let first = self.chunks.get(chunk).map(|c| c.iter_from(within));
934        first
935            .into_iter()
936            .flatten()
937            .chain(self.chunks.iter().skip(chunk + 1).flat_map(Chunk::iter))
938            .take(count)
939    }
940
941    /// The same walk the other way, chunks in reverse and each one backward.
942    fn iter_back(&self) -> impl Iterator<Item = Element<'_>> {
943        self.chunks.iter().rev().flat_map(Chunk::iter_back)
944    }
945
946    /// Which chunk holds the element at `index`, and where in that chunk.
947    ///
948    /// This used to walk the ring from whichever end was closer, which is fine
949    /// for a queue and terrible for anything that reads the middle: a million
950    /// element list is a few thousand chunks, and a `LINDEX` halfway along it
951    /// stepped over half of them to get there. That was two and a half
952    /// microseconds against a hundred and thirty nanoseconds for the same call
953    /// near an end.
954    ///
955    /// Now the ring carries where each chunk starts and the lookup is a binary
956    /// search. `08` section 6 puts it as chunk count arithmetic plus one chunk
957    /// walk, and the arithmetic is this.
958    ///
959    /// The starts are in their own coordinate system, whose origin is wherever
960    /// the head chunk happened to be when the index was last built. What a
961    /// lookup uses is the difference between two entries and never an entry on
962    /// its own, so the origin can be anything, and that is what makes work at
963    /// the front free: pushing an element on to the head chunk moves that
964    /// chunk's start back by one and leaves every other entry correct, where an
965    /// index of real positions would have had to add one to all of them.
966    ///
967    /// Only the first `starts.len()` chunks are described. A mutation in the
968    /// middle of the ring cuts the index back to the chunk it touched and
969    /// nothing more, so the mutation itself never walks, and the next lookup
970    /// that needs the rest pays for it once.
971    fn locate(&self, index: usize) -> Option<(usize, usize)> {
972        if index >= self.len {
973            return None;
974        }
975        // The two end chunks are answered by a comparison each, before any of
976        // the above. A list is a queue and the position a client asks for is
977        // usually near an end, and a binary search over a few thousand entries
978        // is eleven scattered loads to say what one subtraction already knew.
979        // Without this the index made `LINDEX mylist 3` half again as slow as
980        // the walk it replaced.
981        let head = self.chunks.front()?.len();
982        if index < head {
983            return Some((0, index));
984        }
985        let last = self.chunks.len() - 1;
986        let before_tail = self.len - self.chunks[last].len();
987        if index >= before_tail {
988            return Some((last, index - before_tail));
989        }
990        let mut starts = self.starts.borrow_mut();
991        if starts.is_empty() {
992            starts.push_back(0);
993        }
994        // Carry the index on from where the last lookup or the last mutation
995        // left it, and only as far as this index needs. A read near the front
996        // of a ring that was just cut does not describe the whole ring to
997        // answer.
998        let want = starts[0] + index as i64;
999        loop {
1000            let last = starts.len() - 1;
1001            let end = starts[last] + self.chunks[last].len() as i64;
1002            if end > want || starts.len() == self.chunks.len() {
1003                break;
1004            }
1005            starts.push_back(end);
1006        }
1007        // The last chunk that starts at or before the wanted position. An empty
1008        // chunk starts where the next one does, and this lands on the later of
1009        // the two, which is the one holding the element.
1010        let at = starts.partition_point(|&s| s <= want) - 1;
1011        Some((at, (want - starts[at]) as usize))
1012    }
1013
1014    /// Forget where every chunk from `from` onward starts.
1015    ///
1016    /// Cheap on purpose. Every mutation in the middle of the ring calls this
1017    /// and none of them rebuild anything, because the next lookup will.
1018    #[inline]
1019    fn cut(&mut self, from: usize) {
1020        let keep = from.min(self.chunks.len());
1021        let starts = self.starts.get_mut();
1022        if starts.len() > keep {
1023            starts.truncate(keep);
1024        }
1025    }
1026
1027    /// The head chunk's first element moved `by` places later.
1028    ///
1029    /// Negative for a push, positive for a pop. One subtraction, whatever the
1030    /// ring is holding, which is the whole point of the floating origin.
1031    #[inline]
1032    fn head_moved(&mut self, by: i64) {
1033        if let Some(first) = self.starts.get_mut().front_mut() {
1034            *first += by;
1035        }
1036    }
1037
1038    /// A chunk holding `len` elements went on the front of the ring.
1039    #[inline]
1040    fn head_added(&mut self, len: usize) {
1041        let starts = self.starts.get_mut();
1042        if let Some(&first) = starts.front() {
1043            starts.push_front(first - len as i64);
1044        }
1045    }
1046
1047    /// The head chunk left the ring, with everything that was in it.
1048    #[inline]
1049    fn head_dropped(&mut self) {
1050        self.starts.get_mut().pop_front();
1051    }
1052
1053    /// A chunk went on the back of the ring.
1054    ///
1055    /// Described only if everything before it already is, which is the case
1056    /// that matters: a list being filled with `RPUSH` grows a chunk at a time
1057    /// and never invalidates anything, so the index is complete by the time
1058    /// anybody reads the middle of it.
1059    #[inline]
1060    fn tail_added(&mut self) {
1061        let n = self.chunks.len();
1062        let before = if n >= 2 { self.chunks[n - 2].len() } else { 0 };
1063        let starts = self.starts.get_mut();
1064        if n == 1 && starts.is_empty() {
1065            starts.push_back(0);
1066        } else if starts.len() + 1 == n {
1067            let last = starts[n - 2];
1068            starts.push_back(last + before as i64);
1069        }
1070    }
1071
1072    /// Put `value` at the front, in the head chunk or in a new one.
1073    fn push_front(&mut self, value: &[u8]) {
1074        if let Some(head) = self.chunks.front_mut()
1075            && head.push_front(value)
1076        {
1077            self.len += 1;
1078            self.head_moved(-1);
1079            return;
1080        }
1081        // The chunk that was the head stops being an end, so it gives back the
1082        // room it was keeping. One that is empty goes instead, because a chunk
1083        // holding nothing is one every walk from that end has to step over.
1084        if self.chunks.front().is_some_and(Chunk::is_empty) {
1085            self.chunks.pop_front();
1086            self.head_dropped();
1087        } else if let Some(head) = self.chunks.front_mut() {
1088            head.seal();
1089        }
1090        self.chunks.push_front(lone(value, true));
1091        self.len += 1;
1092        self.head_added(1);
1093    }
1094
1095    /// Put `value` at the back, in the tail chunk or in a new one.
1096    fn push_back(&mut self, value: &[u8]) {
1097        if let Some(tail) = self.chunks.back_mut()
1098            && tail.push_back(value)
1099        {
1100            self.len += 1;
1101            return;
1102        }
1103        if self.chunks.back().is_some_and(Chunk::is_empty) {
1104            self.chunks.pop_back();
1105            self.cut(self.chunks.len());
1106        } else if let Some(tail) = self.chunks.back_mut() {
1107            tail.seal();
1108        }
1109        self.chunks.push_back(lone(value, false));
1110        self.len += 1;
1111        self.tail_added();
1112    }
1113
1114    /// Put `value` in at `index`, splitting a chunk if it will not take it.
1115    ///
1116    /// A chunk that refuses is split at the insertion point, which leaves two
1117    /// chunks with room between them for what would not fit. That is Redis's
1118    /// `_quicklistSplitNode` and the reason is the same: the alternative is
1119    /// pushing the rest of the list along one chunk at a time.
1120    fn insert_at(&mut self, index: usize, value: &[u8]) -> bool {
1121        if index > self.len {
1122            return false;
1123        }
1124        if index == 0 {
1125            self.push_front(value);
1126            return true;
1127        }
1128        if index == self.len {
1129            self.push_back(value);
1130            return true;
1131        }
1132        let Some((i, within)) = self.locate(index) else {
1133            return false;
1134        };
1135        if self.chunks[i].insert_at(within, value) {
1136            self.len += 1;
1137            // Chunk `i` still starts where it did. Everything after it moved.
1138            self.cut(i + 1);
1139            return true;
1140        }
1141        let mut rest = self.chunks[i].split_off(within);
1142        // Both halves have room now unless the element needs a chunk of its own.
1143        let put = self.chunks[i].push_back(value) || rest.push_front(value);
1144        self.chunks.insert(i + 1, rest);
1145        if !put {
1146            self.chunks.insert(i + 1, lone(value, false));
1147        }
1148        self.len += 1;
1149        self.cut(i + 1);
1150        true
1151    }
1152
1153    /// Take the element at `index` out.
1154    fn remove_at(&mut self, index: usize) -> bool {
1155        let Some((i, within)) = self.locate(index) else {
1156            return false;
1157        };
1158        if !self.chunks[i].remove_at(within) {
1159            return false;
1160        }
1161        self.len -= 1;
1162        if self.chunks[i].is_empty() && self.chunks.len() > 1 {
1163            self.chunks.remove(i);
1164            // The chunk that takes its place starts where the empty one did,
1165            // so `i` is still right, but keeping it would be an argument and
1166            // cutting it is a memory write.
1167            self.cut(i);
1168        } else {
1169            self.cut(i + 1);
1170        }
1171        true
1172    }
1173
1174    /// Put `value` where the element at `index` is.
1175    ///
1176    /// A replacement that does not fit is the same split an insert does, with
1177    /// the element being replaced dropped off the front of the second half.
1178    fn replace_at(&mut self, index: usize, value: &[u8]) -> bool {
1179        let Some((i, within)) = self.locate(index) else {
1180            return false;
1181        };
1182        if self.chunks[i].replace_at(within, value) {
1183            // One element out and one in, so no chunk moved and the index is
1184            // still true. This is the common case and it costs nothing.
1185            return true;
1186        }
1187        let mut rest = self.chunks[i].split_off(within);
1188        rest.drop_front();
1189        let put = self.chunks[i].push_back(value) || rest.push_front(value);
1190        if !rest.is_empty() {
1191            self.chunks.insert(i + 1, rest);
1192        }
1193        if !put {
1194            self.chunks.insert(i + 1, lone(value, false));
1195        }
1196        if self.chunks[i].is_empty() && self.chunks.len() > 1 {
1197            self.chunks.remove(i);
1198        }
1199        self.cut(i);
1200        true
1201    }
1202
1203    /// Keep `keep` elements from `start` and drop everything else.
1204    ///
1205    /// Whole chunks at either end go without their bytes being touched, and the
1206    /// two chunks the range ends inside move a cursor. A trim of a million
1207    /// element list down to ten is the walk over the chunk list and two walks
1208    /// inside a chunk.
1209    fn trim(&mut self, start: usize, keep: usize) {
1210        let mut front = start;
1211        while front > 0 {
1212            let Some(held) = self.chunks.front().map(Chunk::len) else {
1213                break;
1214            };
1215            if held <= front && self.chunks.len() > 1 {
1216                front -= held;
1217                self.len -= held;
1218                self.chunks.pop_front();
1219                self.head_dropped();
1220            } else {
1221                let took = self.chunks[0].drop_front_n(front);
1222                self.len -= took;
1223                front -= took;
1224                self.head_moved(took as i64);
1225                if took == 0 {
1226                    break;
1227                }
1228            }
1229        }
1230        let mut back = self.len - keep.min(self.len);
1231        while back > 0 {
1232            let Some(held) = self.chunks.back().map(Chunk::len) else {
1233                break;
1234            };
1235            if held <= back && self.chunks.len() > 1 {
1236                back -= held;
1237                self.len -= held;
1238                self.chunks.pop_back();
1239                self.cut(self.chunks.len());
1240            } else {
1241                let last = self.chunks.len() - 1;
1242                let took = self.chunks[last].drop_back_n(back);
1243                self.len -= took;
1244                back -= took;
1245                if took == 0 {
1246                    break;
1247                }
1248            }
1249        }
1250    }
1251
1252    /// Drop the first element, dropping the chunk with it if it was the last.
1253    fn drop_front(&mut self) -> bool {
1254        let Some(head) = self.chunks.front_mut() else {
1255            return false;
1256        };
1257        if !head.drop_front() {
1258            return false;
1259        }
1260        let gone = head.is_empty();
1261        self.len -= 1;
1262        self.head_moved(1);
1263        if gone && self.chunks.len() > 1 {
1264            self.chunks.pop_front();
1265            self.head_dropped();
1266        }
1267        true
1268    }
1269
1270    /// Drop the last element, dropping the chunk with it if it was the last.
1271    fn drop_back(&mut self) -> bool {
1272        let Some(tail) = self.chunks.back_mut() else {
1273            return false;
1274        };
1275        if !tail.drop_back() {
1276            return false;
1277        }
1278        let gone = tail.is_empty();
1279        self.len -= 1;
1280        if gone && self.chunks.len() > 1 {
1281            self.chunks.pop_back();
1282            self.cut(self.chunks.len());
1283        }
1284        true
1285    }
1286
1287    /// Every start the index claims to know, checked against a walk.
1288    ///
1289    /// The index is maintained by hand at nine call sites and a wrong entry
1290    /// would hand back the wrong element without anything else noticing, so
1291    /// the tests that mutate a ring call this rather than trusting the
1292    /// argument that the call sites are right.
1293    #[cfg(test)]
1294    fn index_is_true(&self) {
1295        let starts = self.starts.borrow();
1296        assert!(
1297            starts.len() <= self.chunks.len(),
1298            "the index describes {} chunks and the ring holds {}",
1299            starts.len(),
1300            self.chunks.len()
1301        );
1302        let Some(&base) = starts.front() else {
1303            return;
1304        };
1305        let mut real = 0usize;
1306        for (i, &s) in starts.iter().enumerate() {
1307            assert_eq!(
1308                s - base,
1309                real as i64,
1310                "chunk {i} is indexed at {} and starts at {real}",
1311                s - base
1312            );
1313            real += self.chunks[i].len();
1314        }
1315    }
1316}
1317
1318#[cfg(test)]
1319mod tests {
1320    use super::*;
1321    use crate::many;
1322
1323    fn all(l: &List) -> Vec<Vec<u8>> {
1324        l.iter().map(|e| e.to_vec()).collect()
1325    }
1326
1327    /// The same list in both bands, so a test can run its case over each.
1328    ///
1329    /// The elements differ in length between the two, because that is the only
1330    /// thing that decides which band a list of a given length is in, so every
1331    /// test over this compares against the list it was handed rather than
1332    /// against a literal.
1333    fn both_bands(n: usize) -> [List; 2] {
1334        let limits = Limits::default();
1335        let mut packed = List::new();
1336        let mut chunks = List::new();
1337        for i in 0..n {
1338            packed.push_back(format!("e{i}").as_bytes(), &limits);
1339            chunks.push_back(format!("e{i}:{}", "p".repeat(400)).as_bytes(), &limits);
1340        }
1341        assert_eq!(packed.encoding(), Encoding::Listpack);
1342        assert_eq!(chunks.encoding(), Encoding::Quicklist);
1343        [packed, chunks]
1344    }
1345
1346    /// A list of `n` elements, each long enough that `n` of them do not fit the
1347    /// packed band, so the test is standing on the chunked one.
1348    fn chunked(n: usize) -> List {
1349        let mut l = List::new();
1350        let limits = Limits::default();
1351        for i in 0..n {
1352            l.push_back(format!("value:{i:0>60}").as_bytes(), &limits);
1353        }
1354        assert_eq!(l.encoding(), Encoding::Quicklist, "{n} did not promote");
1355        l
1356    }
1357
1358    #[test]
1359    fn a_new_list_is_empty_and_packed() {
1360        let l = List::new();
1361        assert!(l.is_empty());
1362        assert_eq!(l.len(), 0);
1363        assert_eq!(l.encoding(), Encoding::Listpack);
1364        assert!(l.front().is_none());
1365        assert!(l.back().is_none());
1366        assert!(l.get(0).is_none());
1367    }
1368
1369    #[test]
1370    fn pushing_at_both_ends_puts_the_elements_in_order() {
1371        let mut l = List::new();
1372        let limits = Limits::default();
1373        l.push_back(b"b", &limits);
1374        l.push_back(b"c", &limits);
1375        l.push_front(b"a", &limits);
1376        assert_eq!(all(&l), vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()]);
1377        assert_eq!(l.front().unwrap().to_vec(), b"a");
1378        assert_eq!(l.back().unwrap().to_vec(), b"c");
1379        assert_eq!(l.get(1).unwrap().to_vec(), b"b");
1380        assert_eq!(l.len(), 3);
1381    }
1382
1383    #[test]
1384    fn popping_takes_from_the_end_it_says() {
1385        let mut l = List::new();
1386        let limits = Limits::default();
1387        for m in [b"a", b"b", b"c"] {
1388            l.push_back(m, &limits);
1389        }
1390        assert_eq!(l.pop_front(&limits).unwrap(), b"a");
1391        assert_eq!(l.pop_back(&limits).unwrap(), b"c");
1392        assert_eq!(all(&l), vec![b"b".to_vec()]);
1393        assert_eq!(l.pop_front(&limits).unwrap(), b"b");
1394        assert!(l.pop_front(&limits).is_none());
1395        assert!(l.pop_back(&limits).is_none());
1396        assert!(l.is_empty());
1397    }
1398
1399    /// The band boundary is a size and not a count at the default setting, so a
1400    /// thousand short elements are still one blob.
1401    #[test]
1402    fn a_thousand_short_elements_stay_packed() {
1403        let mut l = List::new();
1404        let limits = Limits::default();
1405        for i in 0..1000 {
1406            l.push_back(i.to_string().as_bytes(), &limits);
1407        }
1408        assert_eq!(l.encoding(), Encoding::Listpack);
1409        assert_eq!(l.len(), 1000);
1410    }
1411
1412    #[test]
1413    fn enough_bytes_promotes_and_keeps_every_element() {
1414        let l = chunked(300);
1415        assert_eq!(l.len(), 300);
1416        for i in 0..300 {
1417            assert_eq!(
1418                l.get(i).unwrap().to_vec(),
1419                format!("value:{i:0>60}").into_bytes(),
1420                "element {i} after promotion"
1421            );
1422        }
1423    }
1424
1425    #[test]
1426    fn a_chunked_list_pushes_and_pops_at_both_ends() {
1427        let mut l = chunked(300);
1428        let limits = Limits::default();
1429        l.push_front(b"first", &limits);
1430        l.push_back(b"last", &limits);
1431        assert_eq!(l.len(), 302);
1432        assert_eq!(l.front().unwrap().to_vec(), b"first");
1433        assert_eq!(l.back().unwrap().to_vec(), b"last");
1434        assert_eq!(l.pop_front(&limits).unwrap(), b"first");
1435        assert_eq!(l.pop_back(&limits).unwrap(), b"last");
1436        assert_eq!(l.len(), 300);
1437        assert_eq!(
1438            l.front().unwrap().to_vec(),
1439            format!("value:{:0>60}", 0).into_bytes()
1440        );
1441    }
1442
1443    /// A queue: everything in at one end, everything out at the other, which is
1444    /// the shape that empties chunks from the front and makes new ones at the
1445    /// back at the same time.
1446    #[test]
1447    fn a_queue_drains_in_the_order_it_filled() {
1448        let mut l = List::new();
1449        let limits = Limits::default();
1450        let n = many(5000);
1451        for i in 0..n {
1452            l.push_back(format!("job:{i:0>40}").as_bytes(), &limits);
1453        }
1454        for i in 0..n {
1455            assert_eq!(
1456                l.pop_front(&limits).unwrap(),
1457                format!("job:{i:0>40}").into_bytes(),
1458                "job {i} came back in the wrong place"
1459            );
1460        }
1461        assert!(l.is_empty());
1462    }
1463
1464    /// A stack: in and out at the same end, which is the shape that leaves a
1465    /// chunk half empty and pushes into it again.
1466    #[test]
1467    fn a_stack_comes_back_in_reverse() {
1468        let mut l = List::new();
1469        let limits = Limits::default();
1470        let n = many(2000);
1471        for i in 0..n {
1472            l.push_front(format!("frame:{i:0>40}").as_bytes(), &limits);
1473        }
1474        for i in (0..n).rev() {
1475            assert_eq!(
1476                l.pop_front(&limits).unwrap(),
1477                format!("frame:{i:0>40}").into_bytes()
1478            );
1479        }
1480        assert!(l.is_empty());
1481    }
1482
1483    #[test]
1484    fn indexing_agrees_with_the_walk_from_both_ends() {
1485        let l = chunked(if cfg!(miri) { 300 } else { 1000 });
1486        let walked = all(&l);
1487        for (i, want) in walked.iter().enumerate() {
1488            assert_eq!(&l.get(i).unwrap().to_vec(), want, "at {i}");
1489        }
1490        assert!(l.get(walked.len()).is_none());
1491    }
1492
1493    /// Redis converts a list back to a listpack when it shrinks under half the
1494    /// limit, and only then, so a list at the boundary does not flap.
1495    #[test]
1496    fn a_list_that_shrinks_far_enough_goes_back_to_one_blob() {
1497        let mut l = chunked(300);
1498        let limits = Limits::default();
1499        while l.len() > 200 {
1500            l.drop_back(&limits);
1501        }
1502        assert_eq!(
1503            l.encoding(),
1504            Encoding::Quicklist,
1505            "under the limit is not under half of it"
1506        );
1507        while l.len() > 50 {
1508            l.drop_back(&limits);
1509        }
1510        assert_eq!(l.encoding(), Encoding::Listpack);
1511        assert_eq!(l.len(), 50);
1512        for i in 0..50 {
1513            assert_eq!(
1514                l.get(i).unwrap().to_vec(),
1515                format!("value:{i:0>60}").into_bytes(),
1516                "element {i} survived the demotion"
1517            );
1518        }
1519    }
1520
1521    /// And it can be pushed straight back up again afterwards, which is the
1522    /// part a demotion that left the wrong length behind would break.
1523    #[test]
1524    fn a_demoted_list_promotes_again() {
1525        let mut l = chunked(300);
1526        let limits = Limits::default();
1527        while l.len() > 20 {
1528            l.drop_back(&limits);
1529        }
1530        assert_eq!(l.encoding(), Encoding::Listpack);
1531        for i in 0..300 {
1532            l.push_back(format!("again:{i:0>60}").as_bytes(), &limits);
1533        }
1534        assert_eq!(l.encoding(), Encoding::Quicklist);
1535        assert_eq!(l.len(), 320);
1536        assert_eq!(
1537            l.get(19).unwrap().to_vec(),
1538            format!("value:{:0>60}", 19).into_bytes(),
1539            "the last of the elements that survived the demotion"
1540        );
1541        assert_eq!(
1542            l.get(20).unwrap().to_vec(),
1543            format!("again:{:0>60}", 0).into_bytes(),
1544            "the first of the elements pushed after it"
1545        );
1546    }
1547
1548    /// An integer element is stored as an integer in both bands, which is what
1549    /// makes a list of numbers cost two bytes an element.
1550    #[test]
1551    fn integers_stay_integers_across_the_band_change() {
1552        let mut l = List::new();
1553        let limits = Limits::default();
1554        for i in 0..300 {
1555            l.push_back(i.to_string().as_bytes(), &limits);
1556            l.push_back(vec![b'x'; 100].as_slice(), &limits);
1557        }
1558        assert_eq!(l.encoding(), Encoding::Quicklist);
1559        assert_eq!(l.get(0), Some(Entry::Int(0)));
1560        assert_eq!(l.get(2), Some(Entry::Int(1)));
1561        assert_eq!(l.len(), 600);
1562    }
1563
1564    /// A positive `list-max-listpack-size` is a count of elements, which is the
1565    /// other half of the configuration and the shape the Redis test suite sets
1566    /// when it wants a quicklist out of four elements.
1567    #[test]
1568    fn a_count_limit_promotes_on_the_count() {
1569        let limits = Limits::of(4);
1570        let mut l = List::new();
1571        for i in 0..4 {
1572            l.push_back(i.to_string().as_bytes(), &limits);
1573        }
1574        assert_eq!(l.encoding(), Encoding::Listpack);
1575        l.push_back(b"5", &limits);
1576        assert_eq!(l.encoding(), Encoding::Quicklist);
1577        assert_eq!(l.len(), 5);
1578    }
1579
1580    #[test]
1581    fn the_limits_are_redis_node_limits() {
1582        assert_eq!(Limits::of(-1).max_packed_bytes, 4096);
1583        assert_eq!(Limits::of(-2).max_packed_bytes, 8192);
1584        assert_eq!(Limits::of(-5).max_packed_bytes, 65536);
1585        assert_eq!(Limits::of(-9).max_packed_bytes, 65536);
1586        assert_eq!(Limits::of(128).max_packed_entries, Some(128));
1587        assert_eq!(Limits::of(0).max_packed_entries, Some(1));
1588        assert_eq!(Limits::of(-2), Limits::default());
1589    }
1590
1591    #[test]
1592    fn memory_is_counted_in_both_bands() {
1593        let mut l = List::new();
1594        let limits = Limits::default();
1595        assert!(l.memory_bytes() > 0);
1596        for i in 0..300 {
1597            l.push_back(format!("value:{i:0>60}").as_bytes(), &limits);
1598        }
1599        // Three hundred elements of sixty six bytes is about twenty kilobytes,
1600        // and the chunks holding them should not be far off that.
1601        let held = l.memory_bytes();
1602        assert!(held > 300 * 66, "{held} is less than the elements");
1603        assert!(held < 300 * 66 * 3, "{held} is three times the elements");
1604    }
1605
1606    /// What a list element costs on top of the bytes it holds.
1607    ///
1608    /// M4's exit gate asks for one byte or less per element and this is the
1609    /// number that says whether that is where we are. Printed rather than
1610    /// asserted, because the point is the breakdown and not a threshold. The
1611    /// guard below is the part that runs every time.
1612    ///
1613    /// Three element lengths, because the answer is a fixed cost per element
1614    /// plus a fixed cost per chunk, and one length cannot tell those apart.
1615    #[test]
1616    #[ignore = "a measurement, run it by name"]
1617    fn measure_bytes_per_element() {
1618        let limits = Limits::default();
1619        for len in [8usize, 16, 64] {
1620            for n in [128usize, 10_000, 1_000_000] {
1621                let (l, payload) = weighed(n, len, &limits);
1622                let total = l.memory_bytes();
1623                println!(
1624                    "n={n:<9} elem={len:<4} band={:<9} total={total:<11} payload={payload:<11} over_per_element={:.2}",
1625                    l.encoding().name(),
1626                    (total as f64 - payload as f64) / n as f64
1627                );
1628            }
1629        }
1630    }
1631
1632    /// A list of `n` elements of `len` bytes each, and what those bytes come to.
1633    fn weighed(n: usize, len: usize, limits: &Limits) -> (List, usize) {
1634        let mut l = List::new();
1635        let mut payload = 0usize;
1636        for i in 0..n {
1637            // A letter in front so that the element is stored as a string. A
1638            // listpack stores something that parses as an integer as one, which
1639            // would be measuring the integer encoding rather than the ring.
1640            let v = format!("e{i:0>w$}", w = len - 1);
1641            debug_assert_eq!(v.len(), len);
1642            payload += v.len();
1643            l.push_back(v.as_bytes(), limits);
1644        }
1645        (l, payload)
1646    }
1647
1648    /// The guard for the measurement above, at a size that runs every time.
1649    ///
1650    /// The threshold is loose on purpose: what it is here to catch is a chunk
1651    /// that stopped giving its spare room back when it was sealed, or a ring
1652    /// that started holding something per element, and either of those is a
1653    /// multiple rather than a few percent.
1654    #[test]
1655    fn a_long_list_does_not_hold_much_more_than_it_stores() {
1656        let limits = Limits::default();
1657        let n = 100_000;
1658        let (l, payload) = weighed(n, 16, &limits);
1659        assert_eq!(l.encoding(), Encoding::Quicklist);
1660        let total = l.memory_bytes();
1661        assert!(
1662            total < payload + n * 4,
1663            "{total} bytes for {payload} of elements, which is {:.2} an element over",
1664            (total as f64 - payload as f64) / n as f64
1665        );
1666    }
1667
1668    /// An element bigger than a whole chunk gets a chunk of its own, which is
1669    /// what Redis calls a plain node. Without it the list would count an element
1670    /// that a chunk sized for something else had refused.
1671    #[test]
1672    fn an_element_too_big_for_a_chunk_gets_one_of_its_own() {
1673        let mut l = List::new();
1674        let limits = Limits::default();
1675        let huge = vec![b'h'; 20_000];
1676        l.push_back(&huge, &limits);
1677        assert_eq!(l.len(), 1);
1678        assert_eq!(l.encoding(), Encoding::Quicklist);
1679        assert_eq!(l.front().unwrap().to_vec(), huge);
1680        l.push_back(b"after", &limits);
1681        l.push_front(b"before", &limits);
1682        assert_eq!(l.len(), 3);
1683        assert_eq!(l.get(1).unwrap().to_vec(), huge);
1684        assert_eq!(l.back().unwrap().to_vec(), b"after");
1685        assert_eq!(l.front().unwrap().to_vec(), b"before");
1686        assert_eq!(l.pop_front(&limits).unwrap(), b"before");
1687        assert_eq!(l.pop_front(&limits).unwrap(), huge);
1688    }
1689
1690    #[test]
1691    fn the_walk_backward_is_the_walk_forward_reversed() {
1692        for mut l in [List::new(), chunked(400)] {
1693            let limits = Limits::default();
1694            l.push_back(b"tail", &limits);
1695            let mut want = all(&l);
1696            want.reverse();
1697            let got: Vec<Vec<u8>> = l.iter_back().map(|e| e.to_vec()).collect();
1698            assert_eq!(got, want, "{:?}", l.encoding());
1699        }
1700    }
1701
1702    #[test]
1703    fn a_range_is_the_window_it_was_asked_for() {
1704        for l in both_bands(50) {
1705            let all_of_it = all(&l);
1706            for (start, count) in [(0, 0), (0, 5), (3, 4), (48, 9), (50, 3), (0, 50)] {
1707                let got: Vec<Vec<u8>> = l.range(start, count).map(|e| e.to_vec()).collect();
1708                let want = &all_of_it[start.min(50)..(start + count).min(50)];
1709                assert_eq!(got, want, "{start} for {count} in {:?}", l.encoding());
1710            }
1711        }
1712    }
1713
1714    /// A window that starts in the middle now steps over whole chunks to get
1715    /// there instead of decoding every element on the way, so every start
1716    /// position and every window that crosses a chunk boundary is worth
1717    /// checking rather than the handful the case above uses.
1718    #[test]
1719    fn a_window_lands_in_the_right_place_whatever_chunk_it_starts_in() {
1720        let limits = Limits::default();
1721        let mut l = List::new();
1722        // Long enough elements that this is many chunks and not one, and enough
1723        // of them that a start position lands in the middle of a chunk, at the
1724        // front of one, and at the back of one. The Miri size is a fifth of
1725        // that, which is still several chunks, and it costs a twentieth rather
1726        // than a fifth because this is every window from every start.
1727        let n = if cfg!(miri) { 100usize } else { 500 };
1728        let counts = if cfg!(miri) {
1729            [0usize, 1, 7, 26, 100]
1730        } else {
1731            [0, 1, 7, 130, 500]
1732        };
1733        for i in 0..n {
1734            l.push_back(format!("e{i}:{}", "p".repeat(200)).as_bytes(), &limits);
1735        }
1736        assert_eq!(l.encoding(), Encoding::Quicklist);
1737        let all_of_it = all(&l);
1738
1739        for start in 0..=n {
1740            for count in counts {
1741                let got: Vec<Vec<u8>> = l.range(start, count).map(|e| e.to_vec()).collect();
1742                let want = &all_of_it[start.min(n)..(start + count).min(n)];
1743                assert_eq!(got, want, "{count} from {start}");
1744            }
1745        }
1746    }
1747
1748    /// The same over a packed list, which seeks by walking the blob from
1749    /// whichever end is nearer rather than by finding a chunk. A list in this
1750    /// band holds eight kilobytes, which is four hundred odd elements and not
1751    /// the hundred and twenty eight the other packed bands stop at, so the half
1752    /// of the blob that the two ended seek saves is worth having and the seam
1753    /// between the two directions is worth checking at every position.
1754    #[test]
1755    fn a_packed_window_lands_in_the_right_place_from_either_end() {
1756        let limits = Limits::default();
1757        let mut l = List::new();
1758        // A quarter of the elements under Miri, which still puts the seam
1759        // between the two directions in the middle of the blob and still costs
1760        // the square of the count rather than the count.
1761        let n = if cfg!(miri) { 100usize } else { 400 };
1762        let counts = if cfg!(miri) {
1763            [0usize, 1, 7, 33, 100]
1764        } else {
1765            [0, 1, 7, 130, 400]
1766        };
1767        for i in 0..n {
1768            l.push_back(format!("e{i:0>9}").as_bytes(), &limits);
1769        }
1770        assert_eq!(l.encoding(), Encoding::Listpack);
1771        let all_of_it = all(&l);
1772
1773        for start in 0..=n {
1774            assert_eq!(
1775                l.get(start).map(|e| e.to_vec()).as_ref(),
1776                all_of_it.get(start),
1777                "element {start}"
1778            );
1779            for count in counts {
1780                let got: Vec<Vec<u8>> = l.range(start, count).map(|e| e.to_vec()).collect();
1781                let want = &all_of_it[start.min(n)..(start + count).min(n)];
1782                assert_eq!(got, want, "{count} from {start}");
1783            }
1784        }
1785    }
1786
1787    /// The chunk start index has a floating origin so that work at the front of
1788    /// the list costs it nothing, which is the one part of it that is clever
1789    /// enough to be wrong. This is the shape that would catch it: a queue being
1790    /// drained and refilled at the head while something reads the middle, where
1791    /// an index of real positions would need every entry rewritten on every
1792    /// push and this one moves a single number.
1793    #[test]
1794    fn reading_the_middle_survives_a_head_that_keeps_moving() {
1795        let limits = Limits::default();
1796        let mut l = List::new();
1797        let mut want: Vec<Vec<u8>> = Vec::new();
1798        for i in 0..many(2000) {
1799            let v = format!("e{i}:{}", "p".repeat(100)).into_bytes();
1800            l.push_back(&v, &limits);
1801            want.push(v);
1802        }
1803        assert_eq!(l.encoding(), Encoding::Quicklist);
1804
1805        // The rounds come down with the list, so the head still walks the same
1806        // share of it and still crosses its own chunk boundary both ways.
1807        for round in 0..many(400) {
1808            // Enough pushes and pops to walk the head chunk across its own
1809            // boundary in both directions rather than only inside it.
1810            if round % 3 == 0 {
1811                for k in 0..7 {
1812                    let v = format!("h{round}:{k}:{}", "q".repeat(100)).into_bytes();
1813                    l.push_front(&v, &limits);
1814                    want.insert(0, v);
1815                }
1816            } else {
1817                for _ in 0..5 {
1818                    assert_eq!(l.pop_front(&limits), Some(want.remove(0)));
1819                }
1820            }
1821            assert_eq!(l.len(), want.len(), "length after round {round}");
1822            for at in [0, 1, want.len() / 3, want.len() / 2, want.len() - 1] {
1823                assert_eq!(
1824                    l.get(at).map(|e| e.to_vec()).as_ref(),
1825                    Some(&want[at]),
1826                    "element {at} after round {round}"
1827                );
1828            }
1829            let mid = want.len() / 2;
1830            let got: Vec<Vec<u8>> = l.range(mid, 30).map(|e| e.to_vec()).collect();
1831            assert_eq!(got, want[mid..mid + 30], "the window after round {round}");
1832            let Body::Chunks(d) = &l.body else {
1833                panic!("the list left the chunked band");
1834            };
1835            d.index_is_true();
1836        }
1837    }
1838
1839    #[test]
1840    fn setting_an_element_replaces_only_that_one() {
1841        for mut l in both_bands(50) {
1842            let limits = Limits::default();
1843            let before = all(&l);
1844            let band = l.encoding();
1845            for at in [0usize, 1, 25, 49] {
1846                let mut want = before.clone();
1847                for value in [
1848                    &b"z"[..],
1849                    &b"a much longer element than the one there"[..],
1850                    b"42",
1851                ] {
1852                    assert!(l.set(at, value, &limits), "setting {at} in {band:?}");
1853                    want[at] = value.to_vec();
1854                    assert_eq!(all(&l), want, "setting {at} to {value:?} in {band:?}");
1855                }
1856                l.set(at, &before[at], &limits);
1857            }
1858            assert!(!l.set(50, b"z", &limits), "past the end is not a set");
1859            assert_eq!(all(&l), before);
1860        }
1861    }
1862
1863    /// The pivot search stopped walking elements and started reading entry
1864    /// headers, and it runs over a ring of chunks rather than one blob, so the
1865    /// two things it could get wrong are the offset it adds for the chunks in
1866    /// front of the one it found the value in, and an element whose encoding
1867    /// takes the long way through the scan.
1868    ///
1869    /// So this puts every kind of element in a list long enough to be several
1870    /// hundred chunks, of mixed lengths so that the chunk boundaries fall in
1871    /// awkward places, and asks for each of them by value. Every answer has to be
1872    /// the position the element is actually at, which is checked against the
1873    /// element walk rather than against a number written down here.
1874    #[test]
1875    fn a_pivot_is_found_at_the_right_position_across_a_ring_of_chunks() {
1876        let limits = Limits::default();
1877        let mut l = List::new();
1878        let mut want: Vec<Vec<u8>> = Vec::new();
1879        for i in 0..4_000usize {
1880            // Four shapes, cycling: a plain number, a number too big for the
1881            // small encodings, a short string and a long one. The lengths vary
1882            // with the index so that no two chunks break in the same place.
1883            let v = match i % 4 {
1884                0 => i.to_string().into_bytes(),
1885                1 => (i64::MAX - i as i64).to_string().into_bytes(),
1886                2 => format!("v{i:0width$}", width = 1 + i % 30).into_bytes(),
1887                _ => format!("value:{i}:{}", "x".repeat(40 + i % 90)).into_bytes(),
1888            };
1889            l.push_back(&v, &limits);
1890            want.push(v);
1891        }
1892        assert_eq!(l.encoding(), Encoding::Quicklist, "this needs the ring");
1893        assert_eq!(l.len(), want.len());
1894        for (at, v) in want.iter().enumerate() {
1895            assert_eq!(l.find(v), Some(at), "element {at} is not where it is");
1896        }
1897        assert_eq!(l.find(b"not in here at all"), None);
1898        // A near miss of a real element at both ends of it, which is what the
1899        // two word comparison is for and what it would get wrong if it only
1900        // looked at one end.
1901        assert_eq!(l.find(b"v2000000000000000000000000000002"), None);
1902    }
1903
1904    /// `LPOS` reads the same headers the pivot search does, and over a ring it
1905    /// has two more things to get wrong: the offset for the chunks in front of
1906    /// this one, and the same offset counted the other way for a negative rank.
1907    /// A ring of several hundred chunks with a match every seventh element
1908    /// catches an off by one in either of them, and every answer is checked
1909    /// against the element walk rather than against a number written down here.
1910    #[test]
1911    fn positions_agree_with_the_element_walk_across_a_ring_of_chunks() {
1912        let limits = Limits::default();
1913        let mut l = List::new();
1914        let n = many(4_000usize);
1915        for i in 0..n {
1916            let v = if i % 7 == 0 {
1917                b"wanted".to_vec()
1918            } else {
1919                format!("element:{i:0width$}", width = 8 + i % 40).into_bytes()
1920            };
1921            l.push_back(&v, &limits);
1922        }
1923        assert_eq!(l.encoding(), Encoding::Quicklist, "this needs the ring");
1924        let want: Vec<usize> = (0..n).filter(|i| i % 7 == 0).collect();
1925
1926        let mut got = Vec::new();
1927        assert_eq!(
1928            l.positions(b"wanted", 1, 0, 0, &mut |at| got.push(at)),
1929            want.len()
1930        );
1931        assert_eq!(got, want);
1932
1933        // The same from the back, which walks the chunks in reverse and turns
1934        // every index round twice.
1935        let mut got = Vec::new();
1936        l.positions(b"wanted", -1, 0, 0, &mut |at| got.push(at));
1937        got.reverse();
1938        assert_eq!(got, want, "the same matches, found the other way round");
1939
1940        // A rank in the middle, forward and back, which drops matches before
1941        // handing any over.
1942        let mut got = Vec::new();
1943        l.positions(b"wanted", 4, 3, 0, &mut |at| got.push(at));
1944        assert_eq!(got, want[3..6].to_vec());
1945        let mut got = Vec::new();
1946        l.positions(b"wanted", -4, 3, 0, &mut |at| got.push(at));
1947        let mut tail = want[want.len() - 6..want.len() - 3].to_vec();
1948        tail.reverse();
1949        assert_eq!(got, tail);
1950
1951        // And a budget, which has to be spent across the whole ring rather than
1952        // per chunk: a quarter of the list reaches the matches in that quarter
1953        // and no others, from whichever end the walk starts.
1954        let budget = n / 4;
1955        let mut got = Vec::new();
1956        l.positions(b"wanted", 1, 0, budget, &mut |at| got.push(at));
1957        assert_eq!(
1958            got,
1959            want.iter()
1960                .copied()
1961                .filter(|&i| i < budget)
1962                .collect::<Vec<_>>()
1963        );
1964        let mut got = Vec::new();
1965        l.positions(b"wanted", -1, 0, budget, &mut |at| got.push(at));
1966        got.reverse();
1967        assert_eq!(
1968            got,
1969            want.iter()
1970                .copied()
1971                .filter(|&i| i >= n - budget)
1972                .collect::<Vec<_>>()
1973        );
1974    }
1975
1976    /// `LREM` reads the same headers and then removes what it found, so on a
1977    /// ring the thing it can get wrong is an index that was right when it was
1978    /// collected and stale by the time it is used.
1979    #[test]
1980    fn removing_across_a_ring_takes_out_exactly_what_was_asked_for() {
1981        let limits = Limits::default();
1982        // Every one in five is a match either way, so the counts below are
1983        // written against the size rather than spelled out.
1984        let n: usize = if cfg!(miri) { 600 } else { 3_000 };
1985        let build = || {
1986            let mut l = List::new();
1987            for i in 0..n {
1988                let v = if i % 5 == 0 {
1989                    b"gone".to_vec()
1990                } else {
1991                    format!("element:{i:0width$}", width = 8 + i % 30).into_bytes()
1992                };
1993                l.push_back(&v, &limits);
1994            }
1995            assert_eq!(l.encoding(), Encoding::Quicklist, "this needs the ring");
1996            l
1997        };
1998        let kept: Vec<Vec<u8>> = (0..n)
1999            .filter(|i| i % 5 != 0)
2000            .map(|i| format!("element:{i:0width$}", width = 8 + i % 30).into_bytes())
2001            .collect();
2002
2003        let mut l = build();
2004        assert_eq!(l.remove(0, b"gone", &limits), n / 5);
2005        assert_eq!(all(&l), kept);
2006
2007        // From the front, which takes the first ten and leaves the rest.
2008        let mut l = build();
2009        assert_eq!(l.remove(10, b"gone", &limits), 10);
2010        assert_eq!(l.len(), n - 10);
2011        assert_eq!(l.find(b"gone"), Some(40), "the eleventh was at 50");
2012
2013        // And from the back, which takes the last ten.
2014        let mut l = build();
2015        assert_eq!(l.remove(-10, b"gone", &limits), 10);
2016        assert_eq!(l.len(), n - 10);
2017        let mut last = 0usize;
2018        l.positions(b"gone", -1, 1, 0, &mut |at| last = at);
2019        // The last ten matches ran up to the end, so the one before them is now
2020        // the last and nothing in front of it moved.
2021        assert_eq!(last, n - 55);
2022    }
2023
2024    #[test]
2025    fn an_insert_goes_where_the_pivot_is() {
2026        for mut l in both_bands(50) {
2027            let limits = Limits::default();
2028            let before = all(&l);
2029            let band = l.encoding();
2030            let pivot = before[25].clone();
2031            assert_eq!(
2032                l.insert_at_pivot(&pivot, b"before", true, &limits),
2033                Some(51)
2034            );
2035            assert_eq!(
2036                l.insert_at_pivot(&pivot, b"after", false, &limits),
2037                Some(52)
2038            );
2039            assert_eq!(l.get(25).unwrap().to_vec(), b"before", "{band:?}");
2040            assert_eq!(l.get(26).unwrap().to_vec(), pivot, "{band:?}");
2041            assert_eq!(l.get(27).unwrap().to_vec(), b"after", "{band:?}");
2042            assert_eq!(l.len(), 52);
2043            assert_eq!(
2044                l.insert_at_pivot(b"nothing like it", b"x", true, &limits),
2045                None
2046            );
2047            assert_eq!(l.len(), 52);
2048        }
2049    }
2050
2051    #[test]
2052    fn an_insert_by_index_takes_both_ends_and_the_middle() {
2053        let n: usize = if cfg!(miri) { 200 } else { 400 };
2054        for at in [0usize, 1, n / 2, n - 1, n] {
2055            let mut l = chunked(n);
2056            let limits = Limits::default();
2057            let mut want = all(&l);
2058            assert!(l.insert(at, b"new", &limits), "inserting at {at}");
2059            want.insert(at, b"new".to_vec());
2060            assert_eq!(all(&l), want, "inserting at {at}");
2061            assert_eq!(l.len(), n + 1);
2062        }
2063        let mut l = chunked(n);
2064        assert!(!l.insert(n + 1, b"new", &Limits::default()));
2065    }
2066
2067    #[test]
2068    fn removing_by_value_counts_from_the_end_it_was_told_to() {
2069        let build = || {
2070            let mut l = List::new();
2071            let limits = Limits::default();
2072            for i in 0..40 {
2073                l.push_back(
2074                    if i % 3 == 0 {
2075                        b"x".to_vec()
2076                    } else {
2077                        format!("e{i}").into_bytes()
2078                    }
2079                    .as_slice(),
2080                    &limits,
2081                );
2082            }
2083            l
2084        };
2085        let limits = Limits::default();
2086
2087        let mut l = build();
2088        assert_eq!(l.remove(0, b"x", &limits), 14, "every one of them");
2089        assert!(!all(&l).contains(&b"x".to_vec()));
2090        assert_eq!(l.len(), 26);
2091
2092        let mut l = build();
2093        assert_eq!(l.remove(2, b"x", &limits), 2);
2094        assert_eq!(l.len(), 38);
2095        assert_eq!(l.get(0).unwrap().to_vec(), b"e1", "the first two went");
2096
2097        let mut l = build();
2098        assert_eq!(l.remove(-2, b"x", &limits), 2);
2099        assert_eq!(l.get(0).unwrap().to_vec(), b"x", "the last two went");
2100        assert_eq!(l.back().unwrap().to_vec(), b"e38");
2101
2102        let mut l = build();
2103        assert_eq!(l.remove(99, b"x", &limits), 14, "more than there are");
2104        assert_eq!(l.remove(1, b"nothing like it", &limits), 0);
2105    }
2106
2107    #[test]
2108    fn a_trim_keeps_the_window_and_nothing_else() {
2109        // Half the elements under Miri, which is still more than one chunk, and
2110        // every window is written against the size so the ends stay the ends.
2111        let n: usize = if cfg!(miri) { 200 } else { 400 };
2112        for (start, count) in [
2113            (0usize, n),
2114            (0, 10),
2115            (n - 10, 10),
2116            (n / 4, n / 2),
2117            (0, 0),
2118            (n - 1, 1),
2119        ] {
2120            let mut l = chunked(n);
2121            let limits = Limits::default();
2122            let want = all(&l)[start..start + count].to_vec();
2123            l.trim(start, count, &limits);
2124            assert_eq!(all(&l), want, "keeping {count} from {start}");
2125            assert_eq!(l.len(), count);
2126        }
2127    }
2128
2129    /// A trim that leaves a handful takes the list back to one blob, which is
2130    /// the shrinking rule reached the other way.
2131    #[test]
2132    fn a_trim_that_leaves_a_handful_goes_back_to_one_blob() {
2133        let mut l = chunked(400);
2134        let limits = Limits::default();
2135        l.trim(10, 5, &limits);
2136        assert_eq!(l.encoding(), Encoding::Listpack);
2137        assert_eq!(l.len(), 5);
2138        assert_eq!(
2139            l.get(0).unwrap().to_vec(),
2140            format!("value:{:0>60}", 10).into_bytes()
2141        );
2142    }
2143
2144    #[test]
2145    fn a_position_is_counted_from_the_end_the_rank_asked_for() {
2146        for mut l in [List::new(), chunked(300)] {
2147            let limits = Limits::default();
2148            let band = l.encoding();
2149            for m in [b"a", b"b", b"a", b"c", b"a"] {
2150                l.push_back(m, &limits);
2151            }
2152            let base = l.len() - 5;
2153            let mut out = Vec::new();
2154
2155            l.positions(b"a", 1, 1, 0, &mut |at| out.push(at));
2156            assert_eq!(out, vec![base], "{band:?}");
2157
2158            out.clear();
2159            l.positions(b"a", 2, 1, 0, &mut |at| out.push(at));
2160            assert_eq!(out, vec![base + 2], "the second from the front");
2161
2162            out.clear();
2163            l.positions(b"a", -1, 1, 0, &mut |at| out.push(at));
2164            assert_eq!(out, vec![base + 4], "the first from the back");
2165
2166            out.clear();
2167            l.positions(b"a", -2, 1, 0, &mut |at| out.push(at));
2168            assert_eq!(out, vec![base + 2], "the second from the back");
2169
2170            out.clear();
2171            l.positions(b"a", 1, 0, 0, &mut |at| out.push(at));
2172            assert_eq!(out, vec![base, base + 2, base + 4], "all of them");
2173
2174            out.clear();
2175            l.positions(b"a", -1, 0, 0, &mut |at| out.push(at));
2176            assert_eq!(out, vec![base + 4, base + 2, base], "all of them backward");
2177
2178            out.clear();
2179            l.positions(b"a", 1, 2, 0, &mut |at| out.push(at));
2180            assert_eq!(out, vec![base, base + 2], "two of them");
2181
2182            out.clear();
2183            l.positions(b"nothing like it", 1, 0, 0, &mut |at| out.push(at));
2184            assert!(out.is_empty());
2185
2186            out.clear();
2187            l.positions(b"a", 0, 0, 0, &mut |at| out.push(at));
2188            assert!(out.is_empty(), "a rank of zero is not a rank");
2189        }
2190    }
2191
2192    /// `MAXLEN` bounds the comparisons and not the answers, so a match past it
2193    /// is not found however few answers have been collected.
2194    #[test]
2195    fn maxlen_stops_the_walk_rather_than_the_answers() {
2196        let mut l = List::new();
2197        let limits = Limits::default();
2198        for i in 0..20 {
2199            l.push_back(
2200                if i == 15 {
2201                    b"x".to_vec()
2202                } else {
2203                    format!("e{i}").into_bytes()
2204                }
2205                .as_slice(),
2206                &limits,
2207            );
2208        }
2209        let mut out = Vec::new();
2210        l.positions(b"x", 1, 0, 10, &mut |at| out.push(at));
2211        assert!(out.is_empty(), "ten comparisons do not reach the sixteenth");
2212        out.clear();
2213        l.positions(b"x", 1, 0, 16, &mut |at| out.push(at));
2214        assert_eq!(out, vec![15]);
2215        out.clear();
2216        l.positions(b"x", -1, 0, 5, &mut |at| out.push(at));
2217        assert_eq!(out, vec![15], "five from the back does reach it");
2218    }
2219
2220    /// Every operation against a plain `Vec`, over a mix of element sizes that
2221    /// crosses the band boundary in both directions several times. A model test
2222    /// rather than more cases, because the interesting bugs here are the ones
2223    /// where a chunk splits and an index moves and the length stops agreeing.
2224    #[test]
2225    fn a_long_run_of_operations_agrees_with_a_vec() {
2226        // Twice, because the default limits keep a list of this size packed the
2227        // whole way and never walk the code that splits and joins chunks. A
2228        // `list-max-listpack-size` of 8 is a real setting and it puts the band
2229        // change within reach of a few pushes, so the second run crosses it in
2230        // both directions over and over.
2231        let (_, chunked) = model_run(&Limits::default());
2232        assert_eq!(chunked, 0, "the default limits should not chunk this list");
2233        let (packed, chunked) = model_run(&Limits::of(8));
2234        // Both counts come down with the round count, so the share of the run
2235        // spent in each band is the thing that stays fixed.
2236        assert!(packed > many(200), "{packed} rounds packed");
2237        assert!(chunked > many(200), "{chunked} rounds chunked");
2238    }
2239
2240    /// A long run of a fixed sequence of list operations against a `Vec` that
2241    /// says what the answer is, and the two band counts it saw.
2242    fn model_run(limits: &Limits) -> (usize, usize) {
2243        let mut l = List::new();
2244        let mut want: Vec<Vec<u8>> = Vec::new();
2245        // A fixed sequence rather than a random one, so a failure is a failure
2246        // every time it is run.
2247        let mut seed = 0x2064_u64;
2248        let mut next = move || {
2249            seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
2250            (seed >> 33) as usize
2251        };
2252        let (mut packed, mut chunked) = (0, 0);
2253        for round in 0..many(4000) {
2254            let n = next();
2255            let value = match n % 4 {
2256                0 => format!("{}", n % 97).into_bytes(),
2257                1 => vec![b'a' + (n % 26) as u8; 1 + n % 40],
2258                2 => vec![b'z'; 200 + n % 400],
2259                _ => format!("e{round}").into_bytes(),
2260            };
2261            match n % 9 {
2262                0 => {
2263                    l.push_front(&value, limits);
2264                    want.insert(0, value);
2265                }
2266                1 | 2 => {
2267                    l.push_back(&value, limits);
2268                    want.push(value);
2269                }
2270                3 if !want.is_empty() => {
2271                    let at = n % want.len();
2272                    assert!(l.insert(at, &value, limits));
2273                    want.insert(at, value);
2274                }
2275                4 if !want.is_empty() => {
2276                    let at = n % want.len();
2277                    assert!(l.set(at, &value, limits));
2278                    want[at] = value;
2279                }
2280                5 if !want.is_empty() => {
2281                    assert_eq!(l.pop_front(limits), Some(want.remove(0)));
2282                }
2283                6 if !want.is_empty() => {
2284                    assert_eq!(l.pop_back(limits), want.pop());
2285                }
2286                7 if want.len() > 4 => {
2287                    let start = n % (want.len() - 2);
2288                    let keep = 1 + n % (want.len() - start);
2289                    l.trim(start, keep, limits);
2290                    want = want[start..start + keep].to_vec();
2291                }
2292                8 if !want.is_empty() => {
2293                    let needle = want[n % want.len()].clone();
2294                    let count = [0i64, 1, -1, 3][n % 4];
2295                    let gone = l.remove(count, &needle, limits);
2296                    let mut hits: Vec<usize> = want
2297                        .iter()
2298                        .enumerate()
2299                        .filter(|(_, m)| **m == needle)
2300                        .map(|(i, _)| i)
2301                        .collect();
2302                    if count < 0 {
2303                        hits.reverse();
2304                    }
2305                    if count != 0 {
2306                        hits.truncate(count.unsigned_abs() as usize);
2307                    }
2308                    assert_eq!(gone, hits.len(), "round {round}");
2309                    hits.sort_unstable();
2310                    for at in hits.iter().rev() {
2311                        want.remove(*at);
2312                    }
2313                }
2314                _ => {}
2315            }
2316            assert_eq!(l.len(), want.len(), "length after round {round}");
2317            // Read at both ends and in the middle every round. That builds the
2318            // chunk start index back up after whatever the round did to it, so
2319            // the audit below is checking a filled index and not an empty one,
2320            // and a stale entry shows up here as the wrong element rather than
2321            // as nothing at all.
2322            if !want.is_empty() {
2323                for at in [0, want.len() / 2, want.len() - 1] {
2324                    assert_eq!(
2325                        l.get(at).map(|e| e.to_vec()).as_ref(),
2326                        Some(&want[at]),
2327                        "element {at} after round {round}"
2328                    );
2329                }
2330            }
2331            if let Body::Chunks(d) = &l.body {
2332                d.index_is_true();
2333            }
2334            match l.encoding() {
2335                Encoding::Listpack => packed += 1,
2336                Encoding::Quicklist => chunked += 1,
2337            }
2338            if round % 25 == 0 {
2339                assert_eq!(all(&l), want, "contents after round {round}");
2340                let mut back = all(&l);
2341                back.reverse();
2342                let walked: Vec<Vec<u8>> = l.iter_back().map(|e| e.to_vec()).collect();
2343                assert_eq!(walked, back, "the backward walk after round {round}");
2344                if let Some(first) = want.first() {
2345                    assert_eq!(l.front().unwrap().to_vec(), *first);
2346                    assert_eq!(l.back().unwrap().to_vec(), *want.last().unwrap());
2347                    assert_eq!(l.find(first), Some(0));
2348                }
2349            }
2350        }
2351        assert_eq!(all(&l), want);
2352        (packed, chunked)
2353    }
2354
2355    /// Freeze a list, read it back, and check nothing about it changed.
2356    fn round_trip(l: &List) -> List {
2357        let mut out = Vec::new();
2358        l.freeze(&mut out);
2359        let back = List::thaw(&out).expect("it came back");
2360        assert_eq!(back.len(), l.len(), "the length");
2361        assert_eq!(back.encoding(), l.encoding(), "the band");
2362        assert_eq!(all(&back), all(l), "the elements");
2363        let mut backward: Vec<Vec<u8>> = back.iter_back().map(|e| e.to_vec()).collect();
2364        backward.reverse();
2365        assert_eq!(backward, all(l), "and the walk the other way");
2366        back
2367    }
2368
2369    #[test]
2370    fn a_frozen_list_comes_back_in_the_band_it_left() {
2371        round_trip(&List::new());
2372        for l in both_bands(40) {
2373            round_trip(&l);
2374        }
2375        for l in both_bands(200) {
2376            round_trip(&l);
2377        }
2378    }
2379
2380    /// The ring comes back with the chunks it left with, not one long one.
2381    ///
2382    /// Both because `MEMORY USAGE` should say the same thing on both sides of a
2383    /// trip to the device, and because what an index costs is a walk over chunks
2384    /// and then a walk inside one.
2385    #[test]
2386    fn a_ring_comes_back_with_the_same_chunk_boundaries() {
2387        // Fewer chunks under Miri but still more than the two that would make
2388        // it a ring only by name.
2389        let l = chunked(if cfg!(miri) { 500 } else { 2000 });
2390        let Body::Chunks(before) = &l.body else {
2391            unreachable!("chunked built a ring");
2392        };
2393        let want: Vec<usize> = before.chunks.iter().map(Chunk::len).collect();
2394        assert!(want.len() > 2, "{} chunks is not a ring", want.len());
2395
2396        let back = round_trip(&l);
2397        let Body::Chunks(after) = &back.body else {
2398            unreachable!("it came back a ring");
2399        };
2400        let got: Vec<usize> = after.chunks.iter().map(Chunk::len).collect();
2401        assert_eq!(got, want);
2402    }
2403
2404    #[test]
2405    fn a_list_that_came_back_takes_more_elements_at_both_ends() {
2406        let limits = Limits::default();
2407        for l in both_bands(200) {
2408            let mut back = round_trip(&l);
2409            back.push_front(b"first", &limits);
2410            back.push_back(b"last", &limits);
2411            assert_eq!(back.len(), l.len() + 2);
2412            assert_eq!(back.front().expect("a front").to_vec(), b"first".to_vec());
2413            assert_eq!(back.back().expect("a back").to_vec(), b"last".to_vec());
2414            assert_eq!(back.get(1).expect("the old front").to_vec(), all(&l)[0]);
2415        }
2416    }
2417
2418    #[test]
2419    fn an_element_too_big_for_a_chunk_survives_the_trip() {
2420        let limits = Limits::default();
2421        let mut l = List::new();
2422        l.push_back(b"before", &limits);
2423        l.push_back(&vec![b'x'; CHUNK_BYTES * 2], &limits);
2424        l.push_back(b"after", &limits);
2425        assert_eq!(l.encoding(), Encoding::Quicklist);
2426        let back = round_trip(&l);
2427        assert_eq!(
2428            back.get(1).expect("the big one").to_vec().len(),
2429            CHUNK_BYTES * 2
2430        );
2431    }
2432
2433    #[test]
2434    fn a_frozen_list_that_arrives_damaged_is_an_error_and_not_a_panic() {
2435        for l in both_bands(40) {
2436            let mut out = Vec::new();
2437            l.freeze(&mut out);
2438            for cut in 0..out.len() {
2439                let _ = List::thaw(&out[..cut]);
2440            }
2441        }
2442        assert_eq!(List::thaw(&[]).err(), Some(Broken::Short));
2443        assert_eq!(List::thaw(&[9]).err(), Some(Broken::Form));
2444        assert_eq!(List::thaw(&[FORM_PACKED, 1, 2]).err(), Some(Broken::Body));
2445        // A chunk count no amount of what is left could fill, and then a chunk
2446        // claiming more elements than it has bytes for.
2447        assert_eq!(
2448            List::thaw(&[FORM_CHUNKS, 0xff, 0xff, 0x7f, 0]).err(),
2449            Some(Broken::Body)
2450        );
2451        assert_eq!(
2452            List::thaw(&[FORM_CHUNKS, 1, 9, 2, b'a', b'b']).err(),
2453            Some(Broken::Body)
2454        );
2455    }
2456}