diff --git a/src/sds.c b/src/sds.c index e722abbca..1b518642f 100644 --- a/src/sds.c +++ b/src/sds.c @@ -343,6 +343,51 @@ sds sdsRemoveFreeSpace(sds s) { return s; } +/* Resize the allocation, this can make the allocation bigger or smaller, + * if the size is smaller than currently used len, the data will be truncated */ +sds sdsResize(sds s, size_t size) { + void *sh, *newsh; + char type, oldtype = s[-1] & SDS_TYPE_MASK; + int hdrlen, oldhdrlen = sdsHdrSize(oldtype); + size_t len = sdslen(s); + sh = (char*)s-oldhdrlen; + + /* Return ASAP if the size is already good. */ + if (sdsalloc(s) == size) return s; + + /* Truncate len if needed. */ + if (size < len) len = size; + + /* Check what would be the minimum SDS header that is just good enough to + * fit this string. */ + type = sdsReqType(size); + /* Don't use type 5, it is not good for strings that are resized. */ + if (type == SDS_TYPE_5) type = SDS_TYPE_8; + hdrlen = sdsHdrSize(type); + + /* If the type is the same, or can hold the size in it with low overhead + * (larger than SDS_TYPE_8), we just realloc(), letting the allocator + * to do the copy only if really needed. Otherwise if the change is + * huge, we manually reallocate the string to use the different header + * type. */ + if (oldtype==type || (type < oldtype && type > SDS_TYPE_8)) { + newsh = s_realloc(sh, oldhdrlen+size+1); + if (newsh == NULL) return NULL; + s = (char*)newsh+oldhdrlen; + } else { + newsh = s_malloc(hdrlen+size+1); + if (newsh == NULL) return NULL; + memcpy((char*)newsh+hdrlen, s, len); + s_free(sh); + s = (char*)newsh+hdrlen; + s[-1] = type; + } + s[len] = 0; + sdssetlen(s, len); + sdssetalloc(s, size); + return s; +} + /* Return the total size of the allocation of the specified sds string, * including: * 1) The sds header before the pointer. @@ -1475,6 +1520,34 @@ int sdsTest(int argc, char **argv, int accurate) { test_cond("sdstemplate() with quoting", memcmp(x,"v1={value1} {} v2=value2",24) == 0); sdsfree(x); + + /* Test sdsresize - extend */ + x = sdsnew("1234567890123456789012345678901234567890"); + x = sdsResize(x, 200); + test_cond("sdsrezie() expand len", sdslen(x) == 40); + test_cond("sdsrezie() expand strlen", strlen(x) == 40); + test_cond("sdsrezie() expand alloc", sdsalloc(x) == 200); + /* Test sdsresize - trim free space */ + x = sdsResize(x, 80); + test_cond("sdsrezie() shrink len", sdslen(x) == 40); + test_cond("sdsrezie() shrink strlen", strlen(x) == 40); + test_cond("sdsrezie() shrink alloc", sdsalloc(x) == 80); + /* Test sdsresize - crop used space */ + x = sdsResize(x, 30); + test_cond("sdsrezie() crop len", sdslen(x) == 30); + test_cond("sdsrezie() crop strlen", strlen(x) == 30); + test_cond("sdsrezie() crop alloc", sdsalloc(x) == 30); + /* Test sdsresize - extend to different class */ + x = sdsResize(x, 400); + test_cond("sdsrezie() expand len", sdslen(x) == 30); + test_cond("sdsrezie() expand strlen", strlen(x) == 30); + test_cond("sdsrezie() expand alloc", sdsalloc(x) == 400); + /* Test sdsresize - shrink to different class */ + x = sdsResize(x, 4); + test_cond("sdsrezie() crop len", sdslen(x) == 4); + test_cond("sdsrezie() crop strlen", strlen(x) == 4); + test_cond("sdsrezie() crop alloc", sdsalloc(x) == 4); + sdsfree(x); } test_report(); return 0; diff --git a/src/sds.h b/src/sds.h index 9b01b4e4e..fce8a289e 100644 --- a/src/sds.h +++ b/src/sds.h @@ -267,6 +267,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen); sds sdsMakeRoomForNonGreedy(sds s, size_t addlen); void sdsIncrLen(sds s, ssize_t incr); sds sdsRemoveFreeSpace(sds s); +sds sdsResize(sds s, size_t size); size_t sdsAllocSize(sds s); void *sdsAllocPtr(sds s); diff --git a/src/server.c b/src/server.c index 27a2f6c46..ddf0f061c 100644 --- a/src/server.c +++ b/src/server.c @@ -1667,22 +1667,31 @@ int clientsCronResizeQueryBuffer(client *c) { size_t querybuf_size = sdsalloc(c->querybuf); time_t idletime = server.unixtime - c->lastinteraction; - /* There are two conditions to resize the query buffer: - * 1) Query buffer is > PROTO_RESIZE_THRESHOLD and too big for latest peak. - * 2) Query buffer is > PROTO_RESIZE_THRESHOLD and client is idle. */ - if (querybuf_size > PROTO_RESIZE_THRESHOLD && - ((querybuf_size/(c->querybuf_peak+1)) > 2 || - idletime > 2)) - { - /* Only resize the query buffer if it is actually wasting - * at least a few kbytes. */ - if (sdsavail(c->querybuf) > 1024*4) { + /* Only resize the query buffer if the buffer is bigger than + * PROTO_RESIZE_THRESHOLD, and it is actually wasting at least a few kbytes. */ + if (querybuf_size > PROTO_RESIZE_THRESHOLD && sdsavail(c->querybuf) > 1024*4) { + /* There are two conditions to resize the query buffer: */ + if (idletime > 2) { + /* 1) Query is idle for a long time. */ c->querybuf = sdsRemoveFreeSpace(c->querybuf); + } else if (querybuf_size/2 > c->querybuf_peak) { + /* 2) Query buffer is too big for latest peak. trim excess space but + * only up to a limit, not below the recent peak and current + * c->querybuf (which will be soon get used). */ + size_t resize = sdslen(c->querybuf); + if (resize < c->querybuf_peak) resize = c->querybuf_peak; + if (c->bulklen != -1 && resize < (size_t)c->bulklen) resize = c->bulklen; + c->querybuf = sdsResize(c->querybuf, resize); } } + /* Reset the peak again to capture the peak memory usage in the next * cycle. */ - c->querybuf_peak = 0; + c->querybuf_peak = sdslen(c->querybuf); + /* We reset to either the current used, or currently processed bulk size, + * which ever is bigger. */ + if (c->bulklen != -1 && (size_t)c->bulklen > c->querybuf_peak) + c->querybuf_peak = c->bulklen; /* Clients representing masters also use a "pending query buffer" that * is the yet not applied part of the stream we are reading. Such buffer