Commits

Gustavo Picon committed fe50f29

Porting fixes and updates from CherryPy.

  • Participants
  • Parent commits f15ce6e

Comments (0)

Files changed (3)

File cheroot/py2makefile.py

 
     if not _fileobject_uses_str_type:
         def read(self, size=-1):
-            # Use max, disallow tiny reads in a loop as they are very inefficient.
-            # We never leave read() with any leftover data from a new recv() call
-            # in our internal buffer.
+            # Use max, disallow tiny reads in a loop as they are very
+            # inefficient.
+            # We never leave read() with any leftover data from a new recv()
+            # call in our internal buffer.
             rbufsize = max(self._rbufsize, self.default_bufsize)
-            # Our use of StringIO rather than lists of string objects returned by
-            # recv() minimizes memory usage and fragmentation that occurs when
-            # rbufsize is large compared to the typical return value of recv().
+            # Our use of StringIO rather than lists of string objects returned
+            # by recv() minimizes memory usage and fragmentation that occurs
+            # when rbufsize is large compared to the typical return value of
+            # recv().
             buf = self._rbuf
             buf.seek(0, 2)  # seek end
             if size < 0:
                 # Read until EOF
-                self._rbuf = StringIO()  # reset _rbuf.  we consume it via buf.
+                # reset _rbuf.  we consume it via buf.
+                self._rbuf = StringIO()
                 while True:
                     data = self.recv(rbufsize)
                     if not data:
                     self._rbuf.write(buf.read())
                     return rv
 
-                self._rbuf = StringIO()  # reset _rbuf.  we consume it via buf.
+                # reset _rbuf.  we consume it via buf.
+                self._rbuf = StringIO()
                 while True:
                     left = size - buf_len
                     # recv() will malloc the amount of memory given as its
                     return "".join(buffers)
 
                 buf.seek(0, 2)  # seek end
-                self._rbuf = StringIO()  # reset _rbuf.  we consume it via buf.
+                # reset _rbuf.  we consume it via buf.
+                self._rbuf = StringIO()
                 while True:
                     data = self.recv(self._rbufsize)
                     if not data:
                     self._rbuf = StringIO()
                     self._rbuf.write(buf.read())
                     return rv
-                self._rbuf = StringIO()  # reset _rbuf.  we consume it via buf.
+                # reset _rbuf.  we consume it via buf.
+                self._rbuf = StringIO()
                 while True:
                     data = self.recv(self._rbufsize)
                     if not data:
                             buf.write(data[:nl])
                             break
                         else:
-                            # Shortcut.  Avoid data copy through buf when returning
-                            # a substring of our first recv().
+                            # Shortcut.  Avoid data copy through buf when
+                            # returning a substring of our first recv().
                             return data[:nl]
                     n = len(data)
                     if n == size and not buf_len:

File cheroot/server.py

 from cheroot._compat import BaseHTTPRequestHandler
 response_codes = BaseHTTPRequestHandler.responses.copy()
 
-# From http://www.cherrypy.org/ticket/361
+# From http://www.bitbucket.org/cherrypy/cherrypy/issue/361
 response_codes[500] = ('Internal Server Error',
                        'The server encountered an unexpected condition '
                        'which prevented it from fulfilling the request.')
 import re
 import socket
 import sys
-if 'win' in sys.platform and not hasattr(socket, 'IPPROTO_IPV6'):
-    socket.IPPROTO_IPV6 = 41
+if 'win' in sys.platform and hasattr(socket, "AF_INET6"):
+    if not hasattr(socket, 'IPPROTO_IPV6'):
+        socket.IPPROTO_IPV6 = 41
+    if not hasattr(socket, 'IPV6_V6ONLY'):
+        socket.IPV6_V6ONLY = 27
 
 if py3k:
     if sys.version_info < (3, 1):
             self.bytes_read += len(data)
             self._check_length()
             res.append(data)
-            # See http://www.cherrypy.org/ticket/421
+            # See http://www.bitbucket.org/cherrypy/cherrypy/issue/421
             if len(data) < 256 or data[-1:] == LF:
                 return EMPTY.join(res)
 
         # but it seems like it would be a big slowdown for such a rare case.
         if self.inheaders.get(ntob("Expect"), EMPTY) == ntob("100-continue"):
             # Don't use simple_response here, because it emits headers
-            # we don't want. See http://www.cherrypy.org/ticket/951
+            # we don't want. See http://www.bitbucket.org/cherrypy/cherrypy/issue/951
             msg = ntob(self.server.protocol, 'ascii') + \
                 ntob(" 100 Continue\r\n\r\n")
             try:
                 # Don't error if we're between requests; only error
                 # if 1) no request has been started at all, or 2) we're
                 # in the middle of a request.
-                # See http://www.cherrypy.org/ticket/853
+                # See http://www.bitbucket.org/cherrypy/cherrypy/issue/853
                 if (not request_seen) or (req and req.started_request):
                     # Don't bother writing the 408 if the response
                     # has already started being written.
 except ImportError:
     try:
         from ctypes import windll, WinError
+        import ctypes.wintypes
+        _SetHandleInformation = windll.kernel32.SetHandleInformation
+        _SetHandleInformation.argtypes = [
+            ctypes.wintypes.HANDLE,
+            ctypes.wintypes.DWORD,
+            ctypes.wintypes.DWORD,
+        ]
+        _SetHandleInformation.restype = ctypes.wintypes.BOOL
     except ImportError:
         def prevent_socket_inheritance(sock):
             """Dummy function, since neither fcntl nor ctypes are available."""
     else:
         def prevent_socket_inheritance(sock):
             """Mark the given socket fd as non-inheritable (Windows)."""
-            if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
+            if not _SetHandleInformation(sock.fileno(), 1, 0):
                 raise WinError()
 else:
     def prevent_socket_inheritance(sock):
             af, socktype, proto, canonname, sa = res
             try:
                 self.bind(af, socktype, proto)
-            except socket.error:
+            except socket.error as serr:
+                msg = "%s -- (%s: %s)" % (msg, sa, serr)
                 if self.socket:
                     self.socket.close()
                 self.socket = None
             self.socket = self.ssl_adapter.bind(self.socket)
 
         # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
-        # activate dual-stack. See http://www.cherrypy.org/ticket/871.
+        # activate dual-stack. See http://www.bitbucket.org/cherrypy/cherrypy/issue/871.
         if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6
                 and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
             try:
                 # is received during the accept() call; all docs say retry
                 # the call, and I *think* I'm reading it right that Python
                 # will then go ahead and poll for and handle the signal
-                # elsewhere. See http://www.cherrypy.org/ticket/707.
+                # elsewhere. See http://www.bitbucket.org/cherrypy/cherrypy/issue/707.
                 return
             if x.args[0] in errors.socket_errors_nonblocking:
-                # Just try again. See http://www.cherrypy.org/ticket/479.
+                # Just try again. See http://www.bitbucket.org/cherrypy/cherrypy/issue/479.
                 return
             if x.args[0] in errors.socket_errors_to_ignore:
                 # Our socket was closed.
-                # See http://www.cherrypy.org/ticket/686.
+                # See http://www.bitbucket.org/cherrypy/cherrypy/issue/686.
                 return
             raise
 
                     x = sys.exc_info()[1]
                     if x.args[0] not in errors.socket_errors_to_ignore:
                         # Changed to use error code and not message
-                        # See http://www.cherrypy.org/ticket/860.
+                        # See http://www.bitbucket.org/cherrypy/cherrypy/issue/860.
                         raise
                 else:
                     # Note that we're explicitly NOT using AI_PASSIVE,

File cheroot/workers/threadpool.py

 
 __all__ = ['WorkerThread', 'ThreadPool']
 
+import functools
+import operator
 try:
     import queue
 except ImportError:
 
     def grow(self, amount):
         """Spawn new worker threads (not above self.max)."""
-        for i in range(amount):
-            if self.max > 0 and len(self._threads) >= self.max:
-                break
-            worker = WorkerThread(self.server)
-            worker.setName("CP Server " + worker.getName())
-            self._threads.append(worker)
-            worker.start()
+        if self.max > 0:
+            budget = max(self.max - len(self._threads), 0)
+        else:
+            # self.max <= 0 indicates no maximum
+            budget = float('inf')
+
+        n_new = min(amount, budget)
+
+        workers = [self._spawn_worker() for i in range(n_new)]
+        while not self._all(operator.attrgetter('ready'), workers):
+            time.sleep(.1)
+        self._threads.extend(workers)
+
+    def _spawn_worker(self):
+        worker = WorkerThread(self.server)
+        worker.setName("CP Server " + worker.getName())
+        worker.start()
+        return worker
+
+    def _all(func, items):
+        results = [func(item) for item in items]
+        return functools.reduce(operator.and_, results, True)
+    _all = staticmethod(_all)
 
     def shrink(self, amount):
         """Kill off worker threads (not below self.min)."""
                 self._threads.remove(t)
                 amount -= 1
 
-        if amount > 0:
-            for i in range(min(amount, len(self._threads) - self.min)):
-                # Put a number of shutdown requests on the queue equal
-                # to 'amount'. Once each of those is processed by a worker,
-                # that worker will terminate and be culled from our list
-                # in self.put.
-                self._queue.put(_SHUTDOWNREQUEST)
+        # calculate the number of threads above the minimum
+        n_extra = max(len(self._threads) - self.min, 0)
+
+        # don't remove more than amount
+        n_to_remove = min(amount, n_extra)
+
+        # put shutdown requests on the queue equal to the number of threads
+        # to remove. As each request is processed by a worker, that worker
+        # will terminate and be culled from the list.
+        for n in range(n_to_remove):
+            self._queue.put(_SHUTDOWNREQUEST)
 
     def stop(self, timeout=5):
         # Must shut down threads here so the code that calls
                             worker.join()
                 except (AssertionError,
                         # Ignore repeated Ctrl-C.
-                        # See http://www.cherrypy.org/ticket/691.
+                        # See http://www.bitbucket.org/cherrypy/cherrypy/issue/691.
                         KeyboardInterrupt):
                     pass