Skip to content

Fix false positive for os.sendfile via asyncio sock_sendfile path - #58

Merged
cbornet merged 1 commit into
cbornet:mainfrom
agners:fix-sendfile-sock-whitelist
May 20, 2026
Merged

Fix false positive for os.sendfile via asyncio sock_sendfile path#58
cbornet merged 1 commit into
cbornet:mainfrom
agners:fix-sendfile-sock-whitelist

Conversation

@agners

@agners agners commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends the os.sendfile whitelist to also allow the selector event loop's socket-level path (asyncio/unix_events.py:_sock_sendfile_native_impl). This is the path aiohttp's FileResponse ends up on when serving static files, so any aiohttp app using blockbuster currently hits a false positive BlockingError.

The whitelisted call site runs os.sendfile on a non-blocking socket and explicitly handles BlockingIOError/InterruptedError by registering a writer on the fd — i.e. the syscall returns EAGAIN instead of blocking, just like the already-whitelisted asyncio/base_events.py:sendfile path.

The two whitelist entries cover the two stack shapes that reach os.sendfile:

  • when the first os.sendfile call sends everything in one go, the call frame chain includes base_events.py:sendfile directly (covered by the existing entry)
  • when the socket fills before the file does, asyncio sets up an add_writer callback and re-enters _sock_sendfile_native_impl from the event loop's callback dispatch — the stack no longer goes through base_events.py:sendfile, so we need the new entry

Fixes #57

Test plan

  • Existing test_os_sendfile still passes (uv run pytest -k sendfile)
  • Reproduced the false positive locally on Python 3.14.5 + aiohttp 3.13.5 + blockbuster 1.5.26 using a slow client + small SO_RCVBUF to force the EAGAIN/add_writer path
  • With this patch applied, the same reproducer streams the full file cleanly with no BlockingError in the server logs

The os.sendfile whitelist only covered the transport-level
loop.sendfile() path through asyncio/base_events.py. The
socket-level loop.sock_sendfile() path goes through
asyncio/unix_events.py:_sock_sendfile_native_impl, which is what
aiohttp's FileResponse ends up on the selector event loop. That
syscall runs on a non-blocking socket and handles EAGAIN by
registering a writer, so it does not actually block.

Fixes cbornet#57
@cbornet
cbornet merged commit eec8333 into cbornet:main May 20, 2026
20 checks passed
agners added a commit to home-assistant/supervisor that referenced this pull request May 20, 2026
Serving aiohttp FileResponse triggers BlockBuster's os.sendfile guard
because the actual syscall happens in asyncio/unix_events.py's
_sock_sendfile_native_impl rather than the base_events.sendfile entry
point that blockbuster whitelists. The socket is non-blocking and
EAGAIN is handled by re-registering a writer, so the call is safe.

Extend the os.sendfile allowlist with the selector-loop caller as a
local workaround. Fixed upstream by
cbornet/blockbuster#58 (issue
cbornet/blockbuster#57); drop this once
blockbuster is bumped to a version that includes the fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
agners added a commit to home-assistant/supervisor that referenced this pull request May 22, 2026
Serving aiohttp FileResponse triggers BlockBuster's os.sendfile guard
because the actual syscall happens in asyncio/unix_events.py's
_sock_sendfile_native_impl rather than the base_events.sendfile entry
point that blockbuster whitelists. The socket is non-blocking and
EAGAIN is handled by re-registering a writer, so the call is safe.

Extend the os.sendfile allowlist with the selector-loop caller as a
local workaround. Fixed upstream by
cbornet/blockbuster#58 (issue
cbornet/blockbuster#57); drop this once
blockbuster is bumped to a version that includes the fix.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

False positive: os.sendfile flagged when called via asyncio selector event loop's sock_sendfile

2 participants