Skip to content

feat(producer): add request-level render concurrency semaphore - #232

Merged
jrusso1020 merged 1 commit into
mainfrom
feat/render-concurrency-semaphore
Apr 9, 2026
Merged

feat(producer): add request-level render concurrency semaphore#232
jrusso1020 merged 1 commit into
mainfrom
feat/render-concurrency-semaphore

Conversation

@jrusso1020

Copy link
Copy Markdown
Collaborator

What

Add a request-level FIFO semaphore to the producer server that limits how many renders execute simultaneously.

Why

Parallel renders cause Chrome CPU contention — when multiple Chrome instances compete for CPU, beginFrame calls fail because the compositor can't
produce frames within the deadline. This semaphore ensures renders are queued FIFO so only N renders run at once (default 2), preventing the contention
that triggers beginFrame timeouts even with the retry logic from #230.

How

  • Semaphore utility (packages/producer/src/utils/semaphore.ts): Simple async counting semaphore with FIFO queue. acquire() returns a release function; callers hold the slot until they call release().
  • Blocking render handler (POST /render): Acquires the semaphore after input validation but before starting the render. Releases in finally so the slot is freed even on errors.
  • SSE render handler (POST /render/stream): Same acquire/release pattern. Additionally sends a "queued" SSE event if the request must wait, so clients know their position.
  • Queue status endpoint (GET /render/queue): Returns { maxConcurrentRenders, activeRenders, queuedRenders } for observability.
  • Configuration: HandlerOptions.maxConcurrentRenders option, PRODUCER_MAX_CONCURRENT_RENDERS env var (default: 2), and --max-concurrent-renders CLI flag (1-10).

Test plan

  • Unit tests for Semaphore class (acquire/release, FIFO ordering, concurrent limit)
  • Manual testing: send 4 concurrent render requests with maxConcurrentRenders=2, verify only 2 execute at a time
  • Verify GET /render/queue returns correct counts during concurrent renders
  • Verify SSE stream receives "queued" event when semaphore is full
  • Verify release() fires in finally even when render errors — no semaphore leak
  • Verify --max-concurrent-renders CLI flag validates range 1-10

@jrusso1020
jrusso1020 force-pushed the feat/render-concurrency-semaphore branch from 7e27e82 to abdfeee Compare April 9, 2026 17:21
@miguel-heygen
miguel-heygen force-pushed the fix/beginframe-retry-on-pending branch from 18869f4 to aab2d2c Compare April 9, 2026 17:27
@jrusso1020
jrusso1020 force-pushed the feat/render-concurrency-semaphore branch from abdfeee to 8842a95 Compare April 9, 2026 17:54
@jrusso1020
jrusso1020 changed the base branch from fix/beginframe-retry-on-pending to main April 9, 2026 17:54
Add a FIFO semaphore to limit concurrent renders in the producer server,
preventing Chrome CPU contention that causes beginFrame failures.

- New Semaphore utility class (packages/producer/src/utils/semaphore.ts)
- Both blocking render and SSE renderStream handlers acquire/release the semaphore
- SSE stream sends a "queued" event when request must wait
- New GET /render/queue endpoint exposes active/queued render counts
- Configurable via HandlerOptions.maxConcurrentRenders or PRODUCER_MAX_CONCURRENT_RENDERS env var (default: 2)
- New --max-concurrent-renders CLI flag (1-10)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jrusso1020
jrusso1020 force-pushed the feat/render-concurrency-semaphore branch from 8842a95 to 458ee97 Compare April 9, 2026 17:55
@jrusso1020
jrusso1020 merged commit 9115d73 into main Apr 9, 2026
22 checks passed
@jrusso1020
jrusso1020 deleted the feat/render-concurrency-semaphore branch April 9, 2026 18:25
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.

2 participants