2014-02-25 16:16:11 +01:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
2016-01-15 14:57:40 +01:00
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2014-02-25 16:16:11 +01:00
|
|
|
**
|
|
|
|
|
** This file is part of Qt Creator.
|
|
|
|
|
**
|
|
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
2016-01-15 14:57:40 +01:00
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
2014-02-25 16:16:11 +01:00
|
|
|
**
|
2016-01-15 14:57:40 +01:00
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
2014-02-25 16:16:11 +01:00
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "stringtable.h"
|
|
|
|
|
|
2014-06-22 11:57:21 -07:00
|
|
|
#include <utils/qtcassert.h>
|
2021-03-03 15:35:34 +01:00
|
|
|
#include <utils/runextensions.h>
|
2014-06-22 11:57:21 -07:00
|
|
|
|
2014-02-25 16:16:11 +01:00
|
|
|
#include <QDebug>
|
2021-03-09 23:01:39 +01:00
|
|
|
#include <QElapsedTimer>
|
2018-02-07 13:18:48 +01:00
|
|
|
#include <QMutex>
|
|
|
|
|
#include <QSet>
|
2014-02-25 16:16:11 +01:00
|
|
|
#include <QThreadPool>
|
2018-02-07 13:18:48 +01:00
|
|
|
#include <QTimer>
|
2014-02-25 16:16:11 +01:00
|
|
|
|
|
|
|
|
using namespace CppTools::Internal;
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
GCTimeOut = 10 * 1000 // 10 seconds
|
|
|
|
|
};
|
|
|
|
|
|
2018-02-07 13:18:48 +01:00
|
|
|
enum {
|
|
|
|
|
DebugStringTable = 0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class StringTablePrivate : public QObject
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
StringTablePrivate();
|
StringTable: Ensure only one GC() thread is running at a time
The possible issue with the current implementation is that
in theory many possible GC() are being executed in parallel.
In this case just one of them is really working and others
are waiting on the locked mutex (first line of the GC()
method). In such a scenario when a call
to StringTablePrivate::insert() is being executed from one
more thread, it may happen that the working GC() thread
is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true)
was executed from insert()) and later the mutex lock may be
granted to the other awaiting GC() thread instead to the
thread which executes insert() method. In this unlikely
scenario the GC() thread won't be canceled and the lock
inside the insert() method may be locked for considerable
amount of time, what is not desired.
The goal of this patch is to resolve the possible issue above
and to simplify the code by eliminating the m_stopGCRequested
variable and make use of QFuture.cancel() / QFuture.isCanceled()
API instead. In addition, since we control now only one
possible thread that executes the GC(), there is no need for
future synchonizer anymore.
GC() function can't be run in parallel in different threads,
as the whole body of GC() is protected with mutex. This means
that whenever a new scheduled call to GC() is being executed,
this new call waits on the mentioned mutex at the beginning of GC().
So, instead of protecting the whole body of GC() with a mutex,
we ensure that the old call to GC() is already finished (if not,
we also cancel the old call) while preparing an asynchronous
call to start a new GC() from inside startGC() method.
Whenever we are calling the insert() method, we still protect the access
to m_strings with a mutex (as insert() is designed to be called
from different threads in parallel). Just after locking the mutex
we are canceling any possible ongoing call to GC(). After canceling
the GC() call, we are sure that no new call to GC() will be executed
until we unlock the mutex, so it's safe now to modify the m_string
data.
Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
|
|
|
~StringTablePrivate() override { cancelAndWait(); }
|
2018-02-07 13:18:48 +01:00
|
|
|
|
StringTable: Ensure only one GC() thread is running at a time
The possible issue with the current implementation is that
in theory many possible GC() are being executed in parallel.
In this case just one of them is really working and others
are waiting on the locked mutex (first line of the GC()
method). In such a scenario when a call
to StringTablePrivate::insert() is being executed from one
more thread, it may happen that the working GC() thread
is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true)
was executed from insert()) and later the mutex lock may be
granted to the other awaiting GC() thread instead to the
thread which executes insert() method. In this unlikely
scenario the GC() thread won't be canceled and the lock
inside the insert() method may be locked for considerable
amount of time, what is not desired.
The goal of this patch is to resolve the possible issue above
and to simplify the code by eliminating the m_stopGCRequested
variable and make use of QFuture.cancel() / QFuture.isCanceled()
API instead. In addition, since we control now only one
possible thread that executes the GC(), there is no need for
future synchonizer anymore.
GC() function can't be run in parallel in different threads,
as the whole body of GC() is protected with mutex. This means
that whenever a new scheduled call to GC() is being executed,
this new call waits on the mentioned mutex at the beginning of GC().
So, instead of protecting the whole body of GC() with a mutex,
we ensure that the old call to GC() is already finished (if not,
we also cancel the old call) while preparing an asynchronous
call to start a new GC() from inside startGC() method.
Whenever we are calling the insert() method, we still protect the access
to m_strings with a mutex (as insert() is designed to be called
from different threads in parallel). Just after locking the mutex
we are canceling any possible ongoing call to GC(). After canceling
the GC() call, we are sure that no new call to GC() will be executed
until we unlock the mutex, so it's safe now to modify the m_string
data.
Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
|
|
|
void cancelAndWait();
|
2018-02-07 13:18:48 +01:00
|
|
|
QString insert(const QString &string);
|
StringTable: Ensure only one GC() thread is running at a time
The possible issue with the current implementation is that
in theory many possible GC() are being executed in parallel.
In this case just one of them is really working and others
are waiting on the locked mutex (first line of the GC()
method). In such a scenario when a call
to StringTablePrivate::insert() is being executed from one
more thread, it may happen that the working GC() thread
is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true)
was executed from insert()) and later the mutex lock may be
granted to the other awaiting GC() thread instead to the
thread which executes insert() method. In this unlikely
scenario the GC() thread won't be canceled and the lock
inside the insert() method may be locked for considerable
amount of time, what is not desired.
The goal of this patch is to resolve the possible issue above
and to simplify the code by eliminating the m_stopGCRequested
variable and make use of QFuture.cancel() / QFuture.isCanceled()
API instead. In addition, since we control now only one
possible thread that executes the GC(), there is no need for
future synchonizer anymore.
GC() function can't be run in parallel in different threads,
as the whole body of GC() is protected with mutex. This means
that whenever a new scheduled call to GC() is being executed,
this new call waits on the mentioned mutex at the beginning of GC().
So, instead of protecting the whole body of GC() with a mutex,
we ensure that the old call to GC() is already finished (if not,
we also cancel the old call) while preparing an asynchronous
call to start a new GC() from inside startGC() method.
Whenever we are calling the insert() method, we still protect the access
to m_strings with a mutex (as insert() is designed to be called
from different threads in parallel). Just after locking the mutex
we are canceling any possible ongoing call to GC(). After canceling
the GC() call, we are sure that no new call to GC() will be executed
until we unlock the mutex, so it's safe now to modify the m_string
data.
Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
|
|
|
void startGC();
|
|
|
|
|
void GC(QFutureInterface<void> &futureInterface);
|
2018-02-07 13:18:48 +01:00
|
|
|
|
StringTable: Ensure only one GC() thread is running at a time
The possible issue with the current implementation is that
in theory many possible GC() are being executed in parallel.
In this case just one of them is really working and others
are waiting on the locked mutex (first line of the GC()
method). In such a scenario when a call
to StringTablePrivate::insert() is being executed from one
more thread, it may happen that the working GC() thread
is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true)
was executed from insert()) and later the mutex lock may be
granted to the other awaiting GC() thread instead to the
thread which executes insert() method. In this unlikely
scenario the GC() thread won't be canceled and the lock
inside the insert() method may be locked for considerable
amount of time, what is not desired.
The goal of this patch is to resolve the possible issue above
and to simplify the code by eliminating the m_stopGCRequested
variable and make use of QFuture.cancel() / QFuture.isCanceled()
API instead. In addition, since we control now only one
possible thread that executes the GC(), there is no need for
future synchonizer anymore.
GC() function can't be run in parallel in different threads,
as the whole body of GC() is protected with mutex. This means
that whenever a new scheduled call to GC() is being executed,
this new call waits on the mentioned mutex at the beginning of GC().
So, instead of protecting the whole body of GC() with a mutex,
we ensure that the old call to GC() is already finished (if not,
we also cancel the old call) while preparing an asynchronous
call to start a new GC() from inside startGC() method.
Whenever we are calling the insert() method, we still protect the access
to m_strings with a mutex (as insert() is designed to be called
from different threads in parallel). Just after locking the mutex
we are canceling any possible ongoing call to GC(). After canceling
the GC() call, we are sure that no new call to GC() will be executed
until we unlock the mutex, so it's safe now to modify the m_string
data.
Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
|
|
|
QFuture<void> m_future;
|
|
|
|
|
QMutex m_lock;
|
2018-02-07 13:18:48 +01:00
|
|
|
QSet<QString> m_strings;
|
|
|
|
|
QTimer m_gcCountDown;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static StringTablePrivate *m_instance = nullptr;
|
|
|
|
|
|
|
|
|
|
StringTablePrivate::StringTablePrivate()
|
2014-02-25 16:16:11 +01:00
|
|
|
{
|
|
|
|
|
m_strings.reserve(1000);
|
|
|
|
|
|
2014-08-27 11:50:53 +02:00
|
|
|
m_gcCountDown.setObjectName(QLatin1String("StringTable::m_gcCountDown"));
|
2014-02-25 16:16:11 +01:00
|
|
|
m_gcCountDown.setSingleShot(true);
|
|
|
|
|
m_gcCountDown.setInterval(GCTimeOut);
|
2018-02-07 13:18:48 +01:00
|
|
|
connect(&m_gcCountDown, &QTimer::timeout, this, &StringTablePrivate::startGC);
|
2014-02-25 16:16:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString StringTable::insert(const QString &string)
|
2018-02-07 13:18:48 +01:00
|
|
|
{
|
|
|
|
|
return m_instance->insert(string);
|
|
|
|
|
}
|
|
|
|
|
|
StringTable: Ensure only one GC() thread is running at a time
The possible issue with the current implementation is that
in theory many possible GC() are being executed in parallel.
In this case just one of them is really working and others
are waiting on the locked mutex (first line of the GC()
method). In such a scenario when a call
to StringTablePrivate::insert() is being executed from one
more thread, it may happen that the working GC() thread
is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true)
was executed from insert()) and later the mutex lock may be
granted to the other awaiting GC() thread instead to the
thread which executes insert() method. In this unlikely
scenario the GC() thread won't be canceled and the lock
inside the insert() method may be locked for considerable
amount of time, what is not desired.
The goal of this patch is to resolve the possible issue above
and to simplify the code by eliminating the m_stopGCRequested
variable and make use of QFuture.cancel() / QFuture.isCanceled()
API instead. In addition, since we control now only one
possible thread that executes the GC(), there is no need for
future synchonizer anymore.
GC() function can't be run in parallel in different threads,
as the whole body of GC() is protected with mutex. This means
that whenever a new scheduled call to GC() is being executed,
this new call waits on the mentioned mutex at the beginning of GC().
So, instead of protecting the whole body of GC() with a mutex,
we ensure that the old call to GC() is already finished (if not,
we also cancel the old call) while preparing an asynchronous
call to start a new GC() from inside startGC() method.
Whenever we are calling the insert() method, we still protect the access
to m_strings with a mutex (as insert() is designed to be called
from different threads in parallel). Just after locking the mutex
we are canceling any possible ongoing call to GC(). After canceling
the GC() call, we are sure that no new call to GC() will be executed
until we unlock the mutex, so it's safe now to modify the m_string
data.
Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
|
|
|
void StringTablePrivate::cancelAndWait()
|
|
|
|
|
{
|
|
|
|
|
if (!m_future.isRunning())
|
|
|
|
|
return;
|
|
|
|
|
m_future.cancel();
|
|
|
|
|
m_future.waitForFinished();
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-07 13:18:48 +01:00
|
|
|
QString StringTablePrivate::insert(const QString &string)
|
2014-02-25 16:16:11 +01:00
|
|
|
{
|
|
|
|
|
if (string.isEmpty())
|
|
|
|
|
return string;
|
|
|
|
|
|
2019-11-12 10:26:38 +01:00
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
2017-06-02 16:31:41 +03:00
|
|
|
#ifndef QT_NO_UNSHARABLE_CONTAINERS
|
2014-06-22 11:57:21 -07:00
|
|
|
QTC_ASSERT(const_cast<QString&>(string).data_ptr()->ref.isSharable(), return string);
|
2019-11-12 10:26:38 +01:00
|
|
|
#endif
|
2014-06-22 11:57:21 -07:00
|
|
|
#endif
|
2014-02-25 16:16:11 +01:00
|
|
|
|
StringTable: Ensure only one GC() thread is running at a time
The possible issue with the current implementation is that
in theory many possible GC() are being executed in parallel.
In this case just one of them is really working and others
are waiting on the locked mutex (first line of the GC()
method). In such a scenario when a call
to StringTablePrivate::insert() is being executed from one
more thread, it may happen that the working GC() thread
is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true)
was executed from insert()) and later the mutex lock may be
granted to the other awaiting GC() thread instead to the
thread which executes insert() method. In this unlikely
scenario the GC() thread won't be canceled and the lock
inside the insert() method may be locked for considerable
amount of time, what is not desired.
The goal of this patch is to resolve the possible issue above
and to simplify the code by eliminating the m_stopGCRequested
variable and make use of QFuture.cancel() / QFuture.isCanceled()
API instead. In addition, since we control now only one
possible thread that executes the GC(), there is no need for
future synchonizer anymore.
GC() function can't be run in parallel in different threads,
as the whole body of GC() is protected with mutex. This means
that whenever a new scheduled call to GC() is being executed,
this new call waits on the mentioned mutex at the beginning of GC().
So, instead of protecting the whole body of GC() with a mutex,
we ensure that the old call to GC() is already finished (if not,
we also cancel the old call) while preparing an asynchronous
call to start a new GC() from inside startGC() method.
Whenever we are calling the insert() method, we still protect the access
to m_strings with a mutex (as insert() is designed to be called
from different threads in parallel). Just after locking the mutex
we are canceling any possible ongoing call to GC(). After canceling
the GC() call, we are sure that no new call to GC() will be executed
until we unlock the mutex, so it's safe now to modify the m_string
data.
Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
|
|
|
QMutexLocker locker(&m_lock);
|
|
|
|
|
// From this point of time any possible new call to startGC() will be held until
|
|
|
|
|
// we finish this function. So we are sure that after canceling the running GC() method now,
|
|
|
|
|
// no new call to GC() will be executed until we finish this function.
|
|
|
|
|
cancelAndWait();
|
|
|
|
|
// A possibly running GC() thread already finished, so it's safe to modify m_strings from
|
|
|
|
|
// now until we unlock the mutex.
|
|
|
|
|
return *m_strings.insert(string);
|
|
|
|
|
}
|
2014-02-25 16:16:11 +01:00
|
|
|
|
StringTable: Ensure only one GC() thread is running at a time
The possible issue with the current implementation is that
in theory many possible GC() are being executed in parallel.
In this case just one of them is really working and others
are waiting on the locked mutex (first line of the GC()
method). In such a scenario when a call
to StringTablePrivate::insert() is being executed from one
more thread, it may happen that the working GC() thread
is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true)
was executed from insert()) and later the mutex lock may be
granted to the other awaiting GC() thread instead to the
thread which executes insert() method. In this unlikely
scenario the GC() thread won't be canceled and the lock
inside the insert() method may be locked for considerable
amount of time, what is not desired.
The goal of this patch is to resolve the possible issue above
and to simplify the code by eliminating the m_stopGCRequested
variable and make use of QFuture.cancel() / QFuture.isCanceled()
API instead. In addition, since we control now only one
possible thread that executes the GC(), there is no need for
future synchonizer anymore.
GC() function can't be run in parallel in different threads,
as the whole body of GC() is protected with mutex. This means
that whenever a new scheduled call to GC() is being executed,
this new call waits on the mentioned mutex at the beginning of GC().
So, instead of protecting the whole body of GC() with a mutex,
we ensure that the old call to GC() is already finished (if not,
we also cancel the old call) while preparing an asynchronous
call to start a new GC() from inside startGC() method.
Whenever we are calling the insert() method, we still protect the access
to m_strings with a mutex (as insert() is designed to be called
from different threads in parallel). Just after locking the mutex
we are canceling any possible ongoing call to GC(). After canceling
the GC() call, we are sure that no new call to GC() will be executed
until we unlock the mutex, so it's safe now to modify the m_string
data.
Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
|
|
|
void StringTablePrivate::startGC()
|
|
|
|
|
{
|
2014-02-25 16:16:11 +01:00
|
|
|
QMutexLocker locker(&m_lock);
|
StringTable: Ensure only one GC() thread is running at a time
The possible issue with the current implementation is that
in theory many possible GC() are being executed in parallel.
In this case just one of them is really working and others
are waiting on the locked mutex (first line of the GC()
method). In such a scenario when a call
to StringTablePrivate::insert() is being executed from one
more thread, it may happen that the working GC() thread
is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true)
was executed from insert()) and later the mutex lock may be
granted to the other awaiting GC() thread instead to the
thread which executes insert() method. In this unlikely
scenario the GC() thread won't be canceled and the lock
inside the insert() method may be locked for considerable
amount of time, what is not desired.
The goal of this patch is to resolve the possible issue above
and to simplify the code by eliminating the m_stopGCRequested
variable and make use of QFuture.cancel() / QFuture.isCanceled()
API instead. In addition, since we control now only one
possible thread that executes the GC(), there is no need for
future synchonizer anymore.
GC() function can't be run in parallel in different threads,
as the whole body of GC() is protected with mutex. This means
that whenever a new scheduled call to GC() is being executed,
this new call waits on the mentioned mutex at the beginning of GC().
So, instead of protecting the whole body of GC() with a mutex,
we ensure that the old call to GC() is already finished (if not,
we also cancel the old call) while preparing an asynchronous
call to start a new GC() from inside startGC() method.
Whenever we are calling the insert() method, we still protect the access
to m_strings with a mutex (as insert() is designed to be called
from different threads in parallel). Just after locking the mutex
we are canceling any possible ongoing call to GC(). After canceling
the GC() call, we are sure that no new call to GC() will be executed
until we unlock the mutex, so it's safe now to modify the m_string
data.
Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
|
|
|
cancelAndWait();
|
|
|
|
|
m_future = Utils::runAsync(&StringTablePrivate::GC, this);
|
2014-02-25 16:16:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void StringTable::scheduleGC()
|
|
|
|
|
{
|
2020-11-11 16:34:39 +01:00
|
|
|
QMetaObject::invokeMethod(&m_instance->m_gcCountDown, QOverload<>::of(&QTimer::start),
|
|
|
|
|
Qt::QueuedConnection);
|
2014-02-25 16:16:11 +01:00
|
|
|
}
|
|
|
|
|
|
2020-02-04 17:10:52 +01:00
|
|
|
StringTable::StringTable()
|
2014-02-25 16:16:11 +01:00
|
|
|
{
|
2018-02-07 13:18:48 +01:00
|
|
|
m_instance = new StringTablePrivate;
|
2014-02-25 16:16:11 +01:00
|
|
|
}
|
|
|
|
|
|
2020-02-04 17:10:52 +01:00
|
|
|
StringTable::~StringTable()
|
2018-02-07 13:18:48 +01:00
|
|
|
{
|
|
|
|
|
delete m_instance;
|
|
|
|
|
m_instance = nullptr;
|
|
|
|
|
}
|
2014-02-25 16:16:11 +01:00
|
|
|
|
2014-06-22 11:57:21 -07:00
|
|
|
static inline bool isQStringInUse(const QString &string)
|
2014-05-28 15:51:31 -04:00
|
|
|
{
|
2020-09-15 15:33:13 +02:00
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
|
|
|
auto data_ptr = const_cast<QString&>(string).data_ptr();
|
|
|
|
|
return data_ptr->ref.isShared() || data_ptr->ref.isStatic() /* QStringLiteral ? */;
|
|
|
|
|
#else
|
|
|
|
|
auto data_ptr = const_cast<QString&>(string).data_ptr();
|
|
|
|
|
return data_ptr->isShared() || !data_ptr->isMutable() /* QStringLiteral ? */;
|
|
|
|
|
#endif
|
2014-05-28 15:51:31 -04:00
|
|
|
}
|
|
|
|
|
|
StringTable: Ensure only one GC() thread is running at a time
The possible issue with the current implementation is that
in theory many possible GC() are being executed in parallel.
In this case just one of them is really working and others
are waiting on the locked mutex (first line of the GC()
method). In such a scenario when a call
to StringTablePrivate::insert() is being executed from one
more thread, it may happen that the working GC() thread
is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true)
was executed from insert()) and later the mutex lock may be
granted to the other awaiting GC() thread instead to the
thread which executes insert() method. In this unlikely
scenario the GC() thread won't be canceled and the lock
inside the insert() method may be locked for considerable
amount of time, what is not desired.
The goal of this patch is to resolve the possible issue above
and to simplify the code by eliminating the m_stopGCRequested
variable and make use of QFuture.cancel() / QFuture.isCanceled()
API instead. In addition, since we control now only one
possible thread that executes the GC(), there is no need for
future synchonizer anymore.
GC() function can't be run in parallel in different threads,
as the whole body of GC() is protected with mutex. This means
that whenever a new scheduled call to GC() is being executed,
this new call waits on the mentioned mutex at the beginning of GC().
So, instead of protecting the whole body of GC() with a mutex,
we ensure that the old call to GC() is already finished (if not,
we also cancel the old call) while preparing an asynchronous
call to start a new GC() from inside startGC() method.
Whenever we are calling the insert() method, we still protect the access
to m_strings with a mutex (as insert() is designed to be called
from different threads in parallel). Just after locking the mutex
we are canceling any possible ongoing call to GC(). After canceling
the GC() call, we are sure that no new call to GC() will be executed
until we unlock the mutex, so it's safe now to modify the m_string
data.
Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
|
|
|
void StringTablePrivate::GC(QFutureInterface<void> &futureInterface)
|
2014-02-25 16:16:11 +01:00
|
|
|
{
|
|
|
|
|
int initialSize = 0;
|
2021-03-09 23:01:39 +01:00
|
|
|
QElapsedTimer timer;
|
2014-02-25 16:16:11 +01:00
|
|
|
if (DebugStringTable) {
|
|
|
|
|
initialSize = m_strings.size();
|
2021-03-09 23:01:39 +01:00
|
|
|
timer.start();
|
2014-02-25 16:16:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect all QStrings which have refcount 1. (One reference in m_strings and nowhere else.)
|
|
|
|
|
for (QSet<QString>::iterator i = m_strings.begin(); i != m_strings.end();) {
|
StringTable: Ensure only one GC() thread is running at a time
The possible issue with the current implementation is that
in theory many possible GC() are being executed in parallel.
In this case just one of them is really working and others
are waiting on the locked mutex (first line of the GC()
method). In such a scenario when a call
to StringTablePrivate::insert() is being executed from one
more thread, it may happen that the working GC() thread
is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true)
was executed from insert()) and later the mutex lock may be
granted to the other awaiting GC() thread instead to the
thread which executes insert() method. In this unlikely
scenario the GC() thread won't be canceled and the lock
inside the insert() method may be locked for considerable
amount of time, what is not desired.
The goal of this patch is to resolve the possible issue above
and to simplify the code by eliminating the m_stopGCRequested
variable and make use of QFuture.cancel() / QFuture.isCanceled()
API instead. In addition, since we control now only one
possible thread that executes the GC(), there is no need for
future synchonizer anymore.
GC() function can't be run in parallel in different threads,
as the whole body of GC() is protected with mutex. This means
that whenever a new scheduled call to GC() is being executed,
this new call waits on the mentioned mutex at the beginning of GC().
So, instead of protecting the whole body of GC() with a mutex,
we ensure that the old call to GC() is already finished (if not,
we also cancel the old call) while preparing an asynchronous
call to start a new GC() from inside startGC() method.
Whenever we are calling the insert() method, we still protect the access
to m_strings with a mutex (as insert() is designed to be called
from different threads in parallel). Just after locking the mutex
we are canceling any possible ongoing call to GC(). After canceling
the GC() call, we are sure that no new call to GC() will be executed
until we unlock the mutex, so it's safe now to modify the m_string
data.
Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
|
|
|
if (futureInterface.isCanceled())
|
2014-02-25 16:16:11 +01:00
|
|
|
return;
|
|
|
|
|
|
2014-06-22 11:57:21 -07:00
|
|
|
if (!isQStringInUse(*i))
|
2014-02-25 16:16:11 +01:00
|
|
|
i = m_strings.erase(i);
|
|
|
|
|
else
|
|
|
|
|
++i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (DebugStringTable) {
|
|
|
|
|
const int currentSize = m_strings.size();
|
|
|
|
|
qDebug() << "StringTable::GC removed" << initialSize - currentSize
|
2021-03-09 23:01:39 +01:00
|
|
|
<< "strings in" << timer.elapsed() << "ms, size is now" << currentSize;
|
2014-02-25 16:16:11 +01:00
|
|
|
}
|
|
|
|
|
}
|