mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-14 19:45:08 +08:00
fix(task): gate every worker completion branch with isConversationCanceled
This commit is contained in:
parent
01c8765c96
commit
d58fd0c3fd
@ -221,6 +221,16 @@ public class ImageGenerationService {
|
|||||||
* 异步任务完成时的回写逻辑:下载图片 → 保存消息 → 广播 SSE
|
* 异步任务完成时的回写逻辑:下载图片 → 保存消息 → 广播 SSE
|
||||||
*/
|
*/
|
||||||
private void handleAsyncCompletion(AsyncTaskEntity task, TaskPollResult result) {
|
private void handleAsyncCompletion(AsyncTaskEntity task, TaskPollResult result) {
|
||||||
|
// The conversation may have been deleted while the poller was running.
|
||||||
|
// Gate every post-completion side effect — file write, message save,
|
||||||
|
// success/failure broadcast — so we never write to a tombstoned
|
||||||
|
// conversation regardless of which sub-branch we'd take.
|
||||||
|
if (asyncTaskService.isConversationCanceled(task.getConversationId())) {
|
||||||
|
log.info("[ImageGen] Task {} (success={}) aborted: conversation {} was deleted",
|
||||||
|
task.getTaskId(), result.succeeded(), task.getConversationId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (result.succeeded()) {
|
if (result.succeeded()) {
|
||||||
try {
|
try {
|
||||||
String imageUrl = result.imageUrl();
|
String imageUrl = result.imageUrl();
|
||||||
@ -231,15 +241,6 @@ public class ImageGenerationService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversation deletion can race with the final poll iteration;
|
|
||||||
// dropping the result avoids resurrecting attachment files and
|
|
||||||
// a dangling mate_message row.
|
|
||||||
if (asyncTaskService.isConversationCanceled(task.getConversationId())) {
|
|
||||||
log.info("[ImageGen] Task {} succeeded but conversation {} was deleted, dropping result",
|
|
||||||
task.getTaskId(), task.getConversationId());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下载图片到本地
|
// 下载图片到本地
|
||||||
Path localPath = fileDownloader.download(imageUrl, task.getConversationId(), task.getTaskId(), 0);
|
Path localPath = fileDownloader.download(imageUrl, task.getConversationId(), task.getTaskId(), 0);
|
||||||
String servingUrl = fileDownloader.toServingUrl(task.getConversationId(), localPath);
|
String servingUrl = fileDownloader.toServingUrl(task.getConversationId(), localPath);
|
||||||
@ -262,6 +263,11 @@ public class ImageGenerationService {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[ImageGen] Completion handling failed for task {}: {}",
|
log.error("[ImageGen] Completion handling failed for task {}: {}",
|
||||||
task.getTaskId(), e.getMessage(), e);
|
task.getTaskId(), e.getMessage(), e);
|
||||||
|
if (asyncTaskService.isConversationCanceled(task.getConversationId())) {
|
||||||
|
log.info("[ImageGen] Skipping failure broadcast for deleted conversation {}",
|
||||||
|
task.getConversationId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
asyncTaskService.broadcastTaskEvent(task, "async_task_completed",
|
asyncTaskService.broadcastTaskEvent(task, "async_task_completed",
|
||||||
false, null, null, "图片下载或保存失败: " + e.getMessage());
|
false, null, null, "图片下载或保存失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,6 +110,18 @@ public class MusicGenerationService {
|
|||||||
asyncTaskService.updateStatus(task.getTaskId(), "running", null, null, null);
|
asyncTaskService.updateStatus(task.getTaskId(), "running", null, null, null);
|
||||||
|
|
||||||
MusicGenerationResult result = generateWithFallback(request, config);
|
MusicGenerationResult result = generateWithFallback(request, config);
|
||||||
|
|
||||||
|
// The conversation may have been deleted while the provider was
|
||||||
|
// blocking (~120s). Gate the entire post-provider tail — status
|
||||||
|
// update, broadcast, persistence — so we never write to a
|
||||||
|
// tombstoned conversation regardless of whether the provider
|
||||||
|
// succeeded or failed.
|
||||||
|
if (asyncTaskService.isConversationCanceled(conversationId)) {
|
||||||
|
log.info("[Music] Task {} (success={}) aborted: conversation {} was deleted",
|
||||||
|
task.getTaskId(), result.isSuccess(), conversationId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!result.isSuccess()) {
|
if (!result.isSuccess()) {
|
||||||
asyncTaskService.updateStatus(task.getTaskId(), "failed", null, null,
|
asyncTaskService.updateStatus(task.getTaskId(), "failed", null, null,
|
||||||
result.getErrorMessage());
|
result.getErrorMessage());
|
||||||
@ -118,16 +130,6 @@ public class MusicGenerationService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversation may have been deleted while the upstream provider was
|
|
||||||
// blocking (~120s). Persisting now would recreate the attachment
|
|
||||||
// directory we just wiped and INSERT a mate_message row pointing at
|
|
||||||
// a non-existent conversation. Drop the result silently.
|
|
||||||
if (asyncTaskService.isConversationCanceled(conversationId)) {
|
|
||||||
log.info("[Music] Task {} succeeded but conversation {} was deleted, dropping result",
|
|
||||||
task.getTaskId(), conversationId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String audioUrl = persistAudio(conversationId, task.getTaskId(), result);
|
String audioUrl = persistAudio(conversationId, task.getTaskId(), result);
|
||||||
|
|
||||||
saveAssistantMessage(conversationId, audioUrl, result);
|
saveAssistantMessage(conversationId, audioUrl, result);
|
||||||
@ -153,6 +155,11 @@ public class MusicGenerationService {
|
|||||||
log.info("[Music] Task {} succeeded, audio at {}", task.getTaskId(), audioUrl);
|
log.info("[Music] Task {} succeeded, audio at {}", task.getTaskId(), audioUrl);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[Music] Task {} worker failed: {}", task.getTaskId(), e.getMessage(), e);
|
log.error("[Music] Task {} worker failed: {}", task.getTaskId(), e.getMessage(), e);
|
||||||
|
if (asyncTaskService.isConversationCanceled(conversationId)) {
|
||||||
|
log.info("[Music] Skipping failure status/broadcast for deleted conversation {}",
|
||||||
|
conversationId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
asyncTaskService.updateStatus(task.getTaskId(), "failed", null, null,
|
asyncTaskService.updateStatus(task.getTaskId(), "failed", null, null,
|
||||||
"音乐生成异常: " + e.getMessage());
|
"音乐生成异常: " + e.getMessage());
|
||||||
asyncTaskService.broadcastTaskEventWithData(task, "async_task_completed",
|
asyncTaskService.broadcastTaskEventWithData(task, "async_task_completed",
|
||||||
|
|||||||
@ -151,6 +151,16 @@ public class VideoGenerationService {
|
|||||||
* 任务完成时的回写逻辑:下载视频 → 保存消息 → 广播 SSE
|
* 任务完成时的回写逻辑:下载视频 → 保存消息 → 广播 SSE
|
||||||
*/
|
*/
|
||||||
private void handleCompletion(AsyncTaskEntity task, TaskPollResult result) {
|
private void handleCompletion(AsyncTaskEntity task, TaskPollResult result) {
|
||||||
|
// The conversation may have been deleted while the poller was running.
|
||||||
|
// Gate every post-completion side effect — file write, message save,
|
||||||
|
// success/failure broadcast — so we never write to a tombstoned
|
||||||
|
// conversation regardless of which sub-branch we'd take.
|
||||||
|
if (asyncTaskService.isConversationCanceled(task.getConversationId())) {
|
||||||
|
log.info("[VideoGen] Task {} (success={}) aborted: conversation {} was deleted",
|
||||||
|
task.getTaskId(), result.succeeded(), task.getConversationId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (result.succeeded()) {
|
if (result.succeeded()) {
|
||||||
try {
|
try {
|
||||||
String videoUrl = result.videoUrl();
|
String videoUrl = result.videoUrl();
|
||||||
@ -161,15 +171,6 @@ public class VideoGenerationService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversation deletion can race with the final poll iteration;
|
|
||||||
// dropping the result avoids resurrecting attachment files and
|
|
||||||
// a dangling mate_message row.
|
|
||||||
if (asyncTaskService.isConversationCanceled(task.getConversationId())) {
|
|
||||||
log.info("[VideoGen] Task {} succeeded but conversation {} was deleted, dropping result",
|
|
||||||
task.getTaskId(), task.getConversationId());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下载视频到本地
|
// 下载视频到本地
|
||||||
Path localPath = fileDownloader.download(videoUrl, task.getConversationId(), task.getTaskId());
|
Path localPath = fileDownloader.download(videoUrl, task.getConversationId(), task.getTaskId());
|
||||||
String servingUrl = fileDownloader.toServingUrl(task.getConversationId(), localPath);
|
String servingUrl = fileDownloader.toServingUrl(task.getConversationId(), localPath);
|
||||||
@ -192,6 +193,11 @@ public class VideoGenerationService {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[VideoGen] Completion handling failed for task {}: {}",
|
log.error("[VideoGen] Completion handling failed for task {}: {}",
|
||||||
task.getTaskId(), e.getMessage(), e);
|
task.getTaskId(), e.getMessage(), e);
|
||||||
|
if (asyncTaskService.isConversationCanceled(task.getConversationId())) {
|
||||||
|
log.info("[VideoGen] Skipping failure broadcast for deleted conversation {}",
|
||||||
|
task.getConversationId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
asyncTaskService.broadcastTaskEvent(task, "async_task_completed",
|
asyncTaskService.broadcastTaskEvent(task, "async_task_completed",
|
||||||
false, null, "视频下载或保存失败: " + e.getMessage());
|
false, null, "视频下载或保存失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user