1. cherrypy
  2. CherryPy
Issue #145 resolved

Running multiple apps in one process

Anonymous created an issue

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.

Reported by ianb@colorstudy.com

Comments (18)

  1. Anonymous

    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.

  2. Anonymous

    The initial report was specific to the wsgi, but this is a more general issue. I renamed the topic to prevent another duplicate ticket.

  3. Anonymous

    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.

  4. Robert Brewer

    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:

    1. 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. 2. 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". 3. 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.* 4. 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. 5. 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. 6. 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.
  5. Anonymous

    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.

  6. Robert Brewer

    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.

  7. Anonymous

    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).

  8. Robert Brewer

    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.

  9. Anonymous

    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.

  10. Log in to comment