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.