Mercurial > hg
changeset 9726:430e59ff3437
keepalive: handle broken pipes gracefully during large POSTs
author | Augie Fackler <durin42@gmail.com> |
---|---|
date | Mon, 02 Nov 2009 11:03:22 -0500 |
parents | 3f522d2fa633 |
children | d00cee04a746 |
files | mercurial/keepalive.py mercurial/url.py |
diffstat | 2 files changed, 77 insertions(+), 3 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/keepalive.py Thu Nov 05 15:18:56 2009 +0100 +++ b/mercurial/keepalive.py Mon Nov 02 11:03:22 2009 -0500 @@ -23,6 +23,9 @@ # - import md5 function from a local util module # Modified by Martin Geisler: # - moved md5 function from local util module to this module +# Modified by Augie Fackler: +# - add safesend method and use it to prevent broken pipe errors +# on large POST requests """An HTTP handler for urllib2 that supports HTTP 1.1 and keepalive. @@ -108,10 +111,11 @@ # $Id: keepalive.py,v 1.14 2006/04/04 21:00:32 mstenner Exp $ -import urllib2 +import errno import httplib import socket import thread +import urllib2 DEBUG = None @@ -495,10 +499,76 @@ break return list +def safesend(self, str): + """Send `str' to the server. + + Shamelessly ripped off from httplib to patch a bad behavior. + """ + # _broken_pipe_resp is an attribute we set in this function + # if the socket is closed while we're sending data but + # the server sent us a response before hanging up. + # In that case, we want to pretend to send the rest of the + # outgoing data, and then let the user use getresponse() + # (which we wrap) to get this last response before + # opening a new socket. + if getattr(self, '_broken_pipe_resp', None) is not None: + return + + if self.sock is None: + if self.auto_open: + self.connect() + else: + raise httplib.NotConnected() + + # send the data to the server. if we get a broken pipe, then close + # the socket. we want to reconnect when somebody tries to send again. + # + # NOTE: we DO propagate the error, though, because we cannot simply + # ignore the error... the caller will know if they can retry. + if self.debuglevel > 0: + print "send:", repr(str) + try: + blocksize=8192 + if hasattr(str,'read') : + if self.debuglevel > 0: print "sendIng a read()able" + data=str.read(blocksize) + while data: + self.sock.sendall(data) + data=str.read(blocksize) + else: + self.sock.sendall(str) + except socket.error, v: + reraise = True + if v[0] == errno.EPIPE: # Broken pipe + if self._HTTPConnection__state == httplib._CS_REQ_SENT: + self._broken_pipe_resp = None + self._broken_pipe_resp = self.getresponse() + reraise = False + self.close() + if reraise: + raise + +def wrapgetresponse(cls): + """Wraps getresponse in cls with a broken-pipe sane version. + """ + def safegetresponse(self): + # In safesend() we might set the _broken_pipe_resp + # attribute, in which case the socket has already + # been closed and we just need to give them the response + # back. Otherwise, we use the normal response path. + r = getattr(self, '_broken_pipe_resp', None) + if r is not None: + return r + return cls.getresponse(self) + safegetresponse.__doc__ = cls.getresponse.__doc__ + return safegetresponse class HTTPConnection(httplib.HTTPConnection): # use the modified response class response_class = HTTPResponse + send = safesend + getresponse = wrapgetresponse(httplib.HTTPConnection) + ######################################################################### ##### TEST FUNCTIONS
--- a/mercurial/url.py Thu Nov 05 15:18:56 2009 +0100 +++ b/mercurial/url.py Mon Nov 02 11:03:22 2009 -0500 @@ -408,10 +408,14 @@ self.close_all() if has_https: - class httpsconnection(httplib.HTTPSConnection): + class BetterHTTPS(httplib.HTTPSConnection): + send = keepalive.safesend + + class httpsconnection(BetterHTTPS): response_class = keepalive.HTTPResponse # must be able to send big bundle as stream. - send = _gen_sendfile(httplib.HTTPSConnection) + send = _gen_sendfile(BetterHTTPS) + getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection) class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler): def __init__(self, ui):