diff options
author | Helmut Grohne <helmut@subdivi.de> | 2013-06-06 10:31:29 +0200 |
---|---|---|
committer | Helmut Grohne <helmut@subdivi.de> | 2013-06-06 10:31:29 +0200 |
commit | 3651d77be1d44b7fb02ab70fb2685bb401b1105b (patch) | |
tree | 63dfef261cf8a90a831fff82625501f00ab82517 /wsgitools/scgi/forkpool.py | |
parent | b0938bb51c915ea5d888e2e88bbb62f4d1da199c (diff) | |
parent | ed6d6c8f06404489ba2301955c8e6f82f8f4f454 (diff) | |
download | wsgitools-3651d77be1d44b7fb02ab70fb2685bb401b1105b.tar.gz |
Merge tag 'wsgitools-0.2.4' into py3k
The intent is to port the changes from 0.2.4 to py3k.
Conflicts:
README
test.py
wsgitools/scgi/forkpool.py
All conflicts were resolved in a minimal way. The test suite now fails
for all python versions.
Diffstat (limited to 'wsgitools/scgi/forkpool.py')
-rw-r--r-- | wsgitools/scgi/forkpool.py | 55 |
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: |