Workers SDK Issue Reports

← Back to Dashboard

#5552 pages dev server throws 500 errors while serving many requests in parallel

Recommendation:KEEP OPEN
Difficulty:medium
Reasoning:

No evidence of fix; race condition in asset serving pipeline under concurrent load remains unaddressed despite major version changes

Suggested Action:

Investigate concurrency handling in miniflare asset serving; add stress tests

Analysis Report

Issue Review: cloudflare/workers-sdk#5552

Summary

Pages dev server throws 500 "internal error" when handling many parallel requests during integration testing with headless Playwright browsers.

Findings

  • Created: 2024-04-07
  • Updated: 2025-10-30 (no new comments, possibly automated update)
  • Version: 3.48.0 -> 4.60.0 (major version change)
  • Component: pages, wrangler, miniflare
  • Labels: bug, pages
  • Comments: 0

Key Evidence

  1. No merged PRs explicitly fix this issue - Searched for PRs mentioning #5552, found only a dependency bump (#11376) that mentions it but doesn't fix the core issue.

  2. No changelog mentions - Neither wrangler nor miniflare changelogs reference issue #5552 as being fixed.

  3. Code Analysis - The stack trace points to pages-template-worker.ts:170 with an "internal error" being caught and returned as 500. The error originates from the asset fetching chain:

    • pages-template-worker.ts handles routing for Pages functions
    • When next() is called and there are no more handlers, it falls back to env.ASSETS.fetch()
    • The asset serving involves multiple services: assets-kv.worker.ts, assets.worker.ts (from @cloudflare/workers-shared)
  4. Reproducibility Details - Issue is well-documented with:

    • Cross-platform reproduction (Windows, macOS, Linux)
    • Affects all invocation modes (--proxy, -- <command>, static directory)
    • Occurs intermittently under sustained parallel load (18 concurrent browsers)
    • Appears equally likely for any static asset type (JS, images, fonts)
  5. No related duplicates - No other open issues describe the same concurrent request problem.

  6. Architecture changes - Since the issue was filed (wrangler 3.48.0), significant refactoring has occurred:

    • Workers Assets simulator implemented in miniflare (#6403, #6525)
    • workers-shared package refactored (#9407)
    • Dev architecture refactored (#11244)

    However, none of these explicitly address concurrent request handling or the reported race condition.

Recommendation

Status: KEEP OPEN

Reasoning: This appears to be an unresolved race condition or resource exhaustion issue in the local development asset serving pipeline. Despite major version changes and architectural refactoring since the issue was reported, there's no evidence the specific concurrency issue has been addressed. The problem is well-documented, reproducible across multiple platforms, and represents a real pain point for teams running integration tests.

Action: Keep open for investigation. This likely requires:

  1. Adding concurrency stress tests to the Pages dev test suite
  2. Investigating potential race conditions in the asset fetching chain
  3. Checking for resource limits (file descriptors, connection pools) in the local proxy

Root Cause Analysis

Likely Cause

The error "internal error" being thrown suggests an unhandled exception in the asset serving pipeline under concurrent load. Based on the code architecture:

  1. Asset Fetching Chain: When a request comes in for a static asset:

    • pages-template-worker.ts routes the request
    • Calls env.ASSETS.fetch() for static files
    • This goes through miniflare's assets-kv.worker.ts -> blobsService.fetch()
    • The blob service reads from disk via a disk service
  2. Potential Race Points:

    • File descriptor exhaustion: Multiple concurrent reads from disk storage service
    • Service binding saturation: The MAYBE_SERVICE_BLOBS fetcher may have connection limits
    • Response body consumption: The cloneResponse() function creates new Response objects with the body stream - if streams aren't properly consumed under concurrent load, this could cause issues
  3. Code Reference: The relevant file at packages/wrangler/templates/pages-template-worker.ts:164-172:

    } else if (__FALLBACK_SERVICE__) {
      // There are no more handlers so finish with the fallback service (`env.ASSETS.fetch` in Pages' case)
      const response = await env[__FALLBACK_SERVICE__].fetch(request);
      return cloneResponse(response);
    }
    

    The error is thrown somewhere in the env[__FALLBACK_SERVICE__].fetch(request) call chain.

Files That Would Need Investigation/Modification

  1. packages/miniflare/src/plugins/assets/index.ts - Asset plugin configuration
  2. packages/miniflare/src/workers/assets/assets-kv.worker.ts - KV-style asset fetching
  3. packages/wrangler/templates/pages-template-worker.ts - Error handling in the template worker
  4. packages/miniflare/src/runtime/index.ts - Workerd runtime configuration (potential resource limits)

Proposed Solution

Option 1: Add Better Error Handling (Easy)

Wrap the asset fetch in try-catch to provide more diagnostic information:

// In pages-template-worker.ts
} else if (__FALLBACK_SERVICE__) {
  try {
    const response = await env[__FALLBACK_SERVICE__].fetch(request);
    return cloneResponse(response);
  } catch (error) {
    console.error(`[pages-dev] Asset fetch failed for ${request.url}:`, error);
    // Return a more informative error
    return new Response(
      JSON.stringify({ 
        error: 'Asset fetch failed', 
        url: request.url,
        details: error.message 
      }),
      { status: 500, headers: { 'Content-Type': 'application/json' } }
    );
  }
}

Option 2: Add Retry Logic with Backoff (Medium)

// In pages-template-worker.ts
const fetchWithRetry = async (service: any, request: Request, maxRetries = 3) => {
  let lastError;
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await service.fetch(request);
    } catch (error) {
      lastError = error;
      if (i < maxRetries - 1) {
        await new Promise(r => setTimeout(r, Math.pow(2, i) * 10));
      }
    }
  }
  throw lastError;
};

Option 3: Investigate and Fix Root Cause in Miniflare (Hard)

This would require:

  1. Adding concurrency limits to the disk service
  2. Implementing connection pooling for service bindings
  3. Adding proper backpressure handling for the asset serving pipeline

Implementation Difficulty

Medium to Hard - The surface-level fix (better error handling) is easy, but properly fixing the race condition requires:

  • Deep understanding of workerd's service binding implementation
  • Potential changes to miniflare's runtime configuration
  • Adding comprehensive concurrency tests

Testing Recommendations

  1. Create a stress test fixture:

    // test/pages-dev-concurrent.test.ts
    test('handles 50 concurrent asset requests', async () => {
      const requests = Array(50).fill(null).map(() => 
        fetch('http://localhost:8788/static/bundle.js')
      );
      const responses = await Promise.all(requests);
      expect(responses.every(r => r.status === 200)).toBe(true);
    });
    
  2. Add Playwright-based integration test:

    • Spin up Pages dev server
    • Launch multiple browser contexts
    • Load a page with many assets simultaneously
    • Assert no 500 errors occur
  3. Add monitoring/logging for failed asset requests to better diagnose production issues.

Suggested Comment

Thank you for this detailed bug report. This issue appears to still be relevant in wrangler 4.x. The concurrent request handling in the Pages dev server's asset serving pipeline may have race conditions under high load.

Could someone from the team investigate:

  1. Whether the miniflare asset serving pipeline has any connection/resource limits that could be exhausted under concurrent load
  2. Adding concurrency stress tests to the Pages dev test suite

For users experiencing this issue: As a workaround, you may be able to reduce the number of concurrent test browsers or add retry logic in your test framework for asset requests.

Notes & Feedback (0)

No notes yet.

Add Note