diff options
author | dormando <dormando@rydia.net> | 2020-06-30 18:19:47 -0700 |
---|---|---|
committer | dormando <dormando@rydia.net> | 2020-07-02 15:05:22 -0700 |
commit | 53b4d74234cdbf42263b49885499ff977b215451 (patch) | |
tree | a62ce1e5f208c4307f041b389d1ac3c184781e8d | |
parent | 0271aa9e4c96a884a700070a3d7f3afffc276620 (diff) | |
download | memcached-53b4d74234cdbf42263b49885499ff977b215451.tar.gz |
fix leak in merged resp/read buffers
The list grows toward next, not prev. Also wasn't zeroing out next ptr.
Also didn't unmark free for first resp object. (thanks Prudhviraj!)
Adds a couple counters so users can tell if something is wrong as well.
response_obj_count is solely for response objects in-flight, they
should not be held when idle (except for one bundle per worker thread).
-rw-r--r-- | doc/protocol.txt | 3 | ||||
-rw-r--r-- | memcached.c | 25 | ||||
-rw-r--r-- | memcached.h | 2 | ||||
-rwxr-xr-x | t/stats.t | 4 |
4 files changed, 28 insertions, 6 deletions
diff --git a/doc/protocol.txt b/doc/protocol.txt index 36fce44..737937c 100644 --- a/doc/protocol.txt +++ b/doc/protocol.txt @@ -1203,6 +1203,9 @@ integers separated by a colon (treat this as a floating point number). | connection_structures | 32u | Number of connection structures allocated | | | | by the server | | response_obj_oom | 64u | Connections closed by lack of memory | +| response_obj_count | 64u | Total response objects in use | +| response_obj_bytes | 64u | Total bytes used for resp. objects. is a | +| | | subset of bytes from read_buf_bytes. | | read_buf_count | 64u | Total read/resp buffers allocated | | read_buf_bytes | 64u | Total read/resp buffer bytes allocated | | read_buf_bytes_free | 64u | Total read/resp buffer bytes cached | diff --git a/memcached.c b/memcached.c index bb56a5b..095bca1 100644 --- a/memcached.c +++ b/memcached.c @@ -1060,12 +1060,13 @@ static mc_resp* resp_allocate(conn *c) { b->refcount++; resp->free = false; if (b->refcount == MAX_RESP_PER_BUNDLE) { - assert(b->next == NULL); + assert(b->prev == NULL); // We only allocate off the head. Assign new head. - th->open_bundle = b->prev; + th->open_bundle = b->next; // Remove ourselves from the list. - if (b->prev) { - b->prev->next = 0; + if (b->next) { + b->next->prev = 0; + b->next = 0; } } } @@ -1075,6 +1076,9 @@ static mc_resp* resp_allocate(conn *c) { assert(th->open_bundle == NULL); b = do_cache_alloc(th->rbuf_cache); if (b) { + THR_STATS_LOCK(c); + c->thread->stats.response_obj_bytes += READ_BUFFER_SIZE; + THR_STATS_UNLOCK(c); b->next_check = 1; b->refcount = 1; for (int i = 0; i < MAX_RESP_PER_BUNDLE; i++) { @@ -1085,6 +1089,7 @@ static mc_resp* resp_allocate(conn *c) { b->prev = 0; th->open_bundle = b; resp = &b->r[0]; + resp->free = false; } else { return NULL; } @@ -1119,6 +1124,9 @@ static void resp_free(conn *c, mc_resp *resp) { // Now completely done with this buffer. do_cache_free(th->rbuf_cache, b); + THR_STATS_LOCK(c); + c->thread->stats.response_obj_bytes -= READ_BUFFER_SIZE; + THR_STATS_UNLOCK(c); } } else { mc_resp_bundle **head = &th->open_bundle; @@ -1144,6 +1152,10 @@ static bool resp_start(conn *c) { THR_STATS_UNLOCK(c); return false; } + // handling the stats counters here to simplify testing + THR_STATS_LOCK(c); + c->thread->stats.response_obj_count++; + THR_STATS_UNLOCK(c); // Skip zeroing the bundle pointer at the start. // TODO: this line is here temporarily to make the code easy to disable. // when it's more mature, move the memset into resp_allocate() and have it @@ -1190,6 +1202,9 @@ static mc_resp* resp_finish(conn *c, mc_resp *resp) { c->resp = NULL; } resp_free(c, resp); + THR_STATS_LOCK(c); + c->thread->stats.response_obj_count--; + THR_STATS_UNLOCK(c); return next; } @@ -3235,6 +3250,8 @@ static void server_stats(ADD_STAT add_stats, conn *c) { } APPEND_STAT("connection_structures", "%u", stats_state.conn_structs); APPEND_STAT("response_obj_oom", "%llu", (unsigned long long)thread_stats.response_obj_oom); + APPEND_STAT("response_obj_count", "%llu", (unsigned long long)thread_stats.response_obj_count); + APPEND_STAT("response_obj_bytes", "%llu", (unsigned long long)thread_stats.response_obj_bytes); APPEND_STAT("read_buf_count", "%llu", (unsigned long long)thread_stats.read_buf_count); APPEND_STAT("read_buf_bytes", "%llu", (unsigned long long)thread_stats.read_buf_bytes); APPEND_STAT("read_buf_bytes_free", "%llu", (unsigned long long)thread_stats.read_buf_bytes_free); diff --git a/memcached.h b/memcached.h index 2cafed9..ec1e0e5 100644 --- a/memcached.h +++ b/memcached.h @@ -302,6 +302,8 @@ struct slab_stats { X(auth_errors) \ X(idle_kicks) /* idle connections killed */ \ X(response_obj_oom) \ + X(response_obj_count) \ + X(response_obj_bytes) \ X(read_buf_oom) #ifdef EXTSTORE @@ -28,9 +28,9 @@ if (MemcachedTest::enabled_tls_testing()) { # when TLS is enabled, stats contains additional keys: # - ssl_handshake_errors # - time_since_server_cert_refresh - is(scalar(keys(%$stats)), 78, "expected count of stats values"); + is(scalar(keys(%$stats)), 80, "expected count of stats values"); } else { - is(scalar(keys(%$stats)), 76, "expected count of stats values"); + is(scalar(keys(%$stats)), 78, "expected count of stats values"); } # Test initial state |