Improve performance of sending of large files when sendfile() is not available (such as on windows)

Use the kernel socket send buffer size as the blocksize for sends.
This commit is contained in:
Kovid Goyal 2015-05-25 22:02:22 +05:30
parent eb72da611d
commit 51b8281151
4 changed files with 25 additions and 13 deletions

View File

@ -154,7 +154,6 @@ class HTTPRequest(Connection):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
Connection.__init__(self, *args, **kwargs) Connection.__init__(self, *args, **kwargs)
self.corked = False
self.max_header_line_size = int(1024 * self.opts.max_header_line_size) self.max_header_line_size = int(1024 * self.opts.max_header_line_size)
self.max_request_body_size = int(1024 * 1024 * self.opts.max_request_body_size) self.max_request_body_size = int(1024 * 1024 * self.opts.max_request_body_size)

View File

@ -7,10 +7,6 @@ __license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import os, httplib, hashlib, uuid, zlib, time, struct, repr as reprlib import os, httplib, hashlib, uuid, zlib, time, struct, repr as reprlib
try:
from select import PIPE_BUF
except ImportError:
PIPE_BUF = 512 # windows
from collections import namedtuple from collections import namedtuple
from io import BytesIO, DEFAULT_BUFFER_SIZE from io import BytesIO, DEFAULT_BUFFER_SIZE
from itertools import chain, repeat, izip_longest from itertools import chain, repeat, izip_longest
@ -23,7 +19,7 @@ from calibre.srv.loop import WRITE
from calibre.srv.errors import HTTP404 from calibre.srv.errors import HTTP404
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.sendfile import file_metadata, sendfile_to_socket_async, CannotSendfile, SendfileInterrupted
from calibre.srv.utils import MultiDict, start_cork, stop_cork, http_date, HTTP1, HTTP11, socket_errors_socket_closed from calibre.srv.utils import MultiDict, http_date, HTTP1, HTTP11, socket_errors_socket_closed
from calibre.utils.monotonic import monotonic from calibre.utils.monotonic import monotonic
Range = namedtuple('Range', 'start stop size') Range = namedtuple('Range', 'start stop size')
@ -283,7 +279,7 @@ class HTTPConnection(HTTPRequest):
self.use_sendfile = self.ready = False self.use_sendfile = self.ready = False
raise IOError('sendfile() failed to write any bytes to the socket') raise IOError('sendfile() failed to write any bytes to the socket')
else: else:
sent = self.send(buf.read(min(limit, PIPE_BUF))) sent = self.send(buf.read(min(limit, self.send_bufsize)))
buf.seek(pos + sent) buf.seek(pos + sent)
return buf.tell() == end return buf.tell() == end
@ -367,8 +363,7 @@ class HTTPConnection(HTTPRequest):
def response_ready(self, header_file, output=None): def response_ready(self, header_file, output=None):
self.response_started = True self.response_started = True
start_cork(self.socket) self.optimize_for_sending_packet()
self.corked = True
self.use_sendfile = False self.use_sendfile = False
self.set_state(WRITE, self.write_response_headers, header_file, output) self.set_state(WRITE, self.write_response_headers, header_file, output)
@ -437,8 +432,7 @@ class HTTPConnection(HTTPRequest):
def reset_state(self): def reset_state(self):
self.connection_ready() self.connection_ready()
self.ready = not self.close_after_response self.ready = not self.close_after_response
stop_cork(self.socket) self.end_send_optimization()
self.corked = False
def report_unhandled_exception(self, e, formatted_traceback): def report_unhandled_exception(self, e, formatted_traceback):
self.simple_response(httplib.INTERNAL_SERVER_ERROR) self.simple_response(httplib.INTERNAL_SERVER_ERROR)

View File

@ -14,17 +14,20 @@ from calibre import as_unicode
from calibre.ptempfile import TemporaryDirectory from calibre.ptempfile import TemporaryDirectory
from calibre.srv.opts import Options from calibre.srv.opts import Options
from calibre.srv.utils import ( from calibre.srv.utils import (
socket_errors_socket_closed, socket_errors_nonblocking, HandleInterrupt, socket_errors_eintr) socket_errors_socket_closed, socket_errors_nonblocking, HandleInterrupt,
socket_errors_eintr, start_cork, stop_cork)
from calibre.utils.socket_inheritance import set_socket_inherit from calibre.utils.socket_inheritance import set_socket_inherit
from calibre.utils.logging import ThreadSafeLog from calibre.utils.logging import ThreadSafeLog
from calibre.utils.monotonic import monotonic from calibre.utils.monotonic import monotonic
READ, WRITE, RDWR = 'READ', 'WRITE', 'RDWR' READ, WRITE, RDWR = 'READ', 'WRITE', 'RDWR'
DESIRED_SEND_BUFFER_SIZE = 16 * 1024
class Connection(object): class Connection(object):
def __init__(self, socket, opts, ssl_context, tdir): def __init__(self, socket, opts, ssl_context, tdir):
self.opts = opts self.opts = opts
self.orig_send_bufsize = self.send_bufsize = 4096
self.tdir = tdir self.tdir = tdir
self.ssl_context = ssl_context self.ssl_context = ssl_context
self.wait_for = READ self.wait_for = READ
@ -39,6 +42,22 @@ class Connection(object):
self.connection_ready() self.connection_ready()
self.last_activity = monotonic() self.last_activity = monotonic()
def optimize_for_sending_packet(self):
start_cork(self.socket)
self.orig_send_bufsize = self.send_bufsize = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
if self.send_bufsize < DESIRED_SEND_BUFFER_SIZE:
try:
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, DESIRED_SEND_BUFFER_SIZE)
except socket.error:
pass
else:
self.send_bufsize = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
def end_send_optimization(self):
stop_cork(self.socket)
if self.send_bufsize != self.orig_send_bufsize:
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, self.orig_send_bufsize)
def set_state(self, wait_for, func, *args, **kwargs): def set_state(self, wait_for, func, *args, **kwargs):
self.wait_for = wait_for self.wait_for = wait_for
if args or kwargs: if args or kwargs:

View File

@ -329,6 +329,6 @@ class TestHTTP(BaseTest):
self.ae(len(data), len(rdata)) self.ae(len(data), len(rdata))
self.ae(hashlib.sha1(data).hexdigest(), hashlib.sha1(rdata).hexdigest()) self.ae(hashlib.sha1(data).hexdigest(), hashlib.sha1(rdata).hexdigest())
self.ae(data, rdata) self.ae(data, rdata)
self.assertLess(monotonic() - start_time, 5, 'Large file transfer took too long') self.assertLess(monotonic() - start_time, 1, 'Large file transfer took too long')
# }}} # }}}