Skip to content
Merged
Show file tree
Hide file tree
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
10 changes: 6 additions & 4 deletions packages/loopover-miner/lib/portfolio-queue-expiry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ function leaseAgeMs(item: QueueLeaseEntry, nowMs: number): number | null {

/**
* Return in-flight items whose lease age is strictly greater than `maxLeaseMs`. An item whose age equals
* `maxLeaseMs` exactly is still within the window (not stuck). Items that are not 'in_progress', or whose
* `leasedAt` is missing/unparseable, are never returned.
* `maxLeaseMs` exactly is still within the window (not stuck). Items that are not 'in_progress' are never
* returned; an item whose `leasedAt` is missing/unparseable fails closed and IS returned (swept), so a
* corrupted lease can't strand an item 'in_progress' forever (#8007, matching claim-ledger-expiry post-#7732).
*/
export function findStuckItems(items: QueueLeaseEntry[], nowMs: number, maxLeaseMs: number): QueueLeaseEntry[] {
if (!Number.isFinite(nowMs) || nowMs < 0) throw new Error("invalid_now_ms");
Expand All @@ -33,8 +34,9 @@ export function findStuckItems(items: QueueLeaseEntry[], nowMs: number, maxLease
for (const item of items) {
if (item?.status !== "in_progress") continue;
const ageMs = leaseAgeMs(item, nowMs);
if (ageMs === null) continue;
if (ageMs > maxLeaseMs) stuck.push(item);
// Fail closed on an unparseable leasedAt (#8007): a corrupted/hand-edited row whose age can't be computed
// must still be reclaimable, not left 'in_progress' forever -- mirroring findExpiredClaims post-#7732.
if (ageMs === null || ageMs > maxLeaseMs) stuck.push(item);
}
return stuck;
}
Expand Down
10 changes: 8 additions & 2 deletions test/unit/miner-portfolio-queue-expiry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,18 @@ describe("findStuckItems (#4827)", () => {
expect(findStuckItems([atBound], now, max)).toEqual([]);
});

it("ignores fresh, non-in_progress, and unparseable-lease items", () => {
it("ignores fresh and non-in_progress items", () => {
const fresh = leaseItem({ identifier: "fresh", leasedAt: new Date(now - 1).toISOString() });
const queued = leaseItem({ identifier: "queued", status: "queued", leasedAt: new Date(now - max - 5).toISOString() });
expect(findStuckItems([fresh, queued], now, max)).toEqual([]);
});

it("fails closed: sweeps an in_progress item whose leasedAt is missing or unparseable (#8007)", () => {
// A corrupted/hand-edited lease whose age can't be computed must be reclaimable, not stranded
// 'in_progress' forever -- matching claim-ledger-expiry's post-#7732 posture.
const noLease = leaseItem({ identifier: "nolease", leasedAt: null });
const bogus = leaseItem({ identifier: "bogus", leasedAt: "not-a-date" });
expect(findStuckItems([fresh, queued, noLease, bogus], now, max)).toEqual([]);
expect(findStuckItems([noLease, bogus], now, max)).toEqual([noLease, bogus]);
});

it("validates its arguments", () => {
Expand Down