Ticket #145 (defect)
Opened 3 years ago
Last modified 3 years ago
Running multiple apps in one process
Status: closed (fixed)
| Reported by: | ianb@colorstudy.com | Assigned to: | rdelon |
|---|---|---|---|
| Priority: | normal | Milestone: | |
| Component: | CherryPy code | Keywords: | wsgi dispatch root |
| Cc: | ianb@colorstudy.com |
Because of the way wsgiapp.init works, it seems like you can't have multiple isolated CherryPy applications running in the same process. Ideally the interface would look more like:
from cherrypy import wsgiapp app = wsgiapp.createApp(configFile=...)
This would be useful to integrating CherryPy into Paste, because it would make it easier to intermingle CherryPy apps with other apps without hitting walls when you have multiple apps.
Change History
05/17/05 22:49:59: Modified by fumanchu@amor.org
- keywords changed from wsgi to wsgi dispatch root.
07/14/05 22:04:49: Modified by mikerobi
- summary changed from wsgiapp.init doesn't allow for multiple CherryPy WSGI apps to Running multiple apps in one process.
The initial report was specific to the wsgi, but this is a more general issue. I renamed the topic to prevent another duplicate ticket.
10/09/05 16:08:07: Modified by ianb@colorstudy.com
Note that this also makes CherryPy difficult and error-prone to use in a paste.deploy environment, which wants to treat WSGI applications as isolated and encapsulated. However, CherryPy apps will leak into each other because of this.
I hope this gets more attention, because I would like to support CherryPy apps well with paste.deploy, but can't without this change.
01/02/06 18:06:36: Modified by fumanchu
- status changed from new to closed.
- resolution set to fixed.
Fixed in [910]. This was never really "broken", just not as clear as it could have been. This fix does not break existing applications, but instead makes the following enhancements:
- A new cherrypy/_cptree.py module, with Root, Branch, and Tree classes. The cherrypy.tree object is an instance of _cptree.Tree, and has a mount(app_root, baseurl="/", conf=None) method. The default cherrypy.root object is an instance of _cptree.Root.
- Therefore, you can now call cherrypy.tree.mount(AppRoot(), "/path/to/app", conf), whereupon CherryPy will form cherrypy.root.path.to using new Branch objects as necessary, and bind your AppRoot? instance to the tail. In other words, it mounts AppRoot? at "/path/to/app".
- You can still write cherrypy.root = AppRoot(); however, this will clobber any apps that have already been mounted (as it always has). CherryPy developers are therefore encouraged to stop writing that, and use cherrypy.tree.mount(AppRoot(), baseurl, conf) instead.*
- The "conf" argument to mount() allows you to supply a configuration dict (or filename), written as if the app were mounted at "/". All section names (that start with "/") will be prefixed with the mount point url before they are inserted into cherrypy.config.configs.
- When the core searches for a page handler, config values, or special attributes, it uses the request path (in reverse order). Once that search reaches an AppRoot() (mounted via tree.mount()), then the search skips straight to "global". In this way, you can mount one app as a child of another, and not have configs, default handlers, or special attributes of the parent leak into the child.
- lib.httptools has a handy new urljoin function.
* Anybody writing a reusable app (or framework on top of CP) should read "encouraged" as "required". ;) If you're a framework author, and find yourself subclassing or overriding the Tree class, talk to us about folding that back into the core, preferably before 2.2 beta is officially released.
01/06/06 11:52:31: Modified by ianb@colorstudy.com
- status changed from closed to reopened.
- resolution deleted.
Looking at cherrypy._cpwsgi.wsgiApp (assuming it is still the way you get a CherryPy WSGI application), it still seems bound to a single root object.
The API for getting a CherryPy WSGI application should be makeWSGIApp(rootObject, conf) (actual name unimportant, of course). If this is currently possible, then it should be available in some clear function.
The application's location can be determined at the time of the request using SCRIPT_NAME.
01/09/06 16:30:30: Modified by fumanchu
I think you're using the phrase "get a WSGI application" in a way that the WSGI spec doesn't (it's unfortunate that the callable used to make requests is called the "application object" in the spec). cherrypy._cpwsgi.wsgiApp does not "create a WSGI application"; WSGI does not specify how to create applications, only how to interface with them (make requests) once they exist.
It's my understanding that you want Paste to manage the actual creation of applications. If you want to create a CherryPy application, call:
cherrypy.tree.mount(rootObject, baseurl, conf) cherrypy.tree.mount(rootObject2, baseurl2, conf2) cherrypy.server.start(initOnly=True, serverClass)
Within a given process, all running CP apps will share the same WSGI request callable: wsgiApp (which does use SCRIPT_NAME to dispatch to the correct app's handler). I can understand that other frameworks might provide a different WSGI "application object" (callable to handle requests) for each deployed webapp, but CherryPy doesn't do that--it provides the same callable for all webapps deployed in the same process.
01/11/06 12:08:25: Modified by ianb@colorstudy.com
- cc set to ianb@colorstudy.com.
Does cherrypy.server.start have to be called after the calls to tree.mount? Looking at the code, it doesn't seem like it should be a problem. It is okay to call start() multiple times?
The other issue is that I have to know up front what base urls go with what applications, which cuts down on what kind of dynamic dispatching is possible. Looking at the code, maybe I just need a mostly-stubbed-out Tree. For instance, in the WSGI environment I can put in a 'cherrypy.root_object' key, that contains the object I want to serve. I guess this could be done with a cherrypy.root object that fakes all its attributes (a default method won't work, I think, because it would not allow further parsing).
01/11/06 13:51:49: Modified by ianb@colorstudy.com
After looking at the code some more, a threadlocal root seems like a possible resolution. I have an experimental package here: http://svn.colorstudy.com/home/ianb/CherryPaste/trunk
It's not working yet, but I'll put the link here for reference.
01/11/06 16:36:46: Modified by fumanchu
A threadlocal root solution has already been tried in [908]. There, the threadlocal is called "app" instead of root, but the idea is the same. But it was decided that that breaks the CP 2.x API too much, and would have to at least wait until CP 3.0. You can make cherrypy.root itself a threadlocal now, because legacy apps would overwrite cherrypy.root (and there's no way to override __setattr__ on a module-level global. :( Read today's CP IRC log if you want the long conversation I had with Ben about it.
01/11/06 16:42:00: Modified by fumanchu
Sorry; I meant to type "you cannot make cherrypy.root itself a threadlocal now" (meaning in the 2.x line).
01/11/06 22:10:09: Modified by ianb@colorstudy.com
Well, as an extension module backward compatibility isn't as important to me, I just want to support some of the more modern CherryPy apps, or apps specifically written for CherryPy+Paste. That's enough for me.
Something that *could* be done in the 2.x line is an alternate API that has the root passed in explicitly. I think all the cherrypy.root-using code could be easily phrased in terms of this more generic API. This wouldn't actually change the situation from CherryPaste? that much, as it would still only work for apps that were written for it in mind, but it would open up a chance for people using 2.x to plan for the future.
You actually can override __setattr__, if you are okay being a bit clever/hacky. You can do something like (untested):
import sys
import cherrypy
class FakeModule(object):
def __init__(self, wrapping):
self.__dict__['wrapping'] = wrapping
def __getattr__(self, attr):
print "Get", attr
return getattr(self.wrapping, attr)
def __setattr__(self, attr, value):
print "Set", attr, '=', value
return setattr(self.wrapping, attr, value)
fake_cherrypy = FakeModule(cherrypy)
sys.modules['cherrypy'] = fake_cherrypy
I wouldn't necessarily recommend this, but it's there if you want it. I also don't know what you could usefully do with that setattr, except perhaps simply disallow it.
02/19/06 01:49:44: Modified by fumanchu
- status changed from reopened to closed.
- resolution set to fixed.
This has been fixed with the new cherrypy.tree and the new multiple-app WSGI server.


If I'm not mistaken, that's an issue with the core, not just wsgiapp (for which I'm posting an update tonight, by the way). Since CherryPy apps all import a common module (cpg) and use that as the basis for method dispatch (cpg.root), all apps within the same process necessarily share the same dispatch tree.