Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions backend/src/services/streamStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,9 +453,6 @@ export function calculateProgress(
const effectiveAt =
stream.pausedAt !== undefined ? Math.min(at, stream.pausedAt) : at;

const elapsed = Math.max(0, Math.min(effectiveAt - stream.startAt - stream.pausedDuration, stream.durationSeconds));

const ratio = Math.min(1, elapsed / stream.durationSeconds);
const elapsed = Math.max(0, Math.max(0, effectiveAt - stream.startAt) - stream.pausedDuration);
const ratio = stream.durationSeconds <= 0 ? 1 : Math.min(1, elapsed / stream.durationSeconds);
const elapsedSeconds = stream.durationSeconds <= 0 ? 0 : Math.min(elapsed, stream.durationSeconds);
Expand Down Expand Up @@ -1178,20 +1175,22 @@ export async function cancelStream(
logger.warn({ err, streamId: id }, "failed to get refund amount from chain");
}

// Invalidate cache
await invalidateCache(`stream:${id}`);
await invalidateCache("streams:list:");
await invalidateCache("streams:export:");
resetStatsCache();
resetStreamMetricsCache();

// Atomically write the updated stream row and the cancellation event.
// DB must be updated BEFORE cache invalidation to prevent a race where a
// concurrent GET /api/streams re-caches the stale (pre-cancel) state.
const db = getDb();
db.transaction(() => {
upsertStream(stream);
recordEventWithDb(db, stream.id, "canceled", stream.canceledAt!, stream.sender);
})();

// Invalidate cache after DB commit so the next read picks up canceled status.
await invalidateCache(`stream:${id}`);
await invalidateCache("streams:list:");
await invalidateCache("streams:export:");
resetStatsCache();
resetStreamMetricsCache();
Comment on lines +1187 to +1192

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make post-commit cache invalidation resilient.

If any getCache().del() call rejects, cancelStream throws after the SQLite transaction has committed, so the API can return 500 while resetStatsCache() and resetStreamMetricsCache() are skipped. Run invalidations independently with Promise.allSettled or try/finally, log failures, and always reset dependent caches.

As per coding guidelines, backend TypeScript code must access the in-memory LRU cache through getCache() and call resetStatsCache() after mutations that affect stats.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/streamStore.ts` around lines 1187 - 1192, Update the
post-commit cache invalidation in cancelStream to make each invalidateCache call
resilient to rejection, using Promise.allSettled or equivalent failure handling
and logging any invalidation errors. Ensure resetStatsCache() and
resetStreamMetricsCache() always execute after the committed mutation, while
continuing to access the cache through getCache() and reset stats via
resetStatsCache().

Source: Coding guidelines


// Webhook fires after the transaction commits.
triggerWebhook("canceled", stream);
return stream;
Expand Down