Fix memory leak in build_kv_error_map_info when building KV exception

Description

When building the KV error map, build_kv_error_map_info(), creates two memory leaks by adding strings directly to the err_info dict using the PyDict_SetItemString().

To fix the leak:

  1. created a PyObject* that stores the string

  2. add the created PyObject* to the err_info dict

  3. decrement the created PyObject* after it has been added to the dict

Leak:

if (-1 == PyDict_SetItemString(err_info, "name", PyUnicode_FromString(error_info.name.c_str()))) { PyErr_Print(); PyErr_Clear(); } if (-1 == PyDict_SetItemString(err_info, "description", PyUnicode_FromString(error_info.description.c_str()))) { PyErr_Print(); PyErr_Clear(); }

Also, call tp_free() in the exception_base destructor (see associated ticket https://couchbasecloud.atlassian.net/browse/PYCBC-1382#icft=PYCBC-1382).

Environment

None

Gerrit Reviews

None

Release Notes Description

None

Activity

Show:

Jared Casey July 21, 2022 at 2:51 PM

Fixed memory leak when building KV error map and fix memory leak related to exception_base custom type.

Jared Casey July 16, 2022 at 12:10 AM
Edited

Attached both some tracemalloc outputs and valgrind outputs (valgrind outputs are ~20 MB, so all are attached in a zip).

tracemalloc

A simple program was used to loop a handful of times and do a basic key-value operation (or multi key-value operation) that looks up non-existent keys and for each iteration a diff of tracemalloc snapshots are taken (snapshot taken after manually running garbage collection to reduce noise) to see changes that might be happening during each iteration. In the output logs labeled orig_*, one can see that on each iteration more memory is taken up and the amount of blocks taken up correspond to a multiple of 3 to the number of exceptions raised (notice that for a multi result, you have 1 + the number of results in a batch because the multi-result is a result object itself and is used to store all the result objects for the key-value operation it ran). The reason for the multiple is due to the leaks in the build_kv_error_map_info() method (2 blocks) and then the tp_free() method not being called in the exception type's destructor (1 block).

The corresponding fix_* files show the same test run w/ the applied fix mentioned in this ticket.

Valgrind

In orig_simple_kv_fake_keys_loop100.xml OR orig_simple_multi_kv_fake_keys_b20_loop100.xml search for

  • exception_base_new_: shows the exception object leak

If the loop count is changed, the number of leaked blocks for the result will follow the loop count

In fix_simple_kv_fake_keys_loop100.xml OR fix_simple_multi_kv_fake_keys_b20_loop100.xml search results for the follow will come up empty (i.e. indicated the leak has been fixed)

  • exception_base_new_

Example valgrind output (loop of 100):

<error> <unique>0xf23</unique> <tid>1</tid> <kind>Leak_DefinitelyLost</kind> <xwhat> <text>4,800 bytes in 100 blocks are definitely lost in loss record 3,874 of 3,892</text> <leakedbytes>4800</leakedbytes> <leakedblocks>100</leakedblocks> </xwhat> <stack> <frame> <ip>0x483B7F3</ip> <obj>/usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so</obj> <fn>malloc</fn> </frame> <frame> <ip>0x5A55E7</ip> <obj>/usr/bin/python3.8</obj> <fn>PyType_GenericAlloc</fn> <dir>/build/python3.8-uvizni/python3.8-3.8.10/build-static/../Objects</dir> <file>typeobject.c</file> <line>1017</line> </frame> <frame> <ip>0x6073CF8</ip> <obj>/memory_tests/couchbase-python-client/couchbase/pycbc_core.so</obj> <fn>exception_base__new__(_typeobject*, _object*, _object*)</fn> </frame> <frame> <ip>0x5F73E2</ip> <obj>/usr/bin/python3.8</obj> <fn>UnknownInlinedFun</fn> <dir>/build/python3.8-uvizni/python3.8-3.8.10/build-static/../Objects</dir> <file>typeobject.c</file> <line>974</line> </frame> <frame> <ip>0x5F73E2</ip> <obj>/usr/bin/python3.8</obj> <fn>_PyObject_MakeTpCall</fn> <dir>/build/python3.8-uvizni/python3.8-3.8.10/build-static/../Objects</dir> <file>call.c</file> <line>159</line> </frame> <frame> <ip>0x5F8999</ip> <obj>/usr/bin/python3.8</obj> <fn>_PyObject_FastCallDict</fn> <dir>/build/python3.8-uvizni/python3.8-3.8.10/build-static/../Objects</dir> <file>call.c</file> <line>91</line> </frame> <frame> <ip>0x5FF6A39</ip> <obj>/memory_tests/couchbase-python-client/couchbase/pycbc_core.so</obj> <fn>_object* build_exception_from_context&lt;couchbase::error_context::key_value&gt;(couchbase::error_context::key_value const&amp;, char const*, int, std::__cxx11::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;, std::__cxx11::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;)</fn> </frame> <frame> <ip>0x6082F16</ip> <obj>/memory_tests/couchbase-python-client/couchbase/pycbc_core.so</obj> <fn>void create_result_from_get_operation_response&lt;couchbase::operations::get_response&gt;(char const*, couchbase::operations::get_response const&amp;, _object*, _object*, std::shared_ptr&lt;std::promise&lt;_object*&gt; &gt;, result*)</fn> </frame> <frame> <ip>0x6089D26</ip> <obj>/memory_tests/couchbase-python-client/couchbase/pycbc_core.so</obj> <fn>void couchbase::bucket::execute&lt;couchbase::operations::get_request, void do_get&lt;couchbase::operations::get_request&gt;(connection&amp;, couchbase::operations::get_request&amp;, _object*, _object*, std::shared_ptr&lt;std::promise&lt;_object*&gt; &gt;, result*)::{lambda(couchbase::operations::get_response)#1}&gt;(couchbase::operations::get_request, void do_get&lt;couchbase::operations::get_request&gt;(connection&amp;, couchbase::operations::get_request&amp;, _object*, _object*, std::shared_ptr&lt;std::promise&lt;_object*&gt; &gt;, result*)::{lambda(couchbase::operations::get_response)#1}&amp;&amp;)::{lambda(std::error_code, std::optional&lt;couchbase::io::mcbp_message&gt;)#1}::operator()(void do_get&lt;couchbase::operations::get_request&gt;(connection&amp;, couchbase::operations::get_request&amp;, _object*, _object*, std::shared_ptr&lt;std::promise&lt;_object*&gt; &gt;, result*)::{lambda(couchbase::operations::get_response)#1}&amp;&amp;, couchbase::io::mcbp_message)</fn> </frame> <frame> <ip>0x608A611</ip> <obj>/memory_tests/couchbase-python-client/couchbase/pycbc_core.so</obj> <fn>std::_Function_handler&lt;void (std::error_code, std::optional&lt;couchbase::io::mcbp_message&gt;), couchbase::utils::movable_function&lt;void (std::error_code, std::optional&lt;couchbase::io::mcbp_message&gt;)&gt;::wrapper&lt;void couchbase::bucket::execute&lt;couchbase::operations::get_request, void do_get&lt;couchbase::operations::get_request&gt;(connection&amp;, couchbase::operations::get_request&amp;, _object*, _object*, std::shared_ptr&lt;std::promise&lt;_object*&gt; &gt;, result*)::{lambda(couchbase::operations::get_response)#1}&gt;(couchbase::operations::get_request, void do_get&lt;couchbase::operations::get_request&gt;(connection&amp;, couchbase::operations::get_request&amp;, _object*, _object*, std::shared_ptr&lt;std::promise&lt;_object*&gt; &gt;, result*)::{lambda(couchbase::operations::get_response)#1}&amp;&amp;)::{lambda(std::error_code, std::optional&lt;couchbase::io::mcbp_message&gt;)#1}, void&gt; &gt;::_M_invoke(std::_Any_data const&amp;, std::error_code&amp;&amp;, std::optional&lt;couchbase::io::mcbp_message&gt;&amp;&amp;)</fn> </frame> <frame> <ip>0x6092B4C</ip> <obj>/memory_tests/couchbase-python-client/couchbase/pycbc_core.so</obj> <fn>couchbase::operations::mcbp_command&lt;couchbase::bucket, couchbase::operations::get_request&gt;::invoke_handler(std::error_code, std::optional&lt;couchbase::io::mcbp_message&gt;)</fn> </frame> <frame> <ip>0x60AD272</ip> <obj>/memory_tests/couchbase-python-client/couchbase/pycbc_core.so</obj> <fn>couchbase::operations::mcbp_command&lt;couchbase::bucket, couchbase::operations::get_request&gt;::send()::{lambda(std::error_code, couchbase::io::retry_reason, couchbase::io::mcbp_message&amp;&amp;)#1}::operator()(std::error_code, couchbase::io::retry_reason, couchbase::io::mcbp_message&amp;&amp;)</fn> </frame> <frame> <ip>0x5FED3B6</ip> <obj>/memory_tests/couchbase-python-client/couchbase/pycbc_core.so</obj> <fn>operator()</fn> <dir>/usr/include/c++/9/bits</dir> <file>std_function.h</file> <line>688</line> </frame> <frame> <ip>0x5FED3B6</ip> <obj>/memory_tests/couchbase-python-client/couchbase/pycbc_core.so</obj> <fn>operator()&lt;std::error_code, couchbase::io::retry_reason, couchbase::io::mcbp_message&gt;</fn> <dir>/memory_tests/couchbase-python-client/deps/couchbase-cxx-client/couchbase/utils</dir> <file>movable_function.hxx</file> <line>40</line> </frame> <frame> <ip>0x5FED3B6</ip> <obj>/memory_tests/couchbase-python-client/couchbase/pycbc_core.so</obj> <fn>std::_Function_handler&lt;void (std::error_code, couchbase::io::retry_reason, couchbase::io::mcbp_message&amp;&amp;), couchbase::utils::movable_function&lt;void (std::error_code, couchbase::io::retry_reason, couchbase::io::mcbp_message&amp;&amp;)&gt;::wrapper&lt;std::function&lt;void (std::error_code, couchbase::io::retry_reason, couchbase::io::mcbp_message&amp;&amp;)&gt;, void&gt; &gt;::_M_invoke(std::_Any_data const&amp;, std::error_code&amp;&amp;, couchbase::io::retry_reason&amp;&amp;, couchbase::io::mcbp_message&amp;&amp;)</fn> <dir>/usr/include/c++/9/bits</dir> <file>std_function.h</file> <line>300</line> </frame> </stack> </error>
Fixed
Pinned fields
Click on the next to a field label to start pinning.

Details

Assignee

Reporter

Story Points

Fix versions

Affects versions

Priority

Instabug

Open Instabug

PagerDuty

Sentry

Zendesk Support

Created July 15, 2022 at 8:31 PM
Updated October 22, 2024 at 6:23 PM
Resolved July 21, 2022 at 2:51 PM
Instabug