summaryrefslogtreecommitdiff
path: root/wsgitools/scgi/forkpool.py
diff options
context:
space:
mode:
Diffstat (limited to 'wsgitools/scgi/forkpool.py')
-rw-r--r--wsgitools/scgi/forkpool.py55
1 files changed, 46 insertions, 9 deletions
diff --git a/wsgitools/scgi/forkpool.py b/wsgitools/scgi/forkpool.py
index 88f64be..150ed44 100644
--- a/wsgitools/scgi/forkpool.py
+++ b/wsgitools/scgi/forkpool.py
@@ -5,6 +5,10 @@ It works with multiple processes that are periodically cleaned up to prevent
memory leaks having an impact to the system.
"""
+try:
+ import resource
+except ImportError:
+ resource = None
import socket
import os
import select
@@ -181,14 +185,14 @@ class SCGIServer:
def __init__(self, wsgiapp, port, interface="localhost", error=sys.stderr,
minworkers=2, maxworkers=32, maxrequests=1000, config={},
- reusesocket=None):
+ reusesocket=None, cpulimit=None, timelimit=None):
"""
@param wsgiapp: is the WSGI application to be run.
@type port: int
@param port: is the tcp port to listen on
@type interface: str
@param interface: is the interface to bind to (default: C{"localhost"})
- @param error: is a file-like object beeing passed as C{wsgi.error} in
+ @param error: is a file-like object beeing passed as C{wsgi.errors} in
environ
@type minworkers: int
@param minworkers: is the number of worker processes to spawn
@@ -206,17 +210,31 @@ class SCGIServer:
Instead use given socket as listen socket. The passed socket
must be set up for accepting tcp connections (i.e. C{AF_INET},
C{SOCK_STREAM} with bind and listen called).
+ @type cpulimit: (int, int)
+ @param cpulimit: a pair of soft and hard cpu time limit in seconds.
+ This limit is installed for each worker using RLIMIT_CPU if
+ resource limits are available to this platform. After reaching
+ the soft limit workers will continue to process the current
+ request and then cleanly terminate.
+ @type timelimit: int
+ @param timelimit: The maximum number of wall clock seconds processing
+ a request should take. If this is specified, an alarm timer is
+ installed and the default action is to kill the worker.
"""
assert hasattr(error, "write")
self.wsgiapp = wsgiapp
- self.port = port
- self.interface = interface
+ self.bind_address = (interface, port)
self.minworkers = minworkers
self.maxworkers = maxworkers
self.maxrequests = maxrequests
- self.config = config
- self.error = error
+ self.config = config.copy()
+ self.config["wsgi.errors"] = error
self.reusesocket = reusesocket
+ # cpulimit changes meaning:
+ # master: None or a tuple denoting the limit to be configured.
+ # worker: boolean denoting whether the limit is reached.
+ self.cpulimit = cpulimit
+ self.timelimit = timelimit
self.server = None # becomes a socket
# maps filedescriptors to WorkerStates
self.workers = {}
@@ -240,7 +258,7 @@ class SCGIServer:
if self.reusesocket is None:
self.server = socket.socket()
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self.server.bind((self.interface, self.port))
+ self.server.bind(self.bind_address)
self.server.listen(5)
else:
self.server = self.reusesocket
@@ -306,6 +324,15 @@ class SCGIServer:
else:
self.running = False
+ def sigxcpuhandler(self, sig=None, stackframe=None):
+ """
+ Signal hanlder function for the SIGXCUP signal. It is sent to a
+ worker when the soft RLIMIT_CPU is crossed.
+ @param sig: ignored for usage with signal.signal
+ @param stackframe: ignored for usage with signal.signal
+ """
+ self.cpulimit = True
+
def spawnworker(self):
"""
internal! spawns a single worker
@@ -321,6 +348,11 @@ class SCGIServer:
worker.sock.close()
del self.workers
+ if self.cpulimit and resource:
+ signal.signal(signal.SIGXCPU, self.sigxcpuhandler)
+ resource.setrlimit(resource.RLIMIT_CPU, self.cpulimit)
+ self.cpulimit = False
+
try:
self.work(worksock)
except socket.error:
@@ -345,8 +377,14 @@ class SCGIServer:
(con, addr) = self.server.accept()
# we cannot handle socket.errors here.
worksock.sendall(b'1') # tell server we're working
+ if self.timelimit:
+ signal.alarm(self.timelimit)
self.process(con)
+ if self.timelimit:
+ signal.alarm(0)
worksock.sendall(b'0') # tell server we've finished
+ if self.cpulimit:
+ break
def process(self, con):
"""
@@ -440,7 +478,6 @@ class SCGIServer:
_convert_environ(environ, multiprocess=True)
sfw = SocketFileWrapper(con, int(environ["CONTENT_LENGTH"]))
environ["wsgi.input"] = sfw
- environ["wsgi.errors"] = self.error
result = self.wsgiapp(environ, start_response)
assert hasattr(result, "__iter__")
@@ -451,9 +488,9 @@ class SCGIServer:
while sent > 0:
sent = result.transfer(con)
else:
- assert response_head[0] is not None
result_iter = iter(result)
for data in result_iter:
+ assert response_head[0] is not None
assert isinstance(data, bytes)
dumbsend(data)
if response_head[0] != True: