Queue durable Studio 2 jobs with sticky identity and saved metadata

This commit is contained in:
Towsty
2026-09-10 20:56:37 -05:00
parent bd82f5bd5f
commit 70b6b70fcd
12 changed files with 374 additions and 10 deletions
+19 -5
View File
@@ -16,6 +16,7 @@ export type StudioJobStatus = 'waiting' | 'running' | 'held' | 'complete' | 'err
export type StudioJobKind = 'video' | 'edit' | 'music'
export interface StudioJobPayload {
studio2Id?: string
upscale?: { sourceId: string; scale: 2 | 4; target: string; fps: string | number; enhance: string }
prompt: string
promptMid?: string
@@ -285,7 +286,7 @@ function failZombieLiveJob(job: Job, error: string) {
function sweepStaleLiveJobs() {
const now = Date.now()
for (const job of listJobs()) {
if (job.yueGp || job.upscale) continue
if (job.studio2 || job.yueGp || job.upscale) continue
if (job.saving) continue
if (job.status === 'queued' && !job.promptId && now - job.startedAt >= QUEUED_GRACE_MS) {
failZombieLiveJob(job, 'Job never started')
@@ -305,7 +306,7 @@ async function reapZombieLiveJobs() {
const { fetchHistory } = await import('~/server/utils/comfy')
for (const job of listJobs()) {
// Never interrupt download/stitch/library save — Comfy is idle then by design.
if (job.yueGp || job.upscale) continue
if (job.studio2 || job.yueGp || job.upscale) continue
if (job.saving) continue
if (job.library?.chainContinuing) continue
if (job.status !== 'running' && job.status !== 'uploading' && job.status !== 'queued') continue
@@ -350,7 +351,7 @@ async function reapZombieLiveJobs() {
}
function liveJobOwnsGpu(job: Job) {
if ((job.yueGp || job.upscale) && ['running', 'queued', 'uploading'].includes(job.status)) return true
if ((job.studio2 || job.yueGp || job.upscale) && ['running', 'queued', 'uploading'].includes(job.status)) return true
if (job.library?.stopAfterCurrent) return false
if (job.library?.chainContinuing) return true
if (job.saving) return true
@@ -370,7 +371,7 @@ function liveJobOwnsGpu(job: Job) {
*/
function clearDeadGpuClaimsForForceStart() {
for (const live of listJobs()) {
if (live.yueGp || live.upscale) continue
if (live.studio2 || live.yueGp || live.upscale) continue
if (live.saving) continue
if (jobIsLocallySubmitting(live)) continue
if (live.status !== 'running' && live.status !== 'queued' && live.status !== 'uploading') {
@@ -1422,6 +1423,19 @@ export async function startStudioJob(item: StudioJob) {
}
async function startStudioJobReserved(item: StudioJob) {
if (item.payload.studio2Id) {
try {
const { startStudio2Job } = await import('./studio2/runner')
await startStudio2Job(item)
} catch (error) {
await patchStudioJob(item.ownerKey, item.id, row => {
row.status = 'error'
row.lastError = error instanceof Error ? error.message : String(error)
})
scheduleKickRetry()
}
return
}
if (item.payload.upscale) {
try { const { startUpscaleJob } = await import('./videoUpscale'); await startUpscaleJob(item) }
catch (error) { await patchStudioJob(item.ownerKey, item.id, row => { row.status = 'error'; row.lastError = error instanceof Error ? error.message : String(error) }); scheduleKickRetry() }
@@ -1673,7 +1687,7 @@ export async function onLiveVideoSettled(job: Job) {
return
}
const remaining = remainingStudioShots(job)
const wakeFail = !job.yueGp && !job.upscale && job.status === 'error' && remaining > 0 && isTransientComfyError(job.error)
const wakeFail = !job.studio2 && !job.yueGp && !job.upscale && job.status === 'error' && remaining > 0 && isTransientComfyError(job.error)
const failed = (job.status === 'error' || job.status === 'cancelled') && !wakeFail
await mutateStore(owner, (store) => {