Skip to content

Commit b980552

Browse files
miss-islingtongrantlouishermanvstinner
authored
[3.15] gh-150107: Fix asyncio sendfile fallback ignoring non-zero offset (GH-150270) (#150570)
gh-150107: Fix asyncio sendfile fallback ignoring non-zero offset (GH-150270) (cherry picked from commit c72d5ea) Co-authored-by: Grant Herman <grantlouisherman041@gmail.com> Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent d842895 commit b980552

6 files changed

Lines changed: 73 additions & 14 deletions

File tree

Lib/asyncio/base_events.py

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

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

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

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

Lib/asyncio/proactor_events.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -756,8 +756,7 @@ async def _sock_sendfile_native(self, sock, file, offset, count):
756756
offset += blocksize
757757
total_sent += blocksize
758758
finally:
759-
if total_sent > 0:
760-
file.seek(offset)
759+
file.seek(offset)
761760

762761
async def _sendfile_native(self, transp, file, offset, count):
763762
resume_reading = transp.is_reading()

Lib/asyncio/unix_events.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -384,12 +384,12 @@ def _sock_sendfile_native_impl(self, fut, registered_fd, sock, fileno,
384384
# order to simplify the common case.
385385
self.remove_writer(registered_fd)
386386
if fut.cancelled():
387-
self._sock_sendfile_update_filepos(fileno, offset, total_sent)
387+
self._sock_sendfile_update_filepos(fileno, offset)
388388
return
389389
if count:
390390
blocksize = count - total_sent
391391
if blocksize <= 0:
392-
self._sock_sendfile_update_filepos(fileno, offset, total_sent)
392+
self._sock_sendfile_update_filepos(fileno, offset)
393393
fut.set_result(total_sent)
394394
return
395395

@@ -423,20 +423,20 @@ def _sock_sendfile_native_impl(self, fut, registered_fd, sock, fileno,
423423
# plain send().
424424
err = exceptions.SendfileNotAvailableError(
425425
"os.sendfile call failed")
426-
self._sock_sendfile_update_filepos(fileno, offset, total_sent)
426+
self._sock_sendfile_update_filepos(fileno, offset)
427427
fut.set_exception(err)
428428
else:
429-
self._sock_sendfile_update_filepos(fileno, offset, total_sent)
429+
self._sock_sendfile_update_filepos(fileno, offset)
430430
fut.set_exception(exc)
431431
except (SystemExit, KeyboardInterrupt):
432432
raise
433433
except BaseException as exc:
434-
self._sock_sendfile_update_filepos(fileno, offset, total_sent)
434+
self._sock_sendfile_update_filepos(fileno, offset)
435435
fut.set_exception(exc)
436436
else:
437437
if sent == 0:
438438
# EOF
439-
self._sock_sendfile_update_filepos(fileno, offset, total_sent)
439+
self._sock_sendfile_update_filepos(fileno, offset)
440440
fut.set_result(total_sent)
441441
else:
442442
offset += sent
@@ -447,9 +447,9 @@ def _sock_sendfile_native_impl(self, fut, registered_fd, sock, fileno,
447447
fd, sock, fileno,
448448
offset, count, blocksize, total_sent)
449449

450-
def _sock_sendfile_update_filepos(self, fileno, offset, total_sent):
451-
if total_sent > 0:
452-
os.lseek(fileno, offset, os.SEEK_SET)
450+
def _sock_sendfile_update_filepos(self, fileno, offset):
451+
# After this helper runs, the source fd's lseek pointer is at offset."
452+
os.lseek(fileno, offset, os.SEEK_SET)
453453

454454
def _sock_add_cancellation_callback(self, fut, sock):
455455
def cb(fut):

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: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,61 @@ 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+
246+
self.assertEqual(f.tell(), len(data))
247+
248+
sock.close()
249+
self.run_loop(proto.wait_closed())
250+
251+
self.assertEqual(ret, len(data) - offset)
252+
253+
254+
def test_sock_sendfile_offset(self):
255+
data = b'abcdef'
256+
for offset in (0, len(data) // 2, len(data)):
257+
for force_fallback in (False, True):
258+
with self.subTest(offset=offset, force_fallback=force_fallback):
259+
self.check_sock_sendfile_offset(data, offset, force_fallback)
260+
261+
def check_sendfile_offset(self, offset, fallback):
262+
srv_proto, cli_proto = self.prepare_sendfile()
263+
self.file.seek(123)
264+
coro = self.loop.sendfile(cli_proto.transport, self.file, offset, fallback=fallback)
265+
try:
266+
ret = self.run_loop(coro)
267+
except asyncio.SendfileNotAvailableError:
268+
if fallback:
269+
raise
270+
cli_proto.transport.close()
271+
self.run_loop(srv_proto.done)
272+
return
273+
cli_proto.transport.close()
274+
self.run_loop(srv_proto.done)
275+
self.assertEqual(ret, len(self.DATA) - offset)
276+
self.assertEqual(srv_proto.nbytes, len(self.DATA) - offset)
277+
self.assertEqual(srv_proto.data, self.DATA[offset:])
278+
self.assertEqual(self.file.tell(), len(self.DATA))
279+
280+
def test_sendfile_offset(self):
281+
for offset in (0, len(self.DATA) // 2, len(self.DATA)):
282+
for fallback in (False, True):
283+
with self.subTest(offset=offset, fallback=fallback):
284+
self.check_sendfile_offset(offset, fallback)
285+
231286
def test_sock_sendfile_mix_with_regular_send(self):
232287
buf = b"mix_regular_send" * (4 * 1024) # 64 KiB
233288
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)