Skip to content

Commit ef353a1

Browse files
Fix asyncio sendfile fallback ignoring non-zero offset
Signed-off-by: grantlouisherman <grantlouisherman041@gmail.com>
1 parent 4e3ead9 commit ef353a1

4 files changed

Lines changed: 34 additions & 3 deletions

File tree

Lib/asyncio/base_events.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,7 @@ async def _sock_sendfile_native(self, sock, file, offset, count):
969969
f"and file {file!r} combination")
970970

971971
async def _sock_sendfile_fallback(self, sock, file, offset, count):
972-
if offset:
972+
if hasattr(file, 'seek'):
973973
file.seek(offset)
974974
blocksize = (
975975
min(count, constants.SENDFILE_FALLBACK_READBUFFER_SIZE)
@@ -1286,7 +1286,6 @@ async def sendfile(self, transport, file, offset=0, count=None,
12861286
raise RuntimeError(
12871287
f"fallback is disabled and native sendfile is not "
12881288
f"supported for transport {transport!r}")
1289-
12901289
return await self._sendfile_fallback(transport, file,
12911290
offset, count)
12921291

@@ -1295,7 +1294,7 @@ async def _sendfile_native(self, transp, file, offset, count):
12951294
"sendfile syscall is not supported")
12961295

12971296
async def _sendfile_fallback(self, transp, file, offset, count):
1298-
if offset:
1297+
if hasattr(file, 'seek'):
12991298
file.seek(offset)
13001299
blocksize = min(count, 16384) if count else 16384
13011300
buf = bytearray(blocksize)

Lib/asyncio/windows_events.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,9 @@ def sendfile(self, sock, file, offset, count):
610610
ov = _overlapped.Overlapped(NULL)
611611
offset_low = offset & 0xffff_ffff
612612
offset_high = (offset >> 32) & 0xffff_ffff
613+
# TransmitFile ignores OVERLAPPED.Offset for handles not opened with
614+
# FILE_FLAG_OVERLAPPED, so seek the CRT file pointer to match.
615+
file.seek(offset)
613616
ov.TransmitFile(sock.fileno(),
614617
msvcrt.get_osfhandle(file.fileno()),
615618
offset_low, offset_high,

Lib/test/test_asyncio/test_sendfile.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,32 @@ def test_sock_sendfile_zero_size(self):
228228
self.assertEqual(ret, 0)
229229
self.assertEqual(self.file.tell(), 0)
230230

231+
def check_sock_sendfile_offset(self, data, offset, force_fallback=False):
232+
sock, proto = self.prepare_socksendfile()
233+
with tempfile.TemporaryFile() as f:
234+
f.write(data)
235+
f.flush()
236+
self.assertEqual(f.tell(), len(data))
237+
238+
if force_fallback:
239+
async def _sock_sendfile_fail(sock, file, offset, count):
240+
raise asyncio.exceptions.SendfileNotAvailableError()
241+
with support.swap_attr(self.loop, '_sock_sendfile_native', _sock_sendfile_fail):
242+
ret = self.run_loop(self.loop.sock_sendfile(sock, f, offset, None))
243+
else:
244+
ret = self.run_loop(self.loop.sock_sendfile(sock, f, offset, None))
245+
self.assertEqual(f.tell(), len(data))
246+
sock.close()
247+
self.run_loop(proto.wait_closed())
248+
self.assertEqual(ret, len(data) - offset)
249+
250+
def test_sock_sendfile_offset(self):
251+
data = b'abcdef'
252+
for offset in (0, len(data) // 2, len(data)):
253+
for force_fallback in (False, True):
254+
with self.subTest(offset=offset, force_fallback=force_fallback):
255+
self.check_sock_sendfile_offset(data, offset, force_fallback)
256+
231257
def test_sock_sendfile_mix_with_regular_send(self):
232258
buf = b"mix_regular_send" * (4 * 1024) # 64 KiB
233259
sock, proto = self.prepare_socksendfile()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:mod:`asyncio`: ``sendfile()`` and ``sock_sendfile()`` event loop methods
2+
now call ``file.seek(offset)`` if *file* has a ``seek()`` method,
3+
even if *offset* is ``0`` (default value).

0 commit comments

Comments
 (0)