f"and file {file!r} combination")
async def _sock_sendfile_fallback(self, sock, file, offset, count):
- if offset:
+ if hasattr(file, 'seek'):
file.seek(offset)
blocksize = (
min(count, constants.SENDFILE_FALLBACK_READBUFFER_SIZE)
raise RuntimeError(
f"fallback is disabled and native sendfile is not "
f"supported for transport {transport!r}")
-
return await self._sendfile_fallback(transport, file,
offset, count)
"sendfile syscall is not supported")
async def _sendfile_fallback(self, transp, file, offset, count):
- if offset:
+ if hasattr(file, 'seek'):
file.seek(offset)
blocksize = min(count, 16384) if count else 16384
buf = bytearray(blocksize)
offset += blocksize
total_sent += blocksize
finally:
- if total_sent > 0:
- file.seek(offset)
+ file.seek(offset)
async def _sendfile_native(self, transp, file, offset, count):
resume_reading = transp.is_reading()
# order to simplify the common case.
self.remove_writer(registered_fd)
if fut.cancelled():
- self._sock_sendfile_update_filepos(fileno, offset, total_sent)
+ self._sock_sendfile_update_filepos(fileno, offset)
return
if count:
blocksize = count - total_sent
if blocksize <= 0:
- self._sock_sendfile_update_filepos(fileno, offset, total_sent)
+ self._sock_sendfile_update_filepos(fileno, offset)
fut.set_result(total_sent)
return
# plain send().
err = exceptions.SendfileNotAvailableError(
"os.sendfile call failed")
- self._sock_sendfile_update_filepos(fileno, offset, total_sent)
+ self._sock_sendfile_update_filepos(fileno, offset)
fut.set_exception(err)
else:
- self._sock_sendfile_update_filepos(fileno, offset, total_sent)
+ self._sock_sendfile_update_filepos(fileno, offset)
fut.set_exception(exc)
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
- self._sock_sendfile_update_filepos(fileno, offset, total_sent)
+ self._sock_sendfile_update_filepos(fileno, offset)
fut.set_exception(exc)
else:
if sent == 0:
# EOF
- self._sock_sendfile_update_filepos(fileno, offset, total_sent)
+ self._sock_sendfile_update_filepos(fileno, offset)
fut.set_result(total_sent)
else:
offset += sent
fd, sock, fileno,
offset, count, blocksize, total_sent)
- def _sock_sendfile_update_filepos(self, fileno, offset, total_sent):
- if total_sent > 0:
- os.lseek(fileno, offset, os.SEEK_SET)
+ def _sock_sendfile_update_filepos(self, fileno, offset):
+ # After this helper runs, the source fd's lseek pointer is at offset."
+ os.lseek(fileno, offset, os.SEEK_SET)
def _sock_add_cancellation_callback(self, fut, sock):
def cb(fut):
ov = _overlapped.Overlapped(NULL)
offset_low = offset & 0xffff_ffff
offset_high = (offset >> 32) & 0xffff_ffff
+ # TransmitFile ignores OVERLAPPED.Offset for handles not opened with
+ # FILE_FLAG_OVERLAPPED, so seek the CRT file pointer to match.
+ file.seek(offset)
ov.TransmitFile(sock.fileno(),
msvcrt.get_osfhandle(file.fileno()),
offset_low, offset_high,
self.assertEqual(ret, 0)
self.assertEqual(self.file.tell(), 0)
+ def check_sock_sendfile_offset(self, data, offset, force_fallback=False):
+ sock, proto = self.prepare_socksendfile()
+ with tempfile.TemporaryFile() as f:
+ f.write(data)
+ f.flush()
+ self.assertEqual(f.tell(), len(data))
+
+ if force_fallback:
+ async def _sock_sendfile_fail(sock, file, offset, count):
+ raise asyncio.exceptions.SendfileNotAvailableError()
+ with support.swap_attr(self.loop, '_sock_sendfile_native', _sock_sendfile_fail):
+ ret = self.run_loop(self.loop.sock_sendfile(sock, f, offset, None))
+ else:
+ ret = self.run_loop(self.loop.sock_sendfile(sock, f, offset, None))
+
+ self.assertEqual(f.tell(), len(data))
+
+ sock.close()
+ self.run_loop(proto.wait_closed())
+
+ self.assertEqual(ret, len(data) - offset)
+
+
+ def test_sock_sendfile_offset(self):
+ data = b'abcdef'
+ for offset in (0, len(data) // 2, len(data)):
+ for force_fallback in (False, True):
+ with self.subTest(offset=offset, force_fallback=force_fallback):
+ self.check_sock_sendfile_offset(data, offset, force_fallback)
+
+ def check_sendfile_offset(self, offset, fallback):
+ srv_proto, cli_proto = self.prepare_sendfile()
+ self.file.seek(123)
+ coro = self.loop.sendfile(cli_proto.transport, self.file, offset, fallback=fallback)
+ try:
+ ret = self.run_loop(coro)
+ except asyncio.SendfileNotAvailableError:
+ if fallback:
+ raise
+ cli_proto.transport.close()
+ self.run_loop(srv_proto.done)
+ return
+ cli_proto.transport.close()
+ self.run_loop(srv_proto.done)
+ self.assertEqual(ret, len(self.DATA) - offset)
+ self.assertEqual(srv_proto.nbytes, len(self.DATA) - offset)
+ self.assertEqual(srv_proto.data, self.DATA[offset:])
+ self.assertEqual(self.file.tell(), len(self.DATA))
+
+ def test_sendfile_offset(self):
+ for offset in (0, len(self.DATA) // 2, len(self.DATA)):
+ for fallback in (False, True):
+ with self.subTest(offset=offset, fallback=fallback):
+ self.check_sendfile_offset(offset, fallback)
+
def test_sock_sendfile_mix_with_regular_send(self):
buf = b"mix_regular_send" * (4 * 1024) # 64 KiB
sock, proto = self.prepare_socksendfile()
--- /dev/null
+:mod:`asyncio`: ``sendfile()`` and ``sock_sendfile()`` event loop methods
+now call ``file.seek(offset)`` if *file* has a ``seek()`` method,
+even if *offset* is ``0`` (default value).