Best practices for integrating CherryPy web framework, SQLAlchemy and lighttpd sessions to serve a high load web service

I am developing a CherryPy FastCGI server for lighttpd with the following setup to enable the use of SQLAlchemy ORM sessions inside CherryPy controllers. However, when I run stress tests with 14 simultaneous requests for about 500 cycles, it starts to give errors, such as AttributeError: '_ThreadData' object has no attribute 'scoped_session_class' in open_dbsession() or AttributeError: 'Request' object has no attribute 'scoped_session_class' in close_dbsession() after a while. The error rate is about 50%.

This only happens when I start the server for lighttpd, and not when it starts directly through cherrypy.engine.start() . He confirmed that connect() does not throw exceptions.

I also tried assigning the scoped_session return value to GlobalSession (like here ), but then it UnboundExceptionError errors like UnboundExceptionError and other SA level errors. (Concurrency: 10, cycles: 1000, error rate: 16%. Occurs even with a direct start.)

There are several possible reasons, but I do not have enough knowledge to select them.
1. Does the start_thread subscription start_thread in the FastCGI environment? It seems that open_dbsession() is called before connect()
2. For some reason, is cherrypy.thread_data ?

server code

 import sqlalchemy as sa from sqlalchemy.orm import session_maker, scoped_session engine = sa.create_engine(dburi, strategy="threadlocal") GlobalSession = session_maker(bind=engine, transactional=False) def connect(thread_index): cherrypy.thread_data.scoped_session_class = scoped_session(GlobalSession) def open_dbsession(): cherrypy.request.scoped_session_class = cherrypy.thread_data.scoped_session_class def close_dbsession(): cherrypy.request.scoped_session_class.remove() cherrypy.tools.dbsession_open = cherrypy.Tool('on_start_resource', open_dbsession) cherrypy.tools.dbsession_close = cherrypy.Tool('on_end_resource', close_dbsession) cherrypy.engine.subscribe('start_thread', connect) 

lighttpd fastcgi config

 ... var.server_name = "test" var.server_root = "/path/to/root" var.svc_env = "test" fastcgi.server = ( "/" => ( "cherry.fcgi" => ( "bin-path" => server_root + "/fcgi_" + server_name + ".fcgi", "bin-environment" => ( "SVC_ENV" => svc_env ), "bin-copy-environment" => ("PATH", "LC_CTYPE"), "socket" => "/tmp/cherry_" + server_name + "." + svc_env + ".sock", "check-local" => "disable", "disable-time" => 1, "min-procs" => 1, "max-procs" => 4, ), ), ) 

edits

  • Missing thread_index argument in sample code from source code restored (thanks to comment)
  • It is clarified that errors do not occur immediately.
  • Narrowing conditions on lighttpd
+6
python cherrypy sqlalchemy
source share
2 answers

If you look at plugins.ThreadManager.acquire_thread , you will see the line self.bus.publish('start_thread', i) , where i is the index of the array of the visible thread. Any listener subscribing to the start_thread channel should accept this i as a positional argument. Therefore, rewrite the connection function as follows: def connect(i):

My guess is that it didn’t work out that way; I will see if I can track, test and fix this.

+1
source

I also tried assigning a scoped_session return value to GlobalSession (e.g. here), but then it threw errors like UnboundExceptionError and other SA level errors. (Concurrency: 10, cycles: 1000, error rate: 16%)

This error did not occur if I did not instantiate the scoped_session class explicitly.

i.e.

 GlobalSession = scoped_session(session_maker(bind=engine, transactional=False)) def connect(thread_index): cherrypy.thread_data.scoped_session_class = GlobalSession def open_dbsession(): cherrypy.request.scoped_session_class = cherrypy.thread_data.scoped_session_class def close_dbsession(): cherrypy.request.scoped_session_class.remove() 
0
source

All Articles