1
0
forked from BRT/arc

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.

This commit is contained in:
slava 2026-05-28 22:21:11 +05:00
parent b8da23fa6c
commit 3832a658ce
2 changed files with 64 additions and 0 deletions

View File

@ -271,8 +271,58 @@ static void sendEventStatus(const QString &deviceId, const QString &externalId,
paymentThread->start(); 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) { 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; const EventStatus newStatus = result.isEmpty() ? EventStatus::Complete : EventStatus::Error;
if (!EventDAO::updateEvent(event.id, newStatus, result)) { 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); const QJsonObject parsedTx = m_parsedTxs.take(key);
if (parsedTx.isEmpty()) { if (parsedTx.isEmpty()) {
qWarning() << "[EventHandler] FETCH_TRANSACTIONS: script did not emit parsed tx"; 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, sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName,
"Transaction not parsed from screen", {}, -1, event.transactionId); "Transaction not parsed from screen", {}, -1, event.transactionId);
EventDAO::updateEvent(event.id, EventStatus::Error, EventDAO::updateEvent(event.id, EventStatus::Error,
"Transaction not parsed from screen"); "Transaction not parsed from screen");
EventDAO::markSentToServer(event.id); EventDAO::markSentToServer(event.id);
m_threads.remove(key); m_threads.remove(key);
m_retryCount.remove(event.id);
return; return;
} }
QJsonArray arr; QJsonArray arr;
@ -507,6 +559,7 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
tm->deleteLater(); tm->deleteLater();
} }
m_threads.remove(key); 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); 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)"); EventDAO::updateEvent(event.id, EventStatus::Error, "Script timeout (5 min)");
const QString logMsg = QString("[%1] bank=%2 material=%3: Script timeout") const QString logMsg = QString("[%1] bank=%2 material=%3: Script timeout")

View File

@ -35,6 +35,7 @@ private:
QMap<QString, QTimer *> m_timers; QMap<QString, QTimer *> m_timers;
QMap<QString, QByteArray> m_screenshots; // deviceId → скриншот от скрипта QMap<QString, QByteArray> m_screenshots; // deviceId → скриншот от скрипта
QMap<QString, QJsonObject> m_parsedTxs; // key → распарсенная транзакция для FETCH_TRANSACTIONS QMap<QString, QJsonObject> m_parsedTxs; // key → распарсенная транзакция для FETCH_TRANSACTIONS
QMap<int, int> m_retryCount; // event.id → число уже сделанных ретраев (только FETCH_TRANSACTIONS)
QTimer *m_pollTimer = nullptr; QTimer *m_pollTimer = nullptr;
NetworkService *m_network = nullptr; NetworkService *m_network = nullptr;
@ -42,6 +43,10 @@ private:
void updateEventThread(const QString &key, const EventInfo &event, const QString &result); 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 startThreadForTask(const MaterialInfo &account, const EventInfo &event);
void processNextTask(const QString &deviceId); void processNextTask(const QString &deviceId);