Tornado “error: Demasiados archivos abiertos” error

He trabajado bastante con Tornado, pero esta es la primera vez que me encuentro con este tipo de error. He estado trabajando en un acortador de URL muy básico. Las URL se colocan en la base de datos mediante una aplicación diferente, esta solo lee las URL de una tienda MongoDB y redirige a los clientes. Después de haber escrito el código básico, configuré una simple prueba de ‘Asedio’ contra él, después de unos 30 segundos de ejecutar el asedio (ejecutar con siege -c 64 -t 5m -r 1 http://example.com/MKy contra 4 hilos de aplicación) Empecé a recibir 500 respuestas. Mirando en el registro de errores vi esto;

 ERROR:root:500 GET /MKy (127.0.0.1) 2.05ms ERROR:root:Exception in I/O handler for fd 4 Traceback (most recent call last): File "/opt/python2.7/lib/python2.7/site-packages/tornado-2.1-py2.7.egg/tornado/ioloop.py", line 309, in start File "/opt/python2.7/lib/python2.7/site-packages/tornado-2.1-py2.7.egg/tornado/netutil.py", line 314, in accept_handler File "/opt/python2.7/lib/python2.7/socket.py", line 200, in accept error: [Errno 24] Too many open files ERROR:root:Uncaught exception GET /MKy (127.0.0.1) HTTPRequest(protocol='http', host='shortener', method='GET', uri='/MKy', version='HTTP/1.0', remote_ip='127.0.0.1', body='', headers={'Host': 'shortener', 'Accept-Encoding': 'gzip', 'X-Real-Ip': '94.23.155.32', 'X-Forwarded-For': '94.23.155.32', 'Connection': 'close', 'Accept': '*/*', 'User-Agent': 'JoeDog/1.00 [en] (X11; I; Siege 2.66)'}) Traceback (most recent call last): File "/opt/python2.7/lib/python2.7/site-packages/tornado-2.1-py2.7.egg/tornado/web.py", line 1040, in wrapper File "main.py", line 58, in get File "main.py", line 21, in dbmongo File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 349, in __init__ File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 510, in __find_master File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 516, in __try_node File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/database.py", line 301, in command File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/collection.py", line 441, in find_one File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/cursor.py", line 539, in loop File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/cursor.py", line 560, in _refresh File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/cursor.py", line 620, in __send_message File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 735, in _send_message_with_response File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 591, in __stream File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 200, in get_stream File "/opt/python2.7/lib/python2.7/site-packages/apymongo-0.0.1-py2.7-linux-x86_64.egg/apymongo/connection.py", line 559, in __connect AutoReconnect: could not connect to [('127.0.0.1', 27017)] 

Importante (supongo);

error: [Errno 24] Demasiados archivos abiertos

El código; (Es muy sencillo)

 import tornado.ioloop import tornado.web import tornado.escape import apymongo import time import sys #Useful stuff (Connect to Mongo) class setup(tornado.web.RequestHandler): def dbmongo(self): if not hasattr(self, '_dbmongo'): self._dbmongo = apymongo.Connection("127.0.0.1", 27017) return self._dbmongo #Basic method to lookup URLs from Mongo and redirect accordingly class expand(setup): @tornado.web.asynchronous def get(self, url): self.mongo = self.dbmongo() #Lookup the URL cursor = self.mongo.rmgshortlinks.links.find_one({'short':url}, self.direct) def direct(self, response): if response == None: self.send_error(404) self.finish() return link = tornado.escape.url_unescape(response['long']) #Bounce the client self.write("<meta http-equiv=\"refresh\" content=\"0;URL="+link+"\"Click Here") self.finish(); #Define the URL routes application = tornado.web.Application([ (r"/([a-zA-Z0-9]+)", expand) ]) #Start the server if __name__ == "__main__": listening_port = int(sys.argv[1]) if listening_port > 0: application.listen(listening_port) tornado.ioloop.IOLoop.instance().start() else: sys.stderr.write("No port specified!") 

El servidor de desarrollo que estoy usando tiene 8 núcleos y 64 GB de memoria, ejecutando RedHat Enterprise Linux 5 y Python 2.6. Nunca he tenido este tipo de problemas con las aplicaciones Tornado / Async Mongo.

Probablemente información útil;

 [root@puma ~]# ulimit -a core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 31374 max locked memory (kbytes, -l) 64 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 31374 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited 

(Los archivos abiertos solo están configurados en 1024, pero hubiera pensado que eso es más que suficiente)

¿Tornado / Apymongo no está cerrando las conexiones correctamente? Las aplicaciones se ubican detrás de NGINX pero se conectan mediante HTTP, Apymongo debería conectarse a través de TCP pero podría estar usando sockets. Aun así, debería ser compartir / agrupar conexiones, ¿no es así?

Editar

Como se sugirió, movimos la aplicación a uno de nuestros servidores de prueba con un límite máximo de archivos abiertos de 61440, el mismo error después de aproximadamente 30 segundos de ejecución en el sitio.

Muy simple, el objeto RequestHandler se crea una instancia para cada solicitud. Lo que significa que el objeto en caché que está guardando está en el objeto RequestHandler (por ejemplo, expand).

Si agregara una simple “impresión ‘CREADO!'” A la función dbmongo (…) verá que se crea en cada solicitud GET.

Lo que debe hacer es adjuntar el controlador al objeto de clase, o un “global” según sea necesario, aunque el mejor caso es colocarlo en el objeto de la aplicación Tornado.

Sencillo:

 class setup(tornado.web.RequestHandler): @classmethod def dbmongo(cls): if not hasattr(cls, '_dbmongo'): cls._dbmongo = apymongo.Connection("127.0.0.1", 27017) return cls._dbmongo 

El segundo enfoque es hacer que sea un archivo global en tu archivo:

 dbmongo_connection = None def dbmongo(): if not dbmongo_connection: dbmongo_connection = apymongo.Connection("127.0.0.1", 27017) return dbmongo_connection 

Ambos tienen el mismo problema, y ​​es que si tienes muchas clases que quieren usar la conexión de base de datos, es más difícil compartirla. Dado que la base de datos es una entidad compartida, es probable que desee una para toda su aplicación.

 class MongoMixin(object): def mongodb(self): if not hasattr(self.application, 'mongodb'): self.application.mongodb = apymongo.Connection(self.application.settings.get("mongohost", "127.0.0.1"), 27017) return self.application.mongodb class expand(tornado.web.RequestHandler, MongoMixin): def get(self): db = self.mongodb()