From 576bd830cf77dae71b2cd10f0241667fe48930a6 Mon Sep 17 00:00:00 2001 From: Helmut Grohne Date: Thu, 1 Nov 2012 11:35:49 +0100 Subject: scgi.forkpool: implement RLIMIT_CPU The limit is only set on workers does not apply to the master. Upon reaching the soft limit the worker terminates after finished the current request. --- wsgitools/scgi/forkpool.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/wsgitools/scgi/forkpool.py b/wsgitools/scgi/forkpool.py index 52240f2..f27cd25 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 @@ -178,7 +182,7 @@ 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): """ @param wsgiapp: is the WSGI application to be run. @type port: int @@ -203,6 +207,12 @@ 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. """ assert hasattr(error, "write") self.wsgiapp = wsgiapp @@ -213,6 +223,10 @@ class SCGIServer: 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.server = None # becomes a socket # maps filedescriptors to WorkerStates self.workers = {} @@ -302,6 +316,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 @@ -317,6 +340,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: @@ -343,6 +371,8 @@ class SCGIServer: worksock.sendall('1') # tell server we're working self.process(con) worksock.sendall('0') # tell server we've finished + if self.cpulimit: + break def process(self, con): """ -- cgit v1.2.3