331 lines
8.8 KiB
C++
Executable File
331 lines
8.8 KiB
C++
Executable File
#include "generic_ftc_threading.h"
|
|
|
|
#include <QAtomicInt>
|
|
#include <QTimer>
|
|
//#include <QRandomGenerator>
|
|
#include <QRegularExpression>
|
|
|
|
#include <chrono>
|
|
#include <future>
|
|
|
|
#ifdef Q_OS_LINUX
|
|
#include <pthread.h>
|
|
#endif
|
|
|
|
|
|
//using namespace std::chrono_literals;
|
|
|
|
|
|
Threading::~Threading()
|
|
{
|
|
if (m_threadLifetime != nullptr)
|
|
{
|
|
m_threadLifetime->stop();
|
|
m_threadLifetime.reset();
|
|
}
|
|
|
|
setCleanupNeeded(false); // dtor will clean everything up itself
|
|
|
|
{
|
|
std::lock_guard<std::mutex> l(m_conditionMutex);
|
|
setRun(false);
|
|
}
|
|
|
|
if (!m_used)
|
|
{
|
|
//fprintf(stderr, "This FTC (%s @ %d) was enabled without ever doing a request! Consider calling 'enable' right before doing a request only!\n", m_threadName.toStdString().c_str(), m_partitionId);
|
|
}
|
|
|
|
// fprintf(stderr, "~Threading: joinable? %d (%s)\n", m_thread.joinable(), m_threadName.toStdString().c_str());
|
|
if (m_thread.joinable())
|
|
{
|
|
// fprintf(stderr, "~Threading: joining... (%s)\n", m_threadName.toStdString().c_str());
|
|
m_condition.notify_all(); // wake thread from sleeping
|
|
m_thread.join(); // wait for thread to die... :(
|
|
// fprintf(stderr, "~Threading: joined (%s)\n", m_threadName.toStdString().c_str());
|
|
}
|
|
}
|
|
|
|
|
|
void Threading::preInit() const
|
|
{
|
|
m_partitionId = partitionId();
|
|
m_partitionType = partitionType();
|
|
|
|
//fprintf(stderr, "functionTopicName() = %s\n", functionTopicName().toStdString().c_str());
|
|
|
|
// F = FTC; %1 = partition type; %2 partition id; %3 = function topic name; %4 = thread id
|
|
const QString base(QStringLiteral("F%1%2").arg(m_partitionType.left(1)).arg(m_partitionId));
|
|
//fprintf(stderr, "base = %s\n", base.toStdString().c_str());
|
|
|
|
const int baseLen = base.length();
|
|
const int maxLen = 16 - 1 - baseLen; // 16 chars allowed; 1 char = 0; base already n chars
|
|
|
|
// fprintf(stderr, "baseLen = %d\n", baseLen);
|
|
// fprintf(stderr, "maxLen = %d\n", maxLen);
|
|
|
|
if (maxLen < 1)
|
|
{
|
|
Q_ASSERT(false);
|
|
m_threadName = functionTopicName();
|
|
return;
|
|
}
|
|
|
|
const QString orgName(functionTopicName());
|
|
QString name;
|
|
|
|
if (orgName.length() > maxLen)
|
|
{
|
|
QRegularExpression regexp(QStringLiteral("[A-Z][a-z]*"));
|
|
auto matches = regexp.globalMatch(functionTopicName());
|
|
|
|
QStringList words;
|
|
while (matches.hasNext())
|
|
{
|
|
const auto word = matches.next().capturedTexts().join(QString());
|
|
// fprintf(stderr, "word = %s\n", word.toStdString().c_str());
|
|
words += word;
|
|
}
|
|
|
|
if (words.isEmpty())
|
|
{
|
|
Q_ASSERT(false);
|
|
m_threadName = functionTopicName();
|
|
return;
|
|
}
|
|
|
|
int wordsLeft = words.count();
|
|
// fprintf(stderr, "wordsLeft = %d\n", wordsLeft);
|
|
|
|
if (wordsLeft < 1)
|
|
{
|
|
Q_ASSERT(false);
|
|
m_threadName = functionTopicName();
|
|
return;
|
|
}
|
|
|
|
int bremse = 20;
|
|
while (wordsLeft > 0)
|
|
{
|
|
if (bremse < 1)
|
|
{
|
|
Q_ASSERT(false);
|
|
break;
|
|
}
|
|
bremse--;
|
|
|
|
const int lenRest = maxLen - name.count();
|
|
if (lenRest < 1)
|
|
{
|
|
Q_ASSERT(false);
|
|
m_threadName = functionTopicName();
|
|
return;
|
|
}
|
|
|
|
const int maxWordLen = lenRest / wordsLeft;
|
|
// fprintf(stderr, "maxWordLen = %d\n", maxWordLen);
|
|
|
|
// const int rest = lenRest % wordsLeft;
|
|
// fprintf(stderr, "rest = %d\n", rest);
|
|
|
|
if (words.isEmpty())
|
|
{
|
|
Q_ASSERT(false);
|
|
break;
|
|
}
|
|
|
|
// name += words.takeFirst().left(maxWordLen); // clazy: use leftRef
|
|
name += words.takeFirst().leftRef(maxWordLen);
|
|
|
|
wordsLeft--;
|
|
}
|
|
|
|
// fprintf(stderr, "name = %s\n", name.toStdString().c_str());
|
|
|
|
const int finalLeft = maxLen - name.count();
|
|
if (finalLeft < 0 || (finalLeft > 0 && functionTopicName().length() > maxLen))
|
|
{
|
|
// fprintf(stderr, "[#] Threading::preInit: (%s) maxLen - name.count() = %d - functionTopicName().length() = %d, maxLen = %d\n", name.toStdString().c_str(), maxLen - name.count(), functionTopicName().length(), maxLen);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
name = orgName;
|
|
}
|
|
|
|
/*mutable*/m_threadName = QStringLiteral("%1%2[%3]").arg(base, name).arg(qrand(), 8, 16, QLatin1Char('0'));
|
|
}
|
|
|
|
|
|
const QString &Threading::threadName() const
|
|
{
|
|
if (m_threadName.isEmpty())
|
|
{
|
|
// fprintf(stderr, "[!] Threading: threadName called before preInit!\n");
|
|
Q_ASSERT(false);
|
|
}
|
|
|
|
return m_threadName;
|
|
}
|
|
|
|
|
|
const char *Threading::posixThreadName() const
|
|
{
|
|
if (m_posixThreadName.empty())
|
|
{
|
|
m_posixThreadName = threadName().left(15).toStdString();
|
|
}
|
|
|
|
return m_posixThreadName.c_str();
|
|
}
|
|
|
|
|
|
void Threading::createThread()
|
|
{
|
|
if (!isRun())
|
|
{
|
|
if (m_thread.joinable()) // dying thread still alive? then wait for it to stop :(
|
|
{
|
|
// fprintf(stderr, "Threading::go: joining... (%s)\n", threadName().toStdString().c_str());
|
|
m_condition.notify_all(); // wake from (possible) sleep
|
|
m_thread.join();
|
|
threadDone();
|
|
// fprintf(stderr, "Threading::go: thread exited (%s)\n", threadName().toStdString().c_str());
|
|
}
|
|
|
|
m_alreadyThreaded = 0;
|
|
|
|
// fprintf(stderr, "Threading::go: creating new thread (%s)\n", threadName().toStdString().c_str());
|
|
setRun(true); // NOTE: no need to lock before the thread even exists
|
|
|
|
m_thread = std::thread(&Threading::run, this, threadName());
|
|
|
|
#ifdef Q_OS_LINUX
|
|
pthread_setname_np(m_thread.native_handle(), posixThreadName());
|
|
#endif
|
|
}
|
|
|
|
m_createThreadCalledBefore = true;
|
|
}
|
|
|
|
|
|
void Threading::runThread()
|
|
{
|
|
m_used = true;
|
|
|
|
Q_ASSERT(m_createThreadCalledBefore);
|
|
m_createThreadCalledBefore = false;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> l(m_conditionMutex);
|
|
setWorkAvailable(true);
|
|
}
|
|
m_condition.notify_all(); // wake thread from possible sleep
|
|
|
|
if (!m_threadLifetime) // limit thread's lifetime for saving resources, but keep it alive as long as possible to not re-create a new thread for each request
|
|
{
|
|
m_threadLifetime.reset(new QTimer); // 42.3
|
|
m_threadLifetime->setSingleShot(true);
|
|
m_threadLifetime->setInterval(11*1000); // 10 seconds + 1 to make it an ugly, undesireable number - for e.g. requests that may be timed to execute all 10 seconds
|
|
QObject::connect(m_threadLifetime.get(), &QTimer::timeout, m_threadLifetime.get(),
|
|
[this]
|
|
{
|
|
bool workAvailable = false;
|
|
bool run = false;
|
|
bool working = false;
|
|
{
|
|
std::lock_guard<std::mutex> l(m_conditionMutex);
|
|
workAvailable = isWorkAvailable();
|
|
run = isRun();
|
|
working = isWorking();
|
|
}
|
|
|
|
if (workAvailable || working)
|
|
{
|
|
// fprintf(stderr, "Threading::go: thread lifetime exceeded, but thread still has work to do - trying again later (%s)\n", threadName().toStdString().c_str());
|
|
m_threadLifetime->start(); // re-start
|
|
return;
|
|
}
|
|
|
|
m_threadLifetime->deleteLater();
|
|
m_threadLifetime = nullptr;
|
|
|
|
if (!run)
|
|
{
|
|
// fprintf(stderr, "[!] Threading::go: thread lifetime exceeded while having no work, but run is already false... doing nothing (%s)\n", threadName().toStdString().c_str());
|
|
Q_ASSERT(false);
|
|
return;
|
|
}
|
|
|
|
// fprintf(stderr, "Threading::go: thread lifetime exceeded while having no work (%s)\n", threadName().toStdString().c_str());
|
|
|
|
{
|
|
std::lock_guard<std::mutex> l(m_conditionMutex);
|
|
setRun(false);
|
|
}
|
|
m_condition.notify_all(); // wake thread from possible sleep, so it can stop
|
|
});
|
|
}
|
|
|
|
m_threadLifetime->start(); // start / re-start
|
|
}
|
|
|
|
|
|
void Threading::run(const QString &threadName) // in-thread
|
|
{
|
|
Q_ASSERT(m_alreadyThreaded == 0);
|
|
m_alreadyThreaded = 1;
|
|
|
|
const std::string _tn = threadName.toStdString();
|
|
const char *tn = _tn.c_str();
|
|
|
|
while (isRun())
|
|
{
|
|
try
|
|
{
|
|
{
|
|
std::unique_lock<std::mutex> conditionLock(m_conditionMutex);
|
|
|
|
// fprintf(stderr, "Threading::run: waiting... (%s)\n", tn);
|
|
m_condition.wait(conditionLock, [this]{ return (isWorkAvailable() || !isRun()); /* wake up if work available or the thread has to die */ });
|
|
// fprintf(stderr, "Threading::run: woke up - work? %d - run? %d (%s)\n", isWorkAvailable(), isRun(), tn);
|
|
}
|
|
|
|
cancellationPoint();
|
|
{
|
|
std::lock_guard<std::mutex> l(m_conditionMutex);
|
|
setWorking(true);
|
|
}
|
|
work();
|
|
{
|
|
std::lock_guard<std::mutex> l(m_conditionMutex);
|
|
setWorking(false);
|
|
}
|
|
cancellationPoint();
|
|
}
|
|
catch (const CancelationPointReached &ex)
|
|
{
|
|
// fprintf(stderr, "Threading::run: CancelationPointReached (%s)\n", tn);
|
|
break;
|
|
}
|
|
catch (const std::exception &ex)
|
|
{
|
|
fprintf(stderr, "[!] Threading::run: exception: %s (%s)\n", ex.what(), tn);
|
|
}
|
|
catch(...)
|
|
{
|
|
fprintf(stderr, "[!] Threading::run: unknown exception occured (%s) !!!\n", tn);
|
|
}
|
|
} // while
|
|
|
|
if (isCleanupNeeded())
|
|
{
|
|
// fprintf(stderr, "Threading::run: cleanupThread next! (%s)\n", tn);
|
|
cleanupThread();
|
|
}
|
|
else
|
|
{
|
|
// fprintf(stderr, "Threading::run: no cleanup needed! (%s)\n", tn);
|
|
}
|
|
}
|