{
    "file_diffs": [
        {
            "old_file_content": "#!/usr/bin/env python\n#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Non-blocking HTTP client implementation using pycurl.\"\"\"\n\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nimport collections\nimport functools\nimport logging\nimport pycurl\nimport threading\nimport time\nfrom io import BytesIO\n\nfrom tornado import httputil\nfrom tornado import ioloop\nfrom tornado import stack_context\n\nfrom tornado.escape import utf8, native_str\nfrom tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main\n\ncurl_log = logging.getLogger('tornado.curl_httpclient')\n\n\nclass CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, io_loop, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(io_loop, defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000, io_loop=io_loop)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super(CurlAsyncHTTPClient, self).close()\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    try:\n                        self._curl_setup_request(\n                            curl, request, curl.info[\"buffer\"],\n                            curl.info[\"headers\"])\n                    except Exception as e:\n                        # If there was an error in setup, pass it on\n                        # to the callback. Note that allowing the\n                        # error to escape here will appear to work\n                        # most of the time since we are still in the\n                        # caller's original stack frame, but when\n                        # _process_queue() is called from\n                        # _finish_pending_requests the exceptions have\n                        # nowhere to go.\n                        callback(HTTPResponse(\n                            request=request,\n                            code=599,\n                            error=e))\n                    else:\n                        self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                reason=info['headers'].get(\"X-Http-Reason\", None),\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n    def _curl_create(self):\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        return curl\n\n    def _curl_setup_request(self, curl, request, buffer, headers):\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        curl.setopt(pycurl.HTTPHEADER,\n                    [\"%s: %s\" % (native_str(k), native_str(v))\n                     for k, v in request.headers.get_all()])\n\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    functools.partial(self._curl_header_callback,\n                                      headers, request.header_callback))\n        if request.streaming_callback:\n            def write_function(chunk):\n                self.io_loop.add_callback(request.streaming_callback, chunk)\n        else:\n            write_function = buffer.write\n        if bytes is str:  # py2\n            curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        else:  # py3\n            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n            # a fork/port.  That version has a bug in which it passes unicode\n            # strings instead of bytes to the WRITEFUNCTION.  This means that\n            # if you use a WRITEFUNCTION (which tornado always does), you cannot\n            # download arbitrary binary data.  This needs to be fixed in the\n            # ported pycurl package, but in the meantime this lambda will\n            # make it work for downloading (utf8) text.\n            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, \"none\")\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                credentials = '%s:%s' % (request.proxy_username,\n                                         request.proxy_password)\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n        else:\n            curl.setopt(pycurl.PROXY, '')\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError('unknown method ' + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if ((body_expected and not body_present) or\n                    (body_present and not body_expected)):\n                raise ValueError(\n                    'Body must %sbe None for method %s (unless '\n                    'allow_nonstandard_methods is true)' %\n                    ('not ' if body_expected else '', request.method))\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError('Body must be None for GET request')\n            request_buffer = BytesIO(utf8(request.body or ''))\n\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))\n\n        if request.auth_username is not None:\n            userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            curl.setopt(pycurl.USERPWD, native_str(userpwd))\n            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                           request.auth_username)\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.activeCount() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(self, headers, header_callback, header_line):\n        header_line = native_str(header_line)\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                (__, __, reason) = httputil.parse_response_start_line(header_line)\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type, debug_msg):\n        debug_types = ('I', '<', '>', '<', '>')\n        if debug_type == 0:\n            curl_log.debug('%s', debug_msg.strip())\n        elif debug_type in (1, 2):\n            for line in debug_msg.splitlines():\n                curl_log.debug('%s %s', debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)\n\n\nclass CurlError(HTTPError):\n    def __init__(self, errno, message):\n        HTTPError.__init__(self, 599, message)\n        self.errno = errno\n\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(CurlAsyncHTTPClient)\n    main()\n",
            "new_file_content": "#!/usr/bin/env python\n#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Non-blocking HTTP client implementation using pycurl.\"\"\"\n\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nimport collections\nimport functools\nimport logging\nimport pycurl\nimport threading\nimport time\nfrom io import BytesIO\n\nfrom tornado import httputil\nfrom tornado import ioloop\nfrom tornado import stack_context\n\nfrom tornado.escape import utf8, native_str\nfrom tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main\n\ncurl_log = logging.getLogger('tornado.curl_httpclient')\n\n\nclass CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, io_loop, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(io_loop, defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000, io_loop=io_loop)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super(CurlAsyncHTTPClient, self).close()\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    try:\n                        self._curl_setup_request(\n                            curl, request, curl.info[\"buffer\"],\n                            curl.info[\"headers\"])\n                    except Exception as e:\n                        # If there was an error in setup, pass it on\n                        # to the callback. Note that allowing the\n                        # error to escape here will appear to work\n                        # most of the time since we are still in the\n                        # caller's original stack frame, but when\n                        # _process_queue() is called from\n                        # _finish_pending_requests the exceptions have\n                        # nowhere to go.\n                        self._free_list.append(curl)\n                        callback(HTTPResponse(\n                            request=request,\n                            code=599,\n                            error=e))\n                    else:\n                        self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                reason=info['headers'].get(\"X-Http-Reason\", None),\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n    def _curl_create(self):\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        return curl\n\n    def _curl_setup_request(self, curl, request, buffer, headers):\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        curl.setopt(pycurl.HTTPHEADER,\n                    [\"%s: %s\" % (native_str(k), native_str(v))\n                     for k, v in request.headers.get_all()])\n\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    functools.partial(self._curl_header_callback,\n                                      headers, request.header_callback))\n        if request.streaming_callback:\n            def write_function(chunk):\n                self.io_loop.add_callback(request.streaming_callback, chunk)\n        else:\n            write_function = buffer.write\n        if bytes is str:  # py2\n            curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        else:  # py3\n            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n            # a fork/port.  That version has a bug in which it passes unicode\n            # strings instead of bytes to the WRITEFUNCTION.  This means that\n            # if you use a WRITEFUNCTION (which tornado always does), you cannot\n            # download arbitrary binary data.  This needs to be fixed in the\n            # ported pycurl package, but in the meantime this lambda will\n            # make it work for downloading (utf8) text.\n            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, \"none\")\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                credentials = '%s:%s' % (request.proxy_username,\n                                         request.proxy_password)\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n        else:\n            curl.setopt(pycurl.PROXY, '')\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError('unknown method ' + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if ((body_expected and not body_present) or\n                    (body_present and not body_expected)):\n                raise ValueError(\n                    'Body must %sbe None for method %s (unless '\n                    'allow_nonstandard_methods is true)' %\n                    ('not ' if body_expected else '', request.method))\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError('Body must be None for GET request')\n            request_buffer = BytesIO(utf8(request.body or ''))\n\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))\n\n        if request.auth_username is not None:\n            userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            curl.setopt(pycurl.USERPWD, native_str(userpwd))\n            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                           request.auth_username)\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.activeCount() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(self, headers, header_callback, header_line):\n        header_line = native_str(header_line)\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                (__, __, reason) = httputil.parse_response_start_line(header_line)\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type, debug_msg):\n        debug_types = ('I', '<', '>', '<', '>')\n        if debug_type == 0:\n            curl_log.debug('%s', debug_msg.strip())\n        elif debug_type in (1, 2):\n            for line in debug_msg.splitlines():\n                curl_log.debug('%s %s', debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)\n\n\nclass CurlError(HTTPError):\n    def __init__(self, errno, message):\n        HTTPError.__init__(self, 599, message)\n        self.errno = errno\n\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(CurlAsyncHTTPClient)\n    main()\n",
            "header": {
                "file": {
                    "path": "tornado/curl_httpclient.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "22f25023",
                "new_commit_hash": "6dadedd9",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/tornado/curl_httpclient.py"
            },
            "plus_file": {
                "path": "b/tornado/curl_httpclient.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 221,
                            "length": 6
                        },
                        "new_range": {
                            "start": 221,
                            "length": 7
                        },
                        "section": "class CurlAsyncHTTPClient(AsyncHTTPClient):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "                        # _process_queue() is called from",
                                "type": "context"
                            },
                            {
                                "content": "                        # _finish_pending_requests the exceptions have",
                                "type": "context"
                            },
                            {
                                "content": "                        # nowhere to go.",
                                "type": "context"
                            },
                            {
                                "content": "                        self._free_list.append(curl)",
                                "type": "added"
                            },
                            {
                                "content": "                        callback(HTTPResponse(",
                                "type": "context"
                            },
                            {
                                "content": "                            request=request,",
                                "type": "context"
                            },
                            {
                                "content": "                            code=599,",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/curl_httpclient.py",
                            "type": "class",
                            "name": "CurlAsyncHTTPClient",
                            "content": "class CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, io_loop, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(io_loop, defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000, io_loop=io_loop)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super(CurlAsyncHTTPClient, self).close()\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    try:\n                        self._curl_setup_request(\n                            curl, request, curl.info[\"buffer\"],\n                            curl.info[\"headers\"])\n                    except Exception as e:\n                        # If there was an error in setup, pass it on\n                        # to the callback. Note that allowing the\n                        # error to escape here will appear to work\n                        # most of the time since we are still in the\n                        # caller's original stack frame, but when\n                        # _process_queue() is called from\n                        # _finish_pending_requests the exceptions have\n                        # nowhere to go.\n                        self._free_list.append(curl)\n                        callback(HTTPResponse(\n                            request=request,\n                            code=599,\n                            error=e))\n                    else:\n                        self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                reason=info['headers'].get(\"X-Http-Reason\", None),\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n    def _curl_create(self):\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        return curl\n\n    def _curl_setup_request(self, curl, request, buffer, headers):\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        curl.setopt(pycurl.HTTPHEADER,\n                    [\"%s: %s\" % (native_str(k), native_str(v))\n                     for k, v in request.headers.get_all()])\n\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    functools.partial(self._curl_header_callback,\n                                      headers, request.header_callback))\n        if request.streaming_callback:\n            def write_function(chunk):\n                self.io_loop.add_callback(request.streaming_callback, chunk)\n        else:\n            write_function = buffer.write\n        if bytes is str:  # py2\n            curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        else:  # py3\n            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n            # a fork/port.  That version has a bug in which it passes unicode\n            # strings instead of bytes to the WRITEFUNCTION.  This means that\n            # if you use a WRITEFUNCTION (which tornado always does), you cannot\n            # download arbitrary binary data.  This needs to be fixed in the\n            # ported pycurl package, but in the meantime this lambda will\n            # make it work for downloading (utf8) text.\n            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, \"none\")\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                credentials = '%s:%s' % (request.proxy_username,\n                                         request.proxy_password)\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n        else:\n            curl.setopt(pycurl.PROXY, '')\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError('unknown method ' + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if ((body_expected and not body_present) or\n                    (body_present and not body_expected)):\n                raise ValueError(\n                    'Body must %sbe None for method %s (unless '\n                    'allow_nonstandard_methods is true)' %\n                    ('not ' if body_expected else '', request.method))\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError('Body must be None for GET request')\n            request_buffer = BytesIO(utf8(request.body or ''))\n\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))\n\n        if request.auth_username is not None:\n            userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            curl.setopt(pycurl.USERPWD, native_str(userpwd))\n            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                           request.auth_username)\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.activeCount() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(self, headers, header_callback, header_line):\n        header_line = native_str(header_line)\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                (__, __, reason) = httputil.parse_response_start_line(header_line)\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type, debug_msg):\n        debug_types = ('I', '<', '>', '<', '>')\n        if debug_type == 0:\n            curl_log.debug('%s', debug_msg.strip())\n        elif debug_type in (1, 2):\n            for line in debug_msg.splitlines():\n                curl_log.debug('%s %s', debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 39,
                            "end_lineno": 490,
                            "parent": null
                        },
                        {
                            "file_name": "tornado/curl_httpclient.py",
                            "type": "method",
                            "name": "CurlAsyncHTTPClient._process_queue",
                            "content": "class CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, io_loop, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(io_loop, defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000, io_loop=io_loop)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super(CurlAsyncHTTPClient, self).close()\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    try:\n                        self._curl_setup_request(\n                            curl, request, curl.info[\"buffer\"],\n                            curl.info[\"headers\"])\n                    except Exception as e:\n                        # If there was an error in setup, pass it on\n                        # to the callback. Note that allowing the\n                        # error to escape here will appear to work\n                        # most of the time since we are still in the\n                        # caller's original stack frame, but when\n                        # _process_queue() is called from\n                        # _finish_pending_requests the exceptions have\n                        # nowhere to go.\n                        self._free_list.append(curl)\n                        callback(HTTPResponse(\n                            request=request,\n                            code=599,\n                            error=e))\n                    else:\n                        self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                reason=info['headers'].get(\"X-Http-Reason\", None),\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n    def _curl_create(self):\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        return curl\n\n    def _curl_setup_request(self, curl, request, buffer, headers):\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        curl.setopt(pycurl.HTTPHEADER,\n                    [\"%s: %s\" % (native_str(k), native_str(v))\n                     for k, v in request.headers.get_all()])\n\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    functools.partial(self._curl_header_callback,\n                                      headers, request.header_callback))\n        if request.streaming_callback:\n            def write_function(chunk):\n                self.io_loop.add_callback(request.streaming_callback, chunk)\n        else:\n            write_function = buffer.write\n        if bytes is str:  # py2\n            curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        else:  # py3\n            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n            # a fork/port.  That version has a bug in which it passes unicode\n            # strings instead of bytes to the WRITEFUNCTION.  This means that\n            # if you use a WRITEFUNCTION (which tornado always does), you cannot\n            # download arbitrary binary data.  This needs to be fixed in the\n            # ported pycurl package, but in the meantime this lambda will\n            # make it work for downloading (utf8) text.\n            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, \"none\")\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                credentials = '%s:%s' % (request.proxy_username,\n                                         request.proxy_password)\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n        else:\n            curl.setopt(pycurl.PROXY, '')\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError('unknown method ' + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if ((body_expected and not body_present) or\n                    (body_present and not body_expected)):\n                raise ValueError(\n                    'Body must %sbe None for method %s (unless '\n                    'allow_nonstandard_methods is true)' %\n                    ('not ' if body_expected else '', request.method))\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError('Body must be None for GET request')\n            request_buffer = BytesIO(utf8(request.body or ''))\n\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))\n\n        if request.auth_username is not None:\n            userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            curl.setopt(pycurl.USERPWD, native_str(userpwd))\n            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                           request.auth_username)\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.activeCount() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(self, headers, header_callback, header_line):\n        header_line = native_str(header_line)\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                (__, __, reason) = httputil.parse_response_start_line(header_line)\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type, debug_msg):\n        debug_types = ('I', '<', '>', '<', '>')\n        if debug_type == 0:\n            curl_log.debug('%s', debug_msg.strip())\n        elif debug_type in (1, 2):\n            for line in debug_msg.splitlines():\n                curl_log.debug('%s %s', debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 196,
                            "end_lineno": 233,
                            "parent": {
                                "file_name": "tornado/curl_httpclient.py",
                                "type": "class",
                                "name": "CurlAsyncHTTPClient",
                                "content": "class CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, io_loop, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(io_loop, defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000, io_loop=io_loop)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super(CurlAsyncHTTPClient, self).close()\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    try:\n                        self._curl_setup_request(\n                            curl, request, curl.info[\"buffer\"],\n                            curl.info[\"headers\"])\n                    except Exception as e:\n                        # If there was an error in setup, pass it on\n                        # to the callback. Note that allowing the\n                        # error to escape here will appear to work\n                        # most of the time since we are still in the\n                        # caller's original stack frame, but when\n                        # _process_queue() is called from\n                        # _finish_pending_requests the exceptions have\n                        # nowhere to go.\n                        self._free_list.append(curl)\n                        callback(HTTPResponse(\n                            request=request,\n                            code=599,\n                            error=e))\n                    else:\n                        self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                reason=info['headers'].get(\"X-Http-Reason\", None),\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n    def _curl_create(self):\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        return curl\n\n    def _curl_setup_request(self, curl, request, buffer, headers):\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        curl.setopt(pycurl.HTTPHEADER,\n                    [\"%s: %s\" % (native_str(k), native_str(v))\n                     for k, v in request.headers.get_all()])\n\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    functools.partial(self._curl_header_callback,\n                                      headers, request.header_callback))\n        if request.streaming_callback:\n            def write_function(chunk):\n                self.io_loop.add_callback(request.streaming_callback, chunk)\n        else:\n            write_function = buffer.write\n        if bytes is str:  # py2\n            curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        else:  # py3\n            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n            # a fork/port.  That version has a bug in which it passes unicode\n            # strings instead of bytes to the WRITEFUNCTION.  This means that\n            # if you use a WRITEFUNCTION (which tornado always does), you cannot\n            # download arbitrary binary data.  This needs to be fixed in the\n            # ported pycurl package, but in the meantime this lambda will\n            # make it work for downloading (utf8) text.\n            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, \"none\")\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                credentials = '%s:%s' % (request.proxy_username,\n                                         request.proxy_password)\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n        else:\n            curl.setopt(pycurl.PROXY, '')\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError('unknown method ' + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if ((body_expected and not body_present) or\n                    (body_present and not body_expected)):\n                raise ValueError(\n                    'Body must %sbe None for method %s (unless '\n                    'allow_nonstandard_methods is true)' %\n                    ('not ' if body_expected else '', request.method))\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError('Body must be None for GET request')\n            request_buffer = BytesIO(utf8(request.body or ''))\n\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))\n\n        if request.auth_username is not None:\n            userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            curl.setopt(pycurl.USERPWD, native_str(userpwd))\n            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                           request.auth_username)\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.activeCount() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(self, headers, header_callback, header_line):\n        header_line = native_str(header_line)\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                (__, __, reason) = httputil.parse_response_start_line(header_line)\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type, debug_msg):\n        debug_types = ('I', '<', '>', '<', '>')\n        if debug_type == 0:\n            curl_log.debug('%s', debug_msg.strip())\n        elif debug_type in (1, 2):\n            for line in debug_msg.splitlines():\n                curl_log.debug('%s %s', debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 39,
                                "end_lineno": 490,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "from __future__ import absolute_import, division, print_function, with_statement\n\nfrom hashlib import md5\n\nfrom tornado.escape import utf8\nfrom tornado.httpclient import HTTPRequest\nfrom tornado.stack_context import ExceptionStackContext\nfrom tornado.testing import AsyncHTTPTestCase\nfrom tornado.test import httpclient_test\nfrom tornado.test.util import unittest\nfrom tornado.web import Application, RequestHandler\n\n\ntry:\n    import pycurl\nexcept ImportError:\n    pycurl = None\n\nif pycurl is not None:\n    from tornado.curl_httpclient import CurlAsyncHTTPClient\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):\n    def get_http_client(self):\n        client = CurlAsyncHTTPClient(io_loop=self.io_loop,\n                                     defaults=dict(allow_ipv6=False))\n        # make sure AsyncHTTPClient magic doesn't give us the wrong class\n        self.assertTrue(isinstance(client, CurlAsyncHTTPClient))\n        return client\n\n\nclass DigestAuthHandler(RequestHandler):\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n        username = 'foo'\n        password = 'bar'\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (username, realm, password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))\n\n\nclass CustomReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(200, \"Custom reason\")\n\n\nclass CustomFailReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(400, \"Custom reason\")\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = CurlAsyncHTTPClient(self.io_loop,\n                                               defaults=dict(allow_ipv6=False))\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            self.stop()\n            return True\n\n        with ExceptionStackContext(error_handler):\n            request = HTTPRequest(self.get_url('/'),\n                                  prepare_curl_callback=lambda curl: 1 / 0)\n        self.http_client.fetch(request, callback=self.stop)\n        self.wait()\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n",
            "new_file_content": "# coding: utf-8\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nfrom hashlib import md5\n\nfrom tornado.escape import utf8\nfrom tornado.httpclient import HTTPRequest\nfrom tornado.stack_context import ExceptionStackContext\nfrom tornado.testing import AsyncHTTPTestCase\nfrom tornado.test import httpclient_test\nfrom tornado.test.util import unittest\nfrom tornado.web import Application, RequestHandler\n\n\ntry:\n    import pycurl\nexcept ImportError:\n    pycurl = None\n\nif pycurl is not None:\n    from tornado.curl_httpclient import CurlAsyncHTTPClient\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):\n    def get_http_client(self):\n        client = CurlAsyncHTTPClient(io_loop=self.io_loop,\n                                     defaults=dict(allow_ipv6=False))\n        # make sure AsyncHTTPClient magic doesn't give us the wrong class\n        self.assertTrue(isinstance(client, CurlAsyncHTTPClient))\n        return client\n\n\nclass DigestAuthHandler(RequestHandler):\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n        username = 'foo'\n        password = 'bar'\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (username, realm, password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))\n\n\nclass CustomReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(200, \"Custom reason\")\n\n\nclass CustomFailReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(400, \"Custom reason\")\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(self.io_loop, force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            self.stop()\n            return True\n\n        with ExceptionStackContext(error_handler):\n            request = HTTPRequest(self.get_url('/'),\n                                  prepare_curl_callback=lambda curl: 1 / 0)\n        self.http_client.fetch(request, callback=self.stop)\n        self.wait()\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)\n",
            "header": {
                "file": {
                    "path": "tornado/test/curl_httpclient_test.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "d06a7bd2",
                "new_commit_hash": "097aec3e",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/tornado/test/curl_httpclient_test.py"
            },
            "plus_file": {
                "path": "b/tornado/test/curl_httpclient_test.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1,
                            "length": 3
                        },
                        "new_range": {
                            "start": 1,
                            "length": 4
                        },
                        "section": ""
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "# coding: utf-8",
                                "type": "added"
                            },
                            {
                                "content": "from __future__ import absolute_import, division, print_function, with_statement",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "from hashlib import md5",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 83,
                            "length": 8
                        },
                        "new_range": {
                            "start": 84,
                            "length": 7
                        },
                        "section": "class CustomFailReasonHandler(RequestHandler):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):",
                                "type": "context"
                            },
                            {
                                "content": "    def setUp(self):",
                                "type": "context"
                            },
                            {
                                "content": "        super(CurlHTTPClientTestCase, self).setUp()",
                                "type": "context"
                            },
                            {
                                "content": "        self.http_client = CurlAsyncHTTPClient(self.io_loop,",
                                "type": "deleted"
                            },
                            {
                                "content": "                                               defaults=dict(allow_ipv6=False))",
                                "type": "deleted"
                            },
                            {
                                "content": "        self.http_client = self.create_client()",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def get_app(self):",
                                "type": "context"
                            },
                            {
                                "content": "        return Application([",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "class",
                            "name": "CurlHTTPClientTestCase",
                            "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = CurlAsyncHTTPClient(self.io_loop,\n                                               defaults=dict(allow_ipv6=False))\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            self.stop()\n            return True\n\n        with ExceptionStackContext(error_handler):\n            request = HTTPRequest(self.get_url('/'),\n                                  prepare_curl_callback=lambda curl: 1 / 0)\n        self.http_client.fetch(request, callback=self.stop)\n        self.wait()\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 83,
                            "end_lineno": 123,
                            "parent": null
                        },
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "method",
                            "name": "CurlHTTPClientTestCase.setUp",
                            "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = CurlAsyncHTTPClient(self.io_loop,\n                                               defaults=dict(allow_ipv6=False))\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            self.stop()\n            return True\n\n        with ExceptionStackContext(error_handler):\n            request = HTTPRequest(self.get_url('/'),\n                                  prepare_curl_callback=lambda curl: 1 / 0)\n        self.http_client.fetch(request, callback=self.stop)\n        self.wait()\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 84,
                            "end_lineno": 87,
                            "parent": {
                                "file_name": "tornado/test/curl_httpclient_test.py",
                                "type": "class",
                                "name": "CurlHTTPClientTestCase",
                                "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = CurlAsyncHTTPClient(self.io_loop,\n                                               defaults=dict(allow_ipv6=False))\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            self.stop()\n            return True\n\n        with ExceptionStackContext(error_handler):\n            request = HTTPRequest(self.get_url('/'),\n                                  prepare_curl_callback=lambda curl: 1 / 0)\n        self.http_client.fetch(request, callback=self.stop)\n        self.wait()\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 83,
                                "end_lineno": 123,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 93,
                            "length": 6
                        },
                        "new_range": {
                            "start": 93,
                            "length": 11
                        },
                        "section": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            ('/custom_fail_reason', CustomFailReasonHandler),",
                                "type": "context"
                            },
                            {
                                "content": "        ])",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def create_client(self, **kwargs):",
                                "type": "added"
                            },
                            {
                                "content": "        return CurlAsyncHTTPClient(self.io_loop, force_instance=True,",
                                "type": "added"
                            },
                            {
                                "content": "                                   defaults=dict(allow_ipv6=False),",
                                "type": "added"
                            },
                            {
                                "content": "                                   **kwargs)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def test_prepare_curl_callback_stack_context(self):",
                                "type": "context"
                            },
                            {
                                "content": "        exc_info = []",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "class",
                            "name": "CurlHTTPClientTestCase",
                            "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(self.io_loop, force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            self.stop()\n            return True\n\n        with ExceptionStackContext(error_handler):\n            request = HTTPRequest(self.get_url('/'),\n                                  prepare_curl_callback=lambda curl: 1 / 0)\n        self.http_client.fetch(request, callback=self.stop)\n        self.wait()\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 84,
                            "end_lineno": 134,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "method",
                            "name": "CurlHTTPClientTestCase.create_client",
                            "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(self.io_loop, force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            self.stop()\n            return True\n\n        with ExceptionStackContext(error_handler):\n            request = HTTPRequest(self.get_url('/'),\n                                  prepare_curl_callback=lambda curl: 1 / 0)\n        self.http_client.fetch(request, callback=self.stop)\n        self.wait()\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 96,
                            "end_lineno": 99,
                            "parent": {
                                "file_name": "tornado/test/curl_httpclient_test.py",
                                "type": "class",
                                "name": "CurlHTTPClientTestCase",
                                "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(self.io_loop, force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            self.stop()\n            return True\n\n        with ExceptionStackContext(error_handler):\n            request = HTTPRequest(self.get_url('/'),\n                                  prepare_curl_callback=lambda curl: 1 / 0)\n        self.http_client.fetch(request, callback=self.stop)\n        self.wait()\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 84,
                                "end_lineno": 134,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 122,
                            "length": 3
                        },
                        "new_range": {
                            "start": 127,
                            "length": 8
                        },
                        "section": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        response = self.fetch('/custom_fail_reason')",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def test_failed_setup(self):",
                                "type": "added"
                            },
                            {
                                "content": "        self.http_client = self.create_client(max_clients=1)",
                                "type": "added"
                            },
                            {
                                "content": "        for i in range(5):",
                                "type": "added"
                            },
                            {
                                "content": "            response = self.fetch(u'/ユニコード')",
                                "type": "added"
                            },
                            {
                                "content": "            self.assertIsNot(response.error, None)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "class",
                            "name": "CurlHTTPClientTestCase",
                            "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(self.io_loop, force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            self.stop()\n            return True\n\n        with ExceptionStackContext(error_handler):\n            request = HTTPRequest(self.get_url('/'),\n                                  prepare_curl_callback=lambda curl: 1 / 0)\n        self.http_client.fetch(request, callback=self.stop)\n        self.wait()\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 84,
                            "end_lineno": 134,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "method",
                            "name": "CurlHTTPClientTestCase.test_failed_setup",
                            "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(self.io_loop, force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            self.stop()\n            return True\n\n        with ExceptionStackContext(error_handler):\n            request = HTTPRequest(self.get_url('/'),\n                                  prepare_curl_callback=lambda curl: 1 / 0)\n        self.http_client.fetch(request, callback=self.stop)\n        self.wait()\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 130,
                            "end_lineno": 134,
                            "parent": {
                                "file_name": "tornado/test/curl_httpclient_test.py",
                                "type": "class",
                                "name": "CurlHTTPClientTestCase",
                                "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(self.io_loop, force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            self.stop()\n            return True\n\n        with ExceptionStackContext(error_handler):\n            request = HTTPRequest(self.get_url('/'),\n                                  prepare_curl_callback=lambda curl: 1 / 0)\n        self.http_client.fetch(request, callback=self.stop)\n        self.wait()\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 84,
                                "end_lineno": 134,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "d5a4ebee8c27f24c1eb3e6ce30ad8c4ce0abf6c7^",
    "new_commit_hash": "d5a4ebee8c27f24c1eb3e6ce30ad8c4ce0abf6c7",
    "commit_message": "Merge pull request #1582 from minrk/free-list-on-fail\n\nCurlAsyncHTTPClient: free_list starvation if setup fails",
    "commit_date": "2015-11-11T20:14:02-05:00",
    "metadata": {}
}
{
    "repo_name": "tornado",
    "new_commit_hash": "d5a4ebee8c27f24c1eb3e6ce30ad8c4ce0abf6c7",
    "test_file_codes": [
        "# coding: utf-8\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nfrom hashlib import md5\n\nfrom tornado.escape import utf8\nfrom tornado.httpclient import HTTPRequest\nfrom tornado.stack_context import ExceptionStackContext\nfrom tornado.testing import AsyncHTTPTestCase\nfrom tornado.test import httpclient_test\nfrom tornado.test.util import unittest\nfrom tornado.web import Application, RequestHandler\n\n\ntry:\n    import pycurl\nexcept ImportError:\n    pycurl = None\n\nif pycurl is not None:\n    from tornado.curl_httpclient import CurlAsyncHTTPClient\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):\n    def get_http_client(self):\n        client = CurlAsyncHTTPClient(io_loop=self.io_loop,\n                                     defaults=dict(allow_ipv6=False))\n        # make sure AsyncHTTPClient magic doesn't give us the wrong class\n        self.assertTrue(isinstance(client, CurlAsyncHTTPClient))\n        return client\n\n\nclass DigestAuthHandler(RequestHandler):\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n        username = 'foo'\n        password = 'bar'\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (username, realm, password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))\n\n\nclass CustomReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(200, \"Custom reason\")\n\n\nclass CustomFailReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(400, \"Custom reason\")\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(self.io_loop, force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            self.stop()\n            return True\n\n        with ExceptionStackContext(error_handler):\n            request = HTTPRequest(self.get_url('/'),\n                                  prepare_curl_callback=lambda curl: 1 / 0)\n        self.http_client.fetch(request, callback=self.stop)\n        self.wait()\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)\n",
        "#!/usr/bin/env python\nimport unittest\nimport sys\nimport time\nimport traceback\nimport platform\nimport os\n\n\nclass PytestLikeResult(unittest.TestResult):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.start_time = None\n        # Store (test, outcome, info) for each test:\n        #   outcome ∈ { \"passed\", \"failed\", \"error\", \"skipped\" }\n        #   info can hold the exception or skip reason, etc.\n        self.results = []\n\n    def startTestRun(self):\n        \"\"\"Called once before all tests.\"\"\"\n        self.start_time = time.time()\n        # Print a header similar to pytest\n        print(\"=\" * 29 + \" test session starts \" + \"=\" * 29)\n        # You can customize the platform/versions/paths as you like:\n        print(\n            f\"platform {platform.system().lower()} -- Python {platform.python_version()}\"\n        )\n        print(f\"rootdir: {os.path.abspath(os.curdir)}\")\n\n    def stopTestRun(self):\n        \"\"\"Called once after all tests are done.\"\"\"\n        elapsed = time.time() - self.start_time\n        print()  # blank line after final dot\n\n        # Summarize\n        ntests = len(self.results)\n        print(f\"collected {ntests} items\")\n\n        # Show details of failures/errors\n        failures = [r for r in self.results if r[1] == \"failed\"]\n        errors = [r for r in self.results if r[1] == \"error\"]\n\n        if failures:\n            print(\"\\n\" + \"=\" * 35 + \" FAILURES \" + \"=\" * 35)\n            for test, outcome, err_info in failures:\n                test = test.split(\"::\")\n                if len(test) == 3:\n                    test = f\"{test[1]}.{test[2]}\"\n                else:\n                    test = f\"{test[1]}\"\n                print(f\"__________ {test} __________\")\n                self._print_traceback(err_info)\n        if errors:\n            print(\"\\n\" + \"=\" * 35 + \" ERRORS \" + \"=\" * 37)\n            for test, outcome, err_info in errors:\n                test = test.split(\"::\")\n                if len(test) == 3:\n                    test = f\"{test[1]}.{test[2]}\"\n                else:\n                    test = f\"{test[1]}\"\n                print(f\"__________ {test} __________\")\n                self._print_traceback(err_info)\n\n        # Optional short summary lines for passes/fails/skips\n        print(\"\\n==================== short test summary info ====================\")\n        for test, outcome, info in self.results:\n            if outcome in (\"passed\", \"failed\", \"error\", \"skipped\"):\n                print(f\"{outcome.upper()} {test}\")\n\n        # Final one-line summary: “1 failed, 1 errors, 10 passed in 2.04s”\n        passed_count = sum(1 for r in self.results if r[1] == \"passed\")\n        failed_count = len(failures)\n        error_count = len(errors)\n        skipped_count = sum(1 for r in self.results if r[1] == \"skipped\")\n\n        summary_bits = []\n        if failed_count:\n            summary_bits.append(f\"{failed_count} failed\")\n        if error_count:\n            summary_bits.append(f\"{error_count} error{'s' if error_count != 1 else ''}\")\n        if passed_count:\n            summary_bits.append(f\"{passed_count} passed\")\n        if skipped_count:\n            summary_bits.append(f\"{skipped_count} skipped\")\n\n        summary_str = \", \".join(summary_bits) if summary_bits else \"no tests run\"\n        print(\n            f\"=================== {summary_str} in {elapsed:.2f}s ===================\"\n        )\n\n    def startTest(self, test):\n        \"\"\"Called right before each test method.\"\"\"\n        super().startTest(test)\n        # Print a dot or some indicator\n        print(\".\", end=\"\", flush=True)\n\n    def addSuccess(self, test):\n        super().addSuccess(test)\n        self.results.append((self._test_id(test), \"passed\", None))\n\n    def addFailure(self, test, err):\n        super().addFailure(test, err)\n        self.results.append((self._test_id(test), \"failed\", err))\n\n    def addError(self, test, err):\n        super().addError(test, err)\n        self.results.append((self._test_id(test), \"error\", err))\n\n    def addSkip(self, test, reason):\n        super().addSkip(test, reason)\n        self.results.append((self._test_id(test), \"skipped\", reason))\n\n    def _print_traceback(self, err_info):\n        \"\"\"Helper to print the traceback like pytest does.\"\"\"\n        if isinstance(err_info, tuple) and len(err_info) == 3:\n            exc_type, exc_value, tb = err_info\n            traceback.print_exception(exc_type, exc_value, tb, file=sys.stdout)\n        else:\n            print(str(err_info))\n\n    def _test_id(self, test):\n        try:\n            return f\"{test.__class__.__module__}::{test.__class__.__name__}::{test._testMethodName}\"\n        except AttributeError:\n            # Fallback for _ErrorHolder objects\n            return f\"{test.__class__.__module__}::{test.__class__.__name__}\"\n\n\nclass PytestLikeRunner(unittest.TextTestRunner):\n    \"\"\"A custom TextTestRunner that uses the PytestLikeResult.\"\"\"\n\n    resultclass = PytestLikeResult\n\n    def run(self, test):\n        result = self._makeResult()\n        # Overriding run to ensure we call startTestRun/stopTestRun\n        result.startTestRun()\n        test(result)\n        result.stopTestRun()\n        return result\n\n    def tearDown(self):\n        # Reset signal handlers\n        from twisted.internet import reactor\n\n        reactor.removeAll()\n        reactor.stop()\n        super().tearDown()\n\n\ndef main():\n    loader = unittest.TestLoader()\n    suite = loader.discover(\".\", pattern=\"test_*.py\")  # discover tests in r2e_tests/\n    runner = PytestLikeRunner(verbosity=0)\n    result = runner.run(suite)\n    # Exit with code 0 if all tests passed/skipped, 1 if there were fails/errors\n    sys.exit(not result.wasSuccessful())\n\n\nif __name__ == \"__main__\":\n    main()\n"
    ],
    "test_file_names": [
        "test_1.py",
        "tornado_unittest_runner.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 1 package in 807ms\nPrepared 1 package in 1.02s\nInstalled 1 package in 0.83ms\n + tornado==4.4.dev1 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_d5a4ebee8c27f24c1eb3e6ce30ad8c4ce0abf6c7)\nResolved 29 packages in 13ms\nInstalled 29 packages in 75ms\n + attrs==24.3.0\n + automat==24.8.1\n + cachetools==5.5.0\n + cffi==1.17.1\n + chardet==5.2.0\n + colorama==0.4.6\n + constantly==23.10.4\n + distlib==0.3.9\n + exceptiongroup==1.2.2\n + filelock==3.16.1\n + hyperlink==21.0.0\n + idna==3.10\n + incremental==24.7.2\n + iniconfig==2.0.0\n + packaging==24.2\n + platformdirs==4.3.6\n + pluggy==1.5.0\n + pycares==4.5.0\n + pycparser==2.22\n + pycurl==7.45.4\n + pyproject-api==1.8.0\n + pytest==8.3.4\n + setuptools==75.6.0\n + tomli==2.2.1\n + tox==4.23.2\n + twisted==24.11.0\n + typing-extensions==4.12.2\n + virtualenv==20.28.0\n + zope-interface==7.2\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "============================= test session starts =============================\nplatform linux -- Python 3.9.21\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_d5a4ebee8c27f24c1eb3e6ce30ad8c4ce0abf6c7\n.................................\ncollected 33 items\n\n==================== short test summary info ====================\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_304_with_content_length\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_all_methods\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_basic_auth\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_basic_auth_explicit_mode\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_body_encoding\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_body_sanity_checks\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_chunked\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_chunked_close\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_configure_defaults\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_credentials_in_url\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_final_callback_stack_context\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_follow_redirect\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_future_http_error\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_future_http_error_no_raise\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_future_interface\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_header_callback\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_header_callback_stack_context\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_header_types\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_hello_world\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_multi_line_headers\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_patch_receives_payload\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_post\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_put_307\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_reuse_request_from_response\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_streaming_callback\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_streaming_stack_context\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_types\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_unsupported_auth_mode\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_custom_reason\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_digest_auth\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_fail_custom_reason\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_failed_setup\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_prepare_curl_callback_stack_context\n=================== 33 passed in 0.16s ===================\n",
    "new_commit_res_stderr": "WARNING:tornado.access:405 HEAD /all_methods (127.0.0.1) 0.26ms\nWARNING:tornado.access:404 GET /notfound (127.0.0.1) 0.20ms\nWARNING:tornado.access:404 GET /notfound (127.0.0.1) 0.19ms\nWARNING:tornado.access:401 GET /digest (127.0.0.1) 0.31ms\nWARNING:tornado.access:400 GET /custom_fail_reason (127.0.0.1) 0.26ms\n",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts =============================\nplatform linux -- Python 3.9.21\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_d5a4ebee8c27f24c1eb3e6ce30ad8c4ce0abf6c7\n.................................\ncollected 33 items\n\n=================================== FAILURES ===================================\n__________ CurlHTTPClientTestCase.test_failed_setup __________\nTraceback (most recent call last):\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 59, in testPartExecutor\n    yield\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 592, in run\n    self._callTestMethod(testMethod)\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 550, in _callTestMethod\n    method()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_d5a4ebee8c27f24c1eb3e6ce30ad8c4ce0abf6c7/tornado/testing.py\", line 132, in __call__\n    result = self.orig_method(*args, **kwargs)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_d5a4ebee8c27f24c1eb3e6ce30ad8c4ce0abf6c7/r2e_tests/test_1.py\", line 133, in test_failed_setup\n    response = self.fetch(u'/ユニコード')\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_d5a4ebee8c27f24c1eb3e6ce30ad8c4ce0abf6c7/tornado/testing.py\", line 402, in fetch\n    return self.wait()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_d5a4ebee8c27f24c1eb3e6ce30ad8c4ce0abf6c7/tornado/testing.py\", line 332, in wait\n    self.__rethrow()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_d5a4ebee8c27f24c1eb3e6ce30ad8c4ce0abf6c7/tornado/testing.py\", line 268, in __rethrow\n    raise_exc_info(failure)\n  File \"<string>\", line 3, in raise_exc_info\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_d5a4ebee8c27f24c1eb3e6ce30ad8c4ce0abf6c7/tornado/testing.py\", line 314, in timeout_func\n    raise self.failureException(\nAssertionError: Async operation timed out after 5 seconds\n\n==================== short test summary info ====================\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_304_with_content_length\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_all_methods\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_basic_auth\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_basic_auth_explicit_mode\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_body_encoding\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_body_sanity_checks\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_chunked\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_chunked_close\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_configure_defaults\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_credentials_in_url\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_final_callback_stack_context\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_follow_redirect\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_future_http_error\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_future_http_error_no_raise\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_future_interface\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_header_callback\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_header_callback_stack_context\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_header_types\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_hello_world\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_multi_line_headers\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_patch_receives_payload\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_post\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_put_307\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_reuse_request_from_response\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_streaming_callback\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_streaming_stack_context\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_types\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_unsupported_auth_mode\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_custom_reason\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_digest_auth\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_fail_custom_reason\nFAILED r2e_tests.test_1::CurlHTTPClientTestCase::test_failed_setup\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_prepare_curl_callback_stack_context\n=================== 1 failed, 32 passed in 5.13s ===================\n",
    "old_commit_res_stderr": "WARNING:tornado.access:405 HEAD /all_methods (127.0.0.1) 0.26ms\nWARNING:tornado.access:404 GET /notfound (127.0.0.1) 0.18ms\nWARNING:tornado.access:404 GET /notfound (127.0.0.1) 0.34ms\nWARNING:tornado.access:401 GET /digest (127.0.0.1) 0.18ms\nWARNING:tornado.access:400 GET /custom_fail_reason (127.0.0.1) 0.18ms\n"
}
tornado/curl_httpclient.py tornado/test/curl_httpclient_test.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
**Analysis:**

- **Key Parts of Commit Details and Test Results:**
  - **Commit Patch:** Adds `self._free_list.append(curl)` in `CurlAsyncHTTPClient` when setup fails.
  - **Test Results:** Previously, `test_failed_setup` failed with an `AssertionError: Async operation timed out after 5 seconds`. After the commit, this test passes.

- **Main Problem Highlighted by Test Results:**
  - When the setup of `CurlAsyncHTTPClient` fails, the free list for CURL handles is not replenished, leading to resource starvation. This causes subsequent asynchronous operations to hang and eventually timeout.

- **Expected Behavior:**
  - If the setup fails, the HTTP client should properly release any allocated resources back to the free list to prevent starvation, allowing future operations to proceed without hanging.

- **Actual Behavior:**
  - On setup failure, the HTTP client does not release resources, causing subsequent operations to hang and timeout after waiting for available resources.

- **Issue Description:**
  - The issue should clearly describe that upon a failed setup, the HTTP client fails to release resources, leading to operation timeouts. Including a brief example of how the client is initialized and used will help developers understand the context.

---

[ISSUE]

**Title:** HTTP Client Resource Starvation Causes Operation Timeouts on Setup Failure

**Description:**

When initializing the `CurlAsyncHTTPClient` with a limited number of clients, if the setup process fails, the client does not release resources back to its free list. This leads to subsequent operations hanging and eventually timing out after waiting for available resources.

**Example Code:**

```python
def initialize_client_with_failing_setup():
    client = CurlAsyncHTTPClient(io_loop, max_clients=1)
    # Simulate setup failure by making invalid requests
    for _ in range(5):
        client.fetch('/invalid_endpoint')
```

**Expected Behavior:**

Even if some setup operations fail, the HTTP client should release resources, allowing all fetch operations to complete or fail gracefully without hanging.

**Actual Behavior:**

After initial setup failures, fetch operations hang and eventually timeout, indicating that resources are not being released properly.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### tornado/curl_httpclient.py
#!/usr/bin/env python
#
# Copyright 2009 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Non-blocking HTTP client implementation using pycurl."""

from __future__ import absolute_import, division, print_function, with_statement

import collections
import functools
import logging
import pycurl
import threading
import time
from io import BytesIO

from tornado import httputil
from tornado import ioloop
from tornado import stack_context

from tornado.escape import utf8, native_str
from tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main

curl_log = logging.getLogger('tornado.curl_httpclient')


class CurlAsyncHTTPClient(AsyncHTTPClient):
    def initialize(self, io_loop, max_clients=10, defaults=None):
        super(CurlAsyncHTTPClient, self).initialize(io_loop, defaults=defaults)
        self._multi = pycurl.CurlMulti()
        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)
        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)
        self._curls = [self._curl_create() for i in range(max_clients)]
        self._free_list = self._curls[:]
        self._requests = collections.deque()
        self._fds = {}
        self._timeout = None

        # libcurl has bugs that sometimes cause it to not report all
        # relevant file descriptors and timeouts to TIMERFUNCTION/
        # SOCKETFUNCTION.  Mitigate the effects of such bugs by
        # forcing a periodic scan of all active requests.
        self._force_timeout_callback = ioloop.PeriodicCallback(
            self._handle_force_timeout, 1000, io_loop=io_loop)
        self._force_timeout_callback.start()

        # Work around a bug in libcurl 7.29.0: Some fields in the curl
        # multi object are initialized lazily, and its destructor will
        # segfault if it is destroyed without having been used.  Add
        # and remove a dummy handle to make sure everything is
        # initialized.
        dummy_curl_handle = pycurl.Curl()
        self._multi.add_handle(dummy_curl_handle)
        self._multi.remove_handle(dummy_curl_handle)

    def close(self):
        self._force_timeout_callback.stop()
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
        for curl in self._curls:
            curl.close()
        self._multi.close()
        super(CurlAsyncHTTPClient, self).close()

    def fetch_impl(self, request, callback):
        self._requests.append((request, callback))
        self._process_queue()
        self._set_timeout(0)

    def _handle_socket(self, event, fd, multi, data):
        """Called by libcurl when it wants to change the file descriptors
        it cares about.
        """
        event_map = {
            pycurl.POLL_NONE: ioloop.IOLoop.NONE,
            pycurl.POLL_IN: ioloop.IOLoop.READ,
            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,
            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE
        }
        if event == pycurl.POLL_REMOVE:
            if fd in self._fds:
                self.io_loop.remove_handler(fd)
                del self._fds[fd]
        else:
            ioloop_event = event_map[event]
            # libcurl sometimes closes a socket and then opens a new
            # one using the same FD without giving us a POLL_NONE in
            # between.  This is a problem with the epoll IOLoop,
            # because the kernel can tell when a socket is closed and
            # removes it from the epoll automatically, causing future
            # update_handler calls to fail.  Since we can't tell when
            # this has happened, always use remove and re-add
            # instead of update.
            if fd in self._fds:
                self.io_loop.remove_handler(fd)
            self.io_loop.add_handler(fd, self._handle_events,
                                     ioloop_event)
            self._fds[fd] = ioloop_event

    def _set_timeout(self, msecs):
        """Called by libcurl to schedule a timeout."""
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
        self._timeout = self.io_loop.add_timeout(
            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)

    def _handle_events(self, fd, events):
        """Called by IOLoop when there is activity on one of our
        file descriptors.
        """
        action = 0
        if events & ioloop.IOLoop.READ:
            action |= pycurl.CSELECT_IN
        if events & ioloop.IOLoop.WRITE:
            action |= pycurl.CSELECT_OUT
        while True:
            try:
                ret, num_handles = self._multi.socket_action(fd, action)
            except pycurl.error as e:
                ret = e.args[0]
            if ret != pycurl.E_CALL_MULTI_PERFORM:
                break
        self._finish_pending_requests()

    def _handle_timeout(self):
        """Called by IOLoop when the requested timeout has passed."""
        with stack_context.NullContext():
            self._timeout = None
            while True:
                try:
                    ret, num_handles = self._multi.socket_action(
                        pycurl.SOCKET_TIMEOUT, 0)
                except pycurl.error as e:
                    ret = e.args[0]
                if ret != pycurl.E_CALL_MULTI_PERFORM:
                    break
            self._finish_pending_requests()

        # In theory, we shouldn't have to do this because curl will
        # call _set_timeout whenever the timeout changes.  However,
        # sometimes after _handle_timeout we will need to reschedule
        # immediately even though nothing has changed from curl's
        # perspective.  This is because when socket_action is
        # called with SOCKET_TIMEOUT, libcurl decides internally which
        # timeouts need to be processed by using a monotonic clock
        # (where available) while tornado uses python's time.time()
        # to decide when timeouts have occurred.  When those clocks
        # disagree on elapsed time (as they will whenever there is an
        # NTP adjustment), tornado might call _handle_timeout before
        # libcurl is ready.  After each timeout, resync the scheduled
        # timeout with libcurl's current state.
        new_timeout = self._multi.timeout()
        if new_timeout >= 0:
            self._set_timeout(new_timeout)

    def _handle_force_timeout(self):
        """Called by IOLoop periodically to ask libcurl to process any
        events it may have forgotten about.
        """
        with stack_context.NullContext():
            while True:
                try:
                    ret, num_handles = self._multi.socket_all()
                except pycurl.error as e:
                    ret = e.args[0]
                if ret != pycurl.E_CALL_MULTI_PERFORM:
                    break
            self._finish_pending_requests()

    def _finish_pending_requests(self):
        """Process any requests that were completed by the last
        call to multi.socket_action.
        """
        while True:
            num_q, ok_list, err_list = self._multi.info_read()
            for curl in ok_list:
                self._finish(curl)
            for curl, errnum, errmsg in err_list:
                self._finish(curl, errnum, errmsg)
            if num_q == 0:
                break
        self._process_queue()

    def _process_queue(self):
        with stack_context.NullContext():
            while True:
                started = 0
                while self._free_list and self._requests:
                    started += 1
                    curl = self._free_list.pop()
                    (request, callback) = self._requests.popleft()
                    curl.info = {
                        "headers": httputil.HTTPHeaders(),
                        "buffer": BytesIO(),
                        "request": request,
                        "callback": callback,
                        "curl_start_time": time.time(),
                    }
                    try:
                        self._curl_setup_request(
                            curl, request, curl.info["buffer"],
                            curl.info["headers"])
                    except Exception as e:
                        # If there was an error in setup, pass it on
                        # to the callback. Note that allowing the
                        # error to escape here will appear to work
                        # most of the time since we are still in the
                        # caller's original stack frame, but when
                        # _process_queue() is called from
                        # _finish_pending_requests the exceptions have
                        # nowhere to go.
                        callback(HTTPResponse(
                            request=request,
                            code=599,
                            error=e))
                    else:
                        self._multi.add_handle(curl)

                if not started:
                    break

    def _finish(self, curl, curl_error=None, curl_message=None):
        info = curl.info
        curl.info = None
        self._multi.remove_handle(curl)
        self._free_list.append(curl)
        buffer = info["buffer"]
        if curl_error:
            error = CurlError(curl_error, curl_message)
            code = error.code
            effective_url = None
            buffer.close()
            buffer = None
        else:
            error = None
            code = curl.getinfo(pycurl.HTTP_CODE)
            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)
            buffer.seek(0)
        # the various curl timings are documented at
        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
        time_info = dict(
            queue=info["curl_start_time"] - info["request"].start_time,
            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),
            connect=curl.getinfo(pycurl.CONNECT_TIME),
            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),
            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),
            total=curl.getinfo(pycurl.TOTAL_TIME),
            redirect=curl.getinfo(pycurl.REDIRECT_TIME),
        )
        try:
            info["callback"](HTTPResponse(
                request=info["request"], code=code, headers=info["headers"],
                buffer=buffer, effective_url=effective_url, error=error,
                reason=info['headers'].get("X-Http-Reason", None),
                request_time=time.time() - info["curl_start_time"],
                time_info=time_info))
        except Exception:
            self.handle_callback_exception(info["callback"])

    def handle_callback_exception(self, callback):
        self.io_loop.handle_callback_exception(callback)

    def _curl_create(self):
        curl = pycurl.Curl()
        if curl_log.isEnabledFor(logging.DEBUG):
            curl.setopt(pycurl.VERBOSE, 1)
            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)
        return curl

    def _curl_setup_request(self, curl, request, buffer, headers):
        curl.setopt(pycurl.URL, native_str(request.url))

        # libcurl's magic "Expect: 100-continue" behavior causes delays
        # with servers that don't support it (which include, among others,
        # Google's OpenID endpoint).  Additionally, this behavior has
        # a bug in conjunction with the curl_multi_socket_action API
        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),
        # which increases the delays.  It's more trouble than it's worth,
        # so just turn off the feature (yes, setting Expect: to an empty
        # value is the official way to disable this)
        if "Expect" not in request.headers:
            request.headers["Expect"] = ""

        # libcurl adds Pragma: no-cache by default; disable that too
        if "Pragma" not in request.headers:
            request.headers["Pragma"] = ""

        curl.setopt(pycurl.HTTPHEADER,
                    ["%s: %s" % (native_str(k), native_str(v))
                     for k, v in request.headers.get_all()])

        curl.setopt(pycurl.HEADERFUNCTION,
                    functools.partial(self._curl_header_callback,
                                      headers, request.header_callback))
        if request.streaming_callback:
            def write_function(chunk):
                self.io_loop.add_callback(request.streaming_callback, chunk)
        else:
            write_function = buffer.write
        if bytes is str:  # py2
            curl.setopt(pycurl.WRITEFUNCTION, write_function)
        else:  # py3
            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes
            # a fork/port.  That version has a bug in which it passes unicode
            # strings instead of bytes to the WRITEFUNCTION.  This means that
            # if you use a WRITEFUNCTION (which tornado always does), you cannot
            # download arbitrary binary data.  This needs to be fixed in the
            # ported pycurl package, but in the meantime this lambda will
            # make it work for downloading (utf8) text.
            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))
        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)
        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)
        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))
        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))
        if request.user_agent:
            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))
        else:
            curl.setopt(pycurl.USERAGENT, "Mozilla/5.0 (compatible; pycurl)")
        if request.network_interface:
            curl.setopt(pycurl.INTERFACE, request.network_interface)
        if request.decompress_response:
            curl.setopt(pycurl.ENCODING, "gzip,deflate")
        else:
            curl.setopt(pycurl.ENCODING, "none")
        if request.proxy_host and request.proxy_port:
            curl.setopt(pycurl.PROXY, request.proxy_host)
            curl.setopt(pycurl.PROXYPORT, request.proxy_port)
            if request.proxy_username:
                credentials = '%s:%s' % (request.proxy_username,
                                         request.proxy_password)
                curl.setopt(pycurl.PROXYUSERPWD, credentials)
        else:
            curl.setopt(pycurl.PROXY, '')
            curl.unsetopt(pycurl.PROXYUSERPWD)
        if request.validate_cert:
            curl.setopt(pycurl.SSL_VERIFYPEER, 1)
            curl.setopt(pycurl.SSL_VERIFYHOST, 2)
        else:
            curl.setopt(pycurl.SSL_VERIFYPEER, 0)
            curl.setopt(pycurl.SSL_VERIFYHOST, 0)
        if request.ca_certs is not None:
            curl.setopt(pycurl.CAINFO, request.ca_certs)
        else:
            # There is no way to restore pycurl.CAINFO to its default value
            # (Using unsetopt makes it reject all certificates).
            # I don't see any way to read the default value from python so it
            # can be restored later.  We'll have to just leave CAINFO untouched
            # if no ca_certs file was specified, and require that if any
            # request uses a custom ca_certs file, they all must.
            pass

        if request.allow_ipv6 is False:
            # Curl behaves reasonably when DNS resolution gives an ipv6 address
            # that we can't reach, so allow ipv6 unless the user asks to disable.
            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)
        else:
            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)

        # Set the request method through curl's irritating interface which makes
        # up names for almost every single method
        curl_options = {
            "GET": pycurl.HTTPGET,
            "POST": pycurl.POST,
            "PUT": pycurl.UPLOAD,
            "HEAD": pycurl.NOBODY,
        }
        custom_methods = set(["DELETE", "OPTIONS", "PATCH"])
        for o in curl_options.values():
            curl.setopt(o, False)
        if request.method in curl_options:
            curl.unsetopt(pycurl.CUSTOMREQUEST)
            curl.setopt(curl_options[request.method], True)
        elif request.allow_nonstandard_methods or request.method in custom_methods:
            curl.setopt(pycurl.CUSTOMREQUEST, request.method)
        else:
            raise KeyError('unknown method ' + request.method)

        body_expected = request.method in ("POST", "PATCH", "PUT")
        body_present = request.body is not None
        if not request.allow_nonstandard_methods:
            # Some HTTP methods nearly always have bodies while others
            # almost never do. Fail in this case unless the user has
            # opted out of sanity checks with allow_nonstandard_methods.
            if ((body_expected and not body_present) or
                    (body_present and not body_expected)):
                raise ValueError(
                    'Body must %sbe None for method %s (unless '
                    'allow_nonstandard_methods is true)' %
                    ('not ' if body_expected else '', request.method))

        if body_expected or body_present:
            if request.method == "GET":
                # Even with `allow_nonstandard_methods` we disallow
                # GET with a body (because libcurl doesn't allow it
                # unless we use CUSTOMREQUEST). While the spec doesn't
                # forbid clients from sending a body, it arguably
                # disallows the server from doing anything with them.
                raise ValueError('Body must be None for GET request')
            request_buffer = BytesIO(utf8(request.body or ''))

            def ioctl(cmd):
                if cmd == curl.IOCMD_RESTARTREAD:
                    request_buffer.seek(0)
            curl.setopt(pycurl.READFUNCTION, request_buffer.read)
            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)
            if request.method == "POST":
                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))
            else:
                curl.setopt(pycurl.UPLOAD, True)
                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))

        if request.auth_username is not None:
            userpwd = "%s:%s" % (request.auth_username, request.auth_password or '')

            if request.auth_mode is None or request.auth_mode == "basic":
                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
            elif request.auth_mode == "digest":
                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)
            else:
                raise ValueError("Unsupported auth_mode %s" % request.auth_mode)

            curl.setopt(pycurl.USERPWD, native_str(userpwd))
            curl_log.debug("%s %s (username: %r)", request.method, request.url,
                           request.auth_username)
        else:
            curl.unsetopt(pycurl.USERPWD)
            curl_log.debug("%s %s", request.method, request.url)

        if request.client_cert is not None:
            curl.setopt(pycurl.SSLCERT, request.client_cert)

        if request.client_key is not None:
            curl.setopt(pycurl.SSLKEY, request.client_key)

        if request.ssl_options is not None:
            raise ValueError("ssl_options not supported in curl_httpclient")

        if threading.activeCount() > 1:
            # libcurl/pycurl is not thread-safe by default.  When multiple threads
            # are used, signals should be disabled.  This has the side effect
            # of disabling DNS timeouts in some environments (when libcurl is
            # not linked against ares), so we don't do it when there is only one
            # thread.  Applications that use many short-lived threads may need
            # to set NOSIGNAL manually in a prepare_curl_callback since
            # there may not be any other threads running at the time we call
            # threading.activeCount.
            curl.setopt(pycurl.NOSIGNAL, 1)
        if request.prepare_curl_callback is not None:
            request.prepare_curl_callback(curl)

    def _curl_header_callback(self, headers, header_callback, header_line):
        header_line = native_str(header_line)
        if header_callback is not None:
            self.io_loop.add_callback(header_callback, header_line)
        # header_line as returned by curl includes the end-of-line characters.
        # whitespace at the start should be preserved to allow multi-line headers
        header_line = header_line.rstrip()
        if header_line.startswith("HTTP/"):
            headers.clear()
            try:
                (__, __, reason) = httputil.parse_response_start_line(header_line)
                header_line = "X-Http-Reason: %s" % reason
            except httputil.HTTPInputError:
                return
        if not header_line:
            return
        headers.parse_line(header_line)

    def _curl_debug(self, debug_type, debug_msg):
        debug_types = ('I', '<', '>', '<', '>')
        if debug_type == 0:
            curl_log.debug('%s', debug_msg.strip())
        elif debug_type in (1, 2):
            for line in debug_msg.splitlines():
                curl_log.debug('%s %s', debug_types[debug_type], line)
        elif debug_type == 4:
            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)


class CurlError(HTTPError):
    def __init__(self, errno, message):
        HTTPError.__init__(self, 599, message)
        self.errno = errno


if __name__ == "__main__":
    AsyncHTTPClient.configure(CurlAsyncHTTPClient)
    main()

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
**Analysis:**

- **Key Parts of Commit Details and Test Results:**
  - **Commit Patch:** Adds `self._free_list.append(curl)` in `CurlAsyncHTTPClient` when setup fails.
  - **Test Results:** Previously, `test_failed_setup` failed with an `AssertionError: Async operation timed out after 5 seconds`. After the commit, this test passes.

- **Main Problem Highlighted by Test Results:**
  - When the setup of `CurlAsyncHTTPClient` fails, the free list for CURL handles is not replenished, leading to resource starvation. This causes subsequent asynchronous operations to hang and eventually timeout.

- **Expected Behavior:**
  - If the setup fails, the HTTP client should properly release any allocated resources back to the free list to prevent starvation, allowing future operations to proceed without hanging.

- **Actual Behavior:**
  - On setup failure, the HTTP client does not release resources, causing subsequent operations to hang and timeout after waiting for available resources.

- **Issue Description:**
  - The issue should clearly describe that upon a failed setup, the HTTP client fails to release resources, leading to operation timeouts. Including a brief example of how the client is initialized and used will help developers understand the context.

---

[ISSUE]

**Title:** HTTP Client Resource Starvation Causes Operation Timeouts on Setup Failure

**Description:**

When initializing the `CurlAsyncHTTPClient` with a limited number of clients, if the setup process fails, the client does not release resources back to its free list. This leads to subsequent operations hanging and eventually timing out after waiting for available resources.

**Example Code:**

```python
def initialize_client_with_failing_setup():
    client = CurlAsyncHTTPClient(io_loop, max_clients=1)
    # Simulate setup failure by making invalid requests
    for _ in range(5):
        client.fetch('/invalid_endpoint')
```

**Expected Behavior:**

Even if some setup operations fail, the HTTP client should release resources, allowing all fetch operations to complete or fail gracefully without hanging.

**Actual Behavior:**

After initial setup failures, fetch operations hang and eventually timeout, indicating that resources are not being released properly.

[/ISSUE]
{
    "CurlHTTPClientCommonTestCase.test_304_with_content_length": "PASSED",
    "CurlHTTPClientCommonTestCase.test_all_methods": "PASSED",
    "CurlHTTPClientCommonTestCase.test_basic_auth": "PASSED",
    "CurlHTTPClientCommonTestCase.test_basic_auth_explicit_mode": "PASSED",
    "CurlHTTPClientCommonTestCase.test_body_encoding": "PASSED",
    "CurlHTTPClientCommonTestCase.test_body_sanity_checks": "PASSED",
    "CurlHTTPClientCommonTestCase.test_chunked": "PASSED",
    "CurlHTTPClientCommonTestCase.test_chunked_close": "PASSED",
    "CurlHTTPClientCommonTestCase.test_configure_defaults": "PASSED",
    "CurlHTTPClientCommonTestCase.test_credentials_in_url": "PASSED",
    "CurlHTTPClientCommonTestCase.test_final_callback_stack_context": "PASSED",
    "CurlHTTPClientCommonTestCase.test_follow_redirect": "PASSED",
    "CurlHTTPClientCommonTestCase.test_future_http_error": "PASSED",
    "CurlHTTPClientCommonTestCase.test_future_http_error_no_raise": "PASSED",
    "CurlHTTPClientCommonTestCase.test_future_interface": "PASSED",
    "CurlHTTPClientCommonTestCase.test_header_callback": "PASSED",
    "CurlHTTPClientCommonTestCase.test_header_callback_stack_context": "PASSED",
    "CurlHTTPClientCommonTestCase.test_header_types": "PASSED",
    "CurlHTTPClientCommonTestCase.test_hello_world": "PASSED",
    "CurlHTTPClientCommonTestCase.test_multi_line_headers": "PASSED",
    "CurlHTTPClientCommonTestCase.test_patch_receives_payload": "PASSED",
    "CurlHTTPClientCommonTestCase.test_post": "PASSED",
    "CurlHTTPClientCommonTestCase.test_put_307": "PASSED",
    "CurlHTTPClientCommonTestCase.test_reuse_request_from_response": "PASSED",
    "CurlHTTPClientCommonTestCase.test_streaming_callback": "PASSED",
    "CurlHTTPClientCommonTestCase.test_streaming_stack_context": "PASSED",
    "CurlHTTPClientCommonTestCase.test_types": "PASSED",
    "CurlHTTPClientCommonTestCase.test_unsupported_auth_mode": "PASSED",
    "CurlHTTPClientTestCase.test_custom_reason": "PASSED",
    "CurlHTTPClientTestCase.test_digest_auth": "PASSED",
    "CurlHTTPClientTestCase.test_fail_custom_reason": "PASSED",
    "CurlHTTPClientTestCase.test_failed_setup": "PASSED",
    "CurlHTTPClientTestCase.test_prepare_curl_callback_stack_context": "PASSED"
}
diff --git a/tornado/curl_httpclient.py b/tornado/curl_httpclient.py
index 22f25023..6dadedd9 100644
--- a/tornado/curl_httpclient.py
+++ b/tornado/curl_httpclient.py
@@ -221,6 +221,7 @@ class CurlAsyncHTTPClient(AsyncHTTPClient):
                         # _process_queue() is called from
                         # _finish_pending_requests the exceptions have
                         # nowhere to go.
+                        self._free_list.append(curl)
                         callback(HTTPResponse(
                             request=request,
                             code=599,

{"tornado/curl_httpclient.py": "#!/usr/bin/env python\n#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Non-blocking HTTP client implementation using pycurl.\"\"\"\n\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nimport collections\nimport functools\nimport logging\nimport pycurl\nimport threading\nimport time\nfrom io import BytesIO\n\nfrom tornado import httputil\nfrom tornado import ioloop\nfrom tornado import stack_context\n\nfrom tornado.escape import utf8, native_str\nfrom tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main\n\ncurl_log = logging.getLogger('tornado.curl_httpclient')\n\n\nclass CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, io_loop, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(io_loop, defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000, io_loop=io_loop)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super(CurlAsyncHTTPClient, self).close()\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    try:\n                        self._curl_setup_request(\n                            curl, request, curl.info[\"buffer\"],\n                            curl.info[\"headers\"])\n                    except Exception as e:\n                        # If there was an error in setup, pass it on\n                        # to the callback. Note that allowing the\n                        # error to escape here will appear to work\n                        # most of the time since we are still in the\n                        # caller's original stack frame, but when\n                        # _process_queue() is called from\n                        # _finish_pending_requests the exceptions have\n                        # nowhere to go.\n                        callback(HTTPResponse(\n                            request=request,\n                            code=599,\n                            error=e))\n                    else:\n                        self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                reason=info['headers'].get(\"X-Http-Reason\", None),\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n    def _curl_create(self):\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        return curl\n\n    def _curl_setup_request(self, curl, request, buffer, headers):\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        curl.setopt(pycurl.HTTPHEADER,\n                    [\"%s: %s\" % (native_str(k), native_str(v))\n                     for k, v in request.headers.get_all()])\n\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    functools.partial(self._curl_header_callback,\n                                      headers, request.header_callback))\n        if request.streaming_callback:\n            def write_function(chunk):\n                self.io_loop.add_callback(request.streaming_callback, chunk)\n        else:\n            write_function = buffer.write\n        if bytes is str:  # py2\n            curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        else:  # py3\n            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n            # a fork/port.  That version has a bug in which it passes unicode\n            # strings instead of bytes to the WRITEFUNCTION.  This means that\n            # if you use a WRITEFUNCTION (which tornado always does), you cannot\n            # download arbitrary binary data.  This needs to be fixed in the\n            # ported pycurl package, but in the meantime this lambda will\n            # make it work for downloading (utf8) text.\n            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, \"none\")\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                credentials = '%s:%s' % (request.proxy_username,\n                                         request.proxy_password)\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n        else:\n            curl.setopt(pycurl.PROXY, '')\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError('unknown method ' + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if ((body_expected and not body_present) or\n                    (body_present and not body_expected)):\n                raise ValueError(\n                    'Body must %sbe None for method %s (unless '\n                    'allow_nonstandard_methods is true)' %\n                    ('not ' if body_expected else '', request.method))\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError('Body must be None for GET request')\n            request_buffer = BytesIO(utf8(request.body or ''))\n\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))\n\n        if request.auth_username is not None:\n            userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            curl.setopt(pycurl.USERPWD, native_str(userpwd))\n            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                           request.auth_username)\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.activeCount() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(self, headers, header_callback, header_line):\n        header_line = native_str(header_line)\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                (__, __, reason) = httputil.parse_response_start_line(header_line)\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type, debug_msg):\n        debug_types = ('I', '<', '>', '<', '>')\n        if debug_type == 0:\n            curl_log.debug('%s', debug_msg.strip())\n        elif debug_type in (1, 2):\n            for line in debug_msg.splitlines():\n                curl_log.debug('%s %s', debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)\n\n\nclass CurlError(HTTPError):\n    def __init__(self, errno, message):\n        HTTPError.__init__(self, 599, message)\n        self.errno = errno\n\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(CurlAsyncHTTPClient)\n    main()\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
**Analysis:**

- **Key Parts of Commit Details and Test Results:**
  - **Commit Patch:** Adds `self._free_list.append(curl)` in `CurlAsyncHTTPClient` when setup fails.
  - **Test Results:** Previously, `test_failed_setup` failed with an `AssertionError: Async operation timed out after 5 seconds`. After the commit, this test passes.

- **Main Problem Highlighted by Test Results:**
  - When the setup of `CurlAsyncHTTPClient` fails, the free list for CURL handles is not replenished, leading to resource starvation. This causes subsequent asynchronous operations to hang and eventually timeout.

- **Expected Behavior:**
  - If the setup fails, the HTTP client should properly release any allocated resources back to the free list to prevent starvation, allowing future operations to proceed without hanging.

- **Actual Behavior:**
  - On setup failure, the HTTP client does not release resources, causing subsequent operations to hang and timeout after waiting for available resources.

- **Issue Description:**
  - The issue should clearly describe that upon a failed setup, the HTTP client fails to release resources, leading to operation timeouts. Including a brief example of how the client is initialized and used will help developers understand the context.

---

[ISSUE]

**Title:** HTTP Client Resource Starvation Causes Operation Timeouts on Setup Failure

**Description:**

When initializing the `CurlAsyncHTTPClient` with a limited number of clients, if the setup process fails, the client does not release resources back to its free list. This leads to subsequent operations hanging and eventually timing out after waiting for available resources.

**Example Code:**

```python
def initialize_client_with_failing_setup():
    client = CurlAsyncHTTPClient(io_loop, max_clients=1)
    # Simulate setup failure by making invalid requests
    for _ in range(5):
        client.fetch('/invalid_endpoint')
```

**Expected Behavior:**

Even if some setup operations fail, the HTTP client should release resources, allowing all fetch operations to complete or fail gracefully without hanging.

**Actual Behavior:**

After initial setup failures, fetch operations hang and eventually timeout, indicating that resources are not being released properly.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### tornado/curl_httpclient.py
#!/usr/bin/env python
#
# Copyright 2009 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Non-blocking HTTP client implementation using pycurl."""

from __future__ import absolute_import, division, print_function, with_statement

import collections
import functools
import logging
import pycurl
import threading
import time
from io import BytesIO

from tornado import httputil
from tornado import ioloop
from tornado import stack_context

from tornado.escape import utf8, native_str
from tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main

curl_log = logging.getLogger('tornado.curl_httpclient')


class CurlAsyncHTTPClient(AsyncHTTPClient):
    def initialize(self, io_loop, max_clients=10, defaults=None):
        super(CurlAsyncHTTPClient, self).initialize(io_loop, defaults=defaults)
        self._multi = pycurl.CurlMulti()
        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)
        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)
        self._curls = [self._curl_create() for i in range(max_clients)]
        self._free_list = self._curls[:]
        self._requests = collections.deque()
        self._fds = {}
        self._timeout = None

        # libcurl has bugs that sometimes cause it to not report all
        # relevant file descriptors and timeouts to TIMERFUNCTION/
        # SOCKETFUNCTION.  Mitigate the effects of such bugs by
        # forcing a periodic scan of all active requests.
        self._force_timeout_callback = ioloop.PeriodicCallback(
            self._handle_force_timeout, 1000, io_loop=io_loop)
        self._force_timeout_callback.start()

        # Work around a bug in libcurl 7.29.0: Some fields in the curl
        # multi object are initialized lazily, and its destructor will
        # segfault if it is destroyed without having been used.  Add
        # and remove a dummy handle to make sure everything is
        # initialized.
        dummy_curl_handle = pycurl.Curl()
        self._multi.add_handle(dummy_curl_handle)
        self._multi.remove_handle(dummy_curl_handle)

    def close(self):
        self._force_timeout_callback.stop()
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
        for curl in self._curls:
            curl.close()
        self._multi.close()
        super(CurlAsyncHTTPClient, self).close()

    def fetch_impl(self, request, callback):
        self._requests.append((request, callback))
        self._process_queue()
        self._set_timeout(0)

    def _handle_socket(self, event, fd, multi, data):
        """Called by libcurl when it wants to change the file descriptors
        it cares about.
        """
        event_map = {
            pycurl.POLL_NONE: ioloop.IOLoop.NONE,
            pycurl.POLL_IN: ioloop.IOLoop.READ,
            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,
            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE
        }
        if event == pycurl.POLL_REMOVE:
            if fd in self._fds:
                self.io_loop.remove_handler(fd)
                del self._fds[fd]
        else:
            ioloop_event = event_map[event]
            # libcurl sometimes closes a socket and then opens a new
            # one using the same FD without giving us a POLL_NONE in
            # between.  This is a problem with the epoll IOLoop,
            # because the kernel can tell when a socket is closed and
            # removes it from the epoll automatically, causing future
            # update_handler calls to fail.  Since we can't tell when
            # this has happened, always use remove and re-add
            # instead of update.
            if fd in self._fds:
                self.io_loop.remove_handler(fd)
            self.io_loop.add_handler(fd, self._handle_events,
                                     ioloop_event)
            self._fds[fd] = ioloop_event

    def _set_timeout(self, msecs):
        """Called by libcurl to schedule a timeout."""
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
        self._timeout = self.io_loop.add_timeout(
            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)

    def _handle_events(self, fd, events):
        """Called by IOLoop when there is activity on one of our
        file descriptors.
        """
        action = 0
        if events & ioloop.IOLoop.READ:
            action |= pycurl.CSELECT_IN
        if events & ioloop.IOLoop.WRITE:
            action |= pycurl.CSELECT_OUT
        while True:
            try:
                ret, num_handles = self._multi.socket_action(fd, action)
            except pycurl.error as e:
                ret = e.args[0]
            if ret != pycurl.E_CALL_MULTI_PERFORM:
                break
        self._finish_pending_requests()

    def _handle_timeout(self):
        """Called by IOLoop when the requested timeout has passed."""
        with stack_context.NullContext():
            self._timeout = None
            while True:
                try:
                    ret, num_handles = self._multi.socket_action(
                        pycurl.SOCKET_TIMEOUT, 0)
                except pycurl.error as e:
                    ret = e.args[0]
                if ret != pycurl.E_CALL_MULTI_PERFORM:
                    break
            self._finish_pending_requests()

        # In theory, we shouldn't have to do this because curl will
        # call _set_timeout whenever the timeout changes.  However,
        # sometimes after _handle_timeout we will need to reschedule
        # immediately even though nothing has changed from curl's
        # perspective.  This is because when socket_action is
        # called with SOCKET_TIMEOUT, libcurl decides internally which
        # timeouts need to be processed by using a monotonic clock
        # (where available) while tornado uses python's time.time()
        # to decide when timeouts have occurred.  When those clocks
        # disagree on elapsed time (as they will whenever there is an
        # NTP adjustment), tornado might call _handle_timeout before
        # libcurl is ready.  After each timeout, resync the scheduled
        # timeout with libcurl's current state.
        new_timeout = self._multi.timeout()
        if new_timeout >= 0:
            self._set_timeout(new_timeout)

    def _handle_force_timeout(self):
        """Called by IOLoop periodically to ask libcurl to process any
        events it may have forgotten about.
        """
        with stack_context.NullContext():
            while True:
                try:
                    ret, num_handles = self._multi.socket_all()
                except pycurl.error as e:
                    ret = e.args[0]
                if ret != pycurl.E_CALL_MULTI_PERFORM:
                    break
            self._finish_pending_requests()

    def _finish_pending_requests(self):
        """Process any requests that were completed by the last
        call to multi.socket_action.
        """
        while True:
            num_q, ok_list, err_list = self._multi.info_read()
            for curl in ok_list:
                self._finish(curl)
            for curl, errnum, errmsg in err_list:
                self._finish(curl, errnum, errmsg)
            if num_q == 0:
                break
        self._process_queue()

    def _process_queue(self):
        with stack_context.NullContext():
            while True:
                started = 0
                while self._free_list and self._requests:
                    started += 1
                    curl = self._free_list.pop()
                    (request, callback) = self._requests.popleft()
                    curl.info = {
                        "headers": httputil.HTTPHeaders(),
                        "buffer": BytesIO(),
                        "request": request,
                        "callback": callback,
                        "curl_start_time": time.time(),
                    }
                    try:
                        self._curl_setup_request(
                            curl, request, curl.info["buffer"],
                            curl.info["headers"])
                    except Exception as e:
                        # If there was an error in setup, pass it on
                        # to the callback. Note that allowing the
                        # error to escape here will appear to work
                        # most of the time since we are still in the
                        # caller's original stack frame, but when
                        # _process_queue() is called from
                        # _finish_pending_requests the exceptions have
                        # nowhere to go.
                        callback(HTTPResponse(
                            request=request,
                            code=599,
                            error=e))
                    else:
                        self._multi.add_handle(curl)

                if not started:
                    break

    def _finish(self, curl, curl_error=None, curl_message=None):
        info = curl.info
        curl.info = None
        self._multi.remove_handle(curl)
        self._free_list.append(curl)
        buffer = info["buffer"]
        if curl_error:
            error = CurlError(curl_error, curl_message)
            code = error.code
            effective_url = None
            buffer.close()
            buffer = None
        else:
            error = None
            code = curl.getinfo(pycurl.HTTP_CODE)
            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)
            buffer.seek(0)
        # the various curl timings are documented at
        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
        time_info = dict(
            queue=info["curl_start_time"] - info["request"].start_time,
            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),
            connect=curl.getinfo(pycurl.CONNECT_TIME),
            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),
            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),
            total=curl.getinfo(pycurl.TOTAL_TIME),
            redirect=curl.getinfo(pycurl.REDIRECT_TIME),
        )
        try:
            info["callback"](HTTPResponse(
                request=info["request"], code=code, headers=info["headers"],
                buffer=buffer, effective_url=effective_url, error=error,
                reason=info['headers'].get("X-Http-Reason", None),
                request_time=time.time() - info["curl_start_time"],
                time_info=time_info))
        except Exception:
            self.handle_callback_exception(info["callback"])

    def handle_callback_exception(self, callback):
        self.io_loop.handle_callback_exception(callback)

    def _curl_create(self):
        curl = pycurl.Curl()
        if curl_log.isEnabledFor(logging.DEBUG):
            curl.setopt(pycurl.VERBOSE, 1)
            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)
        return curl

    def _curl_setup_request(self, curl, request, buffer, headers):
        curl.setopt(pycurl.URL, native_str(request.url))

        # libcurl's magic "Expect: 100-continue" behavior causes delays
        # with servers that don't support it (which include, among others,
        # Google's OpenID endpoint).  Additionally, this behavior has
        # a bug in conjunction with the curl_multi_socket_action API
        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),
        # which increases the delays.  It's more trouble than it's worth,
        # so just turn off the feature (yes, setting Expect: to an empty
        # value is the official way to disable this)
        if "Expect" not in request.headers:
            request.headers["Expect"] = ""

        # libcurl adds Pragma: no-cache by default; disable that too
        if "Pragma" not in request.headers:
            request.headers["Pragma"] = ""

        curl.setopt(pycurl.HTTPHEADER,
                    ["%s: %s" % (native_str(k), native_str(v))
                     for k, v in request.headers.get_all()])

        curl.setopt(pycurl.HEADERFUNCTION,
                    functools.partial(self._curl_header_callback,
                                      headers, request.header_callback))
        if request.streaming_callback:
            def write_function(chunk):
                self.io_loop.add_callback(request.streaming_callback, chunk)
        else:
            write_function = buffer.write
        if bytes is str:  # py2
            curl.setopt(pycurl.WRITEFUNCTION, write_function)
        else:  # py3
            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes
            # a fork/port.  That version has a bug in which it passes unicode
            # strings instead of bytes to the WRITEFUNCTION.  This means that
            # if you use a WRITEFUNCTION (which tornado always does), you cannot
            # download arbitrary binary data.  This needs to be fixed in the
            # ported pycurl package, but in the meantime this lambda will
            # make it work for downloading (utf8) text.
            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))
        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)
        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)
        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))
        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))
        if request.user_agent:
            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))
        else:
            curl.setopt(pycurl.USERAGENT, "Mozilla/5.0 (compatible; pycurl)")
        if request.network_interface:
            curl.setopt(pycurl.INTERFACE, request.network_interface)
        if request.decompress_response:
            curl.setopt(pycurl.ENCODING, "gzip,deflate")
        else:
            curl.setopt(pycurl.ENCODING, "none")
        if request.proxy_host and request.proxy_port:
            curl.setopt(pycurl.PROXY, request.proxy_host)
            curl.setopt(pycurl.PROXYPORT, request.proxy_port)
            if request.proxy_username:
                credentials = '%s:%s' % (request.proxy_username,
                                         request.proxy_password)
                curl.setopt(pycurl.PROXYUSERPWD, credentials)
        else:
            curl.setopt(pycurl.PROXY, '')
            curl.unsetopt(pycurl.PROXYUSERPWD)
        if request.validate_cert:
            curl.setopt(pycurl.SSL_VERIFYPEER, 1)
            curl.setopt(pycurl.SSL_VERIFYHOST, 2)
        else:
            curl.setopt(pycurl.SSL_VERIFYPEER, 0)
            curl.setopt(pycurl.SSL_VERIFYHOST, 0)
        if request.ca_certs is not None:
            curl.setopt(pycurl.CAINFO, request.ca_certs)
        else:
            # There is no way to restore pycurl.CAINFO to its default value
            # (Using unsetopt makes it reject all certificates).
            # I don't see any way to read the default value from python so it
            # can be restored later.  We'll have to just leave CAINFO untouched
            # if no ca_certs file was specified, and require that if any
            # request uses a custom ca_certs file, they all must.
            pass

        if request.allow_ipv6 is False:
            # Curl behaves reasonably when DNS resolution gives an ipv6 address
            # that we can't reach, so allow ipv6 unless the user asks to disable.
            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)
        else:
            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)

        # Set the request method through curl's irritating interface which makes
        # up names for almost every single method
        curl_options = {
            "GET": pycurl.HTTPGET,
            "POST": pycurl.POST,
            "PUT": pycurl.UPLOAD,
            "HEAD": pycurl.NOBODY,
        }
        custom_methods = set(["DELETE", "OPTIONS", "PATCH"])
        for o in curl_options.values():
            curl.setopt(o, False)
        if request.method in curl_options:
            curl.unsetopt(pycurl.CUSTOMREQUEST)
            curl.setopt(curl_options[request.method], True)
        elif request.allow_nonstandard_methods or request.method in custom_methods:
            curl.setopt(pycurl.CUSTOMREQUEST, request.method)
        else:
            raise KeyError('unknown method ' + request.method)

        body_expected = request.method in ("POST", "PATCH", "PUT")
        body_present = request.body is not None
        if not request.allow_nonstandard_methods:
            # Some HTTP methods nearly always have bodies while others
            # almost never do. Fail in this case unless the user has
            # opted out of sanity checks with allow_nonstandard_methods.
            if ((body_expected and not body_present) or
                    (body_present and not body_expected)):
                raise ValueError(
                    'Body must %sbe None for method %s (unless '
                    'allow_nonstandard_methods is true)' %
                    ('not ' if body_expected else '', request.method))

        if body_expected or body_present:
            if request.method == "GET":
                # Even with `allow_nonstandard_methods` we disallow
                # GET with a body (because libcurl doesn't allow it
                # unless we use CUSTOMREQUEST). While the spec doesn't
                # forbid clients from sending a body, it arguably
                # disallows the server from doing anything with them.
                raise ValueError('Body must be None for GET request')
            request_buffer = BytesIO(utf8(request.body or ''))

            def ioctl(cmd):
                if cmd == curl.IOCMD_RESTARTREAD:
                    request_buffer.seek(0)
            curl.setopt(pycurl.READFUNCTION, request_buffer.read)
            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)
            if request.method == "POST":
                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))
            else:
                curl.setopt(pycurl.UPLOAD, True)
                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))

        if request.auth_username is not None:
            userpwd = "%s:%s" % (request.auth_username, request.auth_password or '')

            if request.auth_mode is None or request.auth_mode == "basic":
                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
            elif request.auth_mode == "digest":
                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)
            else:
                raise ValueError("Unsupported auth_mode %s" % request.auth_mode)

            curl.setopt(pycurl.USERPWD, native_str(userpwd))
            curl_log.debug("%s %s (username: %r)", request.method, request.url,
                           request.auth_username)
        else:
            curl.unsetopt(pycurl.USERPWD)
            curl_log.debug("%s %s", request.method, request.url)

        if request.client_cert is not None:
            curl.setopt(pycurl.SSLCERT, request.client_cert)

        if request.client_key is not None:
            curl.setopt(pycurl.SSLKEY, request.client_key)

        if request.ssl_options is not None:
            raise ValueError("ssl_options not supported in curl_httpclient")

        if threading.activeCount() > 1:
            # libcurl/pycurl is not thread-safe by default.  When multiple threads
            # are used, signals should be disabled.  This has the side effect
            # of disabling DNS timeouts in some environments (when libcurl is
            # not linked against ares), so we don't do it when there is only one
            # thread.  Applications that use many short-lived threads may need
            # to set NOSIGNAL manually in a prepare_curl_callback since
            # there may not be any other threads running at the time we call
            # threading.activeCount.
            curl.setopt(pycurl.NOSIGNAL, 1)
        if request.prepare_curl_callback is not None:
            request.prepare_curl_callback(curl)

    def _curl_header_callback(self, headers, header_callback, header_line):
        header_line = native_str(header_line)
        if header_callback is not None:
            self.io_loop.add_callback(header_callback, header_line)
        # header_line as returned by curl includes the end-of-line characters.
        # whitespace at the start should be preserved to allow multi-line headers
        header_line = header_line.rstrip()
        if header_line.startswith("HTTP/"):
            headers.clear()
            try:
                (__, __, reason) = httputil.parse_response_start_line(header_line)
                header_line = "X-Http-Reason: %s" % reason
            except httputil.HTTPInputError:
                return
        if not header_line:
            return
        headers.parse_line(header_line)

    def _curl_debug(self, debug_type, debug_msg):
        debug_types = ('I', '<', '>', '<', '>')
        if debug_type == 0:
            curl_log.debug('%s', debug_msg.strip())
        elif debug_type in (1, 2):
            for line in debug_msg.splitlines():
                curl_log.debug('%s %s', debug_types[debug_type], line)
        elif debug_type == 4:
            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)


class CurlError(HTTPError):
    def __init__(self, errno, message):
        HTTPError.__init__(self, 599, message)
        self.errno = errno


if __name__ == "__main__":
    AsyncHTTPClient.configure(CurlAsyncHTTPClient)
    main()

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user