#include "generic_ftc_threading.h" #include #include //#include #include #include #include #ifdef Q_OS_LINUX #include #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 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 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 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 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 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 l(m_conditionMutex); setWorking(true); } work(); { std::lock_guard 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); } }