Skip to content

Graceful shutdown should cancel in-flight payment verification #81

Description

@ContextVM-org

When a priced request arrives, the payment middlewares spawn detached background tasks that poll processor.verifyPayment until the payment verifies or verifyTimeoutMs (default ≤ 5 min) elapses:

  • transparent: src/payments/server-payments.ts — the inFlight verify loop inside request processing
  • explicit gating: src/payments/server-explicit-gating.ts — the detached verify task after -32042

NostrServerTransport.close() clears the correlation store, session store, and stream factories — but nothing signals these tasks to stop. rs-sdk cancels them via a cancellation token parented to server shutdown.

Observed effects (worst first)

  1. Paid tool executes after shutdown began (transparent mode): a verify that resolves after close() proceeds to forward the invocation to the underlying MCP handler and publish payment_accepted. In-flight-at-close requests only, but it breaks rolling-deploy/graceful-shutdown expectations.
  2. Process exit delayed: the poll timers (withTimeout's setTimeout) keep the event loop alive up to verifyTimeoutMs after close() — hangs shutdown scripts and test teardown.
  3. Wasted external polls: continues hitting the payment processor's API (LNbits/NWC/…) for up to 5 min after the server is gone.
  4. Grant written to dead state (explicit gating): verify success post-close writes into an AuthorizationStore nobody reads. Harmless today (new withServerPayments = new store), noting for completeness.

Bounds / severity

Not a money bug: no double-charge, no unbounded growth, no cross-restart state leak. Everything is capped by pending count (≤ maxPendingPayments) and verifyTimeoutMs. Hygiene + graceful-shutdown correctness.

Proposed fix shape

  • withServerPayments creates an AbortController shared by both middlewares.
  • NostrServerTransport exposes a middleware-observable close hook (none exists for this purpose today).
  • Verify/poll loops check signal.aborted per iteration and bail (explicit: skip the grant; transparent: skip the forward — kills effect 1).
  • On abort, treat as verify-failure cleanup (clear pending) so state stays coherent.

~30 lines across 4 files (server-payments.ts, server-explicit-gating.ts, server-transport-payments.ts, nostr-server-transport.ts), plus a test that closes mid-verify and asserts no forward and no lingering timer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions