Wednesday, November 2, 2011

New Relic is not just for Apache/mod_wsgi.

A number of people have expressed an interest in using the Python agent with New Relic but they didn't want to have to use Apache/mod_wsgi. No such restriction actually exists. The New Relic Python agent should work with any WSGI 1.0 compliant web server.

In addition to Apache/mod_wsgi, we already have people using it with gunicorn, uWSGI, the Tornado WSGI container, CherryPy WSGI server and FASTCGI/flup. In the case of gunicorn you can also use eventlet and gevent modes as the Python agent will adapt and automatically detect whether threads or greenlets are being used where necessary. It is even possible to run the Python agent when using the Django development server although why you would want to do that except to prove it is possible, as I did, I am not sure.

The only restriction we do know of at present is when using uWSGI. uWSGI still appears that it may have some issues with multithreading when using Python sub interpreters. In the case of uWSGI therefore, in addition to needing to supply '--enable-threads' to the 'uwsgi' program to initialise threading in the Python core and so allow additional background Python threads to be created, you also need to use the '--single-interpreter' option to ensure the main Python interpreter is used. I have been conversing with the uWSGI author about this and when I get the time I will sit down and audit the uWSGI code and provide some advice about use of Python thread state objects and sub interpreters gleaned from all my work on doing the same in mod_wsgi.

As far as web frameworks go the best supported is Django. We also have specific support to varying degrees for Bottle, CherryPy, Flask, Pylons and Web2py. We have users for all of those except for Web2py. Pyramid is the next on our list to add support for.

As well as instrumenting the frameworks themselves, a number of other Python modules for databases, memcache and template systems also have special treatment so can track time spent there. We will slowly be building out support for more and more packages as interest is expressed. The next on our list here is support for Celery.

If you are rolling your own web application from component libraries such as Werkzeug, Paste and WebOb, then because you will have a custom structure for URL routing and handler execution, it is not possible to automatically collect certain data we need to make the results more useful. In these cases you will need to make use of the APIs for the Python agent to perform tasks such as naming web transactions and recording exceptions which would otherwise be converted to a 500 error page response internally to your web application.

When it comes to Python web hosting services, we know of the Python agent being used on WebFaction, dotcloud, Heroku and ep.io. It is also possible to use the Python agent with Stackato.

Various documentation can be found on all of this on the New Relic knowledge base and busy working on more which will be up soon.




Friday, October 28, 2011

What excited me about New Relic.

It has been a busy year for me this year with lots of work to do and numerous overseas trips and Python conferences. In the end I spent almost three months away from home. Being so busy I haven't really said anything on my blog about what I am up to at my new job at New Relic. Well, not so new of a job now as has been almost a year since I started.

This time last year I had already been looking for a new job for about 8 months with only minimal leads. I had given my prior job lots of notice that I wasn't going to renew my contract after eight and half years, although I did say that if found something sooner I would leave early. I didn't expect to actually see the full contract period out, but that is what happened. Even a few weeks out from the end of the contract at the end of November I still hadn't found anything. Out of the blue though someone recommended me to New Relic. When I started looking at what New Relic did my reaction was 'oh wow, this is so awesome'.

The reason I found what New Relic was doing so exciting was that a recurring question I would get from users of mod_wsgi was on how to configure it to work best for a specific site. Although I could wave my hands around and prognosticate about it, ultimately the answer was always 'it depends on your specific web application'. In short, I couldn't tell them because I had absolutely no insight into what their application was doing, and neither did they usually. In all probability the real problems were going to be their application, database or client side page rendering times and not how mod_wsgi had been set up.

The problem here was that people would too often assume that all their problems would be solved magically somehow by finding the fastest web server or configuration on the planet. Their weapon of choice in trying to resolve their problems was to run hello world benchmarks against different web servers and then picking which looked better based on some arbitrary requirement. Too many times I could only watch and shake my head at people because although I knew they were barking up the wrong tree, I didn't know of any good production grade tools for Python web applications I could point them at which could be used to show them where the real bottlenecks were in their stack.

I had at various times tried to instrument mod_wsgi to collect data about number of concurrent request executing and other measures as a way of trying to determine whether a specific configuration was working, but the stumbling block was always the lack of a decent infrastructure to collect and display the results of that. Just dumping out occasional statistics to the Apache error log wasn't going to fly and was only ever going to be an amateurish attempt. As a result I pulled out the instrumentation code from mod_wsgi that I had added as didn't want such a half baked solution, especially one where people may still be dependent on me to try and interpret it.

So how did I see New Relic even being able to help? Well the best way to illustrate this is to just look at some of the charts that the New Relic UI can produce from data which is generated from your application.


This is the main overview chart in the New Relic UI and gives a good heads up break down of where time is being spent in your application. Straight away in this example we can already start to ponder why so much time is being spent doing database queries and memcache calls. Is it because of the number being made within any one request or are there network delays or performance issues with the database and memcache system.

Obviously this one chart isn't going to answer those questions but that is where one can start to drill down further using the New Relic UI into the data and look at the different database queries and also at individual transaction traces for sampled slow transactions.

Now you might be saying so what, Django debug toolbar can do these sorts of thing. Yes it can, but there is one important difference. That is that Django debug toolbar only looks at a single web transaction and you should only be running it in a development environment. The chart shown above represents live data streaming out your production web application. So not only can it show aggregated performance metrics over time, it is based on real requests from real customers from your production deployed web application.

This specific chart, insightful as it is, leaves off one thing that got me quite excited as far as the perpetual question of how best to configure mod_wsgi. For that lets look at another example.


In this chart we are also displaying what is labelled as request queueing time. This isn't actually time spent in the web application itself, but is recording the time the request spent waiting to be handled by the web application.

For the case of mod_wsgi in daemon mode this would be the time between when the Apache child worker process first accepted the request and when the request was able to be proxied across to the mod_wsgi daemon process and be handled.

For a well tuned web application the request queueing time should be negligible and ideally in the order of 1ms. Obviously in this case something quite bad was going wrong to cause such spikes in queueing time.

The prime candidate for what could be wrong is that the number of processes/threads which have been configured for mod_wsgi daemon mode was inadequate for the number of concurrent requests being handled. In other words, get too many concurrent requests and you run out of processes/threads to handle them. As a result the requests start to backlog after being initially accepted by the Apache child worker processes. Eventually the daemon processes catch up and finish the requests they were handling and the queued requests get handled, but the user has had to suffer the delay in the request being handled.

Now under normal circumstances for this site the number of configured processes/threads was more than adequate. As it turned out, although that is what showed as the symptom, it was exacerbated by an underlying factor. This was that due to unknown reasons at the time, resolution of host names with a DNS server was some times taking a long time. This would affect a batch of requests at one time and all would appear to hang for a period and as this happened the available processes/threads would get used up and the backlog occur.

So even this one chart in the New Relic UI can say a lot already and yet it only scratches the surface as far as the full set of features available. The only problem was that they didn't support Python and it wasn't even on the radar at that time. It was partly in the hope I guess that they would support Python that I was recommended to them by one of their existing customers who used their product on other other languages.

Needless to say I saw all this and thought I gotta work on this somehow. Luckily the New Relic guys could see I grokked what they were doing and by the time I did talk to them had even already worked out a rough idea in my head of how one would go about doing it for Python.

Bringing all this awesomeness to the Python web community is therefore what I have been working on this year and it certainly has kept me busy. We started our public Beta in conjunction with DjangoCon back in September and I gave a presentation at DjangoCon as well talking about some of the low level techniques one has to deal with in trying to instrument and monkey patch existing code.

At this point the frantic pace is starting to ease up a little so I will be starting to say more about the Python agent and New Relic here on my blog and also on the New Relic blog site, so keep an eye out. Even if I wasn't working there and the guy getting to implement it, I know I would still be excited about what this is going to bring to the Python web community and hopefully you will as well and at least give it a once over.

A final word. Although New Relic is a paid for service with varying subscription levels, you do get a trial period where you can try it out and get access to all the features. Even at the end of that period it will revert to a free Lite subscription level which still provides you with some of the main charts, including the one shown above. So, is a great opportunity to get some instant insight into what your web application is doing at a deep level and even if you don't want to sign up for the higher subscription levels, the Lite version still may prove useful in at least giving you that higher level view of what is going on.





Tuesday, September 20, 2011

Why is WSGI deployment under FASTCGI so painful?

Out of all the deployment methods for Python WSGI applications, the one which seems to generate the most trouble is FASTCGI deployment. So much so that the general advice one often sees in relation to FASTCGI is that one should simply avoid it. In some cases people will go as far as justifying this by saying that FASTCGI is old technology and the cool kids aren't using it so you shouldn't either.

What is reality here. Well, FASTCGI may be old technology but it does work. If it didn't work the bulk of the PHP web sites out there, which there are many many more of than Python web sites, would be falling over left, right and centre. They don't though, so the issue isn't FASTCGI but for Python at least it is the deployment experience. Put simply, PHP provides a simple deployment path where as no one has really gone out of there way to provide a pre canned FASTCGI integration for WSGI which service providers can set up easily and make the users life better.

To try and understand where the problems lie I will go through the setup required for running a Python WSGI script under mod_fcgid, these days the preferred FASTCGI hosting solution for Apache. Unlike other blogs out there I am not just going to present the final recipe, but actually explain the pain points which people seem to encounter and why they arise.

The FASTCGI Script

In the case of Apache/mod_wsgi, all a user need do is drop a WSGI script file into a directory and either map a URL to it using the WSGIScriptAlias directive, or have it automatically mapped to based on its file system location and extension mapping provided by the AddHandler directive. A simple hello world WSGI script file suitable for Apache/mod_wsgi would be as follows.

def application(environ, start_response):
    status = '200 OK'
    output = 'Hello World!'
    response_headers = [('Content-type', 'text/plain'),
                        ('Content-Length', str(len(output)))]
    start_response(status, response_headers)
    return [output]

Important to note here is that the WSGI script file only contains the WSGI application entry point which defines the programmatic API for communicating with the WSGI application. The WSGI script file does not say anything about how the WSGI application gets started or how requests are then passed off to it, this is the job of the hosting container (mod_wsgi) to manage.

In contrast, for mod_fcgid, instead of a WSGI script file like above, it is necessary to provide an executable program. This program must start itself up and must have knowledge of how to communicate back to mod_fcgid across a socket using the FASTCGI wire protocol.

Now it would indeed be really painful if users had to implement this wire protocol themselves from scratch, but luckily they don't. There are actually a few Python packages available which implement an adapter bridging between the FASTCGI socket wire protocol and the programmatic WSGI application interface. The only one of these WSGI adapters for FASTCGI that seems to be used these days is flup. Using flup, the above WSGI script file would be replaced with the following FASTCGI program.


#!/usr/bin/env python

def application(environ, start_response):
    status = '200 OK'
    output = 'Hello World!'
    response_headers = [('Content-type', 'text/plain'),
                        ('Content-Length', str(len(output)))]
    start_response(status, response_headers)
    return [output]

if __name__ == '__main__':
    from flup.server.fcgi import WSGIServer
    WSGIServer(application).run()

When this is executed as a program, __name__ will equate to '__main__' and so the flup WSGI server will be imported and started. The process will then stay persistent and handle the request which triggered it to be started and any subsequent requests.

Seems simple enough, what can possibly go wrong.

Configuration And Startup

Having loaded mod_fcgid into Apache using:


LoadModule fcgid_module modules/mod_fcgid.so

you still need to tell Apache that the program must be processed by mod_fcgid. This can be done in number of different ways. The most often used is to specify that any resource ending with a '.fcgi' extension should be handled by mod_fcgid. This would be done using the AddHandler directive.


<Directory /usr/local/www/htdocs>
Order deny, allow
Allow from All
AddHandler fcgid-script .fcgi
</Directory>

So access control has been defined and the mapping for the extension is defined. Presuming that the directory corresponded to the DocumentRoot for the server and the FASTCGI program was called 'hello.fcgi', we would use the URL:


http://localhost/hello.fcgi

It fails though with the error in the browser of:


Forbidden
You don't have permission to access /hello.fcgi on this server.

But we set access control so what can it be? In this case the problem is that being a script it is necessary to tell Apache that it is okay to be able to execute scripts out of that directory. To do this we need to turn on the ExecCGI option.


<Directory /usr/local/www/htdocs>
Order deny, allow
Allow from All
Options ExecCGI
AddHandler fcgid-script .fcgi
</Directory>

This sort of setup would be done by the Apache administrator and for a shared hosting service they should know what is required and just get it right in the first place. So try again.


Internal Server Error
The server encountered an internal error or misconfiguration and was unable to complete your request.
Please contact the server administrator, you@example.com and inform them of the time the error occurred, and anything you might have done that may have caused the error.
More information about this error may be available in the server error log.

At least this time we get an error in the Apache error logs.

[info] mod_fcgid: server example.com:/usr/local/www/htdocs/hello.fcgi(30887) started
[info] mod_fcgid: process /usr/local/www/htdocs/hello.fcgi(30887) exit(communication error), terminated by calling exit(), return code: 255


You have to remember though that on a shared hosting service a user isn't going to have access to the Apache error logs, all they will see is the message in the browser. It is possible they may have access to the error log for their own virtual host, but mod_fcgid has a tendency to push such error messages to the main Apache error log and not that for the virtual host. Even then, if they do manage to get some one handling support for the hosting service to try and dig the message out of the Apache error logs, it is almost as unhelpful as that displayed in the browser.

One can start to see the frustration that people can experience.

The cause of this cryptic error message in this case was the permissions on our actual FASTCGI program.


8 -rw-r--r--  1 graham  admin  430 20 Sep 21:10 hello.fcgi

Because it is going to be executed as a program, we need to ensure that it is executable. So we need to do:


chmod +x hello.fcgi



to yield:

8 -rwxr-xr-x  1 graham  admin  430 20 Sep 21:10 hello.fcgi

Note here that I at least have made the file readable to others and the directories this is contained in is also readable to others. If this was not the case and the directory and/or files were not accessible/readable to the user that Apache runs as then you encounter even more errors. Lets quickly go back and revisit them.

If the directory the file is in isn't accessible to the user that Apache runs as, then you will get 'Forbidden' in the browser and in the Apache error logs you will get:

[error] [client 127.0.0.1] (13)Permission denied: access to /hello.fcgi denied

If instead the directory was accessible and the file was not readable to the Apache user, you will instead get 'Internal Server Error' in the browser and in the Apache error logs you will get:

[warn] [client 127.0.0.1] mod_fcgid: error reading data, FastCGI server closed connection
[error] [client 127.0.0.1] Premature end of script headers: hello.fcgi

Is your head starting to hurt yet? And we haven't finished yet with all the things that can go wrong.

Anyway if you manage to get past all that, the 'python' executable was actually in the PATH and flup was actually installed into your Python installation then it will hopefully have worked.

The WSGI Adapter

As stated above, success will actually have depended on the flup package having been installed into the Python installation. What if it isn't? You will get 'Internal Server Error' in the browser and in the Apache error log you will get:

[info] mod_fcgid: server fcgid-1.example.com:/usr/local/www/htdocs/hello.fcgi(31130) started
Traceback (most recent call last):
  File "/usr/local/www/htdocs/hello.fcgi", line 19, in <module>
    from flup.server.fcgi import WSGIServer
ImportError: No module named flup.server.fcgi
[info] mod_fcgid: process /usr/local/www/hello.fcgi(31130) exit(communication error), terminated by calling exit(), return code: 1

I guess we at least got a traceback out of Python. Remember though, this is still in the Apache error log file and on a shared hosting service you are likely always going to have to go knock on the door of the support staff to get it. This will be the case even if flup was installed and you made the slightest syntactical error in the file which caused it not to run.

Input And Outputs

Now we did manage to get an error message above, even so, you are probably lucky to even get that message. This is because the FASTCGI specification says:
The initial state of a FastCGI process is more spartan than the initial state of a CGI/1.1 process, because the FastCGI process doesn't begin life connected to anything. It doesn't have the conventional open files stdin, stdout, and stderr, and it doesn't receive much information through environment variables. The key piece of initial state in a FastCGI process is a listening socket, through which it accepts connections from a Web server.
After a FastCGI process accepts a connection on its listening socket, the process executes a simple protocol to receive and send data. The protocol serves two purposes. First, the protocol multiplexes a single transport connection between several independent FastCGI requests. This supports applications that are able to process concurrent requests using event-driven or multi-threaded programming techniques. Second, within each request the protocol provides several independent data streams in each direction. This way, for instance, both stdout and stderr data pass over a single transport connection from the application to the Web server, rather than requiring separate pipes as with CGI/1.1.
Technically this means that prior to the point that you actually manage to start up the FASTCGI communications channel, there is no stderr or stdout. If a FASTCGI server sticks to the specification that error message likely wouldn't have any where to go and behaviour would be completely unpredictable. Most likely the process would just crash. Luckily mod_fcgid preserves stderr, with stderr mapping into the Apache error log file.

What about stdout?

As it happens, mod_fcgid also ignores the specification for stdout and it also maps to the Apache error log. There is a catch though in that stdout is buffered. If you therefore were to add 'print' statements into your code to try and generate debugging output to find out what is going on, they will appear not to even be written to the Apache error logs. For the messages to appear, stdout has to either be explicitly flushed, the buffer fill up, or the process cleanly exited.

So, if you are going to use print statements for debugging, then make sure you direct them to stderr and not the default of stdout. Better still, use the logging module and set up a logging handler to direct it to you own log file, one you can actually access and not be dependent on someone else to get stuff out of.

Finally there is stdin.  Here the relevant part of the FASTCGI specification is:
The Web server leaves a single file descriptor, FCGI_LISTENSOCK_FILENO, open when the application begins execution. This descriptor refers to a listening socket created by the Web server. 
FCGI_LISTENSOCK_FILENO equals STDIN_FILENO. The standard descriptors STDOUT_FILENO and STDERR_FILENO are closed when the application begins execution. A reliable method for an application to determine whether it was invoked using CGI or FastCGI is to call getpeername(FCGI_LISTENSOCK_FILENO), which returns -1 with errno set to ENOTCONN for a FastCGI application.
Remember above how it says that stdin doesn't exist, well guess what the file descriptor got replaced with. Yes, the socket over which FASTCGI communication occurs.

Okay, so you don't think you are touching stdin, so it isn't a problem. Think again. Various third party Python modules will actually perform checks on stdin to see whether it is a tty or has some other properties. Based on that the code may change its behaviour. You just want to hope those packages don't prod too deep, or are at least resilient to errors if they occur due to the underlying file descriptor actually being a socket connection and not a normal file object.

Is It A Lost Cause?

As you can see it isn't the most user friendly deployment system out there. Yet, PHP quite successfully makes use of FASTCGI for many deployments and they don't see to have to deal with these low level issues.

Part of the reason why PHP doesn't come to grief is the way it is packaged up such that all the important stuff is handled by the system administrators. The user therefore just needs to dump stuff in a directory and it works. If there are errors there are means of easily getting the details back in the browser or via their own log.

In contrast, with Python and trying to deploy a WSGI application, it is the user who has to worry about getting the FASTCGI process up and running with flup inside to bridge between the FASTCGI protocol and the WSGI application entry point.

FASTCGI environments though are what newbies or users who know nothing about setting this stuff up encounter due to chasing the cheapest hosting they can find. So the people who are least able to likely cope with it, are given what can be the worst thing to deal with if things go wrong.

Can it be done better? Sure it could. I have though had enough of fighting with bloggers handling of vertical whitespace for now, so that will have to wait until the next instalment.















Thursday, March 24, 2011

Should my next conference talk be on how web servers work?

Last year was actually the first time I had attended a PyCon conference. This was because Australia hadn't ever hosted a PyCon conference before and everywhere else was far enough away from Australia to make it hard to justify. When Singapore announced they would be hosting their first PyCon conference however I just had to go as that part of Asia is an area I like visiting. Coincidentally Australia then announced quite late that they would also host their own PyCon a week after the Singapore PyCon. In the end I went to PyCon conferences in Singapore, Australia and New Zealand last year. This was topped off by getting the opportunity through my new job at New Relic to go to US PyCon this year.

Because of time constraints I didn't present any talk at Singapore PyCon, but did step up and do one in Sydney which I then gave again at New Zealand. I didn't do anything for US PyCon as I only found out I would be going a bit more than a month out from the conference.

The conference talk I did last year was one on getting started with mod_wsgi. The approach the talk took was to show all the things that could go wrong and explain why those situations come about. Having now attended the US PyCon, in hindsight I probably tried to pack a bit too much into the talk and one could argue it was also perhaps too instructional for a conference talk. Most talks seem to be much more high level and along the lines of 'hey look at what I am doing' rather that being a mini tutorial intended to actually teach you a lot of stuff while you are there.

For that reason, rather than try and do further highly technical talks on WSGI deployment or debugging of web applications, I thought for this year that I would instead step back a bit and do a talk on the basics of how web servers work. I realise that this isn't necessarily Python specific, but what keeps coming up out there on the various forums is that many people simply don't understand how the web server they are using actually works. Most see the web server as merely an inconvenience, something that they have to install but which they never understand enough to know how to set it up properly.

The talk I have in mind would therefore cover the common architectures that web servers use. So, topics such as threaded vs event driven systems, single process vs multi process, as well as descriptions of the different request queuing mechanisms and use of backend daemon processes for running the dynamic parts of a web application. There obviously needs to be some Python slant to all this, so would also cover how the architecture of the web server affects the type of interface used between a Python web application and the web server, plus issues such as data sharing between different parts of the Python web application. Finally, would try and cover why you would choose one server architecture over another or when you should actually employ multiple web servers with different architectures for different parts of one overall Python web application.

So my question is, is a talk about such a topic something that would be of interest out there in the Python web community? I sort of feel it is an area where there isn't a great deal of good information out there about, but I could be totally wrong.

If not the above topic then, what would people like to hear me do a talk on? Realistically I might only have time to prepare one good talk, but if there is interest in other topics, I can always try for more. I do have to work it out soon though to get in before call for papers for Singapore and Sydney PyCon conferences closes.

Friday, January 28, 2011

An awesome year for Python web hosting.

This year is shaping up to be an awesome year for Python web hosting. Not only do we have existing reliable providers like WebFaction, but we now also have a gaggle of Python specific web hosting services coming online. So many that one can easily start loosing track of who they are. As such, doing this post to list those that I know of. Are there any more that people know of that are just starting up?

The new contenders that I know of are as follows:
What will be even more awesome will be to see these teamed up with production monitoring tools like those from NewRelic. NewRelic has been building relationships with hosting services for other languages they already support, which provide people who sign up with that hosting service free access to their Bronze level subscription. The latest of these announced was for a PHP web hosting service. So, imagine how things will be if this sort of tool becomes available for the Python web community. As I say, looking to be an awesome year for Python web hosting.

And yes, some will say this is just an unashamed blatant plug for NewRelic just because I am working there now. Seriously though, I reckon this influx of new Python web hosting services, and the monitoring tools to match, is going to be a really great thing for the Python web community. I know I have not been this excited about something for a very very long time. :-)

Saturday, January 22, 2011

Testing a wsgi.file_wrapper implementation.

Not many WSGI servers or gateways have attempted to provide an implementation of the wsgi.file_wrapper extension. Of those, some provide the callable for generating the file wrapper, but then do not go on to implement any high performance optimisation. Part of the reason for this is that the Python standard library does not expose the UNIX sendfile() function. As such, a full implementation of wsgi.file_wrapper is usually restricted to WSGI server or gateway implementations implemented directly in C code.

Despite the UNIX sendfile() call not being present in the Python standard library, it still is possible in a pure Python WSGI server or gateway to fully implement wsgi.file_wrapper with optimisations. This can be done by using the ‘ctypes’ module to access and call the underlying UNIX sendfile() function. I will explain how to do that in a subsequent blog post, but before doing that I am going to describe a series of tests which can be used to validate the operation of a wsgi.file_wrapper implementation.

The first series of tests check aspects of wsgi.file_wrapper which would potentially be exercised under normal use. The remainder of the tests try and break implementations by doing unexpected things.

The tests can be used to validate an existing WSGI server or gateway that provides an implementation. They are also a good indicator of what needs to be taken into consideration when actually implementing wsgi.file_wrapper and which is why I am presenting them before an actual implementation.

Note that the tests assume that wsgi.file_wrapper exists and the code doesn’t supply its own alternative if it doesn’t. Thus if your WSGI server or gateway does not implement wsgi.file_wrapper at all, the tests will fail in the lookup of wsgi.file_wrapper in the first place.

File Objects

The standard use case is to open a file, wrap it using wsgi.file_wrapper and return it.
import os
import string

def application(environ, start_response):
status = '200 OK'

response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)

filelike = file('/tmp/filetest.txt', 'w')
filelike.write(string.ascii_lowercase)
filelike.close()

filelike = file('/tmp/filetest.txt', 'r')

return environ['wsgi.file_wrapper'](filelike)
In this case we have not supplied a Content-Length response header. As such, the expectation would be that the complete file should be returned.

The WSGI specification given in PEP 333 doesn’t make any mention about whether a WSGI server or gateway need add a Content-Length if none is supplied, however the revised PEP 3333 does, stating:

"""If the application doesn't supply a Content-Length, the server may generate one from the file using its knowledge of the underlying file implementation."""

Best practice would be that the Content-Length header is added, especially in the case that sendfile() is used to send the file in one big chunk. This is because there isn’t going to be an opportunity to apply HTTP/1.1 chunked encoding on the response content.

If an implementation isn’t attempting to use sendfile(), but instead is writing the data itself in chunks, then it could if the client protocol was HTTP/1.1, choose to use chunked transfer encoding rather than setting the Content-Length response header. As the content length can be readily calculated though, it would generally be simpler just to set the response header and send the content as is.

Do note though that if the WSGI server or gateway is setting the Content-Length response header, then it should ensure that it only sends that amount of data. This would be important where the file to be sent is being appended to, eg., a log file. In other words, once the WSGI server or gateway indicates it will send a certain amount of data, it shouldn’t then just stream till the end of file in case the size of the file has since changed.

File Like Objects

Using a standard Python file object would be the typical use case, but any file like object can technically be used. For this PEP 3333 says:

"""Note that even if the object is not suitable for the platform API, the wsgi.file_wrapper must still return an iterable that wraps read() and close(), so that applications using file wrappers are portable across platforms."""

This actually applies in two ways. The first is if the platform itself doesn’t provide a way of dealing with a file object in a performant way, or if a file like object doesn’t provide the attributes which would allow a specific mechanism to be used.

The Windows operating system is an example of the first, whereby the sendfile() call is not available. An example of the latter is where rather than a file object being supplied a file like object such as a StringIO object is supplied.

On a UNIX system where an implementation is using sendfile(), the fallback can therefore be tested using an instance of StringIO.
import StringIO
import string

def application(environ, start_response):
status = '200 OK'

response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)

filelike = StringIO.StringIO(string.ascii_lowercase)

return environ['wsgi.file_wrapper'](filelike)
Either way, the content accessible should still be written back to the client and the wsgi.file_wrapper implementation should not fail.

Using StringIO in particular is a good test because it does not provide a fileno() method and if an implementation blindly assumes that a object will always be provided that has a fileno() method it will fail. For the case where the implementation is written in C and the PyObject_AsFileDescriptor() function is used to get the file descriptor the implementation needs to validate that ‘-1’ is not returned and fallback to processing the iterable instead. If this isn’t done, then the implementation may try and access an invalid file descriptor.

For this specific case, we again haven’t supplied a Content-Length and it wouldn’t necessarily be possible to deduce it. As such, if the client protocol was HTTP/1.1, then chunked transfer encoding might be used if the HTTP/WSGI server supports it.

Content Length

No matter what type of file like object is used, be it an actual file object or otherwise, a WSGI application can optionally set a Content-Length response header itself. This header is meant to be the authoritative indicator as to how much response data is to be returned.

If the Content-Length header is supplied and the value it is given equates to the actual amount of content then there is no problem. If however the given content length is less than the actual amount of data accessible via the file like object, then only that amount of data should be returned and no more.

The WSGI specification as outlined in PEP 333 is actually wrong in this respect in that it says:

"""Apart from the handling of close(), the semantics of returning a file wrapper from the application should be the same as if the application had returned iter(filelike.read, ''). In other words, transmission should begin at the current position within the "file" at the time that transmission begins, and continue until the end is reached."""

In other words, it doesn’t indicate that the Content-Length header should be taken into consideration. PEP 3333 corrects this and states:

"""Apart from the handling of close(), the semantics of returning a file wrapper from the application should be the same as if the application had returned iter(filelike.read, ''). In other words, transmission should begin at the current position within the "file" at the time that transmission begins, and continue until the end is reached, or until Content-Length bytes have been written."""

This scenario needs to be tested and for both a file object and the fallback case for any file like object. Thus would want to perform the test:
import os
import string

def application(environ, start_response):
status = '200 OK'

response_headers = [('Content-type', 'text/plain'),
('Content-length', str(len(string.ascii_lowercase)/2))]
start_response(status, response_headers)

filelike = file('/tmp/filetest.txt', 'w+')
filelike.write(string.ascii_lowercase)
filelike.close()

filelike = file('/tmp/filetest.txt', 'r')

return environ['wsgi.file_wrapper'](filelike)
and:
import StringIO
import string

def application(environ, start_response):
status = '200 OK'

response_headers = [('Content-type', 'text/plain'),
('Content-length', str(len(string.ascii_lowercase)/2))]
start_response(status, response_headers)

filelike = StringIO.StringIO(string.ascii_lowercase)

return environ['wsgi.file_wrapper'](filelike)
Note that one can’t rely on using a web browser to validate the output in these cases. This is because a web browser will normally not display any additional data that has been sent beyond what the Content-Length indicated should exist. It is therefore necessary to use a network snooping tool or even telnet directly to the HTTP server port and enter the request and view the raw details of the HTTP response.
$ telnet localhost 8000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.0

HTTP/1.1 200 OK
Content-type: text/plain
Content-length: 13

abcdefghijklmnopqrstuvwxyzConnection closed by foreign host.
A valid implementation should only return the amount of data specified by the Content-Length header and no more, thus not what the above output shows.

Current Position

One of the sections previously quoted from PEP 3333 states:

"""Apart from the handling of close(), the semantics of returning a file wrapper from the application should be the same as if the application had returned iter(filelike.read, ''). In other words, transmission should begin at the current position within the "file" at the time that transmission begins, and continue until the end is reached, or until Content-Length bytes have been written."""

Key to note here is the phrase ‘transmission should begin at the current position within the "file"’.

Normally when a file is opened the current seek position or file pointer will be at the start of the file. This means that where no Content-Length is specified the complete contents of the file would be returned. If however the current position within the file was not at the start of the file, then the complete file should not be returned and instead only from the current position up to the end of the file, or if Content-Length is specified, only that many bytes from the the current position should be returned.

Tests which validate the correct behaviour for these situations are:
import os
import string

def application(environ, start_response):
status = '200 OK'

response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)

filelike = file('/tmp/filetest.txt', 'w+')
filelike.write(string.ascii_lowercase)
filelike.flush()

filelike.seek(len(string.ascii_lowercase)/2, os.SEEK_SET)

return environ['wsgi.file_wrapper'](filelike)
and:
import os
import string

def application(environ, start_response):
status = '200 OK'

response_headers = [('Content-type', 'text/plain'),
('Content-length', str(len(string.ascii_lowercase)/4))]
start_response(status, response_headers)

filelike = file('/tmp/filetest.txt', 'w+')
filelike.write(string.ascii_lowercase)
filelike.flush()

filelike.seek(len(string.ascii_lowercase)/2, os.SEEK_SET)

return environ['wsgi.file_wrapper'](filelike)
For the first test, from the middle of the file to the end of the file should be returned and if the Content-Length header is set automatically, should correctly reflect that reduced length. The second test should again start from the middle of the file, but should only return 6 characters from the 3rd section of the file.

As with the prior test setting Content-Length, you can’t rely on the browser for the latter test as it will only show as much data as indicated by Content-Length even if more data had actually be returned. A check should therefore be made of the raw HTTP response.

There is no strict need to perform this test where a file like object such as StringIO is used as the way data is consumed in that case means that reading can only start from the current position. An actual file object is different because the optimisation to return the file contents would usually work directly with the file descriptor and not via any high level interface.

Multiple Instances

Now it is time to try and start breaking the wsgi.file_wrapper implementation by doing abnormal things. Granted that these things wouldn’t normally be done, but by testing them it tests the robustness of the implementation of wsgi.file_wrapper. If an implementation takes short cuts or uses a bad design, then it could result in incorrect behaviour, or in worst case for a C based implementation, cause the process to crash.

The first test that can be done is to use wsgi.file_wrapper multiple times within the context of the same request. Obviously only the result of one invocation of wsgi.file_wrapper can actually be returned by the WSGI application. This need not even be the result of the last call. No matter which is returned, the original file used with that which is returned should be what is written back to the HTTP client.

We therefore first check for case where last instance of wsgi.file_wrapper created is returned.
import os
import string

def application(environ, start_response):
status = '200 OK'

response_headers = [('Content-type', 'text/plain'),]
start_response(status, response_headers)

filelike1 = file('/tmp/filetest-a.txt', 'w+')
filelike1.write(string.ascii_lowercase)
filelike1.flush()
filelike1.seek(0, os.SEEK_SET)

file_wrapper1 = environ['wsgi.file_wrapper'](filelike1)

filelike2 = file('/tmp/filetest-b.txt', 'w+')
filelike2.write(string.ascii_uppercase)
filelike2.flush()
filelike2.seek(0, os.SEEK_SET)

file_wrapper2 = environ['wsgi.file_wrapper'](filelike2)

return file_wrapper2
and then the for the case of returning instance of wsgi.file_wrapper which wasn’t the last created.
import os
import string

def application(environ, start_response):
status = '200 OK'

response_headers = [('Content-type', 'text/plain'),]
start_response(status, response_headers)

filelike1 = file('/tmp/filetest-a.txt', 'w+')
filelike1.write(string.ascii_lowercase)
filelike1.flush()
filelike1.seek(0, os.SEEK_SET)

file_wrapper1 = environ['wsgi.file_wrapper'](filelike1)

filelike2 = file('/tmp/filetest-b.txt', 'w+')
filelike2.write(string.ascii_uppercase)
filelike2.flush()
filelike2.seek(0, os.SEEK_SET)

file_wrapper2 = environ['wsgi.file_wrapper'](filelike2)

return file_wrapper1
What is being checked for here is that the implementation isn’t just caching the last inputs given to a call of wsgi.file_wrapper and using that. The details must correspond to that used in the instance of wsgi.file_wrapper actually returned.

Closed File Object

A file object in Python is a wrapper around a C FILE pointer. In turn the FILE pointer is a wrapper around an actual file descriptor. When performing optimisations to transmit a file using the UNIX sendfile() call it is necessary to use the file descriptor. That this is what occurs introduces a couple of complications that need to be catered for.

The first is that the file descriptor and FILE pointer can technically differ as to their understanding of the current seek position within the file. This is because a FILE pointer implements a level of buffering and adjustments to the seek position of the FILE pointer, as well as file contents, may not be reflected in the file descriptor until a flush is performed, writing the data to disk.

The prior test relating to the current position within the file object is intended to try and check for this disparity, although in practice whether it will capture a problem may be dependent on how a specific operating system implements FILE pointers. Important thing to note is that the current position within the file object should be determined by using the ‘tell()’ method of the file object and not by interrogating the seek position from the file descriptor. If an implementation gets the information directly from the file descriptor, then ultimately it will likely fail at some point where code is performing seeks on the file object.

The second complication, and which the following test will check, is the fact that there are multiple handles to the actual file. When dealing with a file object, if one closes the file object and then subsequently performs an operation on it a Python exception would be raised. If instead you held a reference to the file descriptor only and the file object was closed, you may not get an error back. This would be the case where the file descriptor had since been reused.

As a result, if an implementation of wsgi.file_wrapper caches a reference to the file descriptor up front when first called, and then uses that when writing the file contents back as the response content, if the file object had been closed in between, then the wrong file contents could be returned if the file descriptor had been reused.

A correct implementation should delay to the last moment obtaining a reference to the file descriptor. If the file object had been closed in the interim this should see a Python exception if implementation of wsgi.file_wrapper is done in Python code, or ‘-1’ being returned by ‘PyObject_AsFileDescriptor()’ if implemented in C code. If in C code, it should technically then fallback onto attempting to stream the file object, since a non file object would also result in ‘-1’ being returned, in which case it should fail when trying to read data from the file object.

The test for this then is to use wsgi.file_wrapper to create the result to return and before that is actually returned close the file object.
import os
import string

def application(environ, start_response):
status = '200 OK'

response_headers = [('Content-type', 'text/plain'),]
start_response(status, response_headers)

filelike = file('/tmp/filetest-a.txt', 'w+')
filelike.write(string.ascii_lowercase)
filelike.flush()
filelike.seek(0, os.SEEK_SET)

file_wrapper = environ['wsgi.file_wrapper'](filelike)

filelike.close()

return file_wrapper
One should also test the variation of where the file object is closed before wsgi.file_wrapper is invoked to create the return value for the WSGI application.
import os
import string

def application(environ, start_response):
status = '200 OK'

response_headers = [('Content-type', 'text/plain'),]
start_response(status, response_headers)

filelike = file('/tmp/filetest.txt', 'w+')
filelike.write(string.ascii_lowercase)
filelike.flush()
filelike.seek(0, os.SEEK_SET)

filelike.close()

file_wrapper = environ['wsgi.file_wrapper'](filelike)

return file_wrapper
However wsgi.file_wrapper is implemented, it should result in the error being detected. Because processing of the result is being done after having returned from the WSGI application, the fact that an error occurred would normally be logged in some way and the request terminated with the connection to HTTP client being abruptly closed. That is, there isn’t a way of raising an exception in the context of the original WSGI application.

So, these are the tests. In a future blog post I will show how wsgi.file_wrapper should be implemented.

Friday, January 7, 2011

Decorating WSGI applications.

One of my recent blog posts showed how one could construct a wrapper for a WSGI application component such that a cleanup action could be associated with requests, with the cleanup action only being executed after any request content had been written back to the HTTP client. This time I am going to show how that can be converted into a Python decorator.

Recalling the final code from before, what we had was:
def FileWrapper(iterable, callback, environ):
class _FileWrapper(type(iterable)):
def close(self):
try:
iterable.close()
finally:
callback(environ)
return _FileWrapper(iterable.filelike, iterable.blksize)

class Generator:
def __init__(self, iterable, callback, environ):
self.__iterable = iterable
self.__callback = callback
self.__environ = environ
def __iter__(self):
for item in self.__iterable:
yield item
def close(self):
try:
if hasattr(self.__iterable, 'close'):
self.__iterable.close()
finally:
self.__callback(self.__environ)

class ExecuteOnCompletion:
def __init__(self, application, callback):
self.__application = application
self.__callback = callback
def __call__(self, environ, start_response):
try:
result = self.__application(environ, start_response)
except:
self.__callback(environ)
raise
file_wrapper = environ.get('wsgi.file_wrapper', None)
if file_wrapper and isinstance(result, file_wrapper):
return FileWrapper(result, self.__callback, environ)
else
return Generator(result, self.__callback, environ)
Note that this is relying on wsgi.file_wrapper being a type object, which as explained in prior post probably should be a requirement in a future WSGI specification. I’ll leave it for the example, but you may want to remove that part if concerned about portability.

The above could then be used as:
def _application(environ, start_response):
...

def cleanup(environ):
# Perform required cleanup task.
...

application = ExecuteOnCompletion(_application, cleanup)
What I instead however want to be able to do is write this using the decorator syntax of Python. That is:
@execute_on_completion(cleanup)
def application(environ, start_response):
...
In another of my recent blog posts I also detailed the different ways that WSGI application objects could be implemented. Namely, as functions, class instances and class objects. As such, we need to make sure it also works for those as well. Specifically:
class Application:

@execute_on_completion(cleanup)
def __call__(self, environ, start_response):
...

application = Application()
and:
@execute_on_completion(cleanup)
class Application:

def __init__(self, environ, start_response):
...

def __iter__(self):
...

application = Application
For this decorator we want to be able to use a parameter, namely the reference to the cleanup function. In that case the pattern we will need to use for the implementation of the decorator is:
def execute_on_completion(cleanup):
def decorator(application):
def wrapper(environ, start_response):
...
return wrapper
return decorator
We already have a wrapper object in the form of the ‘ExecuteOnCompletion’ class though and so don’t need the ‘wrapper()’ function here. The result being:
def execute_on_completion(cleanup):
def decorator(application):
return ExecuteOnCompletion(application, cleanup)
return decorator
And when we try that out for the case where our WSGI application is a normal function:
@execute_on_completion(cleanup)
def application(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)

return ["1\n", "2\n", "3\n", "4\n", "5\n"]
that works fine.

Now let us however try the case where we use the decorator on a class method:
class Application:
@execute_on_completion(cleanup)
def __call__(self, environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)

return iter(["1\n", "2\n", "3\n", "4\n", "5\n"])

application = Application()
For this we find it fails. The specific error being:
Traceback (most recent call last):
File "/some/path/example.wsgi", line 31, in __call__
result = self.__application(environ, start_response)
TypeError: __call__() takes exactly 3 arguments (2 given)
This is occurring at the point in the ‘__call__()’ method of the ‘ExecuteOnCompletion’ class where the wrapped WSGI application object is being called.

I am not going to go into detail here about what the actual cause of the error is, partly because it isn’t something I don’t really understand really well. In summary though, in order to use a class object as a wrapper within a decorator in this way, it needs to have an appropriate descriptor added to it, with the descriptor triggering some magic so that the class method being invoked is seen as a bound method of the class instance, thus ensuring that the ‘self’ parameter is available.

Because descriptors only work for new style classes, which I should have used from the outset anyway, we need to also make ‘ExecuteOnCompletion’ derive from the ‘object’ type. This leaves us with:
class ExecuteOnCompletion(object):

def __init__(self, application, callback):
self.__application = application
self.__callback = callback

def __get__(self, obj, objtype=None):
return types.MethodType(self, obj, objtype)

def __call__(self, environ, start_response):
try:
result = self.__application(environ, start_response)
except:
self.__callback(environ)
raise
file_wrapper = environ.get('wsgi.file_wrapper', None)
if file_wrapper and isinstance(result, file_wrapper):
return FileWrapper(result, self.__callback, environ)
else:
return Generator(result, self.__callback, environ)
The descriptor is the special ‘__get__()’ method which has been added.

We now try again, but it still fails. This time with the different error:
TypeError: __call__() takes exactly 3 arguments (4 given)
Under mod_wsgi at least, this didn’t even come with a traceback, possibly because it is coming from C internals of Python, but what is being referred to here is the ‘__call__()’ method of the ‘ExecuteOnCompletion’ class.

The reason the error arises is that with the descriptor being in place, the ‘self’ parameter required when invoking the target WSGI application object is actually being passed in to ‘__call__()’ in addition to the ‘self’ parameter for the instance of ‘ExecuteOnCompletion’ and the ‘environ’ and ‘start_response’ parameters.

Going quickly back to our example where we wrapped a normal function and not a class method, we find that adding the descriptor has not changed the operation for that case. At least then the addition of the descriptor hasn’t broken that.

What it does indicate though is that our ‘__call__()’ method has to be able to handle two scenarios. In the first it will only get passed ‘environ’ and ‘start_response’, but when the decorator is applied to a class method, it will also be passed in the ‘self’ parameter for the target WSGI application object, inserted into the argument list before that of ‘environ’ and ‘start_response’.

Now, we can just change the prototype of the ‘__call__()’ method to accept a variable number of arguments:
def __call__(self, *args):
...
and the actual invocation of the target WSGI application to:
result = self.__application(*args)
but we have the problem that we need access to the ‘environ’ parameter within the body of the ‘__call__()’ method. This being required where needing access to ‘wsgi.file_wrapper’ but also so we can pass the ‘environ’ parameter onto the cleanup function.

What we do know though is that WSGI application objects can only be called with positional parameters and that there is only ever the two of ‘environ’ and ‘start_response’. Thus, it doesn’t matter whether there are two or three arguments depending on whether a normal function or a bound class method is being called, the ‘environ’ parameter will always be the second last argument. We can therefore rewrite the ‘__call__()’ method as:
class ExecuteOnCompletion(object):

def __init__(self, application, callback):
self.__application = application
self.__callback = callback

def __get__(self, obj, objtype=None):
return types.MethodType(self, obj, objtype)

def __call__(self, *args):
environ = args[-2]
try:
result = self.__application(*args)
except:
self.__callback(environ)
raise
file_wrapper = environ.get('wsgi.file_wrapper', None)
if file_wrapper and isinstance(result, file_wrapper):
return FileWrapper(result, self.__callback, environ)
else:
return Generator(result, self.__callback, environ)
And we have success for both the case of the decorator being applied to a normal function, as well as the class method.

Now for the final scenario of the decorator being applied to a class.
@execute_on_completion(cleanup)
class Application:
def __init__(self, environ, start_response):
self.__environ = environ
self.__start_response = start_response

def __iter__(self):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
self.__start_response(status, response_headers)

for item in ["1\n", "2\n", "3\n", "4\n", "5\n"]:
yield item

application = Application
This also works fine.

One final thing to check is whether this also works if you don’t have access to the source code and need to monkey patch this wrapper to existing code imported from other modules. For example:
application = execute_on_completion(cleanup)(application)
And the answer to that one is that it does also work for that, so we have pretty well covered all bases.