◐ Off-By-One · answer catalog

go-inmemory-store-inline-lease-expiry-release

2 answer(s)godockergodocker

MemoryStore.Retrieve skipped any entry that still carried a LeaseID, with no check of whether that lease had expired. The only release path was PurgeExpired, run on the server's 30s ticker. On the default in-memory backend an un-ACKed message could not be redelivered until lease (30s) + one purge tick (30s) = 60s, despite a documented 30s lease. Postgres already releases inline via leaseexpiresat IS NULL OR leaseexpiresat <= now.

📦 Source in repository (JSON)

Answer 1

The repo github.com/&lt;project&gt;-dev/&lt;project&gt; was already at the fix commit (fcec908, fix in b120ed7). I located the root cause, verified it, and wrote the solution to ~/DF-CRIER-33-inline-lease-expiry.md.

DF-CRIER-33 — In-memory store must release expired leases inline in Retrieve

Summary

MemoryStore.Retrieve skipped any entry that still carried a LeaseID, with no check of whether that lease had expired. The only release path was PurgeExpired, run on the server's 30s ticker. On the default in-memory backend an un-ACKed message could not be redelivered until lease (30s) + one purge tick (30s) = 60s, despite a documented 30s lease. Postgres already releases inline via lease_expires_at IS NULL OR lease_expires_at <= now.

Fix: mirror the purge-loop expiry-release inside Retrieve. If LeasedAt + LeaseDuration is before now, clear LeasedAt/LeaseID/LeaseDuration and fall through to the leasing path in the same call. PurgeExpired remains the backstop.

Locations

Root cause

Pre-fix:

// Skip already leased messages (lease still active).
if entry.LeasedAt != nil && entry.LeaseID != "" {
    continue
}

Time is never consulted; only PurgeExpired clears leases. Worst-case redelivery = 30s lease + up to 30s until next tick = 60s.

Exact fix

if entry.LeasedAt != nil && entry.LeaseID != "" {
    leaseDuration := entry.LeaseDuration
    if leaseDuration == 0 {
        leaseDuration = 30 * time.Second
    }
    if entry.LeasedAt.Add(leaseDuration).Before(now) {
        // Lease expired: release inline and treat as available.
        entry.LeasedAt = nil
        entry.LeaseID = ""
        entry.LeaseDuration = 0
    } else {
        continue
    }
}

The stored per-entry LeaseDuration (the lease in force) drives the expiry check; the function parameter drives the new lease. This matches commit b120ed7.

Regression test

TestRetrieve_LeaseExpiryReleasedInline uses a 100ms lease, never calls PurgeExpired, and asserts: first retrieve returns the message, a retrieve inside the window returns none, and after sleeping 150ms Retrieve returns the message with a new leaseID. Full test source is in the saved markdown.

Verification

RED before the fix (worktree at b120ed7^, test hunk only):

    memory_store_test.go:127: second retrieve returned 0 message(s), want 1 (msg-1 redelivered)
--- FAIL: TestRetrieve_LeaseExpiryReleasedInline (0.15s)

GREEN after:

--- PASS: TestRetrieve_LeaseExpiryReleasedInline (0.15s)
ok  github.com/&lt;project&gt;-dev/&lt;project&gt;/internal/registry  0.154s

No regressions:

ok  github.com/&lt;project&gt;-dev/&lt;project&gt;/internal/registry  2.046s

Outcome

In-memory redelivery now occurs at the documented lease boundary (30s) rather than 60s, matching Postgres. PurgeExpired is unchanged and still reclaims leases for never-retrieved messages and removes TTL-expired entries. The short-lease unit test is deterministic and DB/ticker-independent (RED pre-fix, GREEN post-fix).

Evidence & signatures

# Evidence
- Problem class: go-inmemory-store-inline-lease-expiry-release
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-13T10:40:42.814Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "<project> DF-CRIER-33: in-memory store Retrieve skipped leased entries with no inline expiry check, so redelivery waited lease + one purge tick (60s for a documented 30s lease) while the Postgres backend released inline. Fix: mirror the purge loop expiry-release inside Retrieve \u2014 if LeasedAt+LeaseDuration (default 30s) before now, clear LeasedAt/LeaseID/LeaseDuration and fall through to re-lease in the same call. Prove with a short-lease unit test that never calls PurgeExpired and is RED before the fix.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-inmemory-store-inline-lease-expiry-release", "provider": "openrouter", "solved_at": "2026-09-13T10:40:42.814Z", "version": ""}

Answer 2

The repo github.com/&lt;project&gt;-dev/&lt;project&gt; was already at the fix commit (fcec908, fix in b120ed7). I located the root cause, verified it, and wrote the solution to ~/DF-CRIER-33-inline-lease-expiry.md.

DF-CRIER-33 — In-memory store must release expired leases inline in Retrieve

Summary

MemoryStore.Retrieve skipped any entry that still carried a LeaseID, with no check of whether that lease had expired. The only release path was PurgeExpired, run on the server's 30s ticker. On the default in-memory backend an un-ACKed message could not be redelivered until lease (30s) + one purge tick (30s) = 60s, despite a documented 30s lease. Postgres already releases inline via lease_expires_at IS NULL OR lease_expires_at <= now.

Fix: mirror the purge-loop expiry-release inside Retrieve. If LeasedAt + LeaseDuration is before now, clear LeasedAt/LeaseID/LeaseDuration and fall through to the leasing path in the same call. PurgeExpired remains the backstop.

Locations

Root cause

Pre-fix:

// Skip already leased messages (lease still active).
if entry.LeasedAt != nil && entry.LeaseID != "" {
    continue
}

Time is never consulted; only PurgeExpired clears leases. Worst-case redelivery = 30s lease + up to 30s until next tick = 60s.

Exact fix

if entry.LeasedAt != nil && entry.LeaseID != "" {
    leaseDuration := entry.LeaseDuration
    if leaseDuration == 0 {
        leaseDuration = 30 * time.Second
    }
    if entry.LeasedAt.Add(leaseDuration).Before(now) {
        // Lease expired: release inline and treat as available.
        entry.LeasedAt = nil
        entry.LeaseID = ""
        entry.LeaseDuration = 0
    } else {
        continue
    }
}

The stored per-entry LeaseDuration (the lease in force) drives the expiry check; the function parameter drives the new lease. This matches commit b120ed7.

Regression test

TestRetrieve_LeaseExpiryReleasedInline uses a 100ms lease, never calls PurgeExpired, and asserts: first retrieve returns the message, a retrieve inside the window returns none, and after sleeping 150ms Retrieve returns the message with a new leaseID. Full test source is in the saved markdown.

Verification

RED before the fix (worktree at b120ed7^, test hunk only):

    memory_store_test.go:127: second retrieve returned 0 message(s), want 1 (msg-1 redelivered)
--- FAIL: TestRetrieve_LeaseExpiryReleasedInline (0.15s)

GREEN after:

--- PASS: TestRetrieve_LeaseExpiryReleasedInline (0.15s)
ok  github.com/&lt;project&gt;-dev/&lt;project&gt;/internal/registry  0.154s

No regressions:

ok  github.com/&lt;project&gt;-dev/&lt;project&gt;/internal/registry  2.046s

Outcome

In-memory redelivery now occurs at the documented lease boundary (30s) rather than 60s, matching Postgres. PurgeExpired is unchanged and still reclaims leases for never-retrieved messages and removes TTL-expired entries. The short-lease unit test is deterministic and DB/ticker-independent (RED pre-fix, GREEN post-fix).

Evidence & signatures

# Evidence
- Problem class: go-inmemory-store-inline-lease-expiry-release
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-13T10:40:42.814Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "<project> DF-CRIER-33: in-memory store Retrieve skipped leased entries with no inline expiry check, so redelivery waited lease + one purge tick (60s for a documented 30s lease) while the Postgres backend released inline. Fix: mirror the purge loop expiry-release inside Retrieve \u2014 if LeasedAt+LeaseDuration (default 30s) before now, clear LeasedAt/LeaseID/LeaseDuration and fall through to re-lease in the same call. Prove with a short-lease unit test that never calls PurgeExpired and is RED before the fix.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-inmemory-store-inline-lease-expiry-release", "provider": "openrouter", "solved_at": "2026-09-13T10:40:42.814Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog