Gmail Calendar Documents Reader Web more »
Recently Visited Groups | Help | Sign in
Google Groups Home
Forking server example
There are currently too many topics in this group that display first. To make this topic appear first, remove this option from another topic.
There was an error processing your request. Please try again.
flag
  5 messages - Collapse all  -  Translate all to Translated (View all originals)
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Followup To:
Add Cc | Add Followup-to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers you hear
 
Robert Brewer  
View profile  
 More options Oct 11, 7:10 pm
From: "Robert Brewer" <fuman...@aminus.org>
Date: Sun, 11 Oct 2009 16:10:48 -0700
Local: Sun, Oct 11 2009 7:10 pm
Subject: Forking server example
I know Eric at least has been playing with a forking server. Here's a
toy one, made entirely by modifying wsgiserver/__init__.py from trunk.
It is by no means complete (e.g. needs full signal support, plus lots of
work on separating shared objects), nor even correct (e.g. it should
fork() before starting any threads, but cherrypy.process.servers'
ServerAdapter spawns a new thread), but here it is anyway to inspire
someone. With the normal ThreadPool (10 threads), I get about 1200
req/sec on our first benchmark. With the WorkerMPM class (10 processes,
10 threads each) I get about 1800. With the PreforkMPM class (10
processes), I get around 2000. But Ctrl-C freezes my whole laptop for a
while in that case...

Index: wsgiserver/__init__.py
===================================================================
--- wsgiserver/__init__.py      (revision 2550)
+++ wsgiserver/__init__.py      (working copy)
@@ -74,6 +74,7 @@
 quoted_slash = re.compile("(?i)%2F")
 import rfc822
 import socket
+import signal
 import sys
 if 'win' in sys.platform and not hasattr(socket, 'IPPROTO_IPV6'):
     socket.IPPROTO_IPV6 = 41
@@ -1360,8 +1361,6 @@

     def put(self, obj):
         self._queue.put(obj)
-        if obj is _SHUTDOWNREQUEST:
-            return

     def grow(self, amount):
         """Spawn new worker threads (not above self.max)."""
@@ -1426,9 +1425,281 @@
                         # See http://www.cherrypy.org/ticket/691.
                         KeyboardInterrupt), exc1:
                     pass
+    
+    def run(self):
+        """Continuously accept incoming connections."""
+        while self.server.ready:
+            self.tick()
+            if self.server.interrupt:
+                while self.server.interrupt is True:
+                    # Wait for self.stop() to complete. See
_set_interrupt.
+                    time.sleep(0.1)
+                if self.server.interrupt:
+                    raise self.server.interrupt
+    
+    def tick(self):
+        """Accept a new connection and put it on the Queue."""
+        try:
+            s, addr = self.server.socket.accept()
+            if not self.server.ready:
+                return
+            
+            prevent_socket_inheritance(s)
+            if hasattr(s, 'settimeout'):
+                s.settimeout(self.server.timeout)
+            
+            makefile = CP_fileobject
+            ssl_env = {}
+            # if ssl cert and key are set, we try to be a secure HTTP
server
+            if self.server.ssl_adapter is not None:
+                try:
+                    s, ssl_env = self.server.ssl_adapter.wrap(s)
+                except NoSSLError:
+                    msg = ("The client sent a plain HTTP request, but "
+                           "this server only speaks HTTPS on this
port.")
+                    buf = ["%s 400 Bad Request\r\n" %
self.server.protocol,
+                           "Content-Length: %s\r\n" % len(msg),
+                           "Content-Type: text/plain\r\n\r\n",
+                           msg]
+                    
+                    wfile = CP_fileobject(s, "wb", -1)
+                    try:
+                        wfile.sendall("".join(buf))
+                    except socket.error, x:
+                        if x.args[0] not in socket_errors_to_ignore:
+                            raise
+                    return
+                if not s:
+                    return
+                makefile = self.server.ssl_adapter.makefile
+            
+            conn = self.server.ConnectionClass(self.server, s,
makefile)
+            
+            if not isinstance(self.server.bind_addr, basestring):
+                # optional values
+                # Until we do DNS lookups, omit REMOTE_HOST
+                if addr is None: # sometimes this can happen
+                    # figure out if AF_INET or AF_INET6.
+                    if len(s.getsockname()) == 2:
+                        # AF_INET
+                        addr = ('0.0.0.0', 0)
+                    else:
+                        # AF_INET6
+                        addr = ('::', 0)
+                conn.remote_addr = addr[0]
+                conn.remote_port = addr[1]
+            
+            conn.ssl_env = ssl_env
+            
+            self.put(conn)
+        except socket.timeout:
+            # The only reason for the timeout in start() is so we can
+            # notice keyboard interrupts on Win32, which don't
interrupt
+            # accept() by default
+            return
+        except socket.error, x:
+            if x.args[0] in socket_error_eintr:
+                # I *think* this is right. EINTR should occur when a
signal
+                # 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.
+                return
+            if x.args[0] in socket_errors_nonblocking:
+                # Just try again. See
http://www.cherrypy.org/ticket/479.
+                return
+            if x.args[0] in socket_errors_to_ignore:
+                # Our socket was closed.
+                # See http://www.cherrypy.org/ticket/686.
+                return
+            raise

+class PreforkMPM(object):
+    
+    pid = None
+    min_children = 10
+    
+    def __init__(self, server, min=10, max=-1):
+        self.server = server
+        self.min = min
+        self.max = max
+    
+    def start(self):
+        pass
+    
+    def run(self):
+        """Continuously accept incoming connections."""
+        self.childpids = []
+        for i in range(self.min_children):
+            pid = os.fork()
+            if pid:
+                # Parent process
+                self.childpids.append(pid)
+            else:
+                self.pid = os.getpid()
+                print "starting", self.pid
+                signal.signal(signal.SIGTERM, self._handle_signal)
+                signal.signal(signal.SIGINT, signal.SIG_IGN)
+                signal.signal(signal.SIGHUP, signal.SIG_IGN)
+                self.run_child()
+                print "stopped", self.pid, self.server.ready
+    
+    def stop(self, timeout=5):
+        if self.pid:
+            os._exit(0)
+        else:
+            # Parent process
+            while self.childpids:
+                pid = self.childpids.pop()
+                os.kill(pid, signal.SIGTERM)
+                os.waitpid(pid, 0)
+    
+    def _handle_signal(self, signum=None, frame=None):
+        if signum == signal.SIGTERM:
+            self.stop()
+    
+    def run_child(self):
+        """Continuously accept incoming connections."""
+        while self.server.ready:
+            self.tick()
+            if self.server.interrupt:
+                while self.server.interrupt is True:
+                    # Wait for self.stop() to complete. See
_set_interrupt.
+                    time.sleep(0.1)
+                if self.server.interrupt:
+                    raise self.server.interrupt
+    
+    def tick(self):
+        """Accept a new connection and put it on the Queue."""
+        try:
+            s, addr = self.server.socket.accept()
+            if not self.server.ready:
+                return
+            
+            prevent_socket_inheritance(s)
+            if hasattr(s, 'settimeout'):
+                s.settimeout(self.server.timeout)
+            
+            makefile = CP_fileobject
+            ssl_env = {}
+            # if ssl cert and key are set, we try to be a secure HTTP
server
+            if self.server.ssl_adapter is not None:
+                try:
+                    s, ssl_env = self.server.ssl_adapter.wrap(s)
+                except NoSSLError:
+                    msg = ("The client sent a plain HTTP request, but "
+                           "this server only speaks HTTPS on this
port.")
+                    buf = ["%s 400 Bad Request\r\n" %
self.server.protocol,
+                           "Content-Length: %s\r\n" % len(msg),
+                           "Content-Type: text/plain\r\n\r\n",
+                           msg]
+                    
+                    wfile = CP_fileobject(s, "wb", -1)
+                    try:
+                        wfile.sendall("".join(buf))
+                    except socket.error, x:
+                        if x.args[0] not in socket_errors_to_ignore:
+                            raise
+                    return
+                if not s:
+                    return
+                makefile = self.server.ssl_adapter.makefile
+            
+            conn = self.server.ConnectionClass(self.server, s,
makefile)
+            
+            if not isinstance(self.server.bind_addr, basestring):
+                # optional values
+                # Until we do DNS lookups, omit REMOTE_HOST
+                if addr is None: # sometimes this can happen
+                    # figure out if AF_INET or AF_INET6.
+                    if len(s.getsockname()) == 2:
+                        # AF_INET
+                        addr = ('0.0.0.0', 0)
+                    else:
+                        # AF_INET6
+                        addr = ('::', 0)
+                conn.remote_addr = addr[0]
+                conn.remote_port = addr[1]
+            
+            conn.ssl_env = ssl_env
+            
+            try:
+                conn.communicate()
+            finally:
+                conn.close()
+        except socket.timeout:
+            # The only reason for the timeout in start() is so we can
+            # notice keyboard interrupts on Win32, which don't
interrupt
+            # accept() by default
+            return
+        except socket.error, x:
+            if x.args[0] in socket_error_eintr:
+                # I *think* this is right. EINTR should occur when a
signal
+                # is received
...

read more »


    Reply    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Eric Larson  
View profile  
 More options Oct 12, 12:58 am
From: Eric Larson <e...@ionrock.org>
Date: Sun, 11 Oct 2009 23:58:23 -0500
Local: Mon, Oct 12 2009 12:58 am
Subject: Re: [cherrypy-users] Forking server example
At Sun, 11 Oct 2009 16:10:48 -0700,

...

read more »


    Reply    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Sylvain Hellegouarch  
View profile  
 More options Oct 12, 6:37 am
From: Sylvain Hellegouarch <s...@defuze.org>
Date: Mon, 12 Oct 2009 12:37:17 +0200
Local: Mon, Oct 12 2009 6:37 am
Subject: Re: [cherrypy-users] Re: Forking server example

Note that I've found that the cherrypy.log calls were expensive even
when log.screen was set to False. At some point the server itself isn't
the bottleneck any longer, the engine is (calling hooks for instance).

- Sylvain


    Reply    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Robert Brewer  
View profile  
 More options Oct 12, 10:47 am
From: "Robert Brewer" <fuman...@aminus.org>
Date: Mon, 12 Oct 2009 07:47:49 -0700
Local: Mon, Oct 12 2009 10:47 am
Subject: RE: [cherrypy-users] Re: Forking server example

Eric Larson wrote:
> Robert Brewer wrote:
> > I know Eric at least has been playing with a forking server. Here's a
> > toy one, made entirely by modifying wsgiserver/__init__.py from
> > trunk... With the normal ThreadPool (10 threads), I get about 1200
> > req/sec on our first benchmark. With the WorkerMPM class (10
> > processes, 10 threads each) I get about 1800. With the PreforkMPM
> > class (10 processes), I get around 2000...

> Can you share what you're benchmark looks like?

Not my benchmark, *our* benchmark; that is, the 3 benchmark runs in cherrypy/test/benchmark.py :)

Robert Brewer
fuman...@aminus.org


    Reply    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Eric Larson  
View profile  
 More options Oct 12, 10:53 am
From: Eric Larson <e...@ionrock.org>
Date: Mon, 12 Oct 2009 09:53:43 -0500
Local: Mon, Oct 12 2009 10:53 am
Subject: Re: [cherrypy-users] Re: Forking server example
At Mon, 12 Oct 2009 07:47:49 -0700,

Ah hah! Thanks!

Eric Larson


    Reply    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
End of messages
« Back to Discussions « Newer topic     Older topic »

Create a group - Google Groups - Google Home - Terms of Service - Privacy Policy
©2009 Google