Boost :: asio和boost :: bind:函数对象内存永远不会被释放

3

我的代码在分配内存后从未释放它,尽管我认为应该释放。

头文件如下:

typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslSocket_t;

class Object {
    boost::asio::io_service ioService_;
    boost::asio::ip::tcp::acceptor acceptor_;
    boost::asio::ssl::context context_;

    void functionOne();
    void functionTwo(shared_ptr<sslSocket_t>& sslSocket, const boost::system::error_code& error)
}

我的源代码如下:

void Object::functionOne() {
    for (int i = 0; i < 10; i++) {
        shared_ptr<sslSocket_t> sslSocket(new sslSocket_t(ioService_, context_));
        acceptor_.async_accept(sslSocket->lowest_layer(),
                       boost::bind(&Object::functionTwo, this, sslSocket, boost::asio::placeholders::error));
    }
    acceptor_.cancel();

    boost::asio::io_service::work work(ioService_);
    ioService_.run();
}

void functionTwo(shared_ptr<sslSocket_t>& sslSocket, const boost::system::error_code& err) {
    // Do nothing
}

当我调用Object.functionOne()时,内存被分配给Object.ioService_对象,以便能够调用绑定的异步方法。然后在循环之后,所有挂起的异步操作都将被取消。只要调用Object.ioService_.run(),适当的处理程序就会被调用(我一直在测试)。但出于某种原因,分配的内存没有被释放。所以有人能解释一下,为什么内存没有被释放,并给我一个提示如何释放它吗?
顺便说一下:我正在debian上工作,并查看/proc/self/status -> VmRSS以观察使用的内存。
@Vinnie Falco
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>

#include <iostream>
#include <memory>

typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslSocket_t;

using namespace std;

struct T  {

    boost::asio::io_service ioService_;
    boost::asio::ip::tcp::acceptor acceptor_;
    boost::asio::ssl::context context_;

    void functionOne() {
        for (int i = 0; i < 10; i++) {
            shared_ptr<sslSocket_t> sslSocket(new sslSocket_t(ioService_, context_));
            acceptor_.async_accept(sslSocket->lowest_layer(),
                               boost::bind(&T::functionTwo, this, sslSocket, boost::asio::placeholders::error));
        }
        acceptor_.cancel();

        boost::asio::io_service::work work(ioService_);
        ioService_.run();
    }

    void functionTwo(shared_ptr<sslSocket_t>& sslSocket, const boost::system::error_code& err) {
        // Do nothing
    }

    T() : acceptor_(ioService_,
                    boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 443)),
          context_(boost::asio::ssl::context::sslv23_server)  {

    }

    ~T()  {

    }
};

int main()  {

    try  {
        T t;

        t.functionOne();
    } catch (std::exception& e) {
        cout << "Exception: " << e.what() << endl;
  }
}

我的问题不是关于T的析构函数是否被调用,也不是为什么它被调用,这个已经能够正常工作了。但是我们有点奇怪的现象,具体涉及到内存的使用。
如果你增加for循环的限制条件,你会观察到程序会使用大量的内存,即使在所有异步处理程序调用后应该释放了所有内存。但是sslSocket对象没有被解除分配,这就是我的问题所在:为什么与functionTwo相关联的内存(特别是为sslSocket分配的内存)在异步方法functionTwo被调用后,没有被释放,而且不再存在任何对sslSocket的引用?

我最终解释我的疑虑(编辑4月28日)

好吧,我创建了一个可运行的示例来展示我的问题: My Problem in an example

输出:

Before leaking call:     6984 kB
Asynchronous calls of functionTwo: 10000
Memory while ioService is still running:   460244 kB
Memory after ioService is stopped:   460244 kB

更疯狂的是,在我自己的本地实现中,我得到了以下输出:
Memory leaking call:     8352 kB
Asynchronous calls of functionTwo: 10000
Memory while ioService is still running:   471932 kB
Memory after ioService is stopped:     8436 kB

因此可以清楚地看到:即使在调用了所有异步操作之后,内存也没有被释放。
总结和理解的行为(最后编辑)
正如你们中的一些人可能误解了一样,我并不认为我的代码中存在泄漏。我在代码示例中命名了结构为Leak,这可能会让你们感到困惑,但我的问题不是在我的示例中是否发生了内存泄漏,而是关于内存分配与ioService对象的组合。起初,我认为所声称的内存无限增加。我最后尝试了解决这个问题,并得出结论,内存管理是好的。内存没有被操作系统回收,但程序的内存分配正在收敛到一个极限,这对我来说很好。所以这个问题已经解决了。 示例:收敛内存消耗 最让我困扰的是,我的本地实现显示了略微不同的行为。当ioService对象完成其工作并重置时,操作系统会回收内存,这符合我的预期。

总结所有观察结果:

分配的内存由C++运行时和操作系统管理。直接观察分配过程相当困难(甚至不可能?),因为它被优化以减少对新内存页的请求次数,这意味着分配和释放的内存可能不会立即被操作系统重新分配。

为了向我指出这种行为的关键点,我想描述一下我的程序的用途:我正在开发一个服务器应用程序,这意味着程序应该无限运行。如果程序在某个时候声称需要大量峰值内存,那么完全没有问题,但它需要在运行时的某个时刻释放所需内存,而不是在运行后。对我来说,只剩下一个问题:

未使用的已声称内存是否会在某个时候被操作系统回收?还是我需要在运行时自己管理内存(使用newdelete)?


这个服务器可以接受多少个连接? - Nim
@Nim 没有问题。这只涉及到消耗的内存,它可以通过构建 sslSocket 并添加异步操作来简单完成。 - Norman
好的 - 处理程序只有在连接时才会执行一次,否则它将简单地保持套接字直到有一个接受 - 无论如何 - 看看我的答案 - 你使用asio和异步操作的方法是错误的。 - Nim
啊 - 错过了那个,是的 - 应该被中止。您能否将 ssl socket 封装在一个简单的类中,并查看该类的析构函数是否被触发。如果某个共享指针存在悬挂引用,我会非常惊讶... - Nim
@Nim 这是那个程序的链接 http://melpon.org/wandbox/permlink/k3vmCNBvNPxiM2h6 - Vinnie Falco
显示剩余7条评论
4个回答

1

我花了一些时间,但最终成功自己解决了问题。

所以为了澄清事情,让我们确保理解我的问题的根源:我正在开发一个服务器应用程序,它应该运行无限长的时间。这个应用程序必须能够处理大量并发的传入连接。在某个时刻,可能会出现负载峰值,导致我的应用程序要求大量的内存。然后过了一段时间,大部分传入请求已经被处理,导致很多对象在运行时被释放。由于操作系统不需要内存(我的应用程序是服务器上唯一的大内存消费者),所有释放的内存都留给我的应用程序,并可以在另一个时间点重复使用。这对我来说完全没问题,但有些客户和管理员可能会误解不断占用的内存量为内存泄漏应用程序。为了避免这种情况,我想手动将一些未使用的内存交还给操作系统。在我的例子中,绑定到ioService的处理程序(例如接受新连接)将在运行时被释放,但相应的内存不会被操作系统回收。因此,为了手动执行此操作,我找到了以下解决方案:

在C/C++中释放Linux下未使用的堆内存:int malloc_trim(size_t pad)

文档可以在这里找到。简单地解释一下,该方法将未使用的内存从堆中释放到操作系统中,这正是我一直在寻找的。我知道,在内存优化方面,手动使用此函数可能会有危险,但由于我只想每隔几分钟释放一次内存,所以这个性能问题对我来说是可以接受的。

感谢大家的努力和耐心!


1

我不确定问题出在哪里,但我认为你做错了什么。你能提供一个自包含的示例来展示问题吗?这个样例程序可以编译和运行,并且析构函数被调用:

#include <boost/asio.hpp>
#include <functional>
#include <iostream>
#include <memory>

struct T
{
    T()
    {
        std::cerr << "T::T()\n";
    }

    ~T()
    {
        std::cerr << "T::~T()\n";
    }
};

void f(std::shared_ptr<T>&)
{
}

int main()
{
    using namespace boost::asio;
    io_service ios;
    ios.post(std::bind(&f, std::make_shared<T>()));
    ios.run();
}

你可以在这里查看输出:http://melpon.org/wandbox/permlink/0fkIAnoMXDOeedx7 输出结果:
T::T()
T::~T()

0

将您的自包含示例在valgrind下运行,显示没有任何泄漏

==14098== 
==14098== HEAP SUMMARY:
==14098==     in use at exit: 73,696 bytes in 7 blocks
==14098==   total heap usage: 163,524 allocs, 163,517 frees, 733,133,505 bytes allocated
==14098== 
==14098== LEAK SUMMARY:
==14098==    definitely lost: 0 bytes in 0 blocks
==14098==    indirectly lost: 0 bytes in 0 blocks
==14098==      possibly lost: 0 bytes in 0 blocks
==14098==    still reachable: 73,696 bytes in 7 blocks
==14098==         suppressed: 0 bytes in 0 blocks
==14098== Rerun with --leak-check=full to see details of leaked memory

如果您提供valgrind --show-leak-kinds=all --leak-check=full ./test,您会发现“泄漏”的(剩余可达)是通常从libssl/libcrypto分配的静态内容。即使只进行1次迭代,它们也会被分配。10000次迭代也没有变化。
==14214== Memcheck, a memory error detector
==14214== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==14214== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==14214== Command: ./test 10000
==14214== 
Before leaking call:    50056 kB
Asynchronous calls of functionTwo: 10000
Memory while ioService is still running:   265592 kB
Memory after ioService is stopped:   265592 kB
==14214== 
==14214== HEAP SUMMARY:
==14214==     in use at exit: 73,696 bytes in 7 blocks
==14214==   total heap usage: 163,524 allocs, 163,517 frees, 733,133,505 bytes allocated
==14214== 
==14214== 24 bytes in 1 blocks are still reachable in loss record 1 of 7
==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14214==    by 0x5307E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53BF315: lh_insert (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C1863: ??? (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C245D: ERR_get_state (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C25EE: ERR_clear_error (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x40C9CA: context (context.ipp:70)
==14214==    by 0x40C9CA: Leak::Leak() (test.cpp:77)
==14214==    by 0x403E13: main (test.cpp:86)
==14214== 
==14214== 32 bytes in 1 blocks are still reachable in loss record 2 of 7
==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14214==    by 0x5307E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53BE7AE: sk_new (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x507FD69: ??? (in /lib/x86_64-linux-gnu/libssl.so.1.0.0)
==14214==    by 0x5081E68: SSL_COMP_get_compression_methods (in /lib/x86_64-linux-gnu/libssl.so.1.0.0)
==14214==    by 0x5087532: SSL_library_init (in /lib/x86_64-linux-gnu/libssl.so.1.0.0)
==14214==    by 0x40B9A8: do_init (openssl_init.ipp:39)
==14214==    by 0x40B9A8: boost::asio::ssl::detail::openssl_init_base::instance() (openssl_init.ipp:131)
==14214==    by 0x403C3C: openssl_init (openssl_init.hpp:60)
==14214==    by 0x403C3C: __static_initialization_and_destruction_0 (openssl_init.hpp:90)
==14214==    by 0x403C3C: _GLOBAL__sub_I_count (test.cpp:96)
==14214==    by 0x40FE1C: __libc_csu_init (in /home/sehe/Projects/stackoverflow/test)
==14214==    by 0x5EC09CE: (below main) (libc-start.c:245)
==14214== 
==14214== 32 bytes in 1 blocks are still reachable in loss record 3 of 7
==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14214==    by 0x5307E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53BE7CC: sk_new (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x507FD69: ??? (in /lib/x86_64-linux-gnu/libssl.so.1.0.0)
==14214==    by 0x5081E68: SSL_COMP_get_compression_methods (in /lib/x86_64-linux-gnu/libssl.so.1.0.0)
==14214==    by 0x5087532: SSL_library_init (in /lib/x86_64-linux-gnu/libssl.so.1.0.0)
==14214==    by 0x40B9A8: do_init (openssl_init.ipp:39)
==14214==    by 0x40B9A8: boost::asio::ssl::detail::openssl_init_base::instance() (openssl_init.ipp:131)
==14214==    by 0x403C3C: openssl_init (openssl_init.hpp:60)
==14214==    by 0x403C3C: __static_initialization_and_destruction_0 (openssl_init.hpp:90)
==14214==    by 0x403C3C: _GLOBAL__sub_I_count (test.cpp:96)
==14214==    by 0x40FE1C: __libc_csu_init (in /home/sehe/Projects/stackoverflow/test)
==14214==    by 0x5EC09CE: (below main) (libc-start.c:245)
==14214== 
==14214== 128 bytes in 1 blocks are still reachable in loss record 4 of 7
==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14214==    by 0x5307E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53BEFE1: lh_new (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C1512: ??? (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C182F: ??? (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C245D: ERR_get_state (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C25EE: ERR_clear_error (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x40C9CA: context (context.ipp:70)
==14214==    by 0x40C9CA: Leak::Leak() (test.cpp:77)
==14214==    by 0x403E13: main (test.cpp:86)
==14214== 
==14214== 176 bytes in 1 blocks are still reachable in loss record 5 of 7
==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14214==    by 0x5307E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53BEFBF: lh_new (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C1512: ??? (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C182F: ??? (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C245D: ERR_get_state (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C25EE: ERR_clear_error (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x40C9CA: context (context.ipp:70)
==14214==    by 0x40C9CA: Leak::Leak() (test.cpp:77)
==14214==    by 0x403E13: main (test.cpp:86)
==14214== 
==14214== 600 bytes in 1 blocks are still reachable in loss record 6 of 7
==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14214==    by 0x5307E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C23F5: ERR_get_state (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x53C25EE: ERR_clear_error (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
==14214==    by 0x40C9CA: context (context.ipp:70)
==14214==    by 0x40C9CA: Leak::Leak() (test.cpp:77)
==14214==    by 0x403E13: main (test.cpp:86)
==14214== 
==14214== 72,704 bytes in 1 blocks are still reachable in loss record 7 of 7
==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14214==    by 0x57731FF: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==14214==    by 0x4010609: call_init.part.0 (dl-init.c:72)
==14214==    by 0x401071A: call_init (dl-init.c:30)
==14214==    by 0x401071A: _dl_init (dl-init.c:120)
==14214==    by 0x4000D09: ??? (in /lib/x86_64-linux-gnu/ld-2.21.so)
==14214==    by 0x1: ???
==14214==    by 0xFFEFFFF76: ???
==14214==    by 0xFFEFFFF7D: ???
==14214== 
==14214== LEAK SUMMARY:
==14214==    definitely lost: 0 bytes in 0 blocks
==14214==    indirectly lost: 0 bytes in 0 blocks
==14214==      possibly lost: 0 bytes in 0 blocks
==14214==    still reachable: 73,696 bytes in 7 blocks
==14214==         suppressed: 0 bytes in 0 blocks
==14214== 
==14214== For counts of detected and suppressed errors, rerun with: -v
==14214== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

你的内存使用量测量方式不可靠。


感谢您的努力,但正如我在最终总结中提到的那样,我不是在寻找某种泄漏,而是试图理解内存分配和释放与 ioService 对象的组合如何工作。 - Norman

-1

我认为你的方法有问题。你不应该将异步操作与 asio 混合使用。如果你这样做,所有种类的未定义垃圾都会发生。通常实现异步接受的方法如下:

void do_accept() {
  shared_ptr<sslSocket_t> socket(new sslSocket_t(ioService_, context_));
  // Queue an async accept operation
  acceptor_.async_accept(socket->lowest_layer(), [this, socket](auto ec) {
    if (!ec) {
      // Handle the socket
    }
    // If not shutting down
    this->do_accept(); // next accept
  });
}

我并不是给你点了“踩”的那个人,但是我要说的是,你所提出的不应该交错异步操作的大胆论断是不正确的。你可以任意交错它们;只是不能保证执行顺序(实际上可能是并发的)。在某些设计中,这可能完全没有问题。而在其他情况下,则不然。 - Kaz Dragon
@KazDragon - 对于一个套接字,当文档说以下内容时(例如对于async_read)我不确定我是否同意:“此操作是通过调用流的async_read_some函数零次或多次来实现的,并且被称为组合操作。程序必须确保在此操作完成之前,流不执行任何其他读取操作(例如async_read、流的async_read_some函数或执行读取的任何其他组合操作)。" - Nim
@KazDragon,对于 accept 方法来说,从文档中不清楚是否安全地交错使用,但对于其他操作(例如写入),同样适用原则。但是您可以混合使用读写操作 - 但这是显而易见的。 - Nim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接