如何在QNetworkReply :: deleteLater()中调试双删除

时间:2014-04-04 01:30:57

标签: c++ qt valgrind

我有一个用C ++编写的应用程序& Qt做了很多网络请求。我的代码的基本概要如下:

{
    QNetworkReply* reply = networkAccessManager().get( QNetworkRequest( url ) );
    assert( reply );

    connect( reply, &QNetworkReply::finished, [=]
    {
        // do action based on the contents of the reply

        assert( reply->isFinished() );
        reply->deleteLater();
    });
}

代码同时保留多个飞行请求。这两个断言从未被解雇过。

随机(大约每200000个请求),此回复的延迟删除失败,看起来是双重免费的。在Qt 5.0.2和Qt 5.2.x中都会发生这种情况。我已经运行了valgrind,结果如下:

==18792== Invalid read of size 8
==18792==    at 0x53AAC7A: QObject::~QObject() (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x4EB60A8: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Network.so.5.0.2)
==18792==    by 0x53A4357: QObject::event(QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x537EBBC: QCoreApplication::notify(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x537E8BD: QCoreApplication::notifyInternal(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x5380AC5: QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x53C38D4: QEventDispatcherUNIX::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x537D88A: QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x51F422A: QThread::exec() (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x51F8A4A: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x5812B4F: start_thread (pthread_create.c:304)
==18792==    by 0x62A1A7C: clone (clone.S:112)
==18792==  Address 0xb9fd670 is 0 bytes inside a block of size 16 free'd
==18792==    at 0x4C279DC: operator delete(void*) (vg_replace_malloc.c:457)
==18792==    by 0x53A4357: QObject::event(QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x537EBBC: QCoreApplication::notify(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x537E8BD: QCoreApplication::notifyInternal(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x5380AC5: QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x53C38D4: QEventDispatcherUNIX::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x537D88A: QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x538115F: QCoreApplication::exec() (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.0.2)
==18792==    by 0x4093C4: main (main.cpp:38)

我认为以下事情确实如此:

  • 主线程删除资源。
  • 网络线程然后尝试删除相同的资源。然后导致双重删除。哪个在某种程度上失败

我认为,但无法验证,以下情况属实:

  • 此资源是QNetworkReply。它可能是我不知道的一些Qt内部资源。除了这个QNetworkReply之外,我的代码库不包含经常创建或破坏的任何QObject。

我在尝试解决此错误时遇到了困难。从堆栈跟踪的检查中,看起来reply->deleteLater()信号以某种方式传递给网络线程和主线程。但我不明白这是怎么回事。信号和插槽编程风格使得很难确定出现错误的地方。

我将如何调试此错误?


答案提到了同步的可能起源。在我的代码库中,只允许从不同的线程调用1个类。这个类的功能分为两类:

  1. 查询一些内部状态。
  2. 发出信号。
  3. 第二类实现为:

    class Foo {
      Q_OBJECT
    public:
      void Foo() {
        connect( foo, &Foo::doSomethingSignal, this, &Foo:doSomethingInternal, Qt::QueuedConnection );
      }
    
      // this functions gets called from various threads
      void doSomething() {
        emit( doSomethingSignal() );
      }
    
    private slots:
      // this function happens synchronized in the main thread
      void doSomethingInternal() {
        ...
      }
    
    signals:
      void doSomethingSignal();
    }
    

    根据此stackoverflow问题:emit Qt signal from non Qt Thread or ouside Qt main event loop with at 4.5这是安全的。调用者不是QObject。

2 个答案:

答案 0 :(得分:1)

回答我自己的问题:

我已经制作了一个具有相当最小功能的测试用例。经过一些测试后,我的测试用例中似乎存在错误。这就是我制作错误报告的原因:https://bugreports.qt-project.org/browse/QTBUG-38309

可在此处查看测试用例:https://bitbucket.org/sdessens/qnetworkreply-access-violation-testcase/overview

存在一种解决方法,包括在删除回复之前等待几百毫秒,这在测试用例中工作正常,但由于某种原因不在我的应用程序中(几分钟后,事件循环似乎停止工作)。随机崩溃的影响不是地球散射,所以现在我在bash中坚持一个while循环以保持我的应用程序运行,直到Qt开发人员解决这个问题。

答案 1 :(得分:0)

从你的Valgrind错误报告中,看起来我们试图在其他一些线程释放它之后尝试读取内存。它看起来不像双重免费场景,而不是免费使用后。

  

== 18792 ==读取大小为8

     

== 18792 ==地址0xb9fd670是一个大小为16的块内的0字节免费

您可以查看我之前在Valgrind上的post以及如何在程序报告第一次错误时一起使用GDB / Valgrind来执行实时调试。

这个问题似乎是由于线程之间的同步问题,并且有时候一个线程正在释放其他线程不知道的线程。多线程环境中的内存相关问题很难理解和解决。您可能想要考虑使用上述注释中提供的建议(基于C ++ / RAII的类中的智能指针)。