提交更新

This commit is contained in:
super
2026-06-08 18:31:55 +08:00
parent 98e0e54309
commit 967eedcad7
7 changed files with 394 additions and 98 deletions
@@ -569,42 +569,20 @@ public class AppearancePatentTaskService {
}
public void finalizeStaleTasks() {
if (transactionManager != null) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime threshold = now.minusMinutes(Math.max(1, properties.getStaleTimeoutMinutes()));
long thresholdMillis = threshold.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
List<FileTaskEntity> tasks = listStaleFinalizeCandidates(threshold);
for (FileTaskEntity task : tasks) {
if (!isOwnerCurrent(ownerFromTask(task))) {
continue;
}
long heartbeatMillis = taskCacheService.getTaskHeartbeatMillis(task.getId());
if (heartbeatMillis > thresholdMillis) {
continue;
}
TaskDistributedLockService.LockHandle lockHandle = acquireTaskLock(task.getId(), 0L);
if (lockHandle == null) {
continue;
}
try (lockHandle) {
inNewTransaction(() -> {
finalizeStaleTask(task.getId(), "Python interrupted before uploading final appearance patent result");
return null;
});
}
}
return;
}
LocalDateTime now = LocalDateTime.now();
LocalDateTime threshold = now.minusMinutes(Math.max(1, properties.getStaleTimeoutMinutes()));
long thresholdMillis = threshold.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
List<FileTaskEntity> tasks = listStaleFinalizeCandidates(threshold);
// P1-7stale 判定改用 Redis heartbeat 作为主信号。
// 历史实现先按 file_task.updated_at 过滤再二次校验 Redis 心跳,
// 但 file_task.updated_at 会被 Java 端在 "等 Python 上传" 分支自我续命
// resetStuckJobs 把 file_job 改回 PENDING → 重跑 processResultFileJob → touchJavaSideTaskActivity)。
// Redis heartbeat 仅在 Python 心跳/上传 result 时写入,不会被 Java 内部循环刷新。
long thresholdMillis = LocalDateTime.now()
.minusMinutes(Math.max(1, properties.getStaleTimeoutMinutes()))
.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
List<FileTaskEntity> tasks = listStaleFinalizeCandidates();
for (FileTaskEntity task : tasks) {
if (!isOwnerCurrent(ownerFromTask(task))) {
continue;
}
long heartbeatMillis = taskCacheService.getTaskHeartbeatMillis(task.getId());
if (heartbeatMillis > thresholdMillis) {
if (!isHeartbeatStale(task, thresholdMillis)) {
continue;
}
TaskDistributedLockService.LockHandle lockHandle = acquireTaskLock(task.getId(), 0L);
@@ -612,7 +590,14 @@ public class AppearancePatentTaskService {
continue;
}
try (lockHandle) {
finalizeStaleTask(task.getId(), "Python interrupted before uploading final appearance patent result");
if (transactionManager != null) {
inNewTransaction(() -> {
finalizeStaleTask(task.getId(), "Python interrupted before uploading final appearance patent result");
return null;
});
} else {
finalizeStaleTask(task.getId(), "Python interrupted before uploading final appearance patent result");
}
}
}
}
@@ -643,15 +628,38 @@ public class AppearancePatentTaskService {
}
}
private List<FileTaskEntity> listStaleFinalizeCandidates(LocalDateTime threshold) {
private List<FileTaskEntity> listStaleFinalizeCandidates() {
// P1-7:不再用 file_task.updated_at 预过滤,由调用方按 Redis heartbeat 判 stale。
// APPEARANCE_PATENT 在线 RUNNING 任务量级有限(限 200),全扫成本可忽略。
return fileTaskMapper.selectList(new LambdaQueryWrapper<FileTaskEntity>()
.eq(FileTaskEntity::getModuleType, MODULE_TYPE)
.eq(FileTaskEntity::getStatus, STATUS_RUNNING)
.lt(FileTaskEntity::getUpdatedAt, threshold)
.orderByAsc(FileTaskEntity::getUpdatedAt)
.last("limit 200"));
}
/**
* P1-7:判断 APPEARANCE_PATENT 任务是否已 stale。
* 主信号:Redis heartbeat(仅 Python 心跳/上传 result 时刷新,不会被 Java 内部循环续命)。
* Redis 命中:以 heartbeatMillis 与 thresholdMillis 比较。
* Redis 缺失/降级(返回 0):回落到 file_task.updated_at,避免 Redis 故障时全量误杀。
*/
private boolean isHeartbeatStale(FileTaskEntity task, long thresholdMillis) {
if (task == null || task.getId() == null) {
return false;
}
long heartbeatMillis = taskCacheService.getTaskHeartbeatMillis(task.getId());
if (heartbeatMillis > 0L) {
return heartbeatMillis <= thresholdMillis;
}
LocalDateTime updatedAt = task.getUpdatedAt();
if (updatedAt == null) {
return false;
}
long updatedMillis = updatedAt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
return updatedMillis <= thresholdMillis;
}
private SubmitContext persistSubmittedChunk(Long taskId, AppearancePatentSubmitResultRequest request) {
FileTaskEntity task = fileTaskMapper.selectById(taskId);
if (task == null || !MODULE_TYPE.equals(task.getModuleType())) {