跳至主要内容

Backend Request Context Baseline

Purpose

This document records the current request-context boundary for backend DB work tracked by #645. It is a review aid for request timeout cancellation work; it does not introduce new timeout policy, queue architecture, or worker behavior.

Request-scoped DB rule

HTTP handlers should pass c.Request.Context() into service methods that accept context.Context. Service-layer DB reads, writes, and transactions that belong to that request should use db.WithContext(ctx) or a transaction that inherited that context.

Legacy wrapper methods that do not accept a context may use context.Background() only as a compatibility boundary for non-request callers and tests. New request-serving paths should prefer the *Context form.

Transaction context rule

Transactions started from db.WithContext(ctx).Transaction(...) carry the request context through tx.Statement.Context. Helpers that need to re-enter GORM with an explicit context should preserve that statement context rather than falling back to a fresh background context.

Example: loadTachiBalanceValue(ctx, db, userID) accepts an explicit context, and finalizeClaim calls it with gormStatementContext(tx) so balance readback stays aligned with the transaction context.

Intentionally detached DB paths

The following paths are intentionally not a mechanical WithContext(requestCtx) replacement target. Some are true async jobs; others are synchronous post-side-effect persistence paths where cancellation semantics must preserve reconciliation evidence before they are changed.

PathCurrent boundaryWhy detached
RaffleScheduler.Start scheduled snapshotsThe scheduler is launched with its own lifecycle context. Each run creates a five-minute child context and passes it through RunScheduledSnapshots, snapshotOne, and DB calls that use s.db.WithContext(ctx).This is not tied to an HTTP request. The scheduler should stop on process/service lifecycle cancellation, not on any viewer/admin request cancellation.
Raffle winner email / Discord notification goroutinesDrawNext / DrawFromTier start short-lived goroutines that create a fresh 30-second background timeout before calling notification helpers. Those helpers use that timeout for DB lookup and outbound delivery.Notification delivery is intentionally best-effort after the draw is already committed. It should not inherit a canceled request context and undo or block the draw response path.
ClaimService.Claim post-mint / compensation transactionsThe initial reservation transaction uses s.db.WithContext(ctx). Rollback, broadcast recording, receipt-unknown recording, failure compensation, finalize, and finalize-failed marking currently use service DB transactions without the request context.After a mint transaction is broadcast, a canceled HTTP request must not prevent local claim status, compensation, or reconciliation markers from being persisted. A future change needs dedicated tests for canceled requests after broadcast and receipt uncertainty.
SpendService.Redeem after burn / Tachiya handoffThe initial reservation transaction uses s.db.WithContext(ctx). Burn uses a child timeout of the request context. Coupon redemption recording, post-burn status updates, and reservation rollback currently use service DB operations without the request context; Tachiya redemption uses its own background timeout.After a burn is broadcast, local coupon_redemptions rows and compensation-needed state are the recovery trail. Existing tests assert the Tachiya call can outlive a canceled request after burn.

Change requirements

Any PR that converts one of the detached paths above to request-scoped DB work must include regression coverage for the cancellation point it changes. At minimum, tests should prove that a canceled request cannot leave one of these states without a durable recovery marker:

  • claim reservation consumed but no claim status or compensation marker;
  • mint broadcast recorded by chain but missing local claim broadcast evidence;
  • burn broadcast recorded by chain but missing local coupon redemption evidence;
  • Tachiya voucher issued but local redemption status missing reconciliation data.

If the desired behavior is to keep post-chain persistence detached, keep that decision documented here instead of hiding it in broad context-propagation cleanup.