(jobId, attempt)
| 1070 | } |
| 1071 | |
| 1072 | async runSaveRetry(jobId, attempt) { |
| 1073 | const jobBeforeRetry = this.jobs[jobId]; |
| 1074 | if (!jobBeforeRetry) { |
| 1075 | logger.warn({ jobId, attempt }, 'Retry attempt skipped - job no longer exists in memory'); |
| 1076 | return; |
| 1077 | } |
| 1078 | |
| 1079 | try { |
| 1080 | await this.saveJobs(); |
| 1081 | } catch (err) { |
| 1082 | logger.error({ err, jobId, attempt }, 'Retry attempt for job failed while saving jobs'); |
| 1083 | } |
| 1084 | |
| 1085 | const jobAfterRetry = this.jobs[jobId]; |
| 1086 | if (!jobAfterRetry) { |
| 1087 | logger.warn({ jobId, attempt }, 'Retry attempt completed but job is no longer tracked'); |
| 1088 | return; |
| 1089 | } |
| 1090 | |
| 1091 | if (!jobAfterRetry._needsSave) { |
| 1092 | // Retry succeeded - clear counters if present |
| 1093 | if (jobAfterRetry._saveRetries) { |
| 1094 | logger.debug({ jobId, attempt }, 'Successfully saved job after retry attempt, clearing retry flags'); |
| 1095 | delete jobAfterRetry._saveRetries; |
| 1096 | } |
| 1097 | return; |
| 1098 | } |
| 1099 | |
| 1100 | if (attempt >= MAX_SAVE_RETRIES) { |
| 1101 | logger.error({ jobId, maxRetries: MAX_SAVE_RETRIES }, 'Max retries exhausted for job. Video data may be lost. Giving up.'); |
| 1102 | delete jobAfterRetry._needsSave; |
| 1103 | delete jobAfterRetry._saveRetries; |
| 1104 | return; |
| 1105 | } |
| 1106 | |
| 1107 | const nextAttempt = attempt + 1; |
| 1108 | jobAfterRetry._saveRetries = nextAttempt; |
| 1109 | logger.error({ jobId, attempt, nextAttempt, maxRetries: MAX_SAVE_RETRIES }, 'Retry attempt did not clear the pending save. Scheduling next attempt'); |
| 1110 | this.scheduleSaveRetry(jobId, nextAttempt); |
| 1111 | } |
| 1112 | } |
| 1113 | |
| 1114 | module.exports = new JobModule(); |
no test coverage detected