src/t_stream.c C 6,886 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 6,886.
1/*2 * Copyright (c) 2017-Present, Redis Ltd.3 * All rights reserved.4 *5 * Licensed under your choice of (a) the Redis Source Available License 2.06 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the7 * GNU Affero General Public License v3 (AGPLv3).8 */910#include "server.h"11#include "endianconv.h"12#include "stream.h"13#include "xxhash.h"14#include <string.h>1516/* Every stream item inside the listpack, has a flags field that is used to17 * mark the entry as deleted, or having the same field as the "master"18 * entry at the start of the listpack> */19#define STREAM_ITEM_FLAG_NONE 0             /* No special flags. */20#define STREAM_ITEM_FLAG_DELETED (1<<0)     /* Entry is deleted. Skip it. */21#define STREAM_ITEM_FLAG_SAMEFIELDS (1<<1)  /* Same fields as master entry. */2223/* For stream commands that require multiple IDs24 * when the number of IDs is less than 'STREAMID_STATIC_VECTOR_LEN',25 * avoid malloc allocation.*/26#define STREAMID_STATIC_VECTOR_LEN 82728/* Max pre-allocation for listpack. This is done to avoid abuse of a user29 * setting stream_node_max_bytes to a huge number. */30#define STREAM_LISTPACK_MAX_PRE_ALLOCATE 40963132/* Don't let listpacks grow too big, even if the user config allows it.33 * doing so can lead to an overflow (trying to store more than 32bit length34 * into the listpack header), or actually an assertion since lpInsert35 * will return NULL. */36#define STREAM_LISTPACK_MAX_SIZE (1<<30)3738void streamFreeCGGeneric(void *cg, void *s);39void streamFreeNACK(stream *s, streamNACK *na);40size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start, streamID *end, size_t count, streamCG *group, streamConsumer *consumer, long long maxsize, size_t emitted_before);41int streamParseStrictIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq, int *seq_given);42int streamParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq);4344int streamEntryIsReferenced(stream *s, streamID *id);45int streamCleanupEntryCGroupRefs(redisDb *db, stream *s, streamID *id);46void streamUpdateCGroupLastId(stream *s, streamCG *cg, streamID *id);47void trackStreamClaimTimeouts(client *c, robj **keys, int numkeys, uint64_t expire_time);4849/* Forward declarations for IDMP functions (defined at end of file) */50static void trackStreamIdmpEntries(client *c, robj *key);51static void streamClearIdmpEntries(stream *s);52static void idmpInsertEntry(stream *s, idmpProducer *producer, idmpEntry *entry, const streamID *id);53static int idmpLookupAndReply(stream *s, idmpProducer *producer, idmpEntry *entry, client *c);54static int idmpLookup(idmpProducer *producer, idmpEntry *entry, streamID *id);55static idmpProducer *idmpGetOrCreateProducer(stream *s, const char *pid, size_t pid_len);56static int createIdempotencyHash(robj **argv, int64_t numfields, XXH128_hash_t *out_hash);57static void idmpEvictOldestEntry(stream *s, idmpProducer *producer);5859/* Forward declarations for PEL time list functions */60static void pelListInsertAfter(streamCG *cg, streamNACK *after, streamNACK *nack);61static void pelListInsertAtTail(streamCG *cg, streamNACK *nack);62static void pelListUpdate(streamCG *cg, streamNACK *nack, mstime_t new_delivery_time);6364/* Forward declarations for the INFO Streams samplers */65static int64_t streamCGroupSample(streamCG *cg, streamDistribMetric metric);66static int64_t streamStreamSample(stream *s, streamDistribMetric metric);6768/* -----------------------------------------------------------------------69 * Low level stream encoding: a radix tree of listpacks.70 * ----------------------------------------------------------------------- */7172/* Create a new stream data structure. */73stream *streamNew(void) {74    size_t usable;75    stream *s = zmalloc_usable(sizeof(*s), &usable);76    s->alloc_size = usable;77    s->rax = raxNewEx(0, &s->alloc_size, sizeof(streamID));78    s->length = 0;79    s->first_id.ms = 0;80    s->first_id.seq = 0;81    s->last_id.ms = 0;82    s->last_id.seq = 0;83    s->max_deleted_entry_id.seq = 0;84    s->max_deleted_entry_id.ms = 0;85    s->entries_added = 0;86    s->cgroups = NULL; /* Created on demand to save memory when not used. */87    s->cgroups_ref = NULL;88    s->min_cgroup_last_id.ms = UINT64_MAX;89    s->min_cgroup_last_id.seq = UINT64_MAX;90    s->min_cgroup_last_id_valid = 0;91    s->idmp_duration = server.stream_idmp_duration; /* Default from server config */92    s->idmp_max_entries = server.stream_idmp_maxsize; /* Default from server config */ 93    s->idmp_producers = NULL; /* Created on demand to save memory when not used. */94    s->iids_added = 0;95    s->iids_duplicates = 0;96    s->distrib_epoch = STREAM_DISTRIB_NEVER_COUNTED;97    s->distrib_counted = 0;98    return s;99}100101static void streamLpFreeGeneric(void *lp, void *strm) {102    stream *s = strm;103    s->alloc_size -= lpBytes(lp);104    lpFree(lp);105}106107void streamFreeIdmpProducerGeneric(void *producer, void *strm) {108    stream *s = strm;109    idmpProducerFree((idmpProducer *)producer, &s->alloc_size);110}111112/* Free a stream, including the listpacks stored inside the radix tree. */113void freeStream(stream *s) {114    raxFreeWithCbAndContext(s->rax, streamLpFreeGeneric, s);115    if (s->cgroups)116        raxFreeWithCbAndContext(s->cgroups, streamFreeCGGeneric, s);117    if (s->cgroups_ref)118        raxFreeWithCallback(s->cgroups_ref, listReleaseGeneric);119    /* Free IDMP producers rax tree */120    if (s->idmp_producers)121        raxFreeWithCbAndContext(s->idmp_producers, streamFreeIdmpProducerGeneric, s);122    debugServerAssert(s->alloc_size == zmalloc_usable_size(s));123    zfree(s);124}125126/* Return the length of a stream. */127unsigned long streamLength(const robj *subject) {128    stream *s = subject->ptr;129    return s->length;130}131132/* ----------------------------------------------------------------------------133 * INFO Streams statistics134 *135 * Per-db base-2 histograms of consumer groups per stream, pending entries per136 * group and consumers per group, reported by the INFO Streams section while137 * the stream-stats directive is on. They are maintained incrementally: the138 * commands, module APIs and key lifecycle paths that change one of these139 * properties move the object's sample to its new bin, so nothing is scanned.140 * Enabling at runtime does not rescan either: existing streams and groups are141 * entered when a group write command next touches them, so the rows142 * under-count until then and never over-count; streamUpdateStat() explains143 * how that is kept safe. Only properties stored on the stream or group itself144 * are tracked, so a write updates one object.145 * -------------------------------------------------------------------------- */146147/* The INFO field name of each metric, indexed by streamDistribMetric, so a148 * metric is spelled out in exactly one place and the INFO section and the149 * debug assertion cannot drift apart. */150const char *const streamDistribMetricNames[STREAM_DISTRIB_MAX] = {151    [STREAM_DISTRIB_STREAMS_CGROUPS] = "stream_distrib_streams_cgroups",152    [STREAM_DISTRIB_CGROUPS_PEL] = "stream_distrib_cgroups_pel",153    [STREAM_DISTRIB_CGROUPS_CONSUMERS] = "stream_distrib_cgroups_consumers",154};155static_assert(STREAM_DISTRIB_MAX <= 8, "distrib_counted holds one bit per metric");156157/* A consumer group's INFO Streams stamp, kept in the metadata of its PEL rax158 * so that streamCG stays one cache line. */159typedef struct {160    uint32_t epoch;   /* Generation the bits refer to. */161    uint8_t counted;  /* Bit per metric with a sample in its row. */162} streamDistribStamp;163164/* Zero the stream histograms in 'meta' and start a new generation: every165 * stream and group stamped with the old one is uncounted again, and a slot-trim166 * delta captured against it is discarded on completion. Generations come from167 * one server-wide counter, so a stamp carried into another db by MOVE while168 * stats were off cannot match there; the counter skips 0, the value of fresh169 * metadata, and the never-counted sentinel. */170void streamStatsResetMeta(kvstoreMetadata *meta) {171    if (++server.stream_stats_generation == STREAM_DISTRIB_NEVER_COUNTED)172        server.stream_stats_generation = 1;173    meta->stream_stats_epoch = server.stream_stats_generation;174    memset(meta->stream_hist, 0, sizeof(meta->stream_hist));175}176177/* Map a stream property value to its histogram bin like the keysizes178 * histogram: 0 -> bin 0, otherwise floor(log2(value)) + 1. A negative value179 * means "no sample" and maps to -1, which callers skip. */180static int streamDistribBin(int64_t value) {181    if (value < 0) return -1;182    int bin = (value == 0) ? 0 : log2ceil((size_t) value) + 1;183    debugServerAssert(bin < MAX_KEYSIZES_BINS);184    return bin;185}186187/* Move one object's sample for 'metric' from the bin of 'old_val' to the bin188 * of 'new_val'; -1 on either side means no sample. 'epoch' and 'counted' are189 * the object's stamp: the generation its bits refer to and one bit per metric190 * saying whether that row holds its sample. Streams and groups carry the same191 * two fields, so one function serves both. When the row holds the sample this192 * is an exact move; otherwise old_val is not ours to remove and only the new193 * value is entered, which is what makes lazy registration safe. */194static void streamUpdateStat(redisDb *db, uint32_t *epoch, uint8_t *counted,195                             streamDistribMetric metric, int64_t old_val, int64_t new_val)196{197    if (!server.stream_stats) {198        /* The rows fall behind: enabling must start over. Check before storing199         * so the steady state is a read of an already-set flag, not a store to200         * the (hot, shared) cache line this flag lives on. */201        if (!server.stream_stats_needs_reset)202            server.stream_stats_needs_reset = 1;203        return;204    }205    kvstoreMetadata *meta = kvstoreGetMetadata(db->keys);206    if (*epoch != meta->stream_stats_epoch) {  /* new generation: no row holds this object */207        *epoch = meta->stream_stats_epoch;208        *counted = 0;209    }210211    uint8_t bit = 1u << metric;212    int old_bin = streamDistribBin(old_val);213    int new_bin = streamDistribBin(new_val);214    if ((*counted & bit) && old_bin == new_bin) return; /* sample didn't move */215216    int64_t *hist = meta->stream_hist[metric];217    if (*counted & bit) {                      /* this row holds the sample: exact move */218        debugServerAssert(old_bin >= 0 && hist[old_bin] > 0);219        if (old_bin >= 0 && hist[old_bin] > 0) hist[old_bin]--;220        if (new_bin < 0) {                     /* the sample leaves this row */221            *counted &= ~bit;222            return;223        }224        hist[new_bin]++;225        return;226    }227    /* Not in this row: old_val is not ours. Enter the current value, if any. */228    if (new_bin < 0) return;229    hist[new_bin]++;230    *counted |= bit;231}232233/* The four entry points of the hooks. Each names the object and the metric234 * and takes the sample from that metric's sampler, so a hook cannot pair a235 * metric with the wrong value. Always inlined: otherwise GCC outlines them236 * and the stream_stats gate moves out of the caller into a call. */237static inline __attribute__((always_inline))238void streamUpdateCGStat(redisDb *db, streamCG *cg, streamDistribMetric metric, int64_t old) {239    streamDistribStamp *st = (streamDistribStamp *) cg->pel->metadata;240    streamUpdateStat(db, &st->epoch, &st->counted, metric, old, streamCGroupSample(cg, metric));241}242243static inline __attribute__((always_inline))244void streamRemoveCGStat(redisDb *db, streamCG *cg, streamDistribMetric metric) {245    streamDistribStamp *st = (streamDistribStamp *) cg->pel->metadata;246    streamUpdateStat(db, &st->epoch, &st->counted, metric, streamCGroupSample(cg, metric), -1);247}248249static inline __attribute__((always_inline))250void streamUpdateStreamStat(redisDb *db, stream *s, streamDistribMetric metric, int64_t old) {251    streamUpdateStat(db, &s->distrib_epoch, &s->distrib_counted, metric, old, streamStreamSample(s, metric));252}253254static inline __attribute__((always_inline))255void streamRemoveStreamStat(redisDb *db, stream *s, streamDistribMetric metric) {256    streamUpdateStat(db, &s->distrib_epoch, &s->distrib_counted, metric, streamStreamSample(s, metric), -1);257}258259/* After a lazy enable, re-enter the two samples whose own hooks fire rarely:260 * the stream's group count and the group's consumer count. An equal-value261 * update is a no-op when the row holds the sample and enters it otherwise.262 * Called by every group command, after its own updates for that group. */263static void streamStatsReenterStructural(redisDb *db, stream *s, streamCG *cg) {264    if (!server.stream_stats) return;265    streamUpdateStreamStat(db, s, STREAM_DISTRIB_STREAMS_CGROUPS,266                           streamStreamSample(s, STREAM_DISTRIB_STREAMS_CGROUPS));267    streamUpdateCGStat(db, cg, STREAM_DISTRIB_CGROUPS_CONSUMERS,268                       streamCGroupSample(cg, STREAM_DISTRIB_CGROUPS_CONSUMERS));269}270271/* Add (adding=1) or remove (adding=0) every histogram sample stream 's'272 * holds: its own for each per-stream metric and one per group for each273 * per-group metric. Used where a whole stream appears or vanishes at once:274 * the key lifecycle hooks and streamStatsRebuild(). */275static void streamUpdateStreamSamples(redisDb *db, stream *s, int adding) {276    if (!server.stream_stats) {277        /* A stream entering or leaving always moves samples, so the rows fall278         * behind and enabling must start over. Check before storing so the279         * steady state is a read of an already-set flag, not a store. */280        if (!server.stream_stats_needs_reset)281            server.stream_stats_needs_reset = 1;282        return;283    }284    if (adding)285        streamUpdateStreamStat(db, s, STREAM_DISTRIB_STREAMS_CGROUPS, -1);286    else287        streamRemoveStreamStat(db, s, STREAM_DISTRIB_STREAMS_CGROUPS);288    if (!s->cgroups || !raxSize(s->cgroups)) return;289    raxIterator ri;290    raxStart(&ri, s->cgroups);291    raxSeek(&ri, "^", NULL, 0);292    while (raxNext(&ri)) {293        streamCG *cg = ri.data;294        if (adding) {295            streamUpdateCGStat(db, cg, STREAM_DISTRIB_CGROUPS_PEL, -1);296            streamUpdateCGStat(db, cg, STREAM_DISTRIB_CGROUPS_CONSUMERS, -1);297        } else {298            streamRemoveCGStat(db, cg, STREAM_DISTRIB_CGROUPS_PEL);299            streamRemoveCGStat(db, cg, STREAM_DISTRIB_CGROUPS_CONSUMERS);300        }301    }302    raxStop(&ri);303}304305/* Set 'id' to be its successor stream ID.306 * If 'id' is the maximal possible id, it is wrapped around to 0-0 and a307 * C_ERR is returned. */308int streamIncrID(streamID *id) {309    int ret = C_OK;310    if (id->seq == UINT64_MAX) {311        if (id->ms == UINT64_MAX) {312            /* Special case where 'id' is the last possible streamID... */313            id->ms = id->seq = 0;314            ret = C_ERR;315        } else {316            id->ms++;317            id->seq = 0;318        }319    } else {320        id->seq++;321    }322    return ret;323}324325/* Set 'id' to be its predecessor stream ID.326 * If 'id' is the minimal possible id, it remains 0-0 and a C_ERR is327 * returned. */328int streamDecrID(streamID *id) {329    int ret = C_OK;330    if (id->seq == 0) {331        if (id->ms == 0) {332            /* Special case where 'id' is the first possible streamID... */333            id->ms = id->seq = UINT64_MAX;334            ret = C_ERR;335        } else {336            id->ms--;337            id->seq = UINT64_MAX;338        }339    } else {340        id->seq--;341    }342    return ret;343}344345/* Generate the next stream item ID given the previous one. If the current346 * milliseconds Unix time is greater than the previous one, just use this347 * as time part and start with sequence part of zero. Otherwise we use the348 * previous time (and never go backward) and increment the sequence. */349void streamNextID(streamID *last_id, streamID *new_id) {350    uint64_t ms = commandTimeSnapshot();351    if (ms > last_id->ms) {352        new_id->ms = ms;353        new_id->seq = 0;354    } else {355        *new_id = *last_id;356        streamIncrID(new_id);357    }358}359360/* This is a helper function for the COPY command.361 * Duplicate a Stream object, with the guarantee that the returned object362 * has the same encoding as the original one.363 *364 * The resulting object always has refcount set to 1 */365robj *streamDup(robj *o) {366    robj *sobj;367368    serverAssert(o->type == OBJ_STREAM);369370    switch (o->encoding) {371        case OBJ_ENCODING_STREAM:372            sobj = createStreamObject();373            break;374        default:375            serverPanic("Wrong encoding.");376            break;377    }378379    stream *s;380    stream *new_s;381    s = o->ptr;382    new_s = sobj->ptr;383384    raxIterator ri;385    raxStart(&ri, s->rax);386    raxSeek(&ri, "^", NULL, 0);387    size_t lp_bytes = 0;      /* Total bytes in the listpack. */388    unsigned char *lp = NULL; /* listpack pointer. */389    /* Get a reference to the listpack node. */390    while (raxNext(&ri)) {391        serverAssert(ri.key_len == sizeof(streamID));392        lp = ri.data;393        lp_bytes = lpBytes(lp);394        unsigned char *new_lp = zmalloc(lp_bytes);395        new_s->alloc_size += lp_bytes;396        memcpy(new_lp, lp, lp_bytes);397        raxInsert(new_s->rax, ri.key, ri.key_len,398                  new_lp, NULL);399    }400    new_s->length = s->length;401    new_s->first_id = s->first_id;402    new_s->last_id = s->last_id;403    new_s->max_deleted_entry_id = s->max_deleted_entry_id;404    new_s->entries_added = s->entries_added;405    raxStop(&ri);406407    /* IDMP state */408    new_s->idmp_duration = s->idmp_duration;409    new_s->idmp_max_entries = s->idmp_max_entries;410    new_s->iids_added = s->iids_added;411    new_s->iids_duplicates = s->iids_duplicates;412413    if (s->idmp_producers != NULL) {414        new_s->idmp_producers = raxNewEx(0, &new_s->alloc_size, 0);415416        raxIterator ri_prod;417        raxStart(&ri_prod, s->idmp_producers);418        raxSeek(&ri_prod, "^", NULL, 0);419        while (raxNext(&ri_prod)) {420            idmpProducer *src_prod = ri_prod.data;421            idmpProducer *new_prod = idmpProducerCreate(&new_s->alloc_size);422423            /* Walk the linked list and duplicate each entry. */424            idmpEntry *src_entry = src_prod->idmp_head;425            while (src_entry != NULL) {426                idmpEntry *new_entry = idmpEntryCreate(src_entry->iid,427                                                       src_entry->iid_len,428                                                       &new_s->alloc_size);429                new_entry->id = src_entry->id;430431                /* Append to tail of the new producer's linked list. */432                if (new_prod->idmp_tail != NULL) {433                    new_prod->idmp_tail->next = new_entry;434                } else {435                    new_prod->idmp_head = new_entry;436                }437                new_prod->idmp_tail = new_entry;438439                dictAdd(new_prod->idmp_dict, new_entry, NULL);440                src_entry = src_entry->next;441            }442443            raxInsert(new_s->idmp_producers, ri_prod.key, ri_prod.key_len,444                      new_prod, NULL);445        }446        raxStop(&ri_prod);447    }448449    if (s->cgroups == NULL) return sobj;450451    /* Consumer Groups */452    raxIterator ri_cgroups;453    raxStart(&ri_cgroups, s->cgroups);454    raxSeek(&ri_cgroups, "^", NULL, 0);455    while (raxNext(&ri_cgroups)) {456        streamCG *cg = ri_cgroups.data;457        streamCG *new_cg = streamCreateCG(new_s, (char *)ri_cgroups.key,458                                          ri_cgroups.key_len, &cg->last_id,459                                          cg->entries_read);460461        serverAssert(new_cg != NULL);462463        /* Consumer Group PEL -- walk the time-ordered list so we can464         * append directly and preserve NACK zone structure. */465        for (streamNACK *nack = cg->pel_time_head; nack; nack = nack->pel_next) {466            unsigned char buf[sizeof(streamID)];467            streamEncodeID(buf, &nack->id);468            streamNACK *new_nack = streamCreateNACK(new_s, NULL, &nack->id);469            new_nack->delivery_time = nack->delivery_time;470            new_nack->delivery_count = nack->delivery_count;471            new_nack->cgroup_ref_node = streamLinkCGroupToEntry(new_s, new_cg, buf);472            raxInsert(new_cg->pel, buf, sizeof(streamID), new_nack, NULL);473            pelListInsertAtTail(new_cg, new_nack);474            if (nack == cg->pel_nack_tail) new_cg->pel_nack_tail = new_nack;475        }476477        /* Consumers */478        raxIterator ri_consumers;479        raxStart(&ri_consumers, cg->consumers);480        raxSeek(&ri_consumers, "^", NULL, 0);481        while (raxNext(&ri_consumers)) {482            streamConsumer *consumer = ri_consumers.data;483            streamConsumer *new_consumer;484            size_t usable;485            new_consumer = zmalloc_usable(sizeof(*new_consumer), &usable);486            new_s->alloc_size += usable;487            new_consumer->name = sdsdup(consumer->name);488            new_s->alloc_size += sdsAllocSize(new_consumer->name);489            new_consumer->pel = raxNewEx(0, &new_s->alloc_size, sizeof(streamID));490            raxInsert(new_cg->consumers,(unsigned char *)new_consumer->name,491                        sdslen(new_consumer->name), new_consumer, NULL);492            new_consumer->seen_time = consumer->seen_time;493            new_consumer->active_time = consumer->active_time;494495            /* Consumer PEL */496            raxIterator ri_cpel;497            raxStart(&ri_cpel, consumer->pel);498            raxSeek(&ri_cpel, "^", NULL, 0);499            while (raxNext(&ri_cpel)) {500                void *result;501                int found = raxFind(new_cg->pel,ri_cpel.key,sizeof(streamID),&result);502503                serverAssert(found);504505                streamNACK *new_nack = result;506                new_nack->consumer = new_consumer;507                raxInsert(new_consumer->pel,ri_cpel.key,sizeof(streamID),new_nack,NULL);508            }509            raxStop(&ri_cpel);510        }511        raxStop(&ri_consumers);512    }513    raxStop(&ri_cgroups);514    return sobj;515}516517/* This is a wrapper function for lpGet() to directly get an integer value518 * from the listpack (that may store numbers as a string), converting519 * the string if needed.520 * The 'valid' argument is an optional output parameter to get an indication521 * if the record was valid, when this parameter is NULL, the function will522 * fail with an assertion. */523static inline int64_t lpGetIntegerIfValid(unsigned char *ele, int *valid) {524    int64_t v;525    unsigned char *e = lpGet(ele,&v,NULL);526    if (e == NULL) {527        if (valid)528            *valid = 1;529        return v;530    }531    /* The following code path should never be used for how listpacks work:532     * they should always be able to store an int64_t value in integer533     * encoded form. However the implementation may change. */534    long long ll = 0;535    int ret = string2ll((char*)e,v,&ll);536    if (valid)537        *valid = ret;538    else539        serverAssert(ret != 0);540    v = ll;541    return v;542}543544#define lpGetInteger(ele) lpGetIntegerIfValid(ele, NULL)545546/* Get an edge streamID of a given listpack.547 * 'master_id' is an input param, used to build the 'edge_id' output param */548int lpGetEdgeStreamID(unsigned char *lp, int first, streamID *master_id, streamID *edge_id)549{550   if (lp == NULL)551       return 0;552553   unsigned char *lp_ele;554555   /* We need to seek either the first or the last entry depending556    * on the direction of the iteration. */557   if (first) {558       /* Get the master fields count. */559       lp_ele = lpFirst(lp);        /* Seek items count */560       lp_ele = lpNext(lp, lp_ele); /* Seek deleted count. */561       lp_ele = lpNext(lp, lp_ele); /* Seek num fields. */562       int64_t master_fields_count = lpGetInteger(lp_ele);563       lp_ele = lpNext(lp, lp_ele); /* Seek first field. */564565       /* If we are iterating in normal order, skip the master fields566        * to seek the first actual entry. */567       for (int64_t i = 0; i < master_fields_count; i++)568           lp_ele = lpNext(lp, lp_ele);569570       /* If we are going forward, skip the previous entry's571        * lp-count field (or in case of the master entry, the zero572        * term field) */573       lp_ele = lpNext(lp, lp_ele);574       if (lp_ele == NULL)575           return 0;576   } else {577       /* If we are iterating in reverse direction, just seek the578        * last part of the last entry in the listpack (that is, the579        * fields count). */580       lp_ele = lpLast(lp);581582       /* If we are going backward, read the number of elements this583        * entry is composed of, and jump backward N times to seek584        * its start. */585       int64_t lp_count = lpGetInteger(lp_ele);586       if (lp_count == 0) /* We reached the master entry. */587           return 0;588589       while (lp_count--)590           lp_ele = lpPrev(lp, lp_ele);591   }592593   lp_ele = lpNext(lp, lp_ele); /* Seek ID (lp_ele currently points to 'flags'). */594595   /* Get the ID: it is encoded as difference between the master596    * ID and this entry ID. */597   streamID id = *master_id;598   id.ms += lpGetInteger(lp_ele);599   lp_ele = lpNext(lp, lp_ele);600   id.seq += lpGetInteger(lp_ele);601   *edge_id = id;602   return 1;603}604605/* Debugging function to log the full content of a listpack. Useful606 * for development and debugging. */607void streamLogListpackContent(unsigned char *lp) {608    unsigned char *p = lpFirst(lp);609    while(p) {610        unsigned char buf[LP_INTBUF_SIZE];611        int64_t v;612        unsigned char *ele = lpGet(p,&v,buf);613        serverLog(LL_WARNING,"- [%d] '%.*s'", (int)v, (int)v, ele);614        p = lpNext(lp,p);615    }616}617618/* Convert the specified stream entry ID as a 128 bit big endian number, so619 * that the IDs can be sorted lexicographically. */620void streamEncodeID(void *buf, streamID *id) {621    uint64_t e[2];622    e[0] = htonu64(id->ms);623    e[1] = htonu64(id->seq);624    memcpy(buf,e,sizeof(e));625}626627/* This is the reverse of streamEncodeID(): the decoded ID will be stored628 * in the 'id' structure passed by reference. The buffer 'buf' must point629 * to a 128 bit big-endian encoded ID. */630void streamDecodeID(void *buf, streamID *id) {631    uint64_t e[2];632    memcpy(e,buf,sizeof(e));633    id->ms = ntohu64(e[0]);634    id->seq = ntohu64(e[1]);635}636637/* Compare two stream IDs. Return -1 if a < b, 0 if a == b, 1 if a > b. */638int streamCompareID(streamID *a, streamID *b) {639    if (a->ms > b->ms) return 1;640    else if (a->ms < b->ms) return -1;641    /* The ms part is the same. Check the sequence part. */642    else if (a->seq > b->seq) return 1;643    else if (a->seq < b->seq) return -1;644    /* Everything is the same: IDs are equal. */645    return 0;646}647648/* Retrieves the ID of the stream edge entry. An edge is either the first or649 * the last ID in the stream, and may be a tombstone. To filter out tombstones,650 * set the'skip_tombstones' argument to 1. */651void streamGetEdgeID(stream *s, int first, int skip_tombstones, streamID *edge_id)652{653    streamIterator si;654    int64_t numfields;655    streamIteratorStart(&si,s,NULL,NULL,!first);656    si.skip_tombstones = skip_tombstones;657    int found = streamIteratorGetID(&si,edge_id,&numfields);658    if (!found) {659        streamID min_id = {0, 0}, max_id = {UINT64_MAX, UINT64_MAX};660        *edge_id = first ? max_id : min_id;661    }662    streamIteratorStop(&si);663}664665/* Adds a new item into the stream 's' having the specified number of666 * field-value pairs as specified in 'numfields' and stored into 'argv'.667 * Returns the new entry ID populating the 'added_id' structure.668 *669 * If 'use_id' is not NULL, the ID is not auto-generated by the function,670 * but instead the passed ID is used to add the new entry. In this case671 * adding the entry may fail as specified later in this comment.672 * 673 * When 'use_id' is used alongside with a zero 'seq-given', the sequence674 * part of the passed ID is ignored and the function will attempt to use an675 * auto-generated sequence.676 *677 * The function returns C_OK if the item was added, this is always true678 * if the ID was generated by the function. However the function may return679 * C_ERR in several cases:680 * 1. If an ID was given via 'use_id', but adding it failed since the681 *    current top ID is greater or equal. errno will be set to EDOM.682 * 2. If a size of a single element or the sum of the elements is too big to683 *    be stored into the stream. errno will be set to ERANGE. */684int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id, int seq_given) {685686    /* Generate the new entry ID. */687    streamID id;688    if (use_id) {689        if (seq_given) {690            id = *use_id;691        } else {692            /* The automatically generated sequence can be either zero (new693             * timestamps) or the incremented sequence of the last ID. In the694             * latter case, we need to prevent an overflow/advancing forward695             * in time. */696            if (s->last_id.ms == use_id->ms) {697                if (s->last_id.seq == UINT64_MAX) {698                    errno = EDOM;699                    return C_ERR;700                }701                id = s->last_id;702                id.seq++;703            } else {704                id = *use_id;705            }706        }707    } else {708        streamNextID(&s->last_id,&id);709    }710711    /* Check that the new ID is greater than the last entry ID712     * or return an error. Automatically generated IDs might713     * overflow (and wrap-around) when incrementing the sequence714       part. */715    if (streamCompareID(&id,&s->last_id) <= 0) {716        errno = EDOM;717        return C_ERR;718    }719720    /* Avoid overflow when trying to add an element to the stream (listpack721     * can only host up to 32bit length strings, and also a total listpack size722     * can't be bigger than 32bit length. */723    size_t totelelen = 0;724    for (int64_t i = 0; i < numfields*2; i++) {725        sds ele = argv[i]->ptr;726        totelelen += sdslen(ele);727    }728    if (totelelen > STREAM_LISTPACK_MAX_SIZE) {729        errno = ERANGE;730        return C_ERR;731    }732733    /* Add the new entry. */734    raxIterator ri;735    raxStart(&ri,s->rax);736    raxSeek(&ri,"$",NULL,0);737738    size_t lp_bytes = 0;        /* Total bytes in the tail listpack. */739    unsigned char *lp = NULL;   /* Tail listpack pointer. */740741    if (!raxEOF(&ri)) {742        /* Get a reference to the tail node listpack. */743        lp = ri.data;744        lp_bytes = lpBytes(lp);745    }746    raxStop(&ri);747748    /* We have to add the key into the radix tree in lexicographic order,749     * to do so we consider the ID as a single 128 bit number written in750     * big endian, so that the most significant bytes are the first ones. */751    uint64_t rax_key[2];    /* Key in the radix tree containing the listpack.*/752    streamID master_id;     /* ID of the master entry in the listpack. */753754    /* Create a new listpack and radix tree node if needed. Note that when755     * a new listpack is created, we populate it with a "master entry". This756     * is just a set of fields that is taken as references in order to compress757     * the stream entries that we'll add inside the listpack.758     *759     * Note that while we use the first added entry fields to create760     * the master entry, the first added entry is NOT represented in the master761     * entry, which is a stand alone object. But of course, the first entry762     * will compress well because it's used as reference.763     *764     * The master entry is composed like in the following example:765     *766     * +-------+---------+------------+---------+--/--+---------+---------+-+767     * | count | deleted | num-fields | field_1 | field_2 | ... | field_N |0|768     * +-------+---------+------------+---------+--/--+---------+---------+-+769     *770     * count and deleted just represent respectively the total number of771     * entries inside the listpack that are valid, and marked as deleted772     * (deleted flag in the entry flags set). So the total number of items773     * actually inside the listpack (both deleted and not) is count+deleted.774     *775     * The real entries will be encoded with an ID that is just the776     * millisecond and sequence difference compared to the key stored at777     * the radix tree node containing the listpack (delta encoding), and778     * if the fields of the entry are the same as the master entry fields, the779     * entry flags will specify this fact and the entry fields and number780     * of fields will be omitted (see later in the code of this function).781     *782     * The "0" entry at the end is the same as the 'lp-count' entry in the783     * regular stream entries (see below), and marks the fact that there are784     * no more entries, when we scan the stream from right to left. */785786    /* First of all, check if we can append to the current macro node or787     * if we need to switch to the next one. 'lp' will be set to NULL if788     * the current node is full. */789    if (lp != NULL) {790        int new_node = 0;791        size_t node_max_bytes = server.stream_node_max_bytes;792        if (node_max_bytes == 0 || node_max_bytes > STREAM_LISTPACK_MAX_SIZE)793            node_max_bytes = STREAM_LISTPACK_MAX_SIZE;794        if (lp_bytes + totelelen >= node_max_bytes) {795            new_node = 1;796        } else if (server.stream_node_max_entries) {797            unsigned char *lp_ele = lpFirst(lp);798            /* Count both live entries and deleted ones. */799            int64_t count = lpGetInteger(lp_ele) + lpGetInteger(lpNext(lp,lp_ele));800            if (count >= server.stream_node_max_entries) new_node = 1;801        }802803        if (new_node) {804            /* Shrink extra pre-allocated memory */805            lp = lpShrinkToFit(lp);806            s->alloc_size -= lp_bytes;807            s->alloc_size += lpBytes(lp);808            if (ri.data != lp)809                raxIteratorSetData(&ri, lp);810            lp = NULL;811        }812    }813814    int flags = STREAM_ITEM_FLAG_NONE;815    if (lp == NULL) {816        master_id = id;817        streamEncodeID(rax_key,&id);818        /* Create the listpack having the master entry ID and fields.819         * Pre-allocate some bytes when creating listpack to avoid realloc on820         * every XADD. Since listpack.c uses malloc_size, it'll grow in steps,821         * and won't realloc on every XADD.822         * When listpack reaches max number of entries, we'll shrink the823         * allocation to fit the data. */824        size_t prealloc = STREAM_LISTPACK_MAX_PRE_ALLOCATE;825        if (server.stream_node_max_bytes > 0 && server.stream_node_max_bytes < prealloc) {826            prealloc = server.stream_node_max_bytes;827        }828        lp = lpNew(prealloc);829        lp = lpAppendInteger(lp,1); /* One item, the one we are adding. */830        lp = lpAppendInteger(lp,0); /* Zero deleted so far. */831        lp = lpAppendInteger(lp,numfields);832        for (int64_t i = 0; i < numfields; i++) {833            sds field = argv[i*2]->ptr;834            lp = lpAppend(lp,(unsigned char*)field,sdslen(field));835        }836        lp = lpAppendInteger(lp,0); /* Master entry zero terminator. */837        s->alloc_size += lpBytes(lp);838        raxInsert(s->rax,(unsigned char*)&rax_key,sizeof(rax_key),lp,NULL);839        /* The first entry we insert, has obviously the same fields of the840         * master entry. */841        flags |= STREAM_ITEM_FLAG_SAMEFIELDS;842    } else {843        serverAssert(ri.key_len == sizeof(rax_key));844        memcpy(rax_key,ri.key,sizeof(rax_key));845846        /* Read the master ID from the radix tree key. */847        streamDecodeID(rax_key,&master_id);848        unsigned char *lp_ele = lpFirst(lp);849850        /* Update count and skip the deleted fields. */851        int64_t count = lpGetInteger(lp_ele);852        size_t oldsize = lpBytes(lp);853        lp = lpReplaceInteger(lp,&lp_ele,count+1);854        s->alloc_size -= oldsize;855        s->alloc_size += lpBytes(lp);856        lp_ele = lpNext(lp,lp_ele); /* seek deleted. */857        lp_ele = lpNext(lp,lp_ele); /* seek master entry num fields. */858859        /* Check if the entry we are adding, have the same fields860         * as the master entry. */861        int64_t master_fields_count = lpGetInteger(lp_ele);862        lp_ele = lpNext(lp,lp_ele);863        if (numfields == master_fields_count) {864            int64_t i;865            for (i = 0; i < master_fields_count; i++) {866                sds field = argv[i*2]->ptr;867                int64_t e_len;868                unsigned char buf[LP_INTBUF_SIZE];869                unsigned char *e = lpGet(lp_ele,&e_len,buf);870                /* Stop if there is a mismatch. */871                if (sdslen(field) != (size_t)e_len ||872                    memcmp(e,field,e_len) != 0) break;873                lp_ele = lpNext(lp,lp_ele);874            }875            /* All fields are the same! We can compress the field names876             * setting a single bit in the flags. */877            if (i == master_fields_count) flags |= STREAM_ITEM_FLAG_SAMEFIELDS;878        }879    }880881    /* Populate the listpack with the new entry. We use the following882     * encoding:883     *884     * +-----+--------+----------+-------+-------+-/-+-------+-------+--------+885     * |flags|entry-id|num-fields|field-1|value-1|...|field-N|value-N|lp-count|886     * +-----+--------+----------+-------+-------+-/-+-------+-------+--------+887     *888     * However if the SAMEFIELD flag is set, we have just to populate889     * the entry with the values, so it becomes:890     *891     * +-----+--------+-------+-/-+-------+--------+892     * |flags|entry-id|value-1|...|value-N|lp-count|893     * +-----+--------+-------+-/-+-------+--------+894     *895     * The entry-id field is actually two separated fields: the ms896     * and seq difference compared to the master entry.897     *898     * The lp-count field is a number that states the number of listpack pieces899     * that compose the entry, so that it's possible to travel the entry900     * in reverse order: we can just start from the end of the listpack, read901     * the entry, and jump back N times to seek the "flags" field to read902     * the stream full entry. */903    size_t oldsize = lpBytes(lp);904    lp = lpAppendInteger(lp,flags);905    lp = lpAppendInteger(lp,id.ms - master_id.ms);906    lp = lpAppendInteger(lp,id.seq - master_id.seq);907    if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS))908        lp = lpAppendInteger(lp,numfields);909    for (int64_t i = 0; i < numfields; i++) {910        sds field = argv[i*2]->ptr, value = argv[i*2+1]->ptr;911        if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS))912            lp = lpAppend(lp,(unsigned char*)field,sdslen(field));913        lp = lpAppend(lp,(unsigned char*)value,sdslen(value));914    }915    /* Compute and store the lp-count field. */916    int64_t lp_count = numfields;917    lp_count += 3; /* Add the 3 fixed fields flags + ms-diff + seq-diff. */918    if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS)) {919        /* If the item is not compressed, it also has the fields other than920         * the values, and an additional num-fields field. */921        lp_count += numfields+1;922    }923    lp = lpAppendInteger(lp,lp_count);924    s->alloc_size -= oldsize;925    s->alloc_size += lpBytes(lp);926927    /* Insert back into the tree in order to update the listpack pointer. */928    if (ri.data != lp)929        raxInsert(s->rax,(unsigned char*)&rax_key,sizeof(rax_key),lp,NULL);930    s->length++;931    if (s->entries_added < (uint64_t)LLONG_MAX)932        s->entries_added++;933    s->last_id = id;934    if (s->length == 1) s->first_id = id;935    if (added_id) *added_id = id;936    return C_OK;937}938939typedef struct {940    /* XADD options */941    streamID id; /* User-provided ID, for XADD only. */942    int id_given; /* Was an ID different than "*" specified? for XADD only. */943    int seq_given; /* Was an ID different than "ms-*" specified? for XADD only. */944    int no_mkstream; /* if set to 1 do not create new stream */945    robj *idmp_pid; /* IDMP producer id parameter, for XADD only. */946    robj *idmp_iid; /* IDMP idempotent id parameter, for XADD only. */947    int idmp_auto; /* If set to 1, auto-generate IID from field-value pairs, for XADD only. */948949    /* XADD + XTRIM common options */950    int trim_strategy; /* TRIM_STRATEGY_* */951    int trim_strategy_arg_idx; /* Index of the count in MAXLEN/MINID, for rewriting. */952    int delete_strategy; /* DELETE_STRATEGY_* */953    int approx_trim; /* If 1 only delete whole radix tree nodes, so954                      * the trim argument is not applied verbatim.955                      * Note: This flag is ignored when delete_strategy is non-KEEPREF.956                      * Individual entries may still be processed for consumer groups. */957    long long limit; /* Maximum amount of entries to trim. If 0, no limitation958                      * on the amount of trimming work is enforced. */959    /* TRIM_STRATEGY_MAXLEN options */960    long long maxlen; /* After trimming, leave stream at this length . */961    /* TRIM_STRATEGY_MINID options */962    streamID minid; /* Trim by ID (No stream entries with ID < 'minid' will remain) */963} streamAddTrimArgs;964965#define TRIM_STRATEGY_NONE 0966#define TRIM_STRATEGY_MAXLEN 1967#define TRIM_STRATEGY_MINID 2968969typedef struct {970    int startidx; /* Starting index of IDs in argv */971    long numids; /* Number of IDs to process */972    int delete_strategy; /* DELETE_STRATEGY_* */973} streamAckDelArgs;974975#define DELETE_STRATEGY_NONE 0976#define DELETE_STRATEGY_KEEPREF 1   /* Delete and keep references */977#define DELETE_STRATEGY_DELREF 2    /* Delete from pending entries list */978#define DELETE_STRATEGY_ACKED 3     /* Only delete messages that are acknowledged */979980/* XNACK mode flags – control how the delivery counter is adjusted when981 * a pending entry is released back to the group (NACKed). */982#define XNACK_SILENT 0  /* Decrement delivery_count by 1 (undo the delivery) */983#define XNACK_FAIL   1  /* Keep delivery_count unchanged */984#define XNACK_FATAL  2  /* Set delivery_count to LLONG_MAX (permanent failure) */985986/* Set the delivery attempts counter on a NACK entry.  When retrycount >= 0987 * the counter is set to that explicit value; otherwise it is adjusted988 * according to the XNACK mode (SILENT/FAIL/FATAL). */989static void nackSetDeliveryCount(streamNACK *nack, int mode, long long retrycount) {990    if (retrycount >= 0) {991        nack->delivery_count = (uint64_t)retrycount;992    } else {993        switch (mode) {994        case XNACK_SILENT:995            if (nack->delivery_count > 0)996                nack->delivery_count--;997            break;998        case XNACK_FAIL:999            break;1000        case XNACK_FATAL:1001            nack->delivery_count = LLONG_MAX;1002            break;1003        }1004    }1005}10061007/* Trim the stream 's' according to args->trim_strategy, and return the1008 * number of elements removed from the stream. The 'approx' option, if non-zero,1009 * specifies that the trimming must be performed in a approximated way in1010 * order to maximize performances. This means that the stream may contain1011 * entries with IDs < 'id' in case of MINID (or more elements than 'maxlen'1012 * in case of MAXLEN), and elements are only removed if we can remove1013 * a *whole* node of the radix tree. The elements are removed from the head1014 * of the stream (older elements).1015 *1016 * The function may return zero if:1017 *1018 * 1) The minimal entry ID of the stream is already < 'id' (MINID); or1019 * 2) The stream is already shorter or equal to the specified max length (MAXLEN); or1020 * 3) The 'approx' option is true and the head node did not have enough elements1021 *    to be deleted.1022 *1023 * args->limit is the maximum number of entries to delete. The purpose is to1024 * prevent this function from taking to long.1025 * If 'limit' is 0 then we do not limit the number of deleted entries.1026 * Much like the 'approx', if 'limit' is smaller than the number of entries1027 * that should be trimmed, there is a chance we will still have entries with1028 * IDs < 'id' (or number of elements >= maxlen in case of MAXLEN).1029 *1030 * If 'max_deleted_id' is not NULL, it is set to the largest ID removed by this1031 * trim. When a whole node is dropped this is the node's last ID, which may be1032 * an entry already deleted before (a tombstone): that doesn't matter, since no1033 * live entry lies between it and the last one actually deleted. The value is1034 * valid only when the function returns a non-zero count.1035 */1036int64_t streamTrim(redisDb *db, stream *s, streamAddTrimArgs *args, streamID *max_deleted_id) {1037    size_t maxlen = args->maxlen;1038    streamID *id = &args->minid;1039    int approx = args->approx_trim;1040    int64_t limit = args->limit;1041    int trim_strategy = args->trim_strategy;1042    int delete_strategy = args->delete_strategy;10431044    if (max_deleted_id) *max_deleted_id = (streamID){0,0};10451046    if (trim_strategy == TRIM_STRATEGY_NONE)1047        return 0;10481049    raxIterator ri;1050    raxStart(&ri,s->rax);1051    raxSeek(&ri,"^",NULL,0);10521053    int64_t deleted = 0;1054    while (raxNext(&ri)) {1055        if (trim_strategy == TRIM_STRATEGY_MAXLEN && s->length <= maxlen)1056            break;10571058        unsigned char *lp = ri.data, *p = lpFirst(lp);1059        int64_t entries = lpGetInteger(p);10601061        /* Check if we exceeded the amount of work we could do */1062        if (limit && (deleted + entries) > limit)1063            break;10641065        /* Check if we can remove the whole node */1066        int remove_node = 0; /* Final decision flag for node removal */1067        int node_eligible_for_remove = 0; /* Whether node meets the basic criteria for removal */1068        streamID master_id = {0};1069        streamID last_id = {0,0};1070        /* Read the master ID from the radix tree key. */1071        streamDecodeID(ri.key, &master_id);1072        if (trim_strategy == TRIM_STRATEGY_MAXLEN) {1073            node_eligible_for_remove = s->length - entries >= maxlen;1074        } else {1075            /* Read last ID. */1076            lpGetEdgeStreamID(lp, 0, &master_id, &last_id);10771078            /* We can remove the entire node id its last ID < 'id' */1079            node_eligible_for_remove = streamCompareID(&last_id, id) < 0;1080        }10811082        if (node_eligible_for_remove && delete_strategy == DELETE_STRATEGY_KEEPREF) {1083            /* With KEEPREF strategy, we can remove the whole node directly since we don't need1084             * to check or clean up consumer group references. */1085            remove_node = 1;1086        }10871088        if (remove_node) {1089            if (max_deleted_id) {1090                if (trim_strategy == TRIM_STRATEGY_MAXLEN)1091                    lpGetEdgeStreamID(lp, 0, &master_id, &last_id);1092                *max_deleted_id = last_id;1093            }1094            s->alloc_size -= lpBytes(lp);1095            lpFree(lp);1096            raxRemove(s->rax,ri.key,ri.key_len,NULL);1097            raxSeek(&ri,">=",ri.key,ri.key_len);1098            s->length -= entries;1099            deleted += entries;1100            continue;1101        }11021103        /* If we cannot remove a whole element, and approx is true,1104         * stop here. However, for non-KEEPREF strategies, if the node was1105         * eligible for removal but we couldn't remove it (because we need1106         * to check consumer group references), we should continue to process1107         * entries within this node. */1108        if (approx && delete_strategy == DELETE_STRATEGY_KEEPREF) break;11091110        /* Now we have to trim entries from within 'lp' */1111        size_t oldsize = lpBytes(lp);1112        int64_t deleted_from_lp = 0;11131114        p = lpNext(lp, p); /* Skip deleted field. */1115        p = lpNext(lp, p); /* Skip num-of-fields in the master entry. */11161117        /* Skip all the master fields. */1118        int64_t master_fields_count = lpGetInteger(p);1119        p = lpNext(lp,p); /* Skip the first field. */1120        for (int64_t j = 0; j < master_fields_count; j++)1121            p = lpNext(lp,p); /* Skip all master fields. */1122        p = lpNext(lp,p); /* Skip the zero master entry terminator. */11231124        /* 'p' is now pointing to the first entry inside the listpack.1125         * We have to run entry after entry, marking entries as deleted1126         * if they are already not deleted. */1127        while (p) {1128            /* We keep a copy of p (which point to flags part) in order to1129             * update it after (and if) we actually remove the entry */1130            unsigned char *pcopy = p;11311132            int64_t flags = lpGetInteger(p);1133            p = lpNext(lp, p); /* Skip flags. */1134            int64_t to_skip;11351136            int64_t ms_delta = lpGetInteger(p);1137            p = lpNext(lp, p); /* Skip ID ms delta */1138            int64_t seq_delta = lpGetInteger(p);1139            p = lpNext(lp, p); /* Skip ID seq delta */11401141            streamID currid = {0};1142            currid.ms = master_id.ms + ms_delta;1143            currid.seq = master_id.seq + seq_delta;11441145            int stop;1146            if (trim_strategy == TRIM_STRATEGY_MAXLEN) {1147                stop = s->length <= maxlen;1148            } else {1149                /* Following IDs will definitely be greater because the rax1150                 * tree is sorted, no point of continuing. */1151                stop = streamCompareID(&currid, id) >= 0;1152            }1153            if (stop)1154                break;11551156            if (flags & STREAM_ITEM_FLAG_SAMEFIELDS) {1157                to_skip = master_fields_count;1158            } else {1159                to_skip = lpGetInteger(p); /* Get num-fields. */1160                p = lpNext(lp,p); /* Skip num-fields. */1161                to_skip *= 2; /* Fields and values. */1162            }11631164            while(to_skip--) p = lpNext(lp,p); /* Skip the whole entry. */1165            p = lpNext(lp,p); /* Skip the final lp-count field. */11661167            /* Mark the entry as deleted if allowed. */1168            if (!(flags & STREAM_ITEM_FLAG_DELETED)) {1169                int can_delete = 1;1170                if (delete_strategy == DELETE_STRATEGY_ACKED) {1171                    /* Only delete entry that has been acknowledged by all consumer groups. */1172                    can_delete = (streamEntryIsReferenced(s, &currid) == 0);1173                } else if (delete_strategy == DELETE_STRATEGY_DELREF) {1174                    /* Remove all consumer group references for this entry */1175                    streamCleanupEntryCGroupRefs(db, s, &currid);1176                }11771178                if (can_delete) {1179                    /* Mark the entry as deleted. */1180                    intptr_t delta = p ? (p - lp) : 0; /* p may be NULL if this was the last entry */1181                    flags |= STREAM_ITEM_FLAG_DELETED;1182                    lp = lpReplaceInteger(lp, &pcopy, flags);1183                    if (max_deleted_id) *max_deleted_id = currid;1184                    deleted_from_lp++;1185                    s->length--;1186                    if (p) p = lp + delta;1187                }1188            }1189        }1190        deleted += deleted_from_lp;1191        /* Drop the node if no live entries remain (RDB rejects live == 0). */1192        if (deleted_from_lp == entries) {1193            s->alloc_size -= oldsize;1194            lpFree(lp);1195            raxRemove(s->rax,ri.key,ri.key_len,NULL);1196            raxSeek(&ri,">=",ri.key,ri.key_len);1197            continue; /* Node gone; keep scanning later nodes. */1198        }11991200        /* Now we update the entries/deleted counters. */1201        p = lpFirst(lp);1202        lp = lpReplaceInteger(lp,&p,entries-deleted_from_lp);1203        p = lpNext(lp,p); /* Skip deleted field. */1204        int64_t marked_deleted = lpGetInteger(p);1205        lp = lpReplaceInteger(lp,&p,marked_deleted+deleted_from_lp);1206        p = lpNext(lp,p); /* Skip num-of-fields in the master entry. */1207        s->alloc_size -= oldsize;1208        s->alloc_size += lpBytes(lp);12091210        /* Here we should perform garbage collection in case at this point1211         * there are too many entries deleted inside the listpack. */1212        entries -= deleted_from_lp;1213        marked_deleted += deleted_from_lp;1214        if (entries + marked_deleted > 10 && marked_deleted > entries/2) {1215            /* TODO: perform a garbage collection. */1216        }12171218        /* Update the node with the new pointer. */1219        raxIteratorSetData(&ri,lp);12201221        /* If the node is eligible for removal but we couldn't remove it due to delete strategy1222         * constraints (we need to check each entry individually), continue to the next node1223         * instead of stopping here. */1224        if (node_eligible_for_remove)1225            continue;12261227        break; /* If we are here, there was enough to delete in the current1228                  node, so no need to go to the next node. */1229    }1230    raxStop(&ri);12311232    /* Update the stream's first ID after the trimming. */1233    if (s->length == 0) {1234        s->first_id.ms = 0;1235        s->first_id.seq = 0;1236    } else if (deleted) {1237        streamGetEdgeID(s,1,1,&s->first_id);1238    }12391240    return deleted;1241}12421243/* Trims a stream by length. Returns the number of deleted items. */1244int64_t streamTrimByLength(redisDb *db, stream *s, long long maxlen, int approx) {1245    streamAddTrimArgs args = {1246        .trim_strategy = TRIM_STRATEGY_MAXLEN,1247        .approx_trim = approx,1248        .limit = approx ? 100 * server.stream_node_max_entries : 0,1249        .maxlen = maxlen,1250        .delete_strategy = DELETE_STRATEGY_KEEPREF1251    };1252    return streamTrim(db, s, &args, NULL);1253}12541255/* Trims a stream by minimum ID. Returns the number of deleted items. */1256int64_t streamTrimByID(redisDb *db, stream *s, streamID minid, int approx) {1257    streamAddTrimArgs args = {1258        .trim_strategy = TRIM_STRATEGY_MINID,1259        .approx_trim = approx,1260        .limit = approx ? 100 * server.stream_node_max_entries : 0,1261        .minid = minid,1262        .delete_strategy = DELETE_STRATEGY_KEEPREF1263    };1264    return streamTrim(db, s, &args, NULL);1265}12661267/* Parse the arguments of XADD/XTRIM.1268 *1269 * See streamAddTrimArgs for more details about the arguments handled.1270 *1271 * This function returns the position of the ID argument (relevant only to XADD).1272 * On error -1 is returned and a reply is sent. */1273static int streamParseAddOrTrimArgsOrReply(client *c, streamAddTrimArgs *args, int xadd) {1274    /* Initialize arguments to defaults */1275    memset(args, 0, sizeof(*args));1276    args->delete_strategy = DELETE_STRATEGY_NONE;12771278    /* Parse options. */1279    int i = 2; /* This is the first argument position where we could1280                  find an option, or the ID. */1281    int limit_given = 0;1282    for (; i < c->argc; i++) {1283        int moreargs = (c->argc-1) - i; /* Number of additional arguments. */1284        char *opt = c->argv[i]->ptr;1285        if (xadd && opt[0] == '*' && opt[1] == '\0') {1286            /* This is just a fast path for the common case of auto-ID1287             * creation. */1288            break;1289        } else if (!strcasecmp(opt,"maxlen") && moreargs) {1290            if (args->trim_strategy != TRIM_STRATEGY_NONE) {1291                addReplyError(c,"syntax error, MAXLEN and MINID options at the same time are not compatible");1292                return -1;1293            }1294            args->approx_trim = 0;1295            char *next = c->argv[i+1]->ptr;1296            /* Check for the form MAXLEN ~ <count>. */1297            if (moreargs >= 2 && next[0] == '~' && next[1] == '\0') {1298                args->approx_trim = 1;1299                i++;1300            } else if (moreargs >= 2 && next[0] == '=' && next[1] == '\0') {1301                i++;1302            }1303            if (getLongLongFromObjectOrReply(c,c->argv[i+1],&args->maxlen,NULL)1304                != C_OK) return -1;13051306            if (args->maxlen < 0) {1307                addReplyError(c,"The MAXLEN argument must be >= 0.");1308                return -1;1309            }1310            i++;1311            args->trim_strategy = TRIM_STRATEGY_MAXLEN;1312            args->trim_strategy_arg_idx = i;1313        } else if (!strcasecmp(opt,"minid") && moreargs) {1314            if (args->trim_strategy != TRIM_STRATEGY_NONE) {1315                addReplyError(c,"syntax error, MAXLEN and MINID options at the same time are not compatible");1316                return -1;1317            }1318            args->approx_trim = 0;1319            char *next = c->argv[i+1]->ptr;1320            /* Check for the form MINID ~ <id> */1321            if (moreargs >= 2 && next[0] == '~' && next[1] == '\0') {1322                args->approx_trim = 1;1323                i++;1324            } else if (moreargs >= 2 && next[0] == '=' && next[1] == '\0') {1325                i++;1326            }13271328            if (streamParseStrictIDOrReply(c,c->argv[i+1],&args->minid,0,NULL) != C_OK)1329                return -1;1330            1331            i++;1332            args->trim_strategy = TRIM_STRATEGY_MINID;1333            args->trim_strategy_arg_idx = i;1334        } else if (!strcasecmp(opt,"limit") && moreargs) {1335            /* Note about LIMIT: If it was not provided by the caller we set1336             * it to 100*server.stream_node_max_entries, and that's to prevent the1337             * trimming from taking too long, on the expense of not deleting entries1338             * that should be trimmed.1339             * If user wanted exact trimming (i.e. no '~') we never limit the number1340             * of trimmed entries */1341            if (getLongLongFromObjectOrReply(c,c->argv[i+1],&args->limit,NULL) != C_OK)1342                return -1;13431344            if (args->limit < 0) {1345                addReplyError(c,"The LIMIT argument must be >= 0.");1346                return -1;1347            }1348            limit_given = 1;1349            i++;1350        } else if (!strcasecmp(opt,"keepref") && args->delete_strategy == DELETE_STRATEGY_NONE) {1351            args->delete_strategy = DELETE_STRATEGY_KEEPREF;1352        } else if (!strcasecmp(opt,"delref") && args->delete_strategy == DELETE_STRATEGY_NONE) {1353            args->delete_strategy = DELETE_STRATEGY_DELREF;1354        } else if (!strcasecmp(opt,"acked") && args->delete_strategy == DELETE_STRATEGY_NONE) {1355            args->delete_strategy = DELETE_STRATEGY_ACKED;1356        } else if (xadd && !strcasecmp(opt,"nomkstream")) {1357            args->no_mkstream = 1;1358        } else if (xadd && !strcasecmp(opt,"idmpauto") && moreargs) {1359            /* IDMPAUTO pid - auto-generate IID from field-value pairs */1360            if (args->idmp_pid != NULL) {1361                addReplyError(c,"syntax error, IDMP/IDMPAUTO specified multiple times");1362                return -1;1363            }13641365            size_t pid_len = sdslen((sds)c->argv[i+1]->ptr);1366            if (pid_len == 0) {1367                addReplyError(c, "syntax error, IDMPAUTO requires a non-empty producer ID");1368                return -1;1369            }13701371            args->idmp_pid = c->argv[i+1];1372            args->idmp_auto = 1;1373            i++;1374        } else if (xadd && !strcasecmp(opt,"idmp") && moreargs >= 2) {1375            /* IDMP pid iid - explicit producer ID and idempotent ID */1376            if (args->idmp_pid != NULL) {1377                addReplyError(c,"syntax error, IDMP/IDMPAUTO specified multiple times");1378                return -1;1379            }13801381            size_t pid_len = sdslen((sds)c->argv[i+1]->ptr);1382            if (pid_len == 0) {1383                addReplyError(c, "syntax error, IDMP requires a non-empty producer ID");1384                return -1;1385            }13861387            size_t iid_len = sdslen((sds)c->argv[i+2]->ptr);1388            if (iid_len == 0) {1389                addReplyError(c, "syntax error, IDMP requires a non-empty idempotent ID");1390                return -1;1391            }13921393            args->idmp_pid = c->argv[i+1];1394            args->idmp_iid = c->argv[i+2];1395            i += 2;1396        } else if (xadd) {1397            /* If we are here is a syntax error or a valid ID. */1398            if (streamParseStrictIDOrReply(c,c->argv[i],&args->id,0,&args->seq_given) != C_OK)1399                return -1;14001401            /* mustObeyClient is needed because IDMP can only be used with * (auto-generated IDs),1402             * but when we replicate the message we replace the * with the actual StreamID. */1403            if (args->idmp_pid && opt[0] != '*' && !mustObeyClient(c)) {1404                addReplyError(c,"syntax error, IDMP/IDMPAUTO can be used only with auto-generated IDs");1405                return -1;1406            }1407            args->id_given = 1;1408            break;1409        } else {1410            addReplyErrorObject(c,shared.syntaxerr);1411            return -1;1412        }1413    }14141415    if (args->limit && args->trim_strategy == TRIM_STRATEGY_NONE) {1416        addReplyError(c,"syntax error, LIMIT cannot be used without specifying a trimming strategy");1417        return -1;1418    }14191420    if (!xadd && args->trim_strategy == TRIM_STRATEGY_NONE) {1421        addReplyError(c,"syntax error, XTRIM must be called with a trimming strategy");1422        return -1;1423    }14241425    if (mustObeyClient(c)) {1426        /* If command came from master or from AOF we must not enforce maxnodes1427         * (The maxlen/minid argument was re-written to make sure there's no1428         * inconsistency). */1429        args->limit = 0;1430    } else {1431        /* We need to set the limit (only if we got '~') */1432        if (limit_given) {1433            if (!args->approx_trim) {1434                /* LIMIT was provided without ~ */1435                addReplyError(c,"syntax error, LIMIT cannot be used without the special ~ option");1436                return -1;1437            }1438        } else {1439            /* User didn't provide LIMIT, we must set it. */1440            if (args->approx_trim) {1441                /* In order to prevent from trimming to do too much work and 1442                 * cause latency spikes we limit the amount of work it can do.1443                 * We have to cap args->limit from both sides in case 1444                 * stream_node_max_entries is 0 or too big (could cause overflow)1445                 */1446                args->limit = 100 * server.stream_node_max_entries; /* Maximum 100 rax nodes. */1447                if (args->limit <= 0) args->limit = 10000;1448                if (args->limit > 1000000) args->limit = 1000000;1449            } else {1450                /* No LIMIT for exact trimming */1451                args->limit = 0;1452            }1453        }1454    }14551456    /* Set default consumer group reference handling to KEEPREF if none was specified */1457    if (args->delete_strategy == DELETE_STRATEGY_NONE)1458        args->delete_strategy = DELETE_STRATEGY_KEEPREF;14591460    return i;1461}14621463static int streamParseAckDelArgsOrReply(client *c, int start_pos, streamAckDelArgs *args) {1464    /* Initialize arguments to defaults */1465    memset(args, 0, sizeof(*args));1466    args->startidx = -1;1467    args->delete_strategy = DELETE_STRATEGY_NONE;14681469    /* Parse command options */1470    int j = start_pos;1471    while (j < c->argc) {1472        char *opt = c->argv[j]->ptr;1473        if (!strcasecmp(opt, "KEEPREF") && args->delete_strategy == DELETE_STRATEGY_NONE) {1474            args->delete_strategy = DELETE_STRATEGY_KEEPREF;1475            j++;1476        } else if (!strcasecmp(opt, "DELREF") && args->delete_strategy == DELETE_STRATEGY_NONE) {1477            args->delete_strategy = DELETE_STRATEGY_DELREF;1478            j++;1479        } else if (!strcasecmp(opt, "ACKED") && args->delete_strategy == DELETE_STRATEGY_NONE) {1480            args->delete_strategy = DELETE_STRATEGY_ACKED;1481            j++;1482        } else if (!strcasecmp(opt, "IDS") && j+1 < c->argc) {1483            /* Parse the number of IDs */1484            if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1, LONG_MAX,1485                &args->numids, "Number of IDs must be a positive integer") != C_OK)1486            {1487                return 0;1488            }14891490            /* Verify that the specified number of IDs matches the actual arguments */1491            if (args->numids > (c->argc - j - 2)) {1492                addReplyError(c, "The `numids` parameter must match the number of arguments");1493                return 0;1494            }14951496            args->startidx = j + 2;  /* Skip "IDS" and numids */1497            j = args->startidx + args->numids;1498        } else {1499            addReplyErrorObject(c,shared.syntaxerr);1500            return 0;1501        }1502    }15031504    if (args->startidx == -1) {1505        addReplyError(c, "IDS option is required");1506        return 0;1507    }15081509    /* Set default consumer group reference handling to KEEPREF if none was specified */1510    if (args->delete_strategy == DELETE_STRATEGY_NONE)1511        args->delete_strategy = DELETE_STRATEGY_KEEPREF;15121513    return 1;1514}15151516/* Initialize the stream iterator, so that we can call iterating functions1517 * to get the next items. This requires a corresponding streamIteratorStop()1518 * at the end. The 'rev' parameter controls the direction. If it's zero the1519 * iteration is from the start to the end element (inclusive), otherwise1520 * if rev is non-zero, the iteration is reversed.1521 *1522 * Once the iterator is initialized, we iterate like this:1523 *1524 *  streamIterator myiterator;1525 *  streamIteratorStart(&myiterator,...);1526 *  int64_t numfields;1527 *  while(streamIteratorGetID(&myiterator,&ID,&numfields)) {1528 *      while(numfields--) {1529 *          unsigned char *key, *value;1530 *          size_t key_len, value_len;1531 *          streamIteratorGetField(&myiterator,&key,&value,&key_len,&value_len);1532 *1533 *          ... do what you want with key and value ...1534 *      }1535 *  }1536 *  streamIteratorStop(&myiterator); */1537void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev) {1538    /* Initialize the iterator and translates the iteration start/stop1539     * elements into a 128 big big-endian number. */1540    if (start) {1541        streamEncodeID(si->start_key,start);1542    } else {1543        si->start_key[0] = 0;1544        si->start_key[1] = 0;1545    }15461547    if (end) {1548        streamEncodeID(si->end_key,end);1549    } else {1550        si->end_key[0] = UINT64_MAX;1551        si->end_key[1] = UINT64_MAX;1552    }15531554    /* Decode the big-endian keys into native 64-bit integers1555     * for faster comparisons during iteration. */1556    si->start_ms  = htonu64(si->start_key[0]);1557    si->start_seq = htonu64(si->start_key[1]);1558    si->end_ms    = htonu64(si->end_key[0]);1559    si->end_seq   = htonu64(si->end_key[1]);15601561    /* Seek the correct node in the radix tree. */1562    raxStart(&si->ri,s->rax);1563    if (!rev) {1564        if (start && (start->ms || start->seq)) {1565            raxSeek(&si->ri,"<=",(unsigned char*)si->start_key,1566                    sizeof(si->start_key));1567            if (raxEOF(&si->ri)) raxSeek(&si->ri,"^",NULL,0);1568        } else {1569            raxSeek(&si->ri,"^",NULL,0);1570        }1571    } else {1572        if (end && (end->ms || end->seq)) {1573            raxSeek(&si->ri,"<=",(unsigned char*)si->end_key,1574                    sizeof(si->end_key));1575            if (raxEOF(&si->ri)) raxSeek(&si->ri,"$",NULL,0);1576        } else {1577            raxSeek(&si->ri,"$",NULL,0);1578        }1579    }1580    si->stream = s;1581    si->lp = NULL;     /* There is no current listpack right now. */1582    si->lp_last_ele = NULL;1583    si->lp_ele = NULL; /* Current listpack cursor. */1584    si->rev = rev;     /* Direction, if non-zero reversed, from end to start. */1585    si->skip_tombstones = 1;    /* By default tombstones aren't emitted. */1586}15871588/* Return 1 and store the current item ID at 'id' if there are still1589 * elements within the iteration range, otherwise return 0 in order to1590 * signal the iteration terminated. */1591int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {1592    while(1) { /* Will stop when element > stop_key or end of radix tree. */1593        /* Record the previous lp_ele position to detect data corruption1594         * that might cause the iterator to move backwards unexpectedly. */1595        if (si->lp_ele && si->lp_last_ele)1596            serverAssert(si->rev ? si->lp_ele < si->lp_last_ele : si->lp_ele > si->lp_last_ele);1597        si->lp_last_ele = si->lp_ele;15981599        /* If the current listpack is set to NULL, this is the start of the1600         * iteration or the previous listpack was completely iterated.1601         * Go to the next node. */1602        if (si->lp == NULL || si->lp_ele == NULL) {1603            if (!si->rev && !raxNext(&si->ri)) return 0;1604            else if (si->rev && !raxPrev(&si->ri)) return 0;1605            serverAssert(si->ri.key_len == sizeof(streamID));1606            /* Get the master ID. */1607            streamDecodeID(si->ri.key,&si->master_id);1608            /* Get the master fields count. */1609            si->lp = si->ri.data;1610            si->lp_ele = lpFirst(si->lp);           /* Seek items count */1611            si->lp_ele = lpNext(si->lp,si->lp_ele); /* Seek deleted count. */1612            si->lp_ele = lpNext(si->lp,si->lp_ele); /* Seek num fields. */1613            si->master_fields_count = lpGetInteger(si->lp_ele);1614            si->lp_ele = lpNext(si->lp,si->lp_ele); /* Seek first field. */1615            si->master_fields_start = si->lp_ele;1616            /* We are now pointing to the first field of the master entry.1617             * We need to seek either the first or the last entry depending1618             * on the direction of the iteration. */1619            if (!si->rev) {1620                /* If we are iterating in normal order, skip the master fields1621                 * to seek the first actual entry. */1622                for (uint64_t i = 0; i < si->master_fields_count; i++)1623                    si->lp_ele = lpNext(si->lp,si->lp_ele);1624            } else {1625                /* If we are iterating in reverse direction, just seek the1626                 * last part of the last entry in the listpack (that is, the1627                 * fields count). */1628                si->lp_ele = lpLast(si->lp);1629            }1630        } else if (si->rev) {1631            /* If we are iterating in the reverse order, and this is not1632             * the first entry emitted for this listpack, then we already1633             * emitted the current entry, and have to go back to the previous1634             * one. */1635            int64_t lp_count = lpGetInteger(si->lp_ele);1636            while(lp_count--) si->lp_ele = lpPrev(si->lp,si->lp_ele);1637            /* Seek lp-count of prev entry. */1638            si->lp_ele = lpPrev(si->lp,si->lp_ele);1639        }16401641        /* For every radix tree node, iterate the corresponding listpack,1642         * returning elements when they are within range. */1643        while(1) {1644            if (!si->rev) {1645                /* If we are going forward, skip the previous entry1646                 * lp-count field (or in case of the master entry, the zero1647                 * term field) */1648                si->lp_ele = lpNext(si->lp,si->lp_ele);1649                if (si->lp_ele == NULL) break;1650            } else {1651                /* If we are going backward, read the number of elements this1652                 * entry is composed of, and jump backward N times to seek1653                 * its start. */1654                int64_t lp_count = lpGetInteger(si->lp_ele);1655                if (lp_count == 0) { /* We reached the master entry. */1656                    si->lp = NULL;1657                    si->lp_ele = NULL;1658                    break;1659                }1660                while(lp_count--) si->lp_ele = lpPrev(si->lp,si->lp_ele);1661            }16621663            /* Get the flags entry. */1664            si->lp_flags = si->lp_ele;1665            int64_t flags = lpGetInteger(si->lp_ele);1666            si->lp_ele = lpNext(si->lp,si->lp_ele); /* Seek ID. */16671668            /* Get the ID: it is encoded as difference between the master1669             * ID and this entry ID. */1670            *id = si->master_id;1671            id->ms += lpGetInteger(si->lp_ele);1672            si->lp_ele = lpNext(si->lp,si->lp_ele);1673            id->seq += lpGetInteger(si->lp_ele);1674            si->lp_ele = lpNext(si->lp,si->lp_ele);16751676            /* The number of entries is here or not depending on the1677             * flags. */1678            if (flags & STREAM_ITEM_FLAG_SAMEFIELDS) {1679                *numfields = si->master_fields_count;1680            } else {1681                *numfields = lpGetInteger(si->lp_ele);1682                si->lp_ele = lpNext(si->lp,si->lp_ele);1683            }1684            serverAssert(*numfields>=0);16851686            /* If current >= start, and the entry is not marked as1687             * deleted or tombstones are included, emit it. */1688            if (!si->rev) {1689                if ((id->ms > si->start_ms ||1690                    (id->ms == si->start_ms && id->seq >= si->start_seq)) &&1691                    (!si->skip_tombstones || !(flags & STREAM_ITEM_FLAG_DELETED)))1692                {1693                    if (id->ms > si->end_ms ||1694                        (id->ms == si->end_ms && id->seq > si->end_seq))1695                        return 0; /* We are already out of range. */1696                    si->entry_flags = flags;1697                    if (flags & STREAM_ITEM_FLAG_SAMEFIELDS)1698                        si->master_fields_ptr = si->master_fields_start;1699                    return 1; /* Valid item returned. */1700                }1701            } else {1702                if ((id->ms < si->end_ms ||1703                    (id->ms == si->end_ms && id->seq <= si->end_seq)) &&1704                    (!si->skip_tombstones || !(flags & STREAM_ITEM_FLAG_DELETED)))1705                {1706                    if (id->ms < si->start_ms ||1707                        (id->ms == si->start_ms && id->seq < si->start_seq))1708                        return 0; /* We are already out of range. */1709                    si->entry_flags = flags;1710                    if (flags & STREAM_ITEM_FLAG_SAMEFIELDS)1711                        si->master_fields_ptr = si->master_fields_start;1712                    return 1; /* Valid item returned. */1713                }1714            }17151716            /* If we do not emit, we have to discard if we are going1717             * forward, or seek the previous entry if we are going1718             * backward. */1719            if (!si->rev) {1720                int64_t to_discard = (flags & STREAM_ITEM_FLAG_SAMEFIELDS) ?1721                                      *numfields : *numfields*2;1722                for (int64_t i = 0; i < to_discard; i++)1723                    si->lp_ele = lpNext(si->lp,si->lp_ele);1724            } else {1725                int64_t prev_times = 4; /* flag + id ms + id seq + one more to1726                                           go back to the previous entry "count"1727                                           field. */1728                /* If the entry was not flagged SAMEFIELD we also read the1729                 * number of fields, so go back one more. */1730                if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS)) prev_times++;1731                while(prev_times--) si->lp_ele = lpPrev(si->lp,si->lp_ele);1732            }1733        }17341735        /* End of listpack reached. Try the next/prev radix tree node. */1736    }1737}17381739/* Get the field and value of the current item we are iterating. This should1740 * be called immediately after streamIteratorGetID(), and for each field1741 * according to the number of fields returned by streamIteratorGetID().1742 * The function populates the field and value pointers and the corresponding1743 * lengths by reference, that are valid until the next iterator call, assuming1744 * no one touches the stream meanwhile. */1745void streamIteratorGetField(streamIterator *si, unsigned char **fieldptr, unsigned char **valueptr, int64_t *fieldlen, int64_t *valuelen) {1746    if (si->entry_flags & STREAM_ITEM_FLAG_SAMEFIELDS) {1747        *fieldptr = lpGet(si->master_fields_ptr,fieldlen,si->field_buf);1748        si->master_fields_ptr = lpNext(si->lp,si->master_fields_ptr);1749    } else {1750        *fieldptr = lpGet(si->lp_ele,fieldlen,si->field_buf);1751        si->lp_ele = lpNext(si->lp,si->lp_ele);1752    }1753    *valueptr = lpGet(si->lp_ele,valuelen,si->value_buf);1754    si->lp_ele = lpNext(si->lp,si->lp_ele);1755}17561757/* Remove the current entry from the stream: can be called after the1758 * GetID() API or after any GetField() call, however we need to iterate1759 * a valid entry while calling this function. Moreover the function1760 * requires the entry ID we are currently iterating, that was previously1761 * returned by GetID().1762 *1763 * Note that after calling this function, next calls to GetField() can't1764 * be performed: the entry is now deleted. Instead the iterator will1765 * automatically re-seek to the next entry, so the caller should continue1766 * with GetID(). */1767void streamIteratorRemoveEntry(streamIterator *si, streamID *current) {1768    stream *s = si->stream;1769    unsigned char *lp = si->lp;1770    size_t oldsize = lpBytes(lp);1771    int64_t aux;17721773    /* We do not really delete the entry here. Instead we mark it as1774     * deleted by flagging it, and also incrementing the count of the1775     * deleted entries in the listpack header.1776     *1777     * We start flagging: */1778    int64_t flags = lpGetInteger(si->lp_flags);1779    flags |= STREAM_ITEM_FLAG_DELETED;1780    lp = lpReplaceInteger(lp,&si->lp_flags,flags);17811782    /* Change the valid/deleted entries count in the master entry. */1783    unsigned char *p = lpFirst(lp);1784    aux = lpGetInteger(p);17851786    if (aux == 1) {1787        /* If this is the last element in the listpack, we can remove the whole1788         * node. */1789        s->alloc_size -= oldsize;1790        lpFree(lp);1791        raxRemove(s->rax,si->ri.key,si->ri.key_len,NULL);1792    } else {1793        /* In the base case we alter the counters of valid/deleted entries. */1794        lp = lpReplaceInteger(lp,&p,aux-1);1795        p = lpNext(lp,p); /* Seek deleted field. */1796        aux = lpGetInteger(p);1797        lp = lpReplaceInteger(lp,&p,aux+1);1798        s->alloc_size -= oldsize;1799        s->alloc_size += lpBytes(lp);18001801        /* Update the listpack with the new pointer. */1802        if (si->lp != lp)1803            raxInsert(s->rax,si->ri.key,si->ri.key_len,lp,NULL);1804    }18051806    /* Update the number of entries counter. */1807    s->length--;18081809    /* Re-seek the iterator to fix the now messed up state. */1810    streamID start, end;1811    if (si->rev) {1812        streamDecodeID(si->start_key,&start);1813        end = *current;1814    } else {1815        start = *current;1816        streamDecodeID(si->end_key,&end);1817    }1818    streamIteratorStop(si);1819    streamIteratorStart(si,s,&start,&end,si->rev);18201821    /* TODO: perform a garbage collection here if the ratio between1822     * deleted and valid goes over a certain limit. */1823}18241825/* Stop the stream iterator. The only cleanup we need is to free the rax1826 * iterator, since the stream iterator itself is supposed to be stack1827 * allocated. */1828void streamIteratorStop(streamIterator *si) {1829    raxStop(&si->ri);1830}18311832/* Return 1 if `id` exists in `s` (and not marked as deleted) */1833int streamEntryExists(stream *s, streamID *id) {1834    streamIterator si;1835    streamIteratorStart(&si,s,id,id,0);1836    streamID myid;1837    int64_t numfields;1838    int found = streamIteratorGetID(&si,&myid,&numfields);1839    streamIteratorStop(&si);1840    if (!found)1841        return 0;1842    serverAssert(streamCompareID(id,&myid) == 0);1843    return 1;1844}18451846/* Delete the specified item ID from the stream, returning 1 if the item1847 * was deleted 0 otherwise (if it does not exist). */1848int streamDeleteItem(stream *s, streamID *id) {1849    int deleted = 0;1850    streamIterator si;1851    streamIteratorStart(&si,s,id,id,0);1852    streamID myid;1853    int64_t numfields;1854    if (streamIteratorGetID(&si,&myid,&numfields)) {1855        streamIteratorRemoveEntry(&si,&myid);1856        deleted = 1;1857    }1858    streamIteratorStop(&si);1859    return deleted;1860}18611862/* Get the last valid (non-tombstone) streamID of 's'. */1863void streamLastValidID(stream *s, streamID *maxid)1864{1865    streamIterator si;1866    streamIteratorStart(&si,s,NULL,NULL,1);1867    int64_t numfields;1868    if (!streamIteratorGetID(&si,maxid,&numfields) && s->length)1869        serverPanic("Corrupt stream, length is %llu, but no max id", (unsigned long long)s->length);1870    streamIteratorStop(&si);1871}18721873/* Maximum size for a stream ID string. In theory 20*2+1 should be enough,1874 * But to avoid chance for off by one issues and null-term, in case this will1875 * be used as parsing buffer, we use a slightly larger buffer. On the other1876 * hand considering sds header is gonna add 4 bytes, we wanna keep below the1877 * allocator's 48 bytes bin. */1878#define STREAM_ID_STR_LEN 4418791880sds createStreamIDString(streamID *id) {1881    /* Optimization: pre-allocate a big enough buffer to avoid reallocs. */1882    sds str = sdsnewlen(SDS_NOINIT, STREAM_ID_STR_LEN);1883    sdssetlen(str, 0);1884    return sdscatfmt(str,"%U-%U", id->ms,id->seq);1885}18861887/* Emit a reply in the client output buffer by formatting a Stream ID1888 * in the standard <ms>-<seq> format, using the simple string protocol1889 * of REPL. */1890void addReplyStreamID(client *c, streamID *id) {1891    addReplyBulkSds(c,createStreamIDString(id));1892}18931894void setDeferredReplyStreamID(client *c, void *dr, streamID *id) {1895    setDeferredReplyBulkSds(c, dr, createStreamIDString(id));1896}18971898/* Similar to the above function, but just creates an object, usually useful1899 * for replication purposes to create arguments. */1900robj *createObjectFromStreamID(streamID *id) {1901    return createObject(OBJ_STRING, createStreamIDString(id));1902}19031904/* Returns non-zero if the ID is 0-0. */1905int streamIDEqZero(streamID *id) {1906    return !(id->ms || id->seq);1907}19081909/* A helper that returns non-zero if the range from 'start' to `end`1910 * contains a tombstone.1911 *1912 * NOTE: this assumes that the caller had verified that 'start' is less than1913 * 's->last_id'. */1914int streamRangeHasTombstones(stream *s, streamID *start, streamID *end) {1915    streamID start_id, end_id;19161917    if (!s->length || streamIDEqZero(&s->max_deleted_entry_id)) {1918        /* The stream is empty or has no tombstones. */1919        return 0;1920    }19211922    if (start) {1923        start_id = *start;1924    } else {1925        start_id.ms = 0;1926        start_id.seq = 0;1927    }19281929    if (end) {1930        end_id = *end;1931    } else {1932        end_id.ms = UINT64_MAX;1933        end_id.seq = UINT64_MAX;1934    }19351936    if (streamCompareID(&start_id,&s->max_deleted_entry_id) <= 0 &&1937        streamCompareID(&s->max_deleted_entry_id,&end_id) <= 0)1938    {1939        /* start_id <= max_deleted_entry_id <= end_id: The range does include a tombstone. */1940        return 1;1941    }19421943    /* The range doesn't includes a tombstone. */1944    return 0;1945}19461947/* Replies with a consumer group's current lag, that is the number of messages1948 * in the stream that are yet to be delivered. In case that the lag isn't1949 * available due to fragmentation, the reply to the client is a null. */1950void streamReplyWithCGLag(client *c, stream *s, streamCG *cg) {1951    int valid = 0;1952    long long lag = 0;19531954    if (!s->entries_added) {1955        /* The lag of a newly-initialized stream is 0. */1956        lag = 0;1957        valid = 1;1958    } else if (!s->length) { /* All entries deleted, now empty. */1959        lag = 0;1960        valid = 1;1961    } else if (streamCompareID(&cg->last_id,&s->first_id) < 0 &&1962               streamCompareID(&s->max_deleted_entry_id,&s->first_id) < 0)1963    {1964        /* When both the consumer group's last_id and the maximum tombstone are behind1965         * the stream's first entry, the consumer group's lag will always be equal to1966         * the number of remainin entries in the stream. */1967        lag = s->length;1968        valid = 1;1969    } else if (cg->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s,&cg->last_id,NULL)) {1970        /* No fragmentation ahead means that the group's logical reads counter1971         * is valid for performing the lag calculation. */1972        lag = (long long)s->entries_added - cg->entries_read;1973        valid = 1;1974    } else {1975        /* Attempt to retrieve the group's last ID logical read counter. */1976        long long entries_read = streamEstimateDistanceFromFirstEverEntry(s,&cg->last_id);1977        if (entries_read != SCG_INVALID_ENTRIES_READ) {1978            /* A valid counter was obtained. */1979            lag = (long long)s->entries_added - entries_read;1980            valid = 1;1981        }1982    }19831984    if (valid) {1985        addReplyLongLong(c,lag);1986    } else {1987        addReplyNull(c);1988    }1989}19901991/* A group's sample under a per-group metric. Every enumerator is listed, with1992 * the per-stream ones falling through to the assert, so that -Wswitch flags a1993 * new metric that has no sampler case. */1994static int64_t streamCGroupSample(streamCG *cg, streamDistribMetric metric) {1995    switch (metric) {1996    case STREAM_DISTRIB_CGROUPS_PEL: return (int64_t) raxSize(cg->pel);1997    case STREAM_DISTRIB_CGROUPS_CONSUMERS: return (int64_t) raxSize(cg->consumers);1998    case STREAM_DISTRIB_STREAMS_CGROUPS: /* per-stream: streamStreamSample() */1999    case STREAM_DISTRIB_MAX: break;2000    }

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.

Generated by searchcode.com · powered by code_get_file + code_get_findings