cloudflare / cloudflare/workerd

Python Workers: NoGilError race condition during concurrent request initialization (preparePython)

Open
#6,624 23 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
8.7k
Forks
739
Avg merge
2d 20h
Merged PRs (30d)
174

Description

## Bug Description

Python Workers using FastAPI throw `NoGilError: Attempted to use PyProxy when Python GIL not held` when multiple concurrent HTTP requests hit the same isolate. The error occurs in the runtime's `preparePython`/`initPyInstance` code path — not in application code.

## Stack Trace

```
NoGilError: Attempted to use PyProxy when Python GIL not held
at throw_no_gil (pyodide-internal:generated/emscriptenSetup:20688:13)
at wasm://wasm/020fb45e:wasm-function[432]:0x117f9b
at python_getattr (pyodide-internal:generated/emscriptenSetup:23035:13)
at Object.get (pyodide-internal:generated/emscriptenSetup:23106:119)
at async preparePython (pyodide:python-entrypoint-helper:202:28)
at async initPyInstance (pyodide:python-entrypoint-helper:258:24)
at async Proxy. (pyodide:python-entrypoint-helper:300:36)
```

## Reproduction

1. Deploy a Python Worker with FastAPI that makes async HTTP calls
2. Send 3-5 concurrent requests to different endpoints on the same worker
3. Observe `NoGilError` on some requests, with outcome `exception`

The error is more likely when:
- Requests involve CPU-bound work
- Multiple requests arrive within the same ~100ms window
- The isolate is handling concurrent requests in stateless execution mode

## Environment

- `compatibility_date`: `2025-12-01` **NOTE** I just bumped my compat date to `2026-04-01`, so perhaps that will help too.
- `compatibility_flags`: `["python_workers"]`
- Execution model: stateless
- Observed in deployed staging environment (not local dev)

## Analysis

The race condition appears to be in the isolate re-use logic. When a second request arrives while the first is awaiting I/O (GIL released), `preparePython` for the second request tries to access a Python proxy object, but the GIL acquisition fails — likely because the first request's coroutine is resuming or the V8 snapshot restoration leaves the GIL in an inconsistent state.

All application code uses async/await correctly (no threading, no blocking I/O). The error is entirely within `pyodide-internal` and `python-entrypoint-helper`.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.