Phillip J. Eby | 16 Sep 2004 01:12
Gravatar

Re: WSGI woes

At 06:48 PM 9/15/04 -0400, Peter Hunt wrote:
>It looks like WSGI is not well received over at twisted.web.
>
>http://twistedmatrix.com/pipermail/twisted-web/2004-September/000644.html

Excerpting from that post:

"""The WSGI spec is unsuitable for use with asynchronous servers and
applications. Basically, once the application callable returns, the
server (or "gateway" as wsgi calls it) must consider the page finished
rendering."""

This is incorrect.  Here is a simple WSGI application that demonstrates 
yielding 50 data blocks for transmission *after* the "application callable 
returns".

     def an_application(environ, start_response):
         start_response("200 OK", [('Content-Type','text/plain')])
         for i in range(1,51):
             yield "Block %d" % i

This has been a valid WSGI application since the August 8th posting of the 
WSGI pre-PEP.

It may be, however, that Mr. Preston means that applications which want to 
use 'write()' or a similar push-oriented approach to produce data cannot do 
so after the application returns.  If so, we should discuss that use case 
further, preferably on the Web-SIG.

>I thought the blocking call was handled by the iterator, but maybe I'm wrong.

I'm not sure what you mean, but if you're asking whether the iterable is 
allowed to create output blocks after the application callable returns, 
then yes.

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Donovan Preston | 16 Sep 2004 07:13

Re: [Twisted-web] Re: WSGI woes


On Sep 15, 2004, at 7:12 PM, Phillip J. Eby wrote:

> At 06:48 PM 9/15/04 -0400, Peter Hunt wrote:
>> It looks like WSGI is not well received over at twisted.web.
>>
>> http://twistedmatrix.com/pipermail/twisted-web/2004-September/ 
>> 000644.html
>
> Excerpting from that post:
>
> """The WSGI spec is unsuitable for use with asynchronous servers and
> applications. Basically, once the application callable returns, the
> server (or "gateway" as wsgi calls it) must consider the page finished
> rendering."""
>
> This is incorrect.

As I said in my original post, I hadn't mentioned anything about this  
yet because I didn't have a solution or proposal to fix the problem,  
which I maintain remains. I will attempt to suggest solutions, but I am  
unsure whether they will work or make sense in all environments. Allow  
me to explain:

>   Here is a simple WSGI application that demonstrates yielding 50 data  
> blocks for transmission *after* the "application callable returns".
>
>     def an_application(environ, start_response):
>         start_response("200 OK", [('Content-Type','text/plain')])
>         for i in range(1,51):
>             yield "Block %d" % i
>
> This has been a valid WSGI application since the August 8th posting of  
> the WSGI pre-PEP.

According to the spec, """The application object must return an  
iterable yielding strings.""" Whether the application callable calls  
write before returning or yields strings to generate content, the  
effect is the same -- there is no way for the application callable to  
say "Wait, hang on a second, I'm not ready to generate more content  
yet. I'll tell you when I am." This means the only way the application  
can pause for network activity is by blocking. For example, a page  
which performed an XML-RPC call and transformed the output into HTML  
would be required to perform the XML-RPC call synchronously. Or a page  
which initiated a telnet session and streamed the results into a web  
page would be required to perform reads on the socket synchronously.  
The server or gateway, by calling next(), is assuming that the call  
will yield a string value, and only a string value.

Of course, Twisted has a canonical way of indicating that a result is  
not yet ready, the Deferred. An asynchronous application could yield a  
Deferred and an asynchronous server would attach a callback to this  
Deferred which invoked the next() method upon resolution. This is how  
Nevow handles Deferreds (in Nevow SVN head at  
nevow.flat.twist.deferflatten).

However, the WSGI spec says nothing about Deferred and indeed, Deferred  
would be useless in the case of another asynchronous server such as  
Medusa. I would suggest that WSGI include a simple Deferred  
implementation, but WSGI is simply a spec which is not intended to have  
any actual code. Thus, one solution would be for the WSGI spec to be  
amended to state:

"""The application object must return an iterable yielding strings or  
objects implementing the following interface:

def addCallback(callable):
	'''Add 'callable' to the list of callables to be invoked when a string
	is available. Callable should take a single argument, which will be a  
string.'''

The application object must invoke the callable passed to addCallback,  
passing a string which will be written to the request.
"""

This places additional burdens upon implementors of WSGI servers or  
gateways. In the case of a threaded HTTP server which uses blocking  
writes, implementing support for these promises would have to look  
something like this:

import Queue

def handle_request(inSocket, outSocket):
     ... read inSocket, parse the request and dispatch ...

     iterable = application(environ, start_response)

     try:
         while True:
             val = iterable.next()
             if isinstance(val, str):
                 outSocket.write(val)
             else:
                 result = Queue.Queue()
                 val.addCallback(result.put)
                 outSocket.write(result.get())
     except StopIteration:
         outSocket.close()

> It may be, however, that Mr. Preston means that applications which  
> want to use 'write()' or a similar push-oriented approach to produce  
> data cannot do so after the application returns.  If so, we should  
> discuss that use case further, preferably on the Web-SIG.

And now we come to my other half-baked proposal.

Instead of merely returning a write callable, start_response could  
return a tuple of (write, finish) callables. The application would be  
free to call write at any time until it calls finish, at which point  
calling either callable becomes illegal. Again, the synchronous server  
support for this would have to use spin locking in a fashion such as  
this:

import threading

def handle_request(inSocket, outSocket):
     ... read request, dispatch ...
     finished = threading.Semaphore()

     def start_response(...):
         ... write headers ...
         return outSocket.write, finished.release

     iterable = application(environ, start_response)
     if iterable is None:
         finished.acquire()
         # Once we get here, the application is done with the request.

Finally, we come to the task of implementing a server or gateway which  
can asynchronously support either asynchronous or blocking  
applications. Since there is no way for the server or gateway to know  
whether the application object it is about to invoke will block,  
starving the main loop and preventing network activity from being  
serviced, it must invoke all applications in a new thread or process. A  
solution to this would be to require application callables to provide  
additional metadata, perhaps via function or object attributes, which  
indicate whether they are capable of running in asynchronous, threaded,  
or multiprocess environments. Since it's getting late and this message  
is getting long I will leave this discussion for another day.

dp

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Phillip J. Eby | 16 Sep 2004 08:37
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 01:13 AM 9/16/04 -0400, Donovan Preston wrote:

>On Sep 15, 2004, at 7:12 PM, Phillip J. Eby wrote:
>
>>At 06:48 PM 9/15/04 -0400, Peter Hunt wrote:
>>>It looks like WSGI is not well received over at twisted.web.
>>>
>>>http://twistedmatrix.com/pipermail/twisted-web/2004-September/ 000644.html
>>
>>Excerpting from that post:
>>
>>"""The WSGI spec is unsuitable for use with asynchronous servers and
>>applications. Basically, once the application callable returns, the
>>server (or "gateway" as wsgi calls it) must consider the page finished
>>rendering."""
>>
>>This is incorrect.
>
>As I said in my original post, I hadn't mentioned anything about this
>yet because I didn't have a solution or proposal to fix the problem,
>which I maintain remains.

Reading the rest of your post, I see that you are actually addressing the 
issue of asynchronous *applications*, and I have only been addressing 
asynchronous *servers* in the spec to date.  (Technically "half-async" 
servers, since to be properly portable, a WSGI server *must* support 
synchronous applications, and therefore an async WSGI server must have a 
thread pool for running applications, even if it contains only one thread.)

However, I'm not certain that it's actually possible to support *portable* 
asynchronous  applications under WSGI, since such asynchrony requires 
additional support such as an event loop service.  As a practical matter, 
asynchronous applications today require a toolset such as Twisted or 
peak.events in addition to the web server, and I don't really know of a way 
to make such applications portable across web servers, since the web server 
might use a different toolset that insists on having its own event 
loop.  Or it might be like mod_python or CGI, and not really have any 
meaningful way to create an event loop: it could be utterly synchronous in 
nature and impossible to make otherwise.

Thus, as a practical matter, applications that make use of asynchronous I/O 
*may* be effectively outside WSGI's scope, if they have no real chance of 
portability.  As I once said on the Web-SIG, the idea of WSGI is more aimed 
at allowing non-Twisted apps to run under a Twisted web server, than at 
allowing Twisted applications to run under other web servers!  The latter, 
obviously, is much more ambitious than the former.

But I'm happy to nonetheless explore whether there is any way to support 
such applications without unduly complicating middleware.  I don't expect 
it would complicate servers much, but middleware can be quite difficult, 
because middleware currently isn't even required to return when the 
application does!  It's not recommended, but a middleware component can sit 
there and iterate over the return value and call its parent's write() 
method all it wants.  In the presence of this kind of behavior, there isn't 
any real way to guarantee that a thread isn't going to be tied up with 
processing.  But realistically, that's what an async server's thread pool 
is *for*.

Anyway, as you'll see below, WSGI can actually run async apps with minimal 
blocking even without any modifications to the spec, and with 
server-specific extensions you can eliminate *all* the blocking, as long as 
middleware doesn't do anything pathological.  In practice, of course, I 
think the spec *should* be updated so that middleware is prohibited from 
interfering with the control flow, and I'll give some thought as to how 
that should be phrased.

>According to the spec, """The application object must return an
>iterable yielding strings.""" Whether the application callable calls
>write before returning or yields strings to generate content, the
>effect is the same -- there is no way for the application callable to
>say "Wait, hang on a second, I'm not ready to generate more content
>yet. I'll tell you when I am." This means the only way the application
>can pause for network activity is by blocking.

That is correct.  The application must block for such activities.  However, 
as a practical matter, this isn't a problem for e.g. database access, since 
using Twisted's adbapi would still tie up *some* thread with the exact same 
blocking I/O, so there's actually no loss in simply doing unadorned DBAPI 
access from within the application.

>  For example, a page
>which performed an XML-RPC call and transformed the output into HTML
>would be required to perform the XML-RPC call synchronously. Or a page
>which initiated a telnet session and streamed the results into a web
>page would be required to perform reads on the socket synchronously.

Technically, it could perform these tasks asynchronously, as long as the 
data were queued such that the application's return iterable simply 
retrieved results from the queue.  However, this would naturally block 
whenever the client was ready for I/O, but no results were available yet.

However, an asynchronous server isn't going to sit there in a loop calling 
next()!  Presumably, it's going to wait until the previous string gets sent 
to the client, before calling next() again.  And, it's presumably going to 
round-robin the active iterables through the threadpool, so that it doesn't 
keep blocking on iterables that aren't likely to have any data to produce 
as yet.

Yes, this arrangement can still block threads sometimes, if there are only 
a few iterables active and they are waiting for some very slow async 
I/O.  But the frequency of such blockages can be further reduced with a 
couple of extensions.  Suppose there was an 'environ["async.sleep"]' and 
'environ["async.wake"]'.  A call to 'sleep' would mean, "don't bother 
iterating over me again until you get a 'wake' call".

This *still* wouldn't prevent some item of middleware from hogging a thread 
in the threadpool, but I suppose you could actually make the 'sleep' 
function sit in a loop and run active iterables' next() methods until one 
of the suspended iterables in the current thread "wakes", at which point it 
would return control to whatever iterable it was called from.  Or, if you 
want to use Greenlets, you can always return control directly to the 
iterable that needs to "wake up".

Anyway, my point here is that it's possible to get a pretty decent setup 
for async applications, without any need to actually modify the base WSGI 
spec.  And, if you add some optional extensions, you can get an even 
smoother setup for async I/O.

Finally, I'm open to trying to define the 'sleep/wake' facilities as 
"standard options" in WSGI, as well as clarifying the middleware control 
flow to support this better.

>The server or gateway, by calling next(), is assuming that the call
>will yield a string value, and only a string value.

The spec doesn't rule out empty strings, however, which would be the 
natural way to indicate that no data is available.  So, the protocol in an 
async app's iterator would be something like:

      while queue.empty():
          if 'async.wake' in environ:
              someDeferred.addCallback(environ['async.wake'])
              environ['async.sleep']()
              yield ""
              # We should only get to this line once environ['async.wake'] 
has been called
          else:
              yield ""
              # delay an exponentially increasing period if queue is still 
empty

If middleware is required to match the control flow of the application it 
wraps (e.g. write()=>write(), yield=>yield), then this would result in 
complete non-blockingness when the server supports the 'async' extensions.

Of course, a blocking delay *is* required when running in a server that 
doesn't support the async extensions, but that's unavoidable in that 
case.  (Technically, you might be better off just doing synchronous I/O if 
you're being run in a synchronous server, but that's of course optional.)

>"""The application object must return an iterable yielding strings or
>objects implementing the following interface:
>
>def addCallback(callable):
>         '''Add 'callable' to the list of callables to be invoked when a 
> string
>         is available. Callable should take a single argument, which will 
> be a
>string.'''
>
>The application object must invoke the callable passed to addCallback,
>passing a string which will be written to the request.
>"""
>
>This places additional burdens upon implementors of WSGI servers or
>gateways.

And a near-intolerable burden on middleware, which would have to have a way 
to "pass through" this facility.  It would be much better to limit the 
pass-through requirements to covering write and yield, rather than 
requiring middleware to implement addCallback facilities as well.

>Finally, we come to the task of implementing a server or gateway which
>can asynchronously support either asynchronous or blocking
>applications. Since there is no way for the server or gateway to know
>whether the application object it is about to invoke will block,
>starving the main loop and preventing network activity from being
>serviced, it must invoke all applications in a new thread or process.

But *some* thread is going to be working on it, and this is true whether 
you use a thread pool or the server is purely synchronous.  And, because a 
WSGI server *must* support synchronous applications, it *must* have some 
thread available that is amenable to blocking.

Of course "new" threads are not required.  I assume that in the case of 
Twisted, something like reactor.deferToThread() will be used to wrap a WSGI 
application's initial invocation, and each individual 'next()' call.

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

James Y Knight | 16 Sep 2004 17:14

Re: Re: [Web-SIG] WSGI woes

On Sep 16, 2004, at 2:37 AM, Phillip J. Eby wrote:
> Reading the rest of your post, I see that you are actually addressing 
> the issue of asynchronous *applications*, and I have only been 
> addressing asynchronous *servers* in the spec to date.  (Technically 
> "half-async" servers, since to be properly portable, a WSGI server 
> *must* support synchronous applications, and therefore an async WSGI 
> server must have a thread pool for running applications, even if it 
> contains only one thread.)

 From the point of view of Twisted as the server, running a WSGI 
application, the big question is:
Can you (as a host server) assume WSGI applications will run 
non-blocking?

The answer is clearly No and I don't imagine that would change. (well, 
right now it's currently not even possible to write a non-blocking WSGI 
application, but even if it were..). The only sensible thing is to 
assume a WSGI app will block for some arbitrarily long amount of time. 
Therefore, the only solution is to spawn threads for simultaneous WSGI 
applications.

This is not the Twisted Way, BUT, it is very clearly doable in Twisted, 
so it's not like the WSGI spec is unimplementable. For some people it 
might be perfectly acceptable to have an async base Twisted webserver 
running multi-threaded user code. I do not care much about doing that 
myself, but it should be fairly easy to write the WSGI layer to make 
this happen, and I would be happy for someone else to do so and 
contribute it to twisted.

So, basically, I concur: WSGI is implementable for async servers, but 
only to implement blocking applications.

> However, I'm not certain that it's actually possible to support 
> *portable* asynchronous  applications under WSGI, since such 
> asynchrony requires additional support such as an event loop service.  
> As a practical matter, asynchronous applications today require a 
> toolset such as Twisted or peak.events in addition to the web server, 
> and I don't really know of a way to make such applications portable 
> across web servers, since the web server might use a different toolset 
> that insists on having its own event loop.  Or it might be like 
> mod_python or CGI, and not really have any meaningful way to create an 
> event loop: it could be utterly synchronous in nature and impossible 
> to make otherwise.
>
> Thus, as a practical matter, applications that make use of 
> asynchronous I/O *may* be effectively outside WSGI's scope, if they 
> have no real chance of portability.  As I once said on the Web-SIG, 
> the idea of WSGI is more aimed at allowing non-Twisted apps to run 
> under a Twisted web server, than at allowing Twisted applications to 
> run under other web servers!  The latter, obviously, is much more 
> ambitious than the former.

Yes, there is no way that I can see to make WSGI suitable for writing 
async applications without significant work.  There are two obvious 
issues: the input stream only provides blocking read(), not a 
selectable fd, and there is no way to pause output.

If the write callback was extended into a write/finish callback, it 
wouldn't completely fix the second problem. Twisted would have to call 
the write() callback from its reactor loop (having no access to the 
original request thread). Especially if there is any middleware, the 
*write* might block! There's also the question of whether the write() 
and finish() methods are threadsafe or not -- would it even be safe to 
call from a separate thread from that in which the request was started?

Writing an async application *is* an interesting question, because 
then, possibly, you could take the framework half of twisted web and 
run it as a WSGI application. However, if this question is punted by 
WSGI (as I think is likely a good idea..), twisted web framework can 
continue to work with other servers by using HTTP proxying -- which is 
a _perfectly good_ solution, and something major webservers already 
support. HTTP is a pretty good protocol for talking between webservers 
and webapps.

Also, if WSGI becomes really popular on servers that cannot do HTTP 
proxying natively, twisted could provide a WSGI "application" that 
simply proxies the requests over a socket to a separate twisted web 
server process. This would provide essentially no advantage to HTTP 
proxying where that works, however.

James
Phillip J. Eby | 16 Sep 2004 18:18
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 11:14 AM 9/16/04 -0400, James Y Knight wrote:
>On Sep 16, 2004, at 2:37 AM, Phillip J. Eby wrote:
>>Reading the rest of your post, I see that you are actually addressing the 
>>issue of asynchronous *applications*, and I have only been addressing 
>>asynchronous *servers* in the spec to date.  (Technically "half-async" 
>>servers, since to be properly portable, a WSGI server *must* support 
>>synchronous applications, and therefore an async WSGI server must have a 
>>thread pool for running applications, even if it contains only one thread.)
>
> From the point of view of Twisted as the server, running a WSGI 
> application, the big question is:
>Can you (as a host server) assume WSGI applications will run non-blocking?
>
>The answer is clearly No and I don't imagine that would change.

Right, because the ability to wrap existing applications is a must, and 
most existing applications are synchronous.

>  (well, right now it's currently not even possible to write a 
> non-blocking WSGI application, but even if it were..)

That depends on what you define as "non-blocking".  :)

>The only sensible thing is to assume a WSGI app will block for some 
>arbitrarily long amount of time. Therefore, the only solution is to spawn 
>threads for simultaneous WSGI applications.

Right; this has been in the discussions of WSGI since day one, last 
December.  The assumption is that async servers would have to use a thread 
pool (e.g. via reactor.deferToThread) to run WSGI applications.  Since the 
point was to allow non-Twisted applications and frameworks (e.g. Zope) to 
run under Twisted or any other web server, this was the only possible approach.

>So, basically, I concur: WSGI is implementable for async servers, but only 
>to implement blocking applications.

If by "blocking" you mean, you can't absolutely guarantee that no operation 
will tie up the current thread, then yes.  If you mean "tie up the current 
thread for the entire request", then no, since it's possible to pause the 
output with a few minor changes to the spec.

>>However, I'm not certain that it's actually possible to support 
>>*portable* asynchronous  applications under WSGI, since such asynchrony 
>>requires additional support such as an event loop service.
>>As a practical matter, asynchronous applications today require a toolset 
>>such as Twisted or peak.events in addition to the web server, and I don't 
>>really know of a way to make such applications portable across web 
>>servers, since the web server might use a different toolset that insists 
>>on having its own event loop.  Or it might be like mod_python or CGI, and 
>>not really have any meaningful way to create an event loop: it could be 
>>utterly synchronous in nature and impossible to make otherwise.
>>
>>Thus, as a practical matter, applications that make use of asynchronous 
>>I/O *may* be effectively outside WSGI's scope, if they have no real 
>>chance of portability.  As I once said on the Web-SIG, the idea of WSGI 
>>is more aimed at allowing non-Twisted apps to run under a Twisted web 
>>server, than at allowing Twisted applications to run under other web 
>>servers!  The latter, obviously, is much more ambitious than the former.
>
>Yes, there is no way that I can see to make WSGI suitable for writing 
>async applications without significant work.  There are two obvious 
>issues: the input stream only provides blocking read(), not a selectable 
>fd, and there is no way to pause output.

The sleep/wake extensions I proposed would allow pausing output.  I hadn't 
thought about the input stream issue.

>If the write callback was extended into a write/finish callback, it 
>wouldn't completely fix the second problem. Twisted would have to call the 
>write() callback from its reactor loop (having no access to the original 
>request thread). Especially if there is any middleware, the *write* might 
>block! There's also the question of whether the write() and finish() 
>methods are threadsafe or not -- would it even be safe to call from a 
>separate thread from that in which the request was started?

That's one reason why sleep/wake over iterables is a better solution than 
write/finish for the "pausing output" issue.

>Writing an async application *is* an interesting question, because then, 
>possibly, you could take the framework half of twisted web and run it as a 
>WSGI application. However, if this question is punted by WSGI (as I think 
>is likely a good idea..), twisted web framework can continue to work with 
>other servers by using HTTP proxying -- which is a _perfectly good_ 
>solution, and something major webservers already support. HTTP is a pretty 
>good protocol for talking between webservers and webapps.
>
>Also, if WSGI becomes really popular on servers that cannot do HTTP 
>proxying natively, twisted could provide a WSGI "application" that simply 
>proxies the requests over a socket to a separate twisted web server 
>process. This would provide essentially no advantage to HTTP proxying 
>where that works, however.

No *technical* advantage, true, but if WSGI becomes a popular buzzword, the 
mere existence of such a solution allows you to boast that Twisted Web can 
be used with any WSGI-compliant server, as well as any server that supports 
HTTP proxying, which makes it sound like you have twice as many deployment 
options from a "marketecture" perspective.  :)

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Alan Kennedy | 16 Sep 2004 16:59
Favicon

Re: [Twisted-web] Re: WSGI woes

[Phillip J. Eby]
 > However, an asynchronous server isn't going to sit there in a loop
 > calling next()!  Presumably, it's going to wait until the previous
 > string gets sent to the client, before calling next() again.  And,
 > it's presumably going to round-robin the active iterables through the
 > threadpool, so that it doesn't keep blocking on iterables that aren't
 > likely to have any data to produce as yet.
 >
 > Yes, this arrangement can still block threads sometimes, if there are
 > only a few iterables active and they are waiting for some very slow
 > async I/O.  But the frequency of such blockages can be further reduced
 > with a couple of extensions.  Suppose there was an
 > 'environ["async.sleep"]' and 'environ["async.wake"]'.  A call to
 > 'sleep' would mean, "don't bother iterating over me again until you
 > get a 'wake' call".

and

 > Anyway, my point here is that it's possible to get a pretty decent
 > setup for async applications, without any need to actually modify the
 > base WSGI spec.  And, if you add some optional extensions, you can get
 > an even smoother setup for async I/O.
 >
 > Finally, I'm open to trying to define the 'sleep/wake' facilities as
 > "standard options" in WSGI, as well as clarifying the middleware
 > control flow to support this better.

What would be really nice would be if there were some way for the 
application to return, to event-based servers or gateways, an object 
that could be included in the server's event loop, e.g. its select/poll 
loop.

For example, if an application were waiting on return data from a 
database, through a network socket, it could return that 
database-connection-socket descriptor to the server. The server would 
then check for activity on the database socket in its event loop, i.e. 
select.poll.POLLIN. When this event, i.e. database data, appears, the 
server can have *reasonable* confidence that a call to the applications 
iterator will then yield data. Of course, it is not guaranteed that the 
application will have data available (e.g. the database socket contains 
half the data required by the app, or the database connection is shared 
between multiple apps). But it's better than the application blocking.

But I can't think of any unified way to generalise this solution to 
non-descriptor based event loops or applications. For example, what if 
the application is waiting for data on a Queue.Queue? Or a 
threading.Event? How could the application enable the server to check 
for the Queue.Queue or threading.Event it awaits?

Perhaps the server could maintain an extra event loop for checking such 
threaded event notification mechanisms? Or it could associate an "app 
ready" flag with each client connection? It could go something like this:-

1. The application returns to the server an instance of a class that 
indicates it will only generate content when a thread notification 
primitive is set. Or perhaps the thread notification primitive has an 
optional attribute of the returned iterable, e.g. if hasattr(iterable, 
'ready_to_go'): etc

2. The server adds this thread notification primitive to its 
lists/"event loop", or associates the notification primitive with the 
descriptor for the incoming/outgoing client socket.

3. When the client socket becomes ready for output, the server checks 
the ready_to_go flag on the application. If the flag is not set, it 
simply passes over that individual socket to the next.

4. When the client socket is ready to consume output *and* the 
application is ready to produce output, i.e. it's ready flag is set, the 
server gets the data from the app's iterator and transmits it down the 
client socket. The server could conceivably loop until either the client 
socket is full or the application iterator is empty, and then just 
suspend that client/application pair. Or it could spin that app->client 
transfer into a separate dedicated thread.

I don't like the idea of adding callbacks to WSGI: that's too twisted 
specific. I can picture, for example, a very simple coroutine based 
async server that would not need to have callbacks. Instead, they would 
simply yield a NO-OP state to the server/scheduler/dispatcher, 
indicating they have no data ready right now.

And, of course, that's what we're really discussing here: server 
scheduling, and how servers ensure that application output gets 
transmitted to clients with maximum efficiency and timeliness. IMHO, 
asynchronous server scheduling algorithms and concerns have no place in 
core WSGI, although a well-designed optional extension to support 
effiency might have a nice unification effect on python asynchronous 
server architectures.

Just my €0,02

Regards,

Alan.
_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Phillip J. Eby | 16 Sep 2004 17:22
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 03:59 PM 9/16/04 +0100, Alan Kennedy wrote:
>And, of course, that's what we're really discussing here: server 
>scheduling, and how servers ensure that application output gets 
>transmitted to clients with maximum efficiency and timeliness. IMHO, 
>asynchronous server scheduling algorithms and concerns have no place in 
>core WSGI, although a well-designed optional extension to support effiency 
>might have a nice unification effect on python asynchronous server 
>architectures.

Right.  I'd encourage people to experiment with async extensions like my 
sleep/wake idea, and if there's sufficient consensus we could add a 
"standard extension" to the spec.  But I don't want to disturb the 
write()+iterable model, since that allows middleware to be mostly oblivious 
to the sync/async issue, and only apps or servers that care have to deal 
with it.  While asynchronous servers are fairly common, most existing 
asynchronous applications are going to be tied to a particular async server 
architecture no matter what we do in WSGI.

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Alan Kennedy | 16 Sep 2004 18:45
Favicon

Re: [Twisted-web] Re: WSGI woes

[Phillip J. Eby]
 > I'd encourage people to experiment with async extensions like my
 > sleep/wake idea,

Actually, the more I think about it, the more I like your idea.

My solution of using a thread-safe condition variable as an optional 
attribute of application return objects is too heavyweight. Whereas your 
solution can be implement with complexity relative to the server. For 
example, on a single-process server, wsgi.sleep could be defined like this

def sleep():
	# return a closure wrapping a method which sets a simple binary

whereas a threaded server might use

import threading

def sleep():
	# return a closure wrapping a threading.Condition().set()

Also, having the wrapper in the environment means that its meaning can 
be changed by middleware.

The only thing I disagree on are the names "sleep" and "wake", which 
IMHO come with too many semantic hangovers from the threading world. 
When an application calls wsgi.sleep(), it's not really sleeping, it's 
just declaring that it currently has no output: a call to its iterator 
will succeed, but the returned value will be an empty string.

So basically, WSGI is providing an on/off indicator for every instance 
of a middleware stack, which indicates to the server if there is 
currently output available.

Thinking afresh.
================

The server is just acting as a mediator between the client and 
application. When the application has data, and the client is ready to 
receive data, the server transfers data between the two. But that client 
to application conversation is full-duplex, i.e. the client may be 
sending input to the application.

In an asynchronous situation, the application cannot simply do a 
blocking read on the input: that will tie up the server thread. So we 
need a way for the application to be notified/called when input becomes 
available from the client.

Perhaps we need to add an environment entry, e.g. "wsgi.input_handler", 
which the app uses to pass a callable to the server. This callable would 
be called whenever data became available on the input stream.

So how would that work in the middleware stack?

Would the first application in the stack set the callback for the 
input_stream, and perhaps not even invoke the next component up in the 
stack until some input has arrived? Does this mean that input handling 
will have to separated out into a new state in the server->application 
state model?

Or would each component in the stack set its own callback?

I'm beginning to think that we may have to treat output and input 
identically in WSGI: i.e. from the servers point of view, there is no 
difference between the application->client stream and the 
client->application stream: there is symmetry between server's 
connection to the client and server's "connection" to the application. 
Hmmm: Must think some more about this.

Regards,

Alan.
_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Phillip J. Eby | 16 Sep 2004 19:41
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 05:45 PM 9/16/04 +0100, Alan Kennedy wrote:
>The only thing I disagree on are the names "sleep" and "wake", which IMHO 
>come with too many semantic hangovers from the threading world. When an 
>application calls wsgi.sleep(), it's not really sleeping, it's just 
>declaring that it currently has no output: a call to its iterator will 
>succeed, but the returned value will be an empty string.
>
>So basically, WSGI is providing an on/off indicator for every instance of 
>a middleware stack, which indicates to the server if there is currently 
>output available.

Well, I'm proposing it as an optional extension, not a required 
feature.  And, I think I'd like to streamline it to a single 
'wsgi.pause_output' function, e.g.:

     resume = environ['wsgi.pause_output']()

Where 'resume' is then a callback function that can be invoked to resume 
iteration.  This keeps it to a single extension key, helps ensure the 
correct sequence of actions, and makes it easier to implement in some 
cases, while not making other cases any harder.

>In an asynchronous situation, the application cannot simply do a blocking 
>read on the input: that will tie up the server thread.

What do you mean by "server thread"?  A truly asynchronous server (one 
using "no threads") cannot serve multiple WSGI requests simultaneously.  In 
the general case, a WSGI server can only serve as many requests 
simultaneously as it has available threads for.  However, WSGI applications 
that use iteration in place of 'write()' can sometimes be run with fewer 
than one thread per simultaneous request -- that's why iteration is 
recommended for applications that can be implemented that way.

>  So we need a way for the application to be notified/called when input 
> becomes available from the client.
>
>Perhaps we need to add an environment entry, e.g. "wsgi.input_handler", 
>which the app uses to pass a callable to the server. This callable would 
>be called whenever data became available on the input stream.
>
>So how would that work in the middleware stack?

You would have to pass either 'environ' or 'wsgi.input' *into* this input 
handler request function, so that the server can verify it hasn't been 
replaced by any middleware.  This is the standard way in WSGI of providing 
enhanced communication facilities that could "bypass" middleware.  See:

     http://www.python.org/peps/pep-0333.html#server-extension-apis

So, in principle, if the spec is modified to require middleware to honor 
child applications' block boundaries, then you could use an extension API 
to pause iteration until input is available, in much the same way that you 
would pause iteration for any other reason.

Neither of these "pause iteration" solutions are especially elegant, at 
least from the POV of an async application author.  But my objective here 
is only to make it *possible*, not necessarily pretty.  I imagine that if 
there's actual demand for async apps to run under WSGI, it should be 
possible to create wrappers to let an application written in Twisted's 
continuation-passing style be run as a WSGI app.

Such a wrapper would basically be just a function returning an iterator, 
with a bunch of pausing logic and a queue to communicate with the actual 
asynchronous app.  And, such wrappers should only need to be written once 
for each asynchronous API, which as a practical matter probably means only 
Twisted, anyway, as (IMO) it has no real competitors in the Python async 
framework space.

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Alan Kennedy | 16 Sep 2004 21:29
Favicon

Re: [Twisted-web] Re: WSGI woes

[Alan Kennedy]
 >> In an asynchronous situation, the application cannot simply do a
 >> blocking read on the input: that will tie up the server thread.

[Phillip J. Eby]
 > What do you mean by "server thread"?  A truly asynchronous server (one
 > using "no threads") cannot serve multiple WSGI requests
 > simultaneously.  In the general case, a WSGI server can only serve as
 > many requests simultaneously as it has available threads for.

Sorry, I should have paid more attention to phrasing in this context.

By  "server thread" I mean the thread of execution that is running the 
select/poll operation in the server (which needs at least *one* thread). 
If the application did a blocking read of the input running in a simple, 
single-threaded asyncore-style server, that single thread would block, 
holding up event processing.

[Phillip J. Eby]
 >
 > [About asynchronous input handlers]
 >
 > Such a wrapper would basically be just a function returning an
 > iterator, with a bunch of pausing logic and a queue to communicate
 > with the actual asynchronous app.  And, such wrappers should only
 > need to be written once for each asynchronous API, which as a
 > practical matter probably means only Twisted, anyway, as (IMO) it has
 > no real competitors in the Python async framework space.

I see the need for returning an iterator: the application processing the 
input has to produce a response as well: for a form-processing app 
returning a "thank you for your submission" page.

But I don't see the need for pausing logic or queues? Why can't the 
server simply call directly into the application, e.g. using a 
"process_input" method, in effect saying "you have some input ready".

And I'm not sure I see the need for the application to check that the 
wsgi.input hasn't been replaced: if there were middleware further down 
that stack that was intercepting and transforming the input stream, then 
*it* should be the one receiving the asynchronous notification from the 
server. This lower level component would then read some input, process 
it, and then call a "process_input" method on the next component up in 
the stack, etc, etc.

I suppose I'm talking about the server "pushing" the input through the 
middleware stack, whereas you're talking about the application at the 
stop of the stack "pulling" the data up through the stack. Is that right?

And I'd be interested to see how your approach would handle a situation 
where there is both streaming input and output. For example, a server 
that takes strings of any length, say 10**9 bytes, and 
.encode('rot13')'s each byte in turn, before sending it back to the client.

I'll be thinking about this some more.

Regards,

Alan.

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Phillip J. Eby | 16 Sep 2004 22:08
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 08:29 PM 9/16/04 +0100, Alan Kennedy wrote:
>[Alan Kennedy]
> >> In an asynchronous situation, the application cannot simply do a
> >> blocking read on the input: that will tie up the server thread.
>
>[Phillip J. Eby]
> > What do you mean by "server thread"?  A truly asynchronous server (one
> > using "no threads") cannot serve multiple WSGI requests
> > simultaneously.  In the general case, a WSGI server can only serve as
> > many requests simultaneously as it has available threads for.
>
>Sorry, I should have paid more attention to phrasing in this context.
>
>By  "server thread" I mean the thread of execution that is running the 
>select/poll operation in the server (which needs at least *one* thread). 
>If the application did a blocking read of the input running in a simple, 
>single-threaded asyncore-style server, that single thread would block, 
>holding up event processing.

Right, which is (one reason) why a WSGI server can in the general case only 
serve as many WSGI requests simultaneously as it has available threads for, 
although it's possible to improve on that worst-case condition by 
appropriate use of iterators.

>But I don't see the need for pausing logic or queues? Why can't the server 
>simply call directly into the application, e.g. using a "process_input" 
>method, in effect saying "you have some input ready".
>
>And I'm not sure I see the need for the application to check that the 
>wsgi.input hasn't been replaced: if there were middleware further down 
>that stack that was intercepting and transforming the input stream, then 
>*it* should be the one receiving the asynchronous notification from the 
>server. This lower level component would then read some input, process it, 
>and then call a "process_input" method on the next component up in the 
>stack, etc, etc.
>
>I suppose I'm talking about the server "pushing" the input through the 
>middleware stack, whereas you're talking about the application at the stop 
>of the stack "pulling" the data up through the stack. Is that right?

That's correct, and that's what I'm trying to avoid if at all possible, 
because it enormously complicates middleware, to the sole benefit of 
asynchronous apps -- that mostly aren't going to be portable anyway.

So, going by STASCTAP theory (Simple Things Are Simple, Complex Things Are 
Possible), the pause/resume approach makes asynchronous applications 
*possible*, while keeping the nominal synchronous cases and middleware 
*simple*.

>And I'd be interested to see how your approach would handle a situation 
>where there is both streaming input and output. For example, a server that 
>takes strings of any length, say 10**9 bytes, and .encode('rot13')'s each 
>byte in turn, before sending it back to the client.

Presumably, the function to pause for input needs to take a minimum length, 
or have some way to communicate available length to the application.

I don't pretend to fully understand the needed use cases here, because I 
have little experience writing web applications that need to wait on other 
network services (other than databases) while a client is waiting.  And if 
I were writing an asynchronous server, I'd probably at least consider using 
Greenlets to context-switch blocking operations so that they wouldn't tie 
up an active thread.  Such an approach is conceptually easier to deal with, 
IMO, than writing everything in continuation-passing style.

But I *do* want WSGI to make it *possible* to meet async apps' use cases, 
which is why I'm seeking input from those that do have the relevant 
experience.  The trade-off is that it shouldn't excessively complicate 
nominal compliance with WSGI.  In particular, I'd prefer that the current 
"example CGI gateway" in PEP 333 not require any major changes or 
significant expansion.

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Alan Kennedy | 16 Sep 2004 23:41
Favicon

Re: [Twisted-web] Re: WSGI woes

[Alan Kennedy]
 >> I suppose I'm talking about the server "pushing" the input through
 >> the middleware stack, whereas you're talking about the application at
 >> the stop of the stack "pulling" the data up through the stack. Is
 >> that right?

[Phillip J. Eby]
 > That's correct, and that's what I'm trying to avoid if at all
 > possible, because it enormously complicates middleware, to the sole
 > benefit of asynchronous apps -- that mostly aren't going to be
 > portable anyway.

Hmmm. Perhaps I'll resort to explaining my idea through code rather than 
text. Here is my take on a putative blocking *and* asynchronous rot-13 
stream encoder.

But before showing you the blocking and async one, I want to show what I 
think the blocking one would look like

class blocking_rot13_streamer:

   def __init__(self, environ, start_response):
     self.in_stream = environ['wsgi.input']
     start_response("200 OK", [('context-type', 'text/plain-rot13')])

   def __iter__(self):
     return self

   def next(self):
     try:
       return self.in_stream.read().encode('rot-13')
     except EndOfStream:
       raise StopIteration

This looks nice and simple to me. The one that works in both async mode 
and blocking mode looks like this

class rot13_streamer:

   def __init__(self, environ, start_response):
     self.in_stream = environ['wsgi.input']
     self.buffer = []
     self.end_of_stream = False
     if environ.has_key('wsgi.async_input_handler'):
       self.async = True
       environ['wsgi.async_input_handler'](self.input_handler)
     else:
       self.async = False
     self.pause_output = environ['wsgi.pause_output']
     start_response("200 OK", [('context-type', 'text/plain-rot13')])

   def input_handler(self):
     try:
       data = self.environ['wsgi.input'].read()
       self.buffer.append(data)
       if self.resume:
         self.resume()
         self.resume = None # Are resumes one-hit or "re-entrant"?
     except EndOfStream:
       self.end_of_stream = True

   def __iter__(self):
     return self

   def next(self):
     if async:
       if self.buffer:
         return self.buffer.pop().encode('rot-13')
       else:
         if self.end_of_stream:
           raise StopIteration
         else:
           self.resume = self.pause_output()
           return ""
     else:
       try:
         return self.in_stream.read().encode('rot-13')
       except EndOfStream:
         raise StopIteration

In this way, there could be a middleware component below the 
rot13_streamer in the stack that, say, does chunked_transfer encoding 
and decoding. It would be the same in form as the above, except that it 
would

1. Change the environ entry for 'wsgi.async_input_handler' to be its own 
callable that records the callback for the next layer up in the stack, 
the rot13_streamer.input_handler.

2. Create its own buffer, into which it will store chunks decoded from 
the input stream. This buffer, e.g. a StringIO, then replaces 
'wsgi.input' in the environ passed to next middleware component up.

3. When chunks arrive from the client, the server calls the dechunker 
input_handler. This reads the (possibly partial) chunk from the stream, 
decodes it and stores it in its StringIO buffer.

4. When it has a complete chunk it calls the input_handler of the next 
component in the stack, which will then read the decoded chunk from its 
wsgi.input stream, i.e. the dechunkers StringIO.

I think that this proposed approach is clean, and not overly complex for 
async or blocking programmers to handle.

But I think we do have to cleanly separate the two. I think there are 
problems associated with trying to run *all* components seamlessly 
across async or blocking servers. I think that middleware components 
that are always going to behave correctly in an async situation will 
have to be designed like that from the ground up. It's dangerous to take 
components written in a blocking environment and run them in an async 
environment.

And lastly, if it is desired to spin jobs into a different thread, e.g. 
the rot-13 job above, then that should be a middleware concern, not the 
WSGI server's. So if a twisted component wants to pass a job to a 
service thread, some other twisted comonent lower down the stack, 
possibly the framework itself, must have already created the 
threads/queues to enable this. The twisted rot-13 component would then 
have very thin methods (run from the server's main thread) which 
interact with the twisted space i.e. transferring data and receiving 
data back through queues, and layer WSGI semantics on those 
interactions, i.e. pause_output, yield result, yield empty_string, etc.

When I described your approach as "pulling data up the stack", I saw a 
bigger difference between the two approaches. I'm thinking now that 
there is little difference between our proposals, except that in mine 
it's the bottom component that gets notified of the input by the server, 
and in yours it's the top component. Though I suppose having the top 
component pulling input from an iterator chain mirrors nicely the 
situation where the server pulls output from an iterator chain.

And my approach basically entails a bunch of nested calls, which might 
be less efficient elegant than if, say, generators were used in an input 
processing chain.

You're right again Phillip :-)

Regards,

Alan.
_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Phillip J. Eby | 17 Sep 2004 00:37
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 10:41 PM 9/16/04 +0100, Alan Kennedy wrote:

>In this way, there could be a middleware component below the 
>rot13_streamer in the stack that, say, does chunked_transfer encoding and 
>decoding. It would be the same in form as the above, except that it would

FYI, middleware and apps are now banned from dealing in any kind of 
transfer-encodings, per James' very valuable input on that subject.  Like 
connection properties, these should be the exclusive province of the actual 
web server.

>1. Change the environ entry for 'wsgi.async_input_handler' to be its own 
>callable that records the callback for the next layer up in the stack, the 
>rot13_streamer.input_handler.

This would lead to the unacceptable situation of every middleware component 
having to know in principle about extensions.  The "Server Extension APIs" 
section of the PEP demands that any "bypass" API verify replacement for 
this very reason.

>I think that this proposed approach is clean, and not overly complex for 
>async or blocking programmers to handle.

Unless of course they're writing middleware that does something with the input.

>But I think we do have to cleanly separate the two. I think there are 
>problems associated with trying to run *all* components seamlessly across 
>async or blocking servers. I think that middleware components that are 
>always going to behave correctly in an async situation will have to be 
>designed like that from the ground up. It's dangerous to take components 
>written in a blocking environment and run them in an async environment.

It is a non-goal for WSGI to support running multiple requests 
simultaneously in a single-threaded asynchronous server, so the issue 
doesn't really come up.  A WSGI server *must* allow for the fact that WSGI 
apps use up a thread while they're running or producing a value: that's the 
price of being able to run "traditional" web applications under WSGI.

>And lastly, if it is desired to spin jobs into a different thread, e.g. 
>the rot-13 job above, then that should be a middleware concern, not the 
>WSGI server's.

I agree with you -- for *asynchronous* applications.  Synchronous web 
applications are the default case in WSGI and the world in general, so 
servers *must* use a thread pool to start applications and to run 'next()' 
calls, if they are asynchronous.  But, asynchronous applications wish to 
yield control, to avoid hogging resources in that thread pool, so they need 
to delegate the work to their I/O thread, and then yield an empty string to 
pause output, freeing up that thread for another iterable next(), or 
application start.

Notice, however, that if the server is *synchronous* (e.g. CGI, 
single-threaded FastCGI containers, mod_python under Apache 1.x, etc., ), 
then this is a complete waste of time, because you'll only be running one 
simultaneous request in this process anyway, so you're spinning off a 
second thread to keep from tying up the first thread, but all the first 
thread is doing is waiting for the second thread to finish!  This is 
wasteful, to say the least.

The only case where pausing output (whether for unrelated network I/O, or 
because of a need to read from the input stream) is actually useful is when 
the server is *also* asynchronous -- hence the value of making such pausing 
an optional extension API.  The application can then detect when it's 
*useful* to pause, and synchronous applications needn't worry about it.

Of course, even if the server and application are *both* asynchronous, 
that's no guarantee that they're using compatible event loops!  If you try 
to run a Twisted app under asyncore or vice versa, you're going to be 
spinning off an extra thread to run a second event loop, so there's a bit 
of a trade-off to determining whether your asynchrony is going to actually 
*gain* anything.  But that's a separate question.  WSGI will allow you to 
be asynchronous if you really want to, no matter how bad an idea it might 
be in some cases.  :)

>The twisted rot-13 component would then have very thin methods (run from 
>the server's main thread) which interact with the twisted space i.e. 
>transferring data and receiving data back through queues, and layer WSGI 
>semantics on those interactions, i.e. pause_output, yield result, yield 
>empty_string, etc.

You're pretty much describing what I suggested earlier: that async app 
frameworks like Twisted may want to have a model whereby a generic "thin 
wrapper" WSGI application object is used to communicate with an application 
that's written using the underlying framework's async idioms.  So, for 
example, one might perhaps design a Twisted "Transport" that was 
implemented as a WSGI application.  (I don't know if "Transport" is really 
the correct abstraction to use, I'm just giving an example here.)

Anyway, for such a thing to really work, I think you might need 
server-specific reactor plugins, to integrate Twisted's event loop with 
that of the server.

>When I described your approach as "pulling data up the stack", I saw a 
>bigger difference between the two approaches. I'm thinking now that there 
>is little difference between our proposals, except that in mine it's the 
>bottom component that gets notified of the input by the server, and in 
>yours it's the top component. Though I suppose having the top component 
>pulling input from an iterator chain mirrors nicely the situation where 
>the server pulls output from an iterator chain.

Actually, I'm saying you pull data *down* the stack.  The bottom-most 
application iterator calls 'read()' on an input stream provided by a parent 
middleware component, which then calls read on a higher-level component, 
and so on.

>And my approach basically entails a bunch of nested calls, which might be 
>less efficient elegant than if, say, generators were used in an input 
>processing chain.
>
>You're right again Phillip :-)

Not entirely, actually.  For my approach to really work, the middleware 
would have to be guaranteed to return something from read(), as long as the 
parent's read() returns something.  Otherwise, the resumption would block, 
unless the middleware were much smarter.  I've got to think about it some 
more, because right now I'm still not happy with the specifics of any of 
the proposals for pausing and resuming output.

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Phillip J. Eby | 17 Sep 2004 00:59
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 06:37 PM 9/16/04 -0400, Phillip J. Eby wrote:
>Not entirely, actually.  For my approach to really work, the middleware 
>would have to be guaranteed to return something from read(), as long as 
>the parent's read() returns something.  Otherwise, the resumption would 
>block, unless the middleware were much smarter.  I've got to think about 
>it some more, because right now I'm still not happy with the specifics of 
>any of the proposals for pausing and resuming output.

Aha!  There's the problem.  The 'read()' protocol is what's wrong.  If 
'wsgi.input' were an *iterator* instead of a file-like object, it would be 
fairly straightforward for async servers to implement "would block" reads 
as yielding empty strings.  And, servers could actually support streaming 
input via chunked encoding, because they could just yield blocks once 
they've arrived.

The downside to making 'wsgi.input' an iterator is that you lose control 
over how much data to read at a time: the upstream server or middleware 
determines how much data you get.  But, it's quite possible to make a 
buffering, file-like wrapper over such an iterator, if that's what you 
really need, and your code is synchronous.  (This will slightly increase 
the coding burden for interfacing applications and frameworks that expect 
to have a readable stream for CGI input.)  For asynchronous code, you're 
just going to invoke some sort of callback with each block, and it's the 
callback's job to deal with it.

What does everybody think?  If combined with a "pause iterating me until 
there's input data available" extension API, this would let the input 
stream be non-blocking, and solve the chunked-encoding input issue all in 
one change to the protocol.  Or am I missing something here?

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Alan Kennedy | 17 Sep 2004 01:04
Favicon

Re: [Twisted-web] Re: WSGI woes

[Alan Kennedy]
>> When I described your approach as "pulling data up the stack", I saw a 
>> bigger difference between the two approaches. I'm thinking now that 
>> there is little difference between our proposals, except that in mine 
>> it's the bottom component that gets notified of the input by the 
>> server, and in yours it's the top component. Though I suppose having 
>> the top component pulling input from an iterator chain mirrors nicely 
>> the situation where the server pulls output from an iterator chain.

[Phillip J. Eby]
> Actually, I'm saying you pull data *down* the stack.  The bottom-most 
> application iterator calls 'read()' on an input stream provided by a 
> parent middleware component, which then calls read on a higher-level 
> component, and so on.

Hmm. That only makes sense to me if your stacks grow downwards :-)

In my mental picture, stacks grow upwards. The server is level ground, 
and each middleware component is placed on top of the other, with the 
"most wrapped" component at the top.

So to me what your description above says is that the component closest 
to the server is the one that gets to see the input last, after all the 
more wrapped components, with the most wrapped component getting first 
dibs on the input. Which doesn't make sense to me.

Perhaps your stacks grow downwards?

Anyway, I *think* we're talking about the same thing.

Which leads onto the next question: Why not insist on an iterable for 
the input stream as well as the output stream. It appears to me that 
there should be symmetry between the output write()/iterable split and 
the input read()/iterable split.

Regards,

Alan.
_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Phillip J. Eby | 17 Sep 2004 01:08
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 12:04 AM 9/17/04 +0100, Alan Kennedy wrote:
>Which leads onto the next question: Why not insist on an iterable for the 
>input stream as well as the output stream. It appears to me that there 
>should be symmetry between the output write()/iterable split and the input 
>read()/iterable split.

Looks like you had the same "aha" as I just did a few minutes ago, so I'll 
take your comment as a +1 on that approach.  :)

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Alan Kennedy | 17 Sep 2004 00:12
Favicon

Re: [Twisted-web] Re: WSGI woes

[Alan Kennedy]
> Though I suppose having the top 
> component pulling input from an iterator chain mirrors nicely the 
> situation where the server pulls output from an iterator chain.

Which also means that the top component must be prepared to receive "" 
from the component below it in the input chain.

Say for example that the headers for a new chunk body arrive on the 
client socket, but not a chunk-encoded body, yet.

The top iterator, e.g. the uploaded-file processor, pulls data from the 
component below it, which is say the dechunker. The dechunker will read 
the headers and get the relevant metadata for the chunk. But since there 
is no actual data available now, it must yield "" to the next component up.

I was wondering if we might need to mirror the pause/resume facility on 
the input stream. But it's not a required, because the application is 
getting a callback directly from the server when there is data 
available. It's just that the data on socket that gave rise to the 
notification may not translate to actual data for the called application.

Regards,

Alan.

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Donovan Preston | 17 Sep 2004 02:39

Re: [Twisted-web] Re: WSGI woes


On Sep 16, 2004, at 1:41 PM, Phillip J. Eby wrote:

>     resume = environ['wsgi.pause_output']()
>
> Where 'resume' is then a callback function that can be invoked to 
> resume iteration.  This keeps it to a single extension key, helps 
> ensure the correct sequence of actions, and makes it easier to 
> implement in some cases, while not making other cases any harder.

Well, I guess I sparked some discussion here. Great! I am +1 on the 
above construct, calling pause_output and yielding an empty string. I'm 
glad this technique came up because I hadn't paid enough attention to 
the environ dict and how it could be used to do something like this.

I think with servers providing a pause_output callable like this, 
asynchronous applications will be possible and the isolation between 
the layers can be preserved. I am going to try writing some code using 
this construct and provide further feedback after I do.

dp

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Phillip J. Eby | 23 Sep 2004 03:01
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 08:39 PM 9/16/04 -0400, Donovan Preston wrote:

>On Sep 16, 2004, at 1:41 PM, Phillip J. Eby wrote:
>
>>     resume = environ['wsgi.pause_output']()
>>
>>Where 'resume' is then a callback function that can be invoked to resume 
>>iteration.  This keeps it to a single extension key, helps ensure the 
>>correct sequence of actions, and makes it easier to implement in some 
>>cases, while not making other cases any harder.
>
>Well, I guess I sparked some discussion here. Great! I am +1 on the above 
>construct, calling pause_output and yielding an empty string. I'm glad 
>this technique came up because I hadn't paid enough attention to the 
>environ dict and how it could be used to do something like this.
>
>I think with servers providing a pause_output callable like this, 
>asynchronous applications will be possible and the isolation between the 
>layers can be preserved. I am going to try writing some code using this 
>construct and provide further feedback after I do.

So...  how'd it work out?  :)

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Phillip J. Eby | 17 Sep 2004 03:02
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 08:39 PM 9/16/04 -0400, Donovan Preston wrote:

>On Sep 16, 2004, at 1:41 PM, Phillip J. Eby wrote:
>
>>     resume = environ['wsgi.pause_output']()
>>
>>Where 'resume' is then a callback function that can be invoked to resume 
>>iteration.  This keeps it to a single extension key, helps ensure the 
>>correct sequence of actions, and makes it easier to implement in some 
>>cases, while not making other cases any harder.
>
>Well, I guess I sparked some discussion here. Great! I am +1 on the above 
>construct, calling pause_output and yielding an empty string. I'm glad 
>this technique came up because I hadn't paid enough attention to the 
>environ dict and how it could be used to do something like this.
>
>I think with servers providing a pause_output callable like this, 
>asynchronous applications will be possible and the isolation between the 
>layers can be preserved. I am going to try writing some code using this 
>construct and provide further feedback after I do.

Keep in mind that this is proposed as an optional construct, so if the 
server doesn't provide it, the application iterable will either need to be 
okay being next()-ed repeatedly, or else "go synchronous" and either do the 
work in-thread or block on a queue from the I/O thread.

And, until I get some feedback on the other part of this (making 
'wsgi.input' an iterator too, and having a way to "pause until input"), I'm 
not ready to add this to the PEP as 'wsgi.pause_output'.  But again, 
nothing stops a server from providing e.g. a 'twisted.pause_output' 
extension API, with whatever semantics you'd like it to have.

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

William Dode | 16 Sep 2004 10:58
Favicon

Re: [Twisted-web] Re: WSGI woes

"Phillip J. Eby" <pje@...> writes:

> At 01:13 AM 9/16/04 -0400, Donovan Preston wrote:
>
>>On Sep 15, 2004, at 7:12 PM, Phillip J. Eby wrote:
>>
>>>At 06:48 PM 9/15/04 -0400, Peter Hunt wrote:
>>>>It looks like WSGI is not well received over at twisted.web.
>>>>
>>>>http://twistedmatrix.com/pipermail/twisted-web/2004-September/ 000644.html
>>>
>>>Excerpting from that post:
>>>
>>>"""The WSGI spec is unsuitable for use with asynchronous servers and
>>>applications. Basically, once the application callable returns, the
>>>server (or "gateway" as wsgi calls it) must consider the page finished
>>>rendering."""
>>>
>>>This is incorrect.
>>
>>As I said in my original post, I hadn't mentioned anything about this
>>yet because I didn't have a solution or proposal to fix the problem,
>>which I maintain remains.
>
> Reading the rest of your post, I see that you are actually addressing
> the issue of asynchronous *applications*, and I have only been
> addressing asynchronous *servers* in the spec to date.  (Technically
> "half-async" servers, since to be properly portable, a WSGI server
> *must* support synchronous applications, and therefore an async WSGI
> server must have a thread pool for running applications, even if it
> contains only one thread.)
>
> However, I'm not certain that it's actually possible to support
> *portable* asynchronous  applications under WSGI, since such
> asynchrony requires additional support such as an event loop service.

Like others, i did my litle framework who can work on top of twisted,
cgi or BaseHTTPServer. So it's possible ;-)
But it doesn't mean that i whant to run my application on any
server. Generaly i use twisted server when i have specials need, like
telnet, irc... So this application will not run under cgi. But i like
to can reuse quickly somes litle cgi application under twisted.
I need the same framework for all the servers to can share 90% of my
api, to map the url to a resource, for session, cookies...

So, i hope we can find a solution to run simple application anywhere,
and to be open for very specific uses.

Sorry, because of my poor english, i cannot help a lot in the
discussion...

--

-- 
William Dodé - http://flibuste.net
_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Phillip J. Eby | 16 Sep 2004 17:27
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 10:58 AM 9/16/04 +0200, William Dode wrote:
>But it doesn't mean that i whant to run my application on any
>server. Generaly i use twisted server when i have specials need, like
>telnet, irc... So this application will not run under cgi. But i like
>to can reuse quickly somes litle cgi application under twisted.
>I need the same framework for all the servers to can share 90% of my
>api, to map the url to a resource, for session, cookies...
>
>So, i hope we can find a solution to run simple application anywhere,
>and to be open for very specific uses.

As I said, WSGI should let any WSGI application run under more 
sophisticated architectures like Twisted; it's just that an application 
that uses Twisted-specific features isn't going to be able to move to a 
server that's not Twisted-compatible.

And, if you're using Twisted-specific features in a WSGI app (as opposed to 
just writing a pure Twisted app), you'll have some additional work needed 
to deal with the asynchrony.  However, the only reason I can think of why 
you'd want to make such an application use the WSGI interface is if you 
wanted to be able to use WSGI-based middleware features.  At some point, 
that may be attractive, but I really doubt that in the short term anybody 
using Twisted-specific features in an application would want to bother with 
making it WSGI-compatible.

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Peter Hunt | 17 Sep 2004 01:16
Picon

Re: [Twisted-web] Re: WSGI woes

Alan, that design looks okay. A bit complex, but it works well once
you sit down to look at it.

It would be nice if applications that didn't need a separate thread
didn't use one up, so performance-oriented programmers (like the
Twisted/Nevow guys) won't be able to have that excuse. Perhaps
start_response() could have a "threaded" boolean optional argument
that defaults to true which decides whether or not the iterable will
be called in a separate thread. This, of course, requires that the
application callable itself doesn't have any blocking code.

Does this requirement overcomplicate things?
_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Phillip J. Eby | 17 Sep 2004 01:57
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 07:16 PM 9/16/04 -0400, Peter Hunt wrote:
>Alan, that design looks okay. A bit complex, but it works well once
>you sit down to look at it.
>
>It would be nice if applications that didn't need a separate thread
>didn't use one up, so performance-oriented programmers (like the
>Twisted/Nevow guys) won't be able to have that excuse. Perhaps
>start_response() could have a "threaded" boolean optional argument
>that defaults to true which decides whether or not the iterable will
>be called in a separate thread. This, of course, requires that the
>application callable itself doesn't have any blocking code.
>
>Does this requirement overcomplicate things?

Yes.  The vast majority of existing web applications are synchronous, and 
so are a significant number of Python web server environments that would 
run WSGI applications.  Therefore the WSGI "common case" is to have 
synchronous behavior, and WSGI is most efficient with either a synchronous 
server/gateway, or a "half-async" server/gateway (i.e., one that runs 
application code in a thread pool, separate from the main I/O thread.)

The few applications that can behave in a non-blocking fashion, can and 
should use the iterable interface to provide their output, producing empty 
strings when they are not yet ready to produce output.  (Plus, when such 
applications are run in a synchronous server or gateway, they might as well 
behave synchronously, since they will actually incur more overhead by 
trying to be asynchronous!)

The only scenario that isn't served by this approach is a single-threaded, 
asynchronous server with no threading capability.  However, such a server 
*cannot* be WSGI-compatible and still serve multiple requests, and there is 
no way around that without forcing *every* application to be asynchronous, 
which just isn't an acceptable tradeoff.  The idea of having a flag 
(whether passed to start_response, or introspected on the application 
object, etc.) doesn't help the fact that the server still has to be able to 
*have* multiple threads in such a case.

Note, by the way, that the need for a second thread is caused by having a 
possible difference between the synchrony model of a server and an 
application.  That is, if both are synchronous or both are asynchronous, no 
threading is required.  However, a server is not limited to running just 
*one* application, so in the general case, a given server has to be able to 
handle both.

However, since the common case is for apps to be synchronous, then the 
common case for an asynchronous server is that it must be threaded, and the 
common case for a synchronous server is that it need not be 
threaded.  Thus, logically, the case of an asynchronous application is the 
"odd one out", in the sense that it is the only one that ever forces 
additional threading, beyond what was inherently required for that server 
model.

In other words, an async server has to have threading in the common case, 
and a synchronous application doesn't.  So, an async app in an async server 
doesn't *add* any threading requirement: the async server already has to 
have an I/O thread and at least one application thread.  And a synchronous 
app doesn't add any additional threading requirements to either kind of 
server, for the same reason.  Only an asynchronous application in a 
synchronous server forces any extra overhead beyond the effective default 
required threading configuration.  Thus, it makes sense (to me, anyway) to 
in that case put the burden on the asynchronous application to manage 
communication with its extra thread, if any, or to have it adapt to local 
circumstances and behave synchronously (since that's more efficient in that 
case).

But in the end, all of this comes down to a basically simple idea: I think 
that in WSGI, synchronous applications should be simple, and asynchronous 
applications possible, because that will best support the goals of the PEP.

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Peter Hunt | 17 Sep 2004 02:37
Picon

Re: [Twisted-web] Re: WSGI woes

Yes, but an async app running in an async server in a thread is
overkill, don't you think? We don't need to spawn an extra thread to
run it. I'm not talking about "possible", I'm talking about "optimal".

On Thu, 16 Sep 2004 19:57:59 -0400, Phillip J. Eby
<pje@...> wrote:
> 
> 
> At 07:16 PM 9/16/04 -0400, Peter Hunt wrote:
> >Alan, that design looks okay. A bit complex, but it works well once
> >you sit down to look at it.
> >
> >It would be nice if applications that didn't need a separate thread
> >didn't use one up, so performance-oriented programmers (like the
> >Twisted/Nevow guys) won't be able to have that excuse. Perhaps
> >start_response() could have a "threaded" boolean optional argument
> >that defaults to true which decides whether or not the iterable will
> >be called in a separate thread. This, of course, requires that the
> >application callable itself doesn't have any blocking code.
> >
> >Does this requirement overcomplicate things?
> 
> Yes.  The vast majority of existing web applications are synchronous, and
> so are a significant number of Python web server environments that would
> run WSGI applications.  Therefore the WSGI "common case" is to have
> synchronous behavior, and WSGI is most efficient with either a synchronous
> server/gateway, or a "half-async" server/gateway (i.e., one that runs
> application code in a thread pool, separate from the main I/O thread.)
> 
> The few applications that can behave in a non-blocking fashion, can and
> should use the iterable interface to provide their output, producing empty
> strings when they are not yet ready to produce output.  (Plus, when such
> applications are run in a synchronous server or gateway, they might as well
> behave synchronously, since they will actually incur more overhead by
> trying to be asynchronous!)
> 
> The only scenario that isn't served by this approach is a single-threaded,
> asynchronous server with no threading capability.  However, such a server
> *cannot* be WSGI-compatible and still serve multiple requests, and there is
> no way around that without forcing *every* application to be asynchronous,
> which just isn't an acceptable tradeoff.  The idea of having a flag
> (whether passed to start_response, or introspected on the application
> object, etc.) doesn't help the fact that the server still has to be able to
> *have* multiple threads in such a case.
> 
> Note, by the way, that the need for a second thread is caused by having a
> possible difference between the synchrony model of a server and an
> application.  That is, if both are synchronous or both are asynchronous, no
> threading is required.  However, a server is not limited to running just
> *one* application, so in the general case, a given server has to be able to
> handle both.
> 
> However, since the common case is for apps to be synchronous, then the
> common case for an asynchronous server is that it must be threaded, and the
> common case for a synchronous server is that it need not be
> threaded.  Thus, logically, the case of an asynchronous application is the
> "odd one out", in the sense that it is the only one that ever forces
> additional threading, beyond what was inherently required for that server
> model.
> 
> In other words, an async server has to have threading in the common case,
> and a synchronous application doesn't.  So, an async app in an async server
> doesn't *add* any threading requirement: the async server already has to
> have an I/O thread and at least one application thread.  And a synchronous
> app doesn't add any additional threading requirements to either kind of
> server, for the same reason.  Only an asynchronous application in a
> synchronous server forces any extra overhead beyond the effective default
> required threading configuration.  Thus, it makes sense (to me, anyway) to
> in that case put the burden on the asynchronous application to manage
> communication with its extra thread, if any, or to have it adapt to local
> circumstances and behave synchronously (since that's more efficient in that
> case).
> 
> But in the end, all of this comes down to a basically simple idea: I think
> that in WSGI, synchronous applications should be simple, and asynchronous
> applications possible, because that will best support the goals of the PEP.
> 
>
_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

Phillip J. Eby | 17 Sep 2004 02:58
Gravatar

Re: [Twisted-web] Re: WSGI woes

At 08:37 PM 9/16/04 -0400, Peter Hunt wrote:
>Yes, but an async app running in an async server in a thread is
>overkill, don't you think? We don't need to spawn an extra thread to
>run it. I'm not talking about "possible", I'm talking about "optimal".

Nothing in the spec stops an async server from providing a configuration 
option to say, "this app+middleware combination is completely non-blocking, 
so don't bother running it in a separate thread".  I've just been speaking 
about the general case, and what the server is required to do to support 
the general case of "an arbitrary WSGI application", with no additional 
information.

In the same way, nothing in the spec stops servers from providing 
per-application configuration options for any number of extended behaviors; 
WSGI is a starting point for server capabilities, not an ending point.

Still, I will admit that I tend to speak of things almost as if WSGI were 
an ending point, because I just assume we're talking about what the spec 
should or should not *require* or *forbid*.  When a use case doesn't need 
any "musts" or "must nots" added (like your use case above), I tend not to 
focus on it directly, because it seems obvious to me that anybody can add 
it on if they like, as a server-specific extension.

So, this may lead sometimes to people getting the impression WSGI doesn't 
allow a use case that in fact it does; it's just that the use case should 
be implemented using an optional extension, rather than being considered a 
common case and made into a requirement.  If I tried to enumerate every 
possible optional extension to WSGI, I'd go mad sooner than you can say 
"Content-Transfer-Encoding".  :)

_______________________________________________
Web-SIG mailing list
Web-SIG@...
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: http://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org


Gmane