Remove ctypes based sendfile wrapper since os.sendfile is available in py3

This commit is contained in:
Kovid Goyal 2021-01-07 10:39:59 +05:30
parent 2b08beb226
commit f3e5461d00
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 31 additions and 153 deletions

View File

@ -5,28 +5,33 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import os, hashlib, uuid, struct import errno
import hashlib
import os
import struct
import uuid
from collections import namedtuple from collections import namedtuple
from io import BytesIO, DEFAULT_BUFFER_SIZE from functools import wraps
from io import DEFAULT_BUFFER_SIZE, BytesIO
from itertools import chain, repeat from itertools import chain, repeat
from operator import itemgetter from operator import itemgetter
from functools import wraps
from polyglot.builtins import iteritems, itervalues, reraise, map, unicode_type, string_or_bytes from calibre import force_unicode, guess_type
from calibre import guess_type, force_unicode
from calibre.constants import __version__ from calibre.constants import __version__
from calibre.srv.loop import WRITE
from calibre.srv.errors import HTTPSimpleResponse from calibre.srv.errors import HTTPSimpleResponse
from calibre.srv.http_request import HTTPRequest, read_headers from calibre.srv.http_request import HTTPRequest, read_headers
from calibre.srv.sendfile import file_metadata, sendfile_to_socket_async, CannotSendfile, SendfileInterrupted from calibre.srv.loop import WRITE
from calibre.srv.utils import ( from calibre.srv.utils import (
MultiDict, http_date, HTTP1, HTTP11, socket_errors_socket_closed, HTTP1, HTTP11, Cookie, MultiDict, fast_now_strftime, get_translator_for_lang,
sort_q_values, get_translator_for_lang, Cookie, fast_now_strftime) http_date, socket_errors_socket_closed, sort_q_values
from calibre.utils.speedups import ReadOnlyFileBuffer )
from calibre.utils.monotonic import monotonic from calibre.utils.monotonic import monotonic
from calibre.utils.speedups import ReadOnlyFileBuffer
from polyglot import http_client, reprlib from polyglot import http_client, reprlib
from polyglot.builtins import error_message from polyglot.builtins import (
error_message, iteritems, itervalues, map, reraise, string_or_bytes,
unicode_type
)
Range = namedtuple('Range', 'start stop size') Range = namedtuple('Range', 'start stop size')
MULTIPART_SEPARATOR = uuid.uuid4().hex MULTIPART_SEPARATOR = uuid.uuid4().hex
@ -37,6 +42,14 @@ import zlib
from itertools import zip_longest from itertools import zip_longest
def file_metadata(fileobj):
try:
fd = fileobj.fileno()
return os.fstat(fd)
except Exception:
pass
def header_list_to_file(buf): # {{{ def header_list_to_file(buf): # {{{
buf.append('') buf.append('')
return ReadOnlyFileBuffer(b''.join((x + '\r\n').encode('ascii') for x in buf)) return ReadOnlyFileBuffer(b''.join((x + '\r\n').encode('ascii') for x in buf))
@ -374,17 +387,15 @@ class HTTPConnection(HTTPRequest):
if limit <= 0: if limit <= 0:
return True return True
if self.use_sendfile and not isinstance(buf, (BytesIO, ReadOnlyFileBuffer)): if self.use_sendfile and not isinstance(buf, (BytesIO, ReadOnlyFileBuffer)):
limit = min(limit, 2 ** 30)
try: try:
sent = sendfile_to_socket_async(buf, pos, limit, self.socket) sent = os.sendfile(self.socket.fileno(), buf.fileno(), pos, limit)
except CannotSendfile: except OSError as e:
self.use_sendfile = False
return False
except SendfileInterrupted:
return False
except IOError as e:
if e.errno in socket_errors_socket_closed: if e.errno in socket_errors_socket_closed:
self.ready = self.use_sendfile = False self.ready = self.use_sendfile = False
return False return False
if e.errno in (errno.EAGAIN, errno.EINTR):
return False
raise raise
finally: finally:
self.last_activity = monotonic() self.last_activity = monotonic()
@ -561,7 +572,7 @@ class HTTPConnection(HTTPRequest):
self.reset_state() self.reset_state()
return return
if isinstance(output, ReadableOutput): if isinstance(output, ReadableOutput):
self.use_sendfile = output.use_sendfile and self.opts.use_sendfile and sendfile_to_socket_async is not None and self.ssl_context is None self.use_sendfile = output.use_sendfile and self.opts.use_sendfile and hasattr(os, 'sendfile') and self.ssl_context is None
# sendfile() does not work with SSL sockets since encryption has to # sendfile() does not work with SSL sockets since encryption has to
# be done in userspace # be done in userspace
if output.ranges is not None: if output.ranges is not None:

View File

@ -1,133 +0,0 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
__license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import os, ctypes, errno, socket
from io import DEFAULT_BUFFER_SIZE
from select import select
from calibre.constants import islinux, ismacos
from calibre.srv.utils import eintr_retry_call
def file_metadata(fileobj):
try:
fd = fileobj.fileno()
return os.fstat(fd)
except Exception:
pass
def copy_range(src_file, start, size, dest):
total_sent = 0
src_file.seek(start)
while size > 0:
data = eintr_retry_call(src_file.read, min(size, DEFAULT_BUFFER_SIZE))
if len(data) == 0:
break # EOF
dest.write(data)
size -= len(data)
total_sent += len(data)
del data
return total_sent
class CannotSendfile(Exception):
pass
class SendfileInterrupted(Exception):
pass
sendfile_to_socket = sendfile_to_socket_async = None
if ismacos:
libc = ctypes.CDLL(None, use_errno=True)
sendfile = ctypes.CFUNCTYPE(
ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int64, ctypes.POINTER(ctypes.c_int64), ctypes.c_void_p, ctypes.c_int, use_errno=True)(
('sendfile', libc))
del libc
def sendfile_to_socket(fileobj, offset, size, socket_file):
timeout = socket_file.gettimeout()
if timeout == 0:
return copy_range(fileobj, offset, size, socket_file)
num_bytes = ctypes.c_int64(size)
total_sent = 0
while size > 0:
num_bytes.value = size
r, w, x = select([], [socket_file], [], timeout)
if not w:
raise socket.timeout('timed out in sendfile() waiting for socket to become writeable')
ret = sendfile(fileobj.fileno(), socket_file.fileno(), offset, ctypes.byref(num_bytes), None, 0)
if ret != 0:
err = ctypes.get_errno()
if err in (errno.EBADF, errno.ENOTSUP, errno.ENOTSOCK, errno.EOPNOTSUPP):
return copy_range(fileobj, offset, size, socket_file)
if err not in (errno.EINTR, errno.EAGAIN):
raise IOError((err, os.strerror(err)))
if num_bytes.value == 0:
break # EOF
total_sent += num_bytes.value
size -= num_bytes.value
offset += num_bytes.value
return total_sent
def sendfile_to_socket_async(fileobj, offset, size, socket_file):
num_bytes = ctypes.c_int64(size)
ret = sendfile(fileobj.fileno(), socket_file.fileno(), offset, ctypes.byref(num_bytes), None, 0)
if ret != 0:
err = ctypes.get_errno()
if err in (errno.EBADF, errno.ENOTSUP, errno.ENOTSOCK, errno.EOPNOTSUPP):
raise CannotSendfile()
if err == errno.EINTR:
raise SendfileInterrupted()
if err != errno.EAGAIN:
raise IOError((err, os.strerror(err)))
return num_bytes.value
elif islinux:
libc = ctypes.CDLL(None, use_errno=True)
sendfile = ctypes.CFUNCTYPE(
ctypes.c_ssize_t, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int64), ctypes.c_size_t, use_errno=True)(('sendfile64', libc))
del libc
def sendfile_to_socket(fileobj, offset, size, socket_file):
off = ctypes.c_int64(offset)
timeout = socket_file.gettimeout()
if timeout == 0:
return copy_range(fileobj, off.value, size, socket_file)
total_sent = 0
while size > 0:
r, w, x = select([], [socket_file], [], timeout)
if not w:
raise socket.timeout('timed out in sendfile() waiting for socket to become writeable')
sent = sendfile(socket_file.fileno(), fileobj.fileno(), ctypes.byref(off), size)
if sent < 0:
err = ctypes.get_errno()
if err in (errno.ENOSYS, errno.EINVAL):
return copy_range(fileobj, off.value, size, socket_file)
if err not in (errno.EINTR, errno.EAGAIN):
raise IOError((err, os.strerror(err)))
elif sent == 0:
break # EOF
else:
size -= sent
total_sent += sent
return total_sent
def sendfile_to_socket_async(fileobj, offset, size, socket_file):
off = ctypes.c_int64(offset)
sent = sendfile(socket_file.fileno(), fileobj.fileno(), ctypes.byref(off), size)
if sent < 0:
err = ctypes.get_errno()
if err in (errno.ENOSYS, errno.EINVAL):
raise CannotSendfile()
if err in (errno.EINTR, errno.EAGAIN):
raise SendfileInterrupted()
raise IOError((err, os.strerror(err)))
return sent