Skip to content

Commit 141eabd

Browse files
author
Your Name
committed
fix: circuit breaker recovery, recordSuccess race, vibe queue self-heal
Fix 1 — Circuit breaker deadlock (audioAnalysisCleanup.ts): shouldAttemptReset() was measuring time since last failure, which resets every cleanup cycle. Added circuitOpenedAt fixed at the moment the breaker first opens. shouldAttemptReset() now measures from that fixed point so the 5-minute window actually expires regardless of ongoing failure activity. Fix 2 — recordSuccess() race condition (unifiedEnrichment.ts): recordSuccess() compared audioCompletedBefore/After bracketing only cleanupStaleProcessing() — a millisecond window that never captured Python completions (~14s batch cadence → effectively 0% hit rate). Replaced with audioLastCycleCompletedCount tracked across cycles. executeAudioPhase() now fires recordSuccess() whenever completed count grew since the previous cycle (~1 min window), reliably detecting analyzer progress. Fix 3 — CLAP vibe embeddings queue self-healing (unifiedEnrichment.ts): queueVibeEmbeddings() filtered vibeAnalysisStatus = 'pending', silently skipping 7286 tracks left as 'completed' after reduce_embedding_dimension migration dropped their embeddings. Changed filter to <> 'processing' so te.track_id IS NULL (actual embedding existence) is the source of truth. Queue now self-heals if embeddings are ever wiped again.
1 parent 3dcfe09 commit 141eabd

File tree

2 files changed

+20
-12
lines changed

2 files changed

+20
-12
lines changed

backend/src/services/audioAnalysisCleanup.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ class AudioAnalysisCleanupService {
1313
private state: CircuitState = "closed";
1414
private failureCount = 0;
1515
private lastFailureTime: Date | null = null;
16+
private circuitOpenedAt: Date | null = null;
1617

1718
private shouldAttemptReset(): boolean {
18-
if (!this.lastFailureTime) return false;
19-
const timeSinceFailure = Date.now() - this.lastFailureTime.getTime();
20-
return timeSinceFailure >= CIRCUIT_BREAKER_WINDOW_MS;
19+
if (!this.circuitOpenedAt) return false;
20+
const timeSinceOpen = Date.now() - this.circuitOpenedAt.getTime();
21+
return timeSinceOpen >= CIRCUIT_BREAKER_WINDOW_MS;
2122
}
2223

2324
private onSuccess(): void {
@@ -28,12 +29,14 @@ class AudioAnalysisCleanupService {
2829
this.state = "closed";
2930
this.failureCount = 0;
3031
this.lastFailureTime = null;
32+
this.circuitOpenedAt = null;
3133
} else if (this.state === "closed" && this.failureCount > 0) {
3234
logger.debug(
3335
"[AudioAnalysisCleanup] Resetting failure counter on success"
3436
);
3537
this.failureCount = 0;
3638
this.lastFailureTime = null;
39+
this.circuitOpenedAt = null;
3740
}
3841
}
3942

@@ -48,11 +51,14 @@ class AudioAnalysisCleanupService {
4851

4952
if (this.state === "half-open") {
5053
this.state = "open";
54+
// Don't reset circuitOpenedAt — keep original open time so the
55+
// next shouldAttemptReset() fires immediately and retries HALF-OPEN
5156
logger.warn(
5257
`[AudioAnalysisCleanup] Circuit breaker REOPENED - recovery attempt failed (${this.failureCount} total failures)`
5358
);
5459
} else if (this.failureCount >= CIRCUIT_BREAKER_THRESHOLD) {
5560
this.state = "open";
61+
this.circuitOpenedAt = new Date(); // fixed point — not updated on subsequent failures
5662
logger.warn(
5763
`[AudioAnalysisCleanup] Circuit breaker OPEN - ${this.failureCount} failures in window. ` +
5864
`Pausing audio analysis queuing until analyzer shows signs of life.`
@@ -80,6 +86,7 @@ class AudioAnalysisCleanupService {
8086
this.state = "closed";
8187
this.failureCount = 0;
8288
this.lastFailureTime = null;
89+
this.circuitOpenedAt = null;
8390
logger.debug("[AudioAnalysisCleanup] Circuit breaker reset");
8491
}
8592

backend/src/workers/unifiedEnrichment.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ let isStopping = false;
4141
let immediateEnrichmentRequested = false;
4242
let consecutiveSystemFailures = 0; // Track consecutive system-level failures
4343
let lastRunTime = 0;
44+
let audioLastCycleCompletedCount: number | null = null;
4445
const MIN_INTERVAL_MS = 10000; // Minimum 10s between cycles
4546

4647
// Batch failure tracking
@@ -987,7 +988,7 @@ async function queueVibeEmbeddings(): Promise<number> {
987988
LEFT JOIN track_embeddings te ON t.id = te.track_id
988989
WHERE te.track_id IS NULL
989990
AND t."filePath" IS NOT NULL
990-
AND (t."vibeAnalysisStatus" IS NULL OR t."vibeAnalysisStatus" = 'pending')
991+
AND (t."vibeAnalysisStatus" IS NULL OR t."vibeAnalysisStatus" <> 'processing')
991992
LIMIT 1000
992993
`;
993994

@@ -1072,10 +1073,17 @@ async function executeMoodTagsPhase(): Promise<number> {
10721073
}
10731074

10741075
async function executeAudioPhase(): Promise<number> {
1075-
const audioCompletedBefore = await prisma.track.count({
1076+
// Compare completed count to previous cycle (~1 min ago) — a much wider
1077+
// window than the milliseconds between two counts around cleanupStaleProcessing().
1078+
const currentCompleted = await prisma.track.count({
10761079
where: { analysisStatus: "completed" },
10771080
});
10781081

1082+
if (audioLastCycleCompletedCount !== null && currentCompleted > audioLastCycleCompletedCount) {
1083+
audioAnalysisCleanupService.recordSuccess();
1084+
}
1085+
audioLastCycleCompletedCount = currentCompleted;
1086+
10791087
const cleanupResult =
10801088
await audioAnalysisCleanupService.cleanupStaleProcessing();
10811089
if (cleanupResult.reset > 0 || cleanupResult.permanentlyFailed > 0) {
@@ -1084,13 +1092,6 @@ async function executeAudioPhase(): Promise<number> {
10841092
);
10851093
}
10861094

1087-
const audioCompletedAfter = await prisma.track.count({
1088-
where: { analysisStatus: "completed" },
1089-
});
1090-
if (audioCompletedAfter > audioCompletedBefore) {
1091-
audioAnalysisCleanupService.recordSuccess();
1092-
}
1093-
10941095
if (audioAnalysisCleanupService.isCircuitOpen()) {
10951096
logger.warn(
10961097
"[Enrichment] Audio analysis circuit breaker OPEN - skipping queue",

0 commit comments

Comments
 (0)