rtyler / Spawning

Spawning is a wsgi server which supports multiple processes, multiple threads, green threads, non-blocking HTTP io, and automatic graceful upgrading of code
http://pypi.python.org/pypi/Spawning
MIT License
120 stars 18 forks source link

AttributeError: 'module' object has no attribute 'run_controller' #19

Open yang opened 13 years ago

yang commented 13 years ago
$ spawning --version
0.9.4

$ paster --version
PasteScript 1.7.3 from /home/yang/work/pod/env/lib/python2.6/site-packages/PasteScript-1.7.3-py2.6.egg (python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41))

$ grep Spawning out/0.ini 
use = egg:Spawning

$ paster serve out/0.ini
Starting server in PID 7881.
Traceback (most recent call last):
  File "/home/yang/work/pod/env/bin/paster", line 9, in <module>
    load_entry_point('PasteScript==1.7.3', 'console_scripts', 'paster')()
  File "/home/yang/work/pod/env/lib/python2.6/site-packages/PasteScript-1.7.3-py2.6.egg/paste/script/command.py", line 84, in run
    invoke(command, command_name, options, args[1:])
  File "/home/yang/work/pod/env/lib/python2.6/site-packages/PasteScript-1.7.3-py2.6.egg/paste/script/command.py", line 123, in invoke
    exit_code = runner.run(args)
  File "/home/yang/work/pod/env/lib/python2.6/site-packages/PasteScript-1.7.3-py2.6.egg/paste/script/command.py", line 218, in run
    result = self.command()
  File "/home/yang/work/pod/env/lib/python2.6/site-packages/PasteScript-1.7.3-py2.6.egg/paste/script/serve.py", line 303, in command
    serve()
  File "/home/yang/work/pod/env/lib/python2.6/site-packages/PasteScript-1.7.3-py2.6.egg/paste/script/serve.py", line 287, in serve
    server(app)
  File "/home/yang/work/pod/env/lib/python2.6/site-packages/spawning/paste_factory.py", line 99, in run
    spawning_controller.run_controller(
AttributeError: 'module' object has no attribute 'run_controller'
rtyler commented 13 years ago

To be honest I don't use, nor have I ever used paster with Spawning or anything else. Is there any chance you could jump into the paste_factory module and come up with a patch? :-/

yang commented 13 years ago

Ah. I'm misunderstanding this part:

"Spawning can be used to launch a wsgi application from the command line using the "spawn" script, or using Python Paste. To use with paste, specify use = egg:Spawning in the [server:main] section of a paste ini file."

What then does it mean to "use with paste"? (If I just invoke spawning directly, I don't need to adjust the paste ini.)

This isn't a big deal as I'm just invoking spawning directly now, but consider updating/clarifying the docs?

rdw commented 13 years ago

I think it just means that spawning understands Paste Deploy's .ini files. Here's how you launch Spawning with them:

spawning --factory=spawning.paste_factory.config_factory development.ini

yang commented 13 years ago

Yes, that's what I do and that's what I meant with "just invoke spawning directly." Another way to spin this question: what is "use = egg:Spawning" for?

rdw commented 13 years ago

Man I have no idea. I bet that's something that was working at one point but got broken. I'd suggest removing it from the README until we figure out how Paste works. I've looked at its documentation a bunch of times and I still have no idea what the deuce paste factories are for.

countergram commented 13 years ago

Since I use Pylons I tried to fix this so use = egg:Spawning would work. It appears to be impossible without changing much more of spawning, and should be removed from the doc in favor of saying that spawning is meant to be used only as a command-line script.

Because os.execve() is called to replace child processes with new processes just after os.fork(), the child processes can receive only strings from the parent. In the example above of launching from the command line, each child parses the .ini file separately on startup. In other prefork and/or threaded wsgi servers I've used (e.g. flup), the parent parses the config file once, and the wsgi application is available as an object to children (because only fork() is used).

The reason I think use = egg:Spawning can't work is that Paste Deploy is based on the idea of your config file referencing an entry point/function that creates and returns a wsgi application. This wsgi application is not in module global scope (it was created inside a function) and therefore cannot be pickled or referred to by a string name.

sayap commented 11 years ago

With the following diff, I can get paster serve to work fine with Spawning. The first 2 hunks add support for --app-name and --server-name, while the last 2 hunks unbreak support for use = egg:Spawning. I got lazy with arguments parsing, so I just pull in PasteScript as another dependency on top of PasteDeploy.

Note that I have only done minimal testing with this...

diff --git a/spawning/paste_factory.py b/spawning/paste_factory.py
index 0442652..cf89efb 100644
--- a/spawning/paste_factory.py
+++ b/spawning/paste_factory.py
@@ -24,27 +24,33 @@ import os
 import sys

 from paste.deploy import loadwsgi
+from paste.script.serve import ServeCommand

 from spawning import spawning_controller

 def config_factory(args):
+    pvalues, pargs = ServeCommand.parser.parse_args(args['args'])
+
     if 'config_url' in args:
         config_url = args['config_url']
         relative_to = args['relative_to']
         global_conf = args['global_conf']
     else:
-        config_file = os.path.abspath(args['args'][0])
+        config_file = os.path.abspath(pargs[0])
         config_url = 'config:%s' % (os.path.basename(config_file), )
         relative_to = os.path.dirname(config_file)
         global_conf = {}
-        for arg in args['args'][1:]:
+        for arg in pargs[1:]:
+            if '=' not in arg:
+                continue
             key, value = arg.split('=')
             global_conf[key] = value

     ctx = loadwsgi.loadcontext(
         loadwsgi.SERVER,
         config_url,
+        name=pvalues.server_name,
         relative_to=relative_to,
         global_conf=global_conf)

@@ -81,8 +87,11 @@ def config_factory(args):

 def app_factory(config):
+    pvalues, pargs = ServeCommand.parser.parse_args(config['args'])
+
     return loadwsgi.loadapp(
         config['config_url'],
+        name=pvalues.app_name,
         relative_to=config['relative_to'],
         global_conf=config['global_conf'])

@@ -93,10 +102,14 @@ def server_factory(global_conf, host, port, *args, **kw):

     def run(app):
         args = spawning_controller.DEFAULTS.copy()
-        args.update(
-            {'config_url': config_url, 'relative_to': relative_to, 'global_conf': global_conf})
-
-        spawning_controller.run_controller(
-            'spawning.paste_factory.config_factory', args)
+        args.update({
+            'config_url': config_url,
+            'relative_to': relative_to,
+            'global_conf': global_conf,
+            'args': sys.argv[1:],
+        })
+
+        spawning_controller.start_controller(
+            None, 'spawning.paste_factory.config_factory', args)

     return run
diff --git a/spawning/spawning_controller.py b/spawning/spawning_controller.py
index 8f17c32..7a1daea 100644
--- a/spawning/spawning_controller.py
+++ b/spawning/spawning_controller.py
@@ -138,7 +138,7 @@ class Controller(object):
                     str(child_side),
                     self.factory,
                     json.dumps(self.args)]
-                if self.args['reload'] == 'dev':
+                if self.args.get('reload') == 'dev':
                     command.append('--reload')
                 env = environ()
                 tpool_size = int(self.config.get('threadpool_workers', 0))