pistache: Memory leaks (general)

Pistache (as of master/head, right now), has some memory leaks in it. It leaks on every bind() (minor problem) and on every processed request (major problem). These are several open bugs reporting memory leaks in various parts of pistache, however, most of those bugs were filed before the recent major rounds of code changes. In time, I plan to close those as duplicates against this issue, and track memory leaks here.

Once we fix all of the memory leaks, I intend to add memory leak checking (via valgrind) to the travis-ci, and auto-reject all PRs that introduce memory leaks.

IMHO, fixing crashes and memory leaks should be our first priority, followed by test flakiness as second priority.

One can observe the leaks by simply running valgrind against the compiled test binaries:

#!/bin/bash

DIR="."

# Ensure that we are at a pistache git root
[[ -d "${DIR}/examples" ]] || exit $?
[[ -f "${DIR}/src/common/transport.cc" ]] || exit $?

cd "./${DIR}" || exit $?

function do_check {
  CC=$1
  BUILD_DIR="${DIR}/build-${CC}"

  for TEST in ${BUILD_DIR}/tests/run_*; do
    LOG="${TEST}.xml"

    valgrind -q --xml-file=${LOG} --xml=yes --error-exitcode=1 ${TEST}
  done
}

# do_check clang
do_check gcc
$ grep "Leak_DefinitelyLost" -l build-gcc/tests/*.xml
build-gcc/tests/run_async_test.xml
build-gcc/tests/run_cookie_test_3.xml
build-gcc/tests/run_http_client_test.xml
build-gcc/tests/run_http_server_test.xml
build-gcc/tests/run_listener_test.xml
build-gcc/tests/run_payload_test.xml
build-gcc/tests/run_rest_server_test.xml
build-gcc/tests/run_streaming_test.xml

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 1
  • Comments: 40 (31 by maintainers)

Commits related to this issue

Most upvoted comments

Hi,

Thanks for merging the fix. As FYI, this is how to catch this in the future:

  1. Disable SIGINT signal handler (we want to close valgrind with CTRL-C) listener.cc - remove this code - or change the flags
    if (options_.hasFlag(Options::InstallSignalHandler)) {
        if (signal(SIGINT, handle_sigint) == SIG_ERR) {
            throw std::runtime_error("Could not install signal handler");
        }
    }
  1. Compile with debug symbols Use flag: -DCMAKE_BUILD_TYPE=Debug - or just set up ‘-g’ in the cmake files, or both…

  2. Run valgrind with full leak check

# valgrind --leak-check=full run_http_server
  1. Do some requests (telnet to 9080 and close it without sending an HTTP request) Close application (CTRL-C).
==13834== 3,168 (672 direct, 2,496 indirect) bytes in 4 blocks are definitely lost in loss record 51 of 52
==13834==    at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==13834==    by 0x2D0934: __gnu_cxx::new_allocator<std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) (new_allocator.h:111)
==13834==    by 0x2CFC18: std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2> >&, unsigned long) (alloc_traits.h:436)
==13834==    by 0x2CF009: std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2> > > std::__allocate_guarded<std::allocator<std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2> > >(std::allocator<std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2> >&) (allocated_ptr.h:104)
==13834==    by 0x2CE6E2: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, Pistache::Address>(std::_Sp_make_shared_tag, Pistache::Tcp::Peer*, std::allocator<Pistache::Tcp::Peer> const&, Pistache::Address&&) (shared_ptr_base.h:635)
==13834==    by 0x2CDBD9: std::__shared_ptr<Pistache::Tcp::Peer, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<Pistache::Tcp::Peer>, Pistache::Address>(std::_Sp_make_shared_tag, std::allocator<Pistache::Tcp::Peer> const&, Pistache::Address&&) (shared_ptr_base.h:1295)
==13834==    by 0x2CCAE0: std::shared_ptr<Pistache::Tcp::Peer>::shared_ptr<std::allocator<Pistache::Tcp::Peer>, Pistache::Address>(std::_Sp_make_shared_tag, std::allocator<Pistache::Tcp::Peer> const&, Pistache::Address&&) (shared_ptr.h:344)
==13834==    by 0x2CAC8A: std::shared_ptr<Pistache::Tcp::Peer> std::allocate_shared<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, Pistache::Address>(std::allocator<Pistache::Tcp::Peer> const&, Pistache::Address&&) (shared_ptr.h:691)
==13834==    by 0x2C8F01: std::shared_ptr<Pistache::Tcp::Peer> std::make_shared<Pistache::Tcp::Peer, Pistache::Address>(Pistache::Address&&) (shared_ptr.h:707)
==13834==    by 0x2C54A4: Pistache::Tcp::Listener::handleNewConnection() (listener.cc:392)
==13834==    by 0x2C4976: Pistache::Tcp::Listener::run() (listener.cc:270)
==13834==    by 0x2C2FD7: void Pistache::Http::Endpoint::serveImpl<void (Pistache::Tcp::Listener::*)()>(void (Pistache::Tcp::Listener::*)()) (endpoint.h:141)
==13834== 
  1. We now have a shared_ptr leak… so we’d like to track refcount for the shared_ptr For that we just use GDB and set up a breakpoint where the shared_ptr is allocated
# gdb ./run_http_server
(gdb) b listener.cc:392
(gdb) run

Then we trigger a new allocation - In another terminal generate a connection and close it:

# telnet 127.0.0.1 9080
CTRL-]
q
  1. You now have GDB with the breakpoint You can “CTRL-x a” to open up the nice TUI mode (and close it)

We have code:

   │396         make_non_blocking(client_fd);                                                                                                                                                                                            │
   │397                                                                                                                                                                                                                                  │
   │398         auto peer = std::make_shared<Peer>(Address::fromUnix((struct sockaddr *)&peer_addr));                                                                                                                                    │
  >│399         peer->associateFd(client_fd);                                                                                                                                                                                            │
  1. We want to set a hardware breakpoint on the refcount in the shared_ptr, so:

Disable pretty printer - we want to figure what the address of the refcount is

(gdb) disable pretty-printer

Figure out where the refcount is:

(gdb) p peer
$1 = {<std::__shared_ptr<Pistache::Tcp::Peer, (__gnu_cxx::_Lock_policy)2>> = {<std::__shared_ptr_access<Pistache::Tcp::Peer, (__gnu_cxx::_Lock_policy)2, false, false>> = {<No data fields>}, _M_ptr = 0x555555997910, _M_refcount = {
      _M_pi = 0x555555997900}}, <No data fields>}
(gdb) p peer._M_refcount
$2 = {_M_pi = 0x555555997900}
(gdb) p peer._M_refcount._M_pi
$3 = (std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2> *) 0x555555997900
(gdb) p *peer._M_refcount._M_pi
$4 = {<std::_Mutex_base<(__gnu_cxx::_Lock_policy)2>> = {<No data fields>}, 
  _vptr._Sp_counted_base = 0x555555980010 <vtable for std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2>+16>, _M_use_count = 1, _M_weak_count = 1}
(gdb) p peer._M_refcount._M_pi._M_use_count
$5 = 1

Bingo: peer._M_refcount._M_pi._M_use_count

  1. Set hardware breakpoint
(gdb) p &peer._M_refcount._M_pi._M_use_count
$6 = (_Atomic_word *) 0x555555997908
(gdb) watch *0x555555997908
Hardware watchpoint 2: *0x555555997908
  1. Continue and hit breakpoints
(gdb) c
Continuing.

Thread 1 "run_http_server" hit Hardware watchpoint 2: *0x555555997908

Old value = 1
New value = 2
0x000055555569d456 in __gnu_cxx::__atomic_add (__mem=0x555555997908, __val=1) at /usr/include/c++/7/ext/atomicity.h:53
53	  { __atomic_fetch_add(__mem, __val, __ATOMIC_ACQ_REL); }
(gdb) bt
#0  0x000055555569d456 in __gnu_cxx::__atomic_add (__mem=0x555555997908, __val=1) at /usr/include/c++/7/ext/atomicity.h:53
#1  0x000055555569d513 in __gnu_cxx::__atomic_add_dispatch (__mem=0x555555997908, __val=1) at /usr/include/c++/7/ext/atomicity.h:96
#2  0x00005555556a44e1 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_add_ref_copy (this=0x555555997900) at /usr/include/c++/7/bits/shared_ptr_base.h:138
#3  0x00005555556a1589 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count (this=0x7fffffffda28, __r=...) at /usr/include/c++/7/bits/shared_ptr_base.h:691
#4  0x00005555557021df in std::__shared_ptr<Pistache::Tcp::Peer, (__gnu_cxx::_Lock_policy)2>::__shared_ptr (this=0x7fffffffda20) at /usr/include/c++/7/bits/shared_ptr_base.h:1121
#5  0x0000555555702205 in std::shared_ptr<Pistache::Tcp::Peer>::shared_ptr (this=0x7fffffffda20) at /usr/include/c++/7/bits/shared_ptr.h:119
#6  0x00005555556ff429 in Pistache::Tcp::Transport::handleNewPeer (this=0x555555997140, peer=...) at /home/adrian/work/pistache/pistache/src/common/transport.cc:51
#7  0x0000555555711a18 in Pistache::Tcp::Listener::dispatchPeer (this=0x555555994d00, peer=...) at /home/adrian/work/pistache/pistache/src/server/listener.cc:415
#8  0x00005555557118d2 in Pistache::Tcp::Listener::handleNewConnection (this=0x555555994d00) at /home/adrian/work/pistache/pistache/src/server/listener.cc:406
#9  0x0000555555710d69 in Pistache::Tcp::Listener::run (this=0x555555994d00) at /home/adrian/work/pistache/pistache/src/server/listener.cc:276
#10 0x000055555570f33c in Pistache::Http::Endpoint::serveImpl<void (Pistache::Tcp::Listener::*)()> (this=0x555555994cf0, 
    method=(void (Pistache::Tcp::Listener::*)(Pistache::Tcp::Listener * const)) 0x555555710ba0 <Pistache::Tcp::Listener::run()>) at /home/adrian/work/pistache/pistache/include/pistache/endpoint.h:141
#11 0x000055555570efbd in Pistache::Http::Endpoint::serve (this=0x555555994cf0) at /home/adrian/work/pistache/pistache/src/server/endpoint.cc:77
#12 0x000055555569d817 in main (argc=1, argv=0x7fffffffde68) at /home/adrian/work/pistache/pistache/examples/http_server.cc:187
(gdb) c
Continuing.
[Switching to Thread 0x7ffff6e85700 (LWP 3596)]

Thread 2 "run_http_server" hit Hardware watchpoint 2: *0x555555997908

Old value = 2
New value = 1
0x000055555569d43d in __gnu_cxx::__exchange_and_add (__mem=0x555555997908, __val=-1) at /usr/include/c++/7/ext/atomicity.h:49
49	  { return __atomic_fetch_add(__mem, __val, __ATOMIC_ACQ_REL); }
(gdb) bt
#0  0x000055555569d43d in __gnu_cxx::__exchange_and_add (__mem=0x555555997908, __val=-1) at /usr/include/c++/7/ext/atomicity.h:49
#1  0x000055555569d4d0 in __gnu_cxx::__exchange_and_add_dispatch (__mem=0x555555997908, __val=-1) at /usr/include/c++/7/ext/atomicity.h:82
#2  0x00005555556a43a5 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release (this=0x555555997900) at /usr/include/c++/7/bits/shared_ptr_base.h:151
#3  0x00005555556a14a9 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count (this=0x7ffff0000b78, __in_chrg=<optimized out>) at /usr/include/c++/7/bits/shared_ptr_base.h:684
#4  0x00005555556c17de in std::__shared_ptr<Pistache::Tcp::Peer, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr (this=0x7ffff0000b70, __in_chrg=<optimized out>) at /usr/include/c++/7/bits/shared_ptr_base.h:1123
#5  0x00005555556c17fa in std::shared_ptr<Pistache::Tcp::Peer>::~shared_ptr (this=0x7ffff0000b70, __in_chrg=<optimized out>) at /usr/include/c++/7/bits/shared_ptr.h:93
#6  0x000055555570ca2c in std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> >::~pair (this=0x7ffff0000b68, __in_chrg=<optimized out>) at /usr/include/c++/7/bits/stl_pair.h:198
#7  0x000055555570ca4c in __gnu_cxx::new_allocator<std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> > >::destroy<std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> > > (this=0x7ffff6e8399f, __p=0x7ffff0000b68)
    at /usr/include/c++/7/ext/new_allocator.h:140
#8  0x000055555570b52a in std::allocator_traits<std::allocator<std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> > > >::destroy<std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> > > (__a=..., __p=0x7ffff0000b68)
    at /usr/include/c++/7/bits/alloc_traits.h:487
#9  0x0000555555709564 in std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> >, false> > >::_M_deallocate_node (this=0x555555997258, __n=0x7ffff0000b60)
    at /usr/include/c++/7/bits/hashtable_policy.h:2084
#10 0x000055555570a2d3 in std::_Hashtable<int, std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> >, std::allocator<std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> > >, std::__detail::_Select1st, std::equal_to<int>, std::hash<int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase (this=0x555555997258, __bkt=0, 
    __prev_n=0x555555997268, __n=0x7ffff0000b60) at /usr/include/c++/7/bits/hashtable.h:1887
#11 0x0000555555707d14 in std::_Hashtable<int, std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> >, std::allocator<std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> > >, std::__detail::_Select1st, std::equal_to<int>, std::hash<int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::erase (this=0x555555997258, __it=...)
    at /usr/include/c++/7/bits/hashtable.h:1862
#12 0x00005555557055ef in std::_Hashtable<int, std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> >, std::allocator<std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> > >, std::__detail::_Select1st, std::equal_to<int>, std::hash<int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::erase (this=0x555555997258, __it=...)
    at /usr/include/c++/7/bits/hashtable.h:755
#13 0x0000555555703b09 in std::unordered_map<int, std::shared_ptr<Pistache::Tcp::Peer>, std::hash<int>, std::equal_to<int>, std::allocator<std::pair<int const, std::shared_ptr<Pistache::Tcp::Peer> > > >::erase (this=0x555555997258, 
    __position=...) at /usr/include/c++/7/bits/unordered_map.h:800
#14 0x00005555556ffe9a in Pistache::Tcp::Transport::handlePeerDisconnection (this=0x555555997140, peer=...) at /home/adrian/work/pistache/pistache/src/common/transport.cc:188
#15 0x00005555556ffd21 in Pistache::Tcp::Transport::handleIncoming (this=0x555555997140, peer=...) at /home/adrian/work/pistache/pistache/src/common/transport.cc:163
#16 0x00005555556ff797 in Pistache::Tcp::Transport::onReady (this=0x555555997140, fds=...) at /home/adrian/work/pistache/pistache/src/common/transport.cc:83
#17 0x0000555555722a19 in Pistache::Aio::SyncImpl::handleFds (this=0x555555995060, events=...) at /home/adrian/work/pistache/pistache/src/common/reactor.cc:189
#18 0x0000555555722542 in Pistache::Aio::SyncImpl::runOnce (this=0x555555995060) at /home/adrian/work/pistache/pistache/src/common/reactor.cc:149
#19 0x0000555555722697 in Pistache::Aio::SyncImpl::run (this=0x555555995060) at /home/adrian/work/pistache/pistache/src/common/reactor.cc:162
#20 0x0000555555723b58 in Pistache::Aio::AsyncImpl::Worker::run()::{lambda()#1}::operator()() const (__closure=0x555555997648) at /home/adrian/work/pistache/pistache/src/common/reactor.cc:470
#21 0x000055555572602b in std::__invoke_impl<void, Pistache::Aio::AsyncImpl::Worker::run()::{lambda()#1}>(std::__invoke_other, Pistache::Aio::AsyncImpl::Worker::run()::{lambda()#1}&&) (__f=...) at /usr/include/c++/7/bits/invoke.h:60
#22 0x0000555555724bc8 in std::__invoke<Pistache::Aio::AsyncImpl::Worker::run()::{lambda()#1}>(std::__invoke_result&&, (Pistache::Aio::AsyncImpl::Worker::run()::{lambda()#1}&&)...) (__fn=...) at /usr/include/c++/7/bits/invoke.h:95
#23 0x000055555572ac84 in std::thread::_Invoker<std::tuple<Pistache::Aio::AsyncImpl::Worker::run()::{lambda()#1}> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (this=0x555555997648) at /usr/include/c++/7/thread:234
#24 0x000055555572ac27 in std::thread::_Invoker<std::tuple<Pistache::Aio::AsyncImpl::Worker::run()::{lambda()#1}> >::operator()() (this=0x555555997648) at /usr/include/c++/7/thread:243
#25 0x000055555572abc6 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<Pistache::Aio::AsyncImpl::Worker::run()::{lambda()#1}> > >::_M_run() (this=0x555555997640) at /usr/include/c++/7/thread:186
#26 0x00007ffff78ea57f in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#27 0x00007ffff7bbd6db in start_thread (arg=0x7ffff6e85700) at pthread_create.c:463
#28 0x00007ffff734588f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95
  1. As you can see - we only have 1 refcount remaining. It was increased via:
#6  0x00005555556ff429 in Pistache::Tcp::Transport::handleNewPeer (this=0x555555997140, peer=...) at /home/adrian/work/pistache/pistache/src/common/transport.cc:51
#7  0x0000555555711a18 in Pistache::Tcp::Listener::dispatchPeer (this=0x555555994d00, peer=...) at /home/adrian/work/pistache/pistache/src/server/listener.cc:415
#8  0x00005555557118d2 in Pistache::Tcp::Listener::handleNewConnection (this=0x555555994d00) at /home/adrian/work/pistache/pistache/src/server/listener.cc:406
#9  0x0000555555710d69 in Pistache::Tcp::Listener::run (this=0x555555994d00) at /home/adrian/work/pistache/pistache/src/server/listener.cc:276

where code is:

void
Transport::handleNewPeer(const std::shared_ptr<Tcp::Peer>& peer) {
    auto ctx = context();
    const bool isInRightThread = std::this_thread::get_id() == ctx.thread();
    if (!isInRightThread) {
        PeerEntry entry(peer);
        peersQueue.push(std::move(entry));

So, it leaks when pushed to peersQueue. peersQueue doesn’t seem to decrease it. With a bit of looking over code we see that peersQueue is a lock free queue and the shared_ptr is stored via inplace new in a local storage and that’s never cleared.

Hope it helps, Project looks fun, I’ll see if I can help with other bugs.

Regards, Adrian Cruceru

I did not see any remaining in unit tests or my testing.

If you spot one with valgrind you can post it here and I/someone else can help fix it.

Current status:

      Start  1: mime_test
 1/23 MemCheck  #1: mime_test ........................   Passed    1.81 sec
      Start  2: headers_test
 2/23 MemCheck  #2: headers_test .....................   Passed    2.58 sec
      Start  3: async_test
 3/23 MemCheck  #3: async_test .......................   Passed  179.78 sec
      Start  4: typeid_test
 4/23 MemCheck  #4: typeid_test ......................   Passed    1.37 sec
      Start  5: router_test
 5/23 MemCheck  #5: router_test ......................   Passed    4.05 sec
      Start  6: cookie_test
 6/23 MemCheck  #6: cookie_test ......................   Passed    1.91 sec
      Start  7: cookie_test_2
 7/23 MemCheck  #7: cookie_test_2 ....................   Passed    1.58 sec
      Start  8: cookie_test_3
 8/23 MemCheck  #8: cookie_test_3 ....................   Passed    3.82 sec
      Start  9: view_test
 9/23 MemCheck  #9: view_test ........................   Passed    1.47 sec
      Start 10: http_parsing_test
10/23 MemCheck #10: http_parsing_test ................   Passed    2.14 sec
      Start 11: http_uri_test
11/23 MemCheck #11: http_uri_test ....................   Passed    1.89 sec
      Start 12: http_server_test
12/23 MemCheck #12: http_server_test .................   Passed   34.80 sec
      Start 13: http_client_test
13/23 MemCheck #13: http_client_test .................   Passed    8.52 sec
      Start 14: net_test
14/23 MemCheck #14: net_test .........................   Passed    1.50 sec
      Start 15: listener_test
15/23 MemCheck #15: listener_test ....................   Passed    2.45 sec
      Start 16: payload_test
16/23 MemCheck #16: payload_test .....................   Passed   10.81 sec
      Start 17: streaming_test
17/23 MemCheck #17: streaming_test ...................   Passed   27.96 sec
      Start 18: rest_server_test
18/23 MemCheck #18: rest_server_test .................   Passed    5.16 sec
      Start 19: string_view_test
19/23 MemCheck #19: string_view_test .................   Passed    1.60 sec
      Start 20: mailbox_test
20/23 MemCheck #20: mailbox_test .....................   Passed    2.08 sec
      Start 21: stream_test
21/23 MemCheck #21: stream_test ......................   Passed    1.72 sec
      Start 22: reactor_test
22/23 MemCheck #22: reactor_test .....................   Passed    3.45 sec
      Start 23: https_server_test
23/23 MemCheck #23: https_server_test ................   Passed    8.10 sec

100% tests passed, 0 tests failed out of 23

Well, this is unfortunate: “remote: Permission to oktal/pistache.git denied to AdrianCX”

How can I get access? 😃

In the meantime, here are the fixes: memleakfixes.diff.gz

If anyone is interested how I debugged/traced them I can add a follow up comment next few days.

Here’s a possible root cause for the leak:

peer.h:

    std::unordered_map<std::string, std::shared_ptr<void>> data_;

Notice the “void”. When that is hash table is destroyed, the shared_ptrs will not destroy objects.

putData does:

void
Handler::onConnection(const std::shared_ptr<Tcp::Peer>& peer) {
    peer->putData(ParserData, std::make_shared<Private::Parser<Http::Request>>());
}

If peer dies with data in the hashTable then that data is leaked. I don’t see any destructor in peer that would clean the hash table.

To test: Apply fix: https://github.com/arthurafarias/pistache/commit/90b7e324a141ffb48617421a3e57e9e106a5186a

Disable SIGINT signal handler (we want to close valgrind with CTRL-C)

# valgrind --leak-check=full run_http_server

Do some requests (telnet to 9080 and close it without sending an HTTP request) Close application.

==13834== 3,168 (672 direct, 2,496 indirect) bytes in 4 blocks are definitely lost in loss record 51 of 52
==13834==    at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==13834==    by 0x2D0934: __gnu_cxx::new_allocator<std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) (new_allocator.h:111)
==13834==    by 0x2CFC18: std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2> >&, unsigned long) (alloc_traits.h:436)
==13834==    by 0x2CF009: std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2> > > std::__allocate_guarded<std::allocator<std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2> > >(std::allocator<std::_Sp_counted_ptr_inplace<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, (__gnu_cxx::_Lock_policy)2> >&) (allocated_ptr.h:104)
==13834==    by 0x2CE6E2: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, Pistache::Address>(std::_Sp_make_shared_tag, Pistache::Tcp::Peer*, std::allocator<Pistache::Tcp::Peer> const&, Pistache::Address&&) (shared_ptr_base.h:635)
==13834==    by 0x2CDBD9: std::__shared_ptr<Pistache::Tcp::Peer, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<Pistache::Tcp::Peer>, Pistache::Address>(std::_Sp_make_shared_tag, std::allocator<Pistache::Tcp::Peer> const&, Pistache::Address&&) (shared_ptr_base.h:1295)
==13834==    by 0x2CCAE0: std::shared_ptr<Pistache::Tcp::Peer>::shared_ptr<std::allocator<Pistache::Tcp::Peer>, Pistache::Address>(std::_Sp_make_shared_tag, std::allocator<Pistache::Tcp::Peer> const&, Pistache::Address&&) (shared_ptr.h:344)
==13834==    by 0x2CAC8A: std::shared_ptr<Pistache::Tcp::Peer> std::allocate_shared<Pistache::Tcp::Peer, std::allocator<Pistache::Tcp::Peer>, Pistache::Address>(std::allocator<Pistache::Tcp::Peer> const&, Pistache::Address&&) (shared_ptr.h:691)
==13834==    by 0x2C8F01: std::shared_ptr<Pistache::Tcp::Peer> std::make_shared<Pistache::Tcp::Peer, Pistache::Address>(Pistache::Address&&) (shared_ptr.h:707)
==13834==    by 0x2C54A4: Pistache::Tcp::Listener::handleNewConnection() (listener.cc:392)
==13834==    by 0x2C4976: Pistache::Tcp::Listener::run() (listener.cc:270)
==13834==    by 0x2C2FD7: void Pistache::Http::Endpoint::serveImpl<void (Pistache::Tcp::Listener::*)()>(void (Pistache::Tcp::Listener::*)()) (endpoint.h:141)
==13834== 

@kiplingw Thanks, that’s good info, I will try and update the unit test / fix any issues I see while doing that.

Looking over Http::Client there is some weird logic involving multi-threading - doesn’t seem very safe to me so your note is valid - not really ready for production code.

bocse: Unfortunately, no. If I had a quick work around, I would have fixed the bug outright. That last I spoke with knoweldge4igor@ and hydratim@, they were working (independently?) on rewriting the problematic code. I could be mistaken though.

I’ve spent a few hours digging into the leak myself. I suspect that there is a circular smart pointer reference. A destructor is never executing.

For my own project which uses Pistache, the leak is not severe enough to cause a problem until I’ve had 100k’s of requests. Based on that, I suspect that the only thing leaking is the Peer object itself, but not the request or response buffers. But its been a few weeks since I dug into the problem.

I think so. “include/pistache/peer.h” contains a few member variables, but the “data_” is of particular interest. It is the buffered data from the TCP socket.