From 3832a658cee4a91829179c6128a3fc35861ffb9d Mon Sep 17 00:00:00 2001 From: slava Date: Thu, 28 May 2026 22:21:11 +0500 Subject: [PATCH] Add retry logic to `EventHandler` for `FETCH_TRANSACTIONS` script errors and timeouts, with a maximum of two retries per event. Update related event and task handling to ensure retry tracking and cleanup. --- services/EventHandler.cpp | 59 +++++++++++++++++++++++++++++++++++++++ services/EventHandler.h | 5 ++++ 2 files changed, 64 insertions(+) diff --git a/services/EventHandler.cpp b/services/EventHandler.cpp index 590103e..5ab15d0 100644 --- a/services/EventHandler.cpp +++ b/services/EventHandler.cpp @@ -271,8 +271,58 @@ static void sendEventStatus(const QString &deviceId, const QString &externalId, paymentThread->start(); } +bool EventHandler::tryRetry(const QString &key, const EventInfo &event, const QString &reason) { + constexpr int kMaxRetries = 2; // ещё 2 попытки сверх первой → всего 3 запуска + const int used = m_retryCount.value(event.id, 0); + if (used >= kMaxRetries) { + m_retryCount.remove(event.id); // попытки исчерпаны — пусть caller обработает как ошибку + return false; + } + const int attempt = used + 1; + m_retryCount[event.id] = attempt; + + qWarning() << "[EventHandler] FETCH_TRANSACTIONS retry" << attempt << "/" << kMaxRetries + << "for event" << event.id << "reason:" << reason; + GeneralLogDAO::insert("WARNING", "EventHandler", + QString("[FETCH_TRANSACTIONS] retry %1/%2 device=%3 material=%4: %5") + .arg(QString::number(attempt), QString::number(kMaxRetries), + event.deviceId, event.externalId, reason)); + + // Чистим артефакты прошлой попытки + m_screenshots.remove(key); + m_parsedTxs.remove(key); + + // Снимаем текущий поток/таймер (в timeout-ветке они уже сняты — take() вернёт null) + if (const auto tm = m_timers.take(key)) { + tm->stop(); + tm->deleteLater(); + } + m_threads.remove(key); + + // Сбрасываем банковское приложение, чтобы попытка стартовала с чистого экрана + const MaterialInfo account = MaterialDAO::getAccountById(event.accountId); + const BankProfileInfo app = BankProfileDAO::getApplication(event.deviceId, account.appCode); + if (!app.package.isEmpty()) { + const DeviceInfo device = DeviceDAO::getDeviceById(event.deviceId); + const QString adbSerial = device.adbSerial.isEmpty() ? event.deviceId : device.adbSerial; + AdbUtils::tryToKillApplication(adbSerial, app.package); + } + + // Держим событие InProgress и перезапускаем скрипт + EventDAO::updateEvent(event.id, EventStatus::InProgress, ""); + startThreadForTask(account, event); + return true; +} + void EventHandler::updateEventThread(const QString &key, const EventInfo &event, const QString &result) { + // Поиск транзакции: при ошибке скрипта перезапускаем (ещё до 2 раз) вместо + // немедленной отправки ошибки на сервер. + if (!result.isEmpty() && event.type == EventType::FetchTransactions + && tryRetry(key, event, result)) { + return; + } + const EventStatus newStatus = result.isEmpty() ? EventStatus::Complete : EventStatus::Error; if (!EventDAO::updateEvent(event.id, newStatus, result)) { @@ -368,12 +418,14 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event, const QJsonObject parsedTx = m_parsedTxs.take(key); if (parsedTx.isEmpty()) { qWarning() << "[EventHandler] FETCH_TRANSACTIONS: script did not emit parsed tx"; + if (tryRetry(key, event, "Transaction not parsed from screen")) return; sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, "Transaction not parsed from screen", {}, -1, event.transactionId); EventDAO::updateEvent(event.id, EventStatus::Error, "Transaction not parsed from screen"); EventDAO::markSentToServer(event.id); m_threads.remove(key); + m_retryCount.remove(event.id); return; } QJsonArray arr; @@ -507,6 +559,7 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event, tm->deleteLater(); } m_threads.remove(key); + m_retryCount.remove(event.id); // Убиваем банковское приложение на устройстве после завершения задачи { @@ -659,6 +712,12 @@ void EventHandler::startThreadForTask(const MaterialInfo &account, const EventIn } m_threads.remove(key); + // Поиск транзакции: таймаут тоже ретраим (ещё до 2 раз) + if (event.type == EventType::FetchTransactions + && tryRetry(key, event, "Script timeout (5 min)")) { + return; + } + // Помечаем событие как ошибку EventDAO::updateEvent(event.id, EventStatus::Error, "Script timeout (5 min)"); const QString logMsg = QString("[%1] bank=%2 material=%3: Script timeout") diff --git a/services/EventHandler.h b/services/EventHandler.h index 2df4ba9..580419a 100644 --- a/services/EventHandler.h +++ b/services/EventHandler.h @@ -35,6 +35,7 @@ private: QMap m_timers; QMap m_screenshots; // deviceId → скриншот от скрипта QMap m_parsedTxs; // key → распарсенная транзакция для FETCH_TRANSACTIONS + QMap m_retryCount; // event.id → число уже сделанных ретраев (только FETCH_TRANSACTIONS) QTimer *m_pollTimer = nullptr; NetworkService *m_network = nullptr; @@ -42,6 +43,10 @@ private: void updateEventThread(const QString &key, const EventInfo &event, const QString &result); + // Перезапускает скрипт поиска транзакции при ошибке (до 2 ретраев). Возвращает + // true, если ретрай запущен; false — если попытки исчерпаны (обрабатывать как ошибку). + bool tryRetry(const QString &key, const EventInfo &event, const QString &reason); + void startThreadForTask(const MaterialInfo &account, const EventInfo &event); void processNextTask(const QString &deviceId);