Title: Error in BaseEventLoop._sock_sendfile_fallback with offset 0 · Issue #150107 · python/cpython · GitHub
Open Graph Title: Error in BaseEventLoop._sock_sendfile_fallback with offset 0 · Issue #150107 · python/cpython
X Title: Error in BaseEventLoop._sock_sendfile_fallback with offset 0 · Issue #150107 · python/cpython
Description: Bug report Bug description: BaseEventLoop._sock_sendfile_fallback (https://github.com/python/cpython/blob/main/Lib/asyncio/base_events.py#L971) erroneously doesn't seek when passed the offset 0. if offset: file.seek(offset) This check in...
Open Graph Description: Bug report Bug description: BaseEventLoop._sock_sendfile_fallback (https://github.com/python/cpython/blob/main/Lib/asyncio/base_events.py#L971) erroneously doesn't seek when passed the offset 0. if...
X Description: Bug report Bug description: BaseEventLoop._sock_sendfile_fallback (https://github.com/python/cpython/blob/main/Lib/asyncio/base_events.py#L971) erroneously doesn't seek when passed the offset 0...
Opengraph URL: https://github.com/python/cpython/issues/150107
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Error in BaseEventLoop._sock_sendfile_fallback with offset 0","articleBody":"# Bug report\n\n### Bug description:\n\n`BaseEventLoop._sock_sendfile_fallback` (https://github.com/python/cpython/blob/main/Lib/asyncio/base_events.py#L971) erroneously doesn't seek when passed the offset 0.\n```python\n if offset:\n file.seek(offset)\n```\nThis check in the function doesn't call `file.seek` if offset is falsey. If the file in question has a file pointer that is not currently at the 0 offset then this will erroneously send from the current offset and not the start of the file as desired. This can happen when `sendfile` is called with the destination socket is an SSL socket.\n\nRepro code (generated by Claude), because of needing to create an SSL socket it's a bit involved - including making a self signed cert with the openssl command.\n```python\nimport asyncio\nimport os\nimport ssl\nimport subprocess\nimport sys\nimport tempfile\nfrom pathlib import Path\n\nCONTENT = b\"X\" * 1000\n\n\ndef make_throwaway_cert(tmp: Path) -\u003e tuple[Path, Path]:\n cert = tmp / \"cert.pem\"\n key = tmp / \"key.pem\"\n subprocess.run(\n [\n \"openssl\", \"req\", \"-x509\", \"-newkey\", \"rsa:2048\", \"-nodes\",\n \"-keyout\", str(key), \"-out\", str(cert),\n \"-days\", \"1\", \"-subj\", \"/CN=localhost\",\n ],\n check=True,\n capture_output=True,\n )\n return cert, key\n\n\nasync def main() -\u003e int:\n with tempfile.TemporaryDirectory() as tmp_:\n tmp = Path(tmp_)\n cert, key = make_throwaway_cert(tmp)\n\n data_path = tmp / \"data\"\n data_path.write_bytes(CONTENT)\n\n # SSL server that drains whatever it receives.\n server_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n server_ctx.load_cert_chain(cert, key)\n received = 0\n done = asyncio.Event()\n\n async def handle(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -\u003e None:\n nonlocal received\n try:\n while chunk := await reader.read(4096):\n received += len(chunk)\n finally:\n writer.close()\n done.set()\n\n server = await asyncio.start_server(handle, \"127.0.0.1\", 0, ssl=server_ctx)\n port = server.sockets[0].getsockname()[1]\n\n # SSL client → transport's `_sendfile_compatible == UNSUPPORTED` → fallback path.\n client_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n client_ctx.check_hostname = False\n client_ctx.verify_mode = ssl.CERT_NONE\n _, writer = await asyncio.open_connection(\n \"127.0.0.1\", port, ssl=client_ctx, server_hostname=\"localhost\"\n )\n\n # Open the file and pre-advance its position. In the real-world bug\n # this happens because the calling code previously read through the\n # same fh (e.g. to parse an HTTP header).\n f = open(data_path, \"rb\", buffering=0)\n f.seek(len(CONTENT)) # file position is at EOF\n\n # Send from offset=0. Expectation: 1000 bytes transferred.\n loop = asyncio.get_running_loop()\n sent = await loop.sendfile(writer.transport, f, offset=0)\n\n writer.close()\n await writer.wait_closed()\n await done.wait()\n server.close()\n await server.wait_closed()\n f.close()\n\n print(f\"Python: {sys.version}\")\n print(f\"file size: {len(CONTENT)}\")\n print(f\"file.tell() before sendfile: {len(CONTENT)} (EOF)\")\n print(f\"loop.sendfile(..., offset=0) returned: {sent}\")\n print(f\"server received: {received} bytes\")\n print()\n\n if sent == 0 and received == 0:\n print(\"BUG REPRODUCED: offset=0 was ignored; the fallback read from \"\n \"file.tell() instead of from the requested offset.\")\n return 1\n else:\n print(\"Bug NOT reproduced on this Python build.\")\n return 0\n\n\nsys.exit(asyncio.run(main()))\n```\nThe relevant lines are the line `f.seek(len(CONTENT))` and the line `sent = await loop.sendfile(writer.transport, f, offset=0)`. Most of the rest is setup and teardown.\n\n### CPython versions tested on:\n\n3.12, 3.13\n\n### Operating systems tested on:\n\nLinux\n\n\u003c!-- gh-linked-prs --\u003e\n### Linked PRs\n* gh-150142\n* gh-150259\n* gh-150270\n* gh-150569\n* gh-150570\n* gh-150571\n\u003c!-- /gh-linked-prs --\u003e\n","author":{"url":"https://github.com/SteveJones","@type":"Person","name":"SteveJones"},"datePublished":"2026-05-19T19:42:39.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/150107/cpython/issues/150107"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:82035cc0-43cc-02e0-9ab4-1e64bb7c659c |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | D25E:33952:6135745:884CD8F:6A50212E |
| html-safe-nonce | f6f38dbbded7c49ffd125c2c9e8faabda481209fa7153d75892003668a8daa17 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJEMjVFOjMzOTUyOjYxMzU3NDU6ODg0Q0Q4Rjo2QTUwMjEyRSIsInZpc2l0b3JfaWQiOiI3MjEwODQ2MTgwMTU4MjE4NTQyIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | e7d6eecfb6bb7883a2151d8d14ce9538ac6900f3cf019b63603d675d3a5edc8f |
| hovercard-subject-tag | issue:4480474781 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/python/cpython/150107/issue_layout |
| twitter:image | https://opengraph.githubassets.com/48bad39ac84d56d2ab6ea0d315fca743149b58e355f2b836f34776b886ef0f06/python/cpython/issues/150107 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/48bad39ac84d56d2ab6ea0d315fca743149b58e355f2b836f34776b886ef0f06/python/cpython/issues/150107 |
| og:image:alt | Bug report Bug description: BaseEventLoop._sock_sendfile_fallback (https://github.com/python/cpython/blob/main/Lib/asyncio/base_events.py#L971) erroneously doesn't seek when passed the offset 0. if... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | SteveJones |
| hostname | github.com |
| expected-hostname | github.com |
| None | a08040246389072795e3b4e7b33c6d9a0c564eee02e829a809326d67c418b069 |
| turbo-cache-control | no-preview |
| go-import | github.com/python/cpython git https://github.com/python/cpython.git |
| octolytics-dimension-user_id | 1525981 |
| octolytics-dimension-user_login | python |
| octolytics-dimension-repository_id | 81598961 |
| octolytics-dimension-repository_nwo | python/cpython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 81598961 |
| octolytics-dimension-repository_network_root_nwo | python/cpython |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | d1b91b18e7330fc458dfd824c61eaed7daa830d6 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width