Skip to content

fix(billing): mount billing middleware once and acquire dev slots ato… - #1554

Open
devogechukwu wants to merge 1 commit into
CalloraOrg:mainfrom
devogechukwu:fix/billing-portal-double-mount
Open

devogechukwu wants to merge 1 commit into
CalloraOrg:mainfrom
devogechukwu:fix/billing-portal-double-mount

Conversation

@devogechukwu

Copy link
Copy Markdown

Summary

Stops /api/billing/portal from executing the billing middleware chain (billing rate limiter, REST limiter, per-developer concurrency middleware) twice by mounting the portal router inside the billing router before access log and timeout middleware. Replaces the check-then-acquire concurrency race condition with atomic synchronous slot acquisition.

Closes #[issue-number]

Changes

  1. Mount Route Once:
    • src/routes/billing.ts: Mounted /portal using createBillingPortalRouter() before billingAccessLogMiddleware and createTimeoutMiddleware.
    • src/routes/index.ts: Removed the redundant separate /billing/portal mount.
    • Public paths (/api/billing/portal/*) and response shapes remain unchanged.
  2. Atomic Slot Acquisition:
    • src/utils/developerSemaphore.ts: Added tryAcquire(developerId: string): (() => void) | null to synchronously verify capacity/queue and increment activeCount. Returns an idempotent release function.
    • src/middleware/perDevConcurrency.ts: Replaced getCurrentActiveSlotCounts comparison and deferred withSlot with const release = semaphore.tryAcquire(userId). Fails fast with 429 on null and guards slot release on finish and close so it releases exactly once.
  3. Tests & Documentation:
    • Added unit test cases to src/middleware/perDevConcurrency.test.ts covering tryAcquire, idempotent double-release (finish followed by close), and error recovery.
    • Added src/routes/billing.portal.mount.test.ts to verify middleware chains execute exactly once on portal routes.
    • Updated docs/per-dev-concurrency.md and verified route map alignment in ARCHITECTURE.md.

Limiter Invocation Counts

Metric / Limiter Before After
Billing Rate Limiter invocations 2 per portal request 1 per portal request
REST Limiter invocations 2 per portal request 1 per portal request
Concurrency slot acquisition attempts 2 per portal request (causes self-429 when max=1) 1 per portal request
Billing Access Log entries 2 (1 fallthrough billing + 1 portal) 1 (portal only)
Timeout controllers attached 2 1

Closes #1503

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stop /api/billing/portal from running the billing rate-limit and concurrency middleware twice

1 participant