Сбой QSharedData

У меня нечастый, но довольно постоянный сбой в моем приложении Qt 5.2.0, у которого чертовски много времени на диагностику, но я полагаю, что он связан с QSharedData, Приложение является многопоточным, что, вероятно, является частью проблемы.

Рассматриваемый класс здесь:

class RouteData : public QSharedData
{
public:
  RouteData() :
      m_destAddress(0),
      m_valid(false),
      m_movingAverage(ROUTE_INITIAL_QUALITY)
  { }
  RouteData(const RouteData &other) :
      QSharedData(other),
      m_destAddress(other.m_destAddress),
      m_addresses(other.m_addresses),
      m_valid(other.m_valid),
      m_movingAverage(other.m_movingAverage),
      m_lastSuccess(other.m_lastSuccess),
      m_lastFailure(other.m_lastFailure)
  { }
  ~RouteData() { }

  quint16           m_destAddress;
  QList<quint16>    m_addresses;
  bool              m_valid;
  double            m_movingAverage;
  QDateTime         m_lastSuccess;
  QDateTime         m_lastFailure;
};

class Route
{
public:
    Route()
    {
        d = new RouteData;
    }
    Route(quint16 destAddress)
    {
        d = new RouteData;
        d->m_destAddress = destAddress;
    }
    Route(const Route &other) : d(other.d) {}

    QString toString() const;

    bool            isValid() const         { return d->m_valid; }
    quint16         destAddress() const     { return d->m_destAddress; }
    QList<quint16>  addressList() const     { return d->m_addresses; }
    quint32         length() const          { return d->m_addresses.length(); }
    double          quality() const         { return d->m_movingAverage; }
    quint64         msecsSinceLastFailure() const;

    void            setDestination(quint16 dest) { d->m_destAddress = dest; }
    void            setAddressList(const QList<quint16> &addressList);
    void            markResult(bool success);

    bool operator<(const Route& other) const;
    bool operator==(const Route& other) const;
    bool operator!=(const Route& other) const;

private:
    QSharedDataPointer<RouteData> d;
};

Q_DECLARE_TYPEINFO(Route, Q_MOVABLE_TYPE);

Здесь происходит сбой, вставляя маршрут в QMap:

SendResult EmberGateway::ezspSendUnicast(quint16 indexOrDestination, ..., const Route& route)
{
...
    if (route.isValid()) {
        m_lastRouteMap.insert(indexOrDestination, route);

где m_lastRouteMap является QMap<quint16, Route>,

И трассировка стека выглядит так:

(gdb) where
#0  0x00007fa297ced9a8 in QTimeZone::~QTimeZone() () from /usr/local/Trolltech/Qt-5.2.0/lib/libQt5Core.so.5
#1  0x00007fa297c96de5 in QDateTime::~QDateTime() () from /usr/local/Trolltech/Qt-5.2.0/lib/libQt5Core.so.5
#2  0x00000000004644fb in RouteData::~RouteData (this=0x7fa28c00b150, __in_chrg=<optimized out>) at ../libILS/libILS/Route.h:33
#3  0x0000000000600805 in QSharedDataPointer<RouteData>::operator= (this=0x7fa28c0e6420, o=...)
    at /usr/local/Trolltech/Qt-5.2.0/include/QtCore/qshareddata.h:98
#4  0x00000000005ff55b in Route::operator= (this=0x7fa28c0e6420) at ./libILS/Route.h:43
#5  0x0000000000652f8e in QMap<unsigned short, Route>::insert (this=0x7fa28c0880e8, akey=@0x7fa17c6feb44: 25504, avalue=...)
    at /usr/local/Trolltech/Qt-5.2.0/include/QtCore/qmap.h:682
#6  0x0000000000641b4b in ils::EmberGateway::ezspSendUnicast (this=0x7fa28c088090, indexOrDestination=25504, apsFrame=..., 
    data=..., route=...) at libILS/Gateways/EmberGateway.cpp:909
#7  0x00000000006371d5 in ils::EmberGateway::sendUnicast (this=0x7fa28c088090, destAddress=25504, array=..., route=...)
    at libILS/Gateways/EmberGateway.cpp:474
#8  0x00000000005fadc4 in NetworkController::sendMessageViaGateway (this=0x7fa28c03e9b0, message=...)
    at libILS/Controllers/NetworkController.cpp:1668
#9  0x00000000005ed8f4 in NetworkController::dispatchMessage (this=0x7fa28c03e9b0, pendingMessagePair=...)
    at libILS/Controllers/NetworkController.cpp:913
#10 0x0000000000604e2f in QtConcurrent::VoidStoredMemberFunctionPointerCall1<void, NetworkController, QPair<QSharedPointer<Message>, QTimer*>, QPair<QSharedPointer<Message>, QTimer*> >::runFunctor (this=0x7fa23804ac60)
    at /usr/local/Trolltech/Qt-5.2.0/include/QtConcurrent/qtconcurrentstoredfunctioncall.h:410
#11 0x00000000005ff41e in QtConcurrent::RunFunctionTask<void>::run (this=0x7fa23804ac60)
    at /usr/local/Trolltech/Qt-5.2.0/include/QtConcurrent/qtconcurrentrunbase.h:132
#12 0x00007fa297c55e52 in ?? () from /usr/local/Trolltech/Qt-5.2.0/lib/libQt5Core.so.5
#13 0x00007fa297c591c2 in ?? () from /usr/local/Trolltech/Qt-5.2.0/lib/libQt5Core.so.5
#14 0x00007fa297746f3a in start_thread () from /lib64/libpthread.so.0
#15 0x00007fa296c698fd in clone () from /lib64/libc.so.6

Таким образом, на #5 мы делаем QMap::insert, а на #4 создаем копию (с помощью оператора Route "="). На #3 рассматриваемый код Qt выглядит так:

inline QSharedDataPointer<T> & operator=(const QSharedDataPointer<T> &o) {
    if (o.d != d) {
        if (o.d)
            o.d->ref.ref();
        T *old = d;
        d = o.d;
        if (old && !old->ref.deref())
            delete old;
    }
    return *this;
}

Мы нажимаем "удалить старое" и переходим к ошибке в dtor для одного из QDateTimeх (на самом деле это личное QTimeZone членом).

мой ezspSendUnicast() Метод может нормально работать в течение сотен тысяч итераций перед сбоем. Я не думаю, что я теряю память (согласно Вальгринду). Я считаю, что объект Route, который я передаю в ezspSendUnicast(), должным образом защищен мьютексом, но, возможно, я что-то пропустил (но я думал, что QSharedData в любом случае был поточно-ориентированным для копирования при записи).

Любая идея о том, как решить эту проблему, будет принята с благодарностью!

1 ответ

Решение

Случаи QSharedDataпри доступе через отдельный экземпляр QSharedDataPointer, на самом деле, можно получить доступ из нескольких потоков одновременно, и это безопасно. Указатель общих данных будет атомарно принимать копию ссылочных данных по мере необходимости.

Итак, пока вы работаете над своим собственным экземпляром Route Объект, все в порядке. Что вы не можете сделать, это использовать ссылку на элемент, содержащийся в контейнере. Вы можете использовать const-ссылку на временный объект, созданный из объекта, хранящегося в контейнере, то есть на новый экземпляр.

Согласно документации:

Надлежащая блокировка должна использоваться при совместном использовании экземпляра неявно общего класса между потоками.

Неправильно получить доступ к ссылке на общий экземпляр неявно общего класса без удержания блокировки.

У вас всегда должен быть новый экземпляр. Вы можете скопировать конструкцию временного экземпляра и затем передать его через ссылку const, например, в качестве аргумента в вызове функции. Такие const-links-to-временные экземпляры задерживают уничтожение ссылочного временного объекта, пока они больше не нужны.

Это относится ко всем неявно разделяемым классам в Qt 4 и Qt 5 - независимо от того, исходят ли они из собственно Qt (все контейнеры!) Или из вашего собственного дизайна, который использует QSharedDataPointer,

Так что это будет правильно - вы сохраняете свой собственный экземпляр.

Route r(...);
QMutexLocker l(&routeMapMutex);
routeMap.insert(key, r);
l.unlock();
r.setDestination(...);
QMutexLocker l(&routeMapMutex);
routeMap.insert(key, r);
l.unlock();

Как это - опять же, у вас есть свой экземпляр.

QMutexLocker l(&routeMapMutex);
Route r = routeMap[key];
l.unlock();
if (r.isValid()) ...

Но это, безусловно, неверно, хотя ссылка постоянная.

QMutexLocker l(&routeMapMutex);
const Route & r = routeMap[key];
l.unlock();
if (r.isValid()) ...

Но это правильно, поскольку это постоянная ссылка на временный экземпляр, срок жизни которого по мере необходимости увеличивается. Итак, вы ссылаетесь на новый экземпляр, к которому никто кроме вас не имеет доступа.

QMutexLocker l(&routeMapMutex);
const Route & r = Route(routeMap[key]);
l.unlock();
if (r.isValid()) ...

Это также правильно, так как вызов защищен мьютексом:

void fun(const Route &);
QMutexLocker l(&routeMapMutex);
fun(routeMap[key]);
l.unlock();

Я догадываюсь, что где-то в вашем коде вы получаете доступ к ссылке на значение на карте (в отличие от const-ref для временного), не удерживая мьютекс.

Также возможно, что у вас есть какая-то другая ошибка памяти или многопоточности, которая проявляется в несвязанном месте.

Другие вопросы по тегам