A bug in tomcat or Java2D

Today I spent many hours working out a bug in a web application.

The case was this: I had an image that took a while to generate because it depended on live network data. For that reason, people often clicked through to other pages while the servlet rendered the image, and this in turn made the servlet throw a "java.net.SocketException: Broken pipe".

If this request was followed by an Axis request, the Axis request would throw a java.lang.IllegalStateException at setBufferSize(ResponseFacade.java:229). As you can imagine, the connection between these two exceptions took me a while to figure out.

While googling around for this problem, I found Tomcat bug #37516. Which had been closed with an obscure statement: "I looked at that issue in the past, and Java2D accesses the streams outside of the service methods."
To understand what this means, you need to know something about the internals of the request/response objects in a servlet container. Some of the methods on a HttpServletResponse are state-dependant. For example you can't send a content-type-header when you've started writing the body of the response. These are the cases where a method throws an IllegalStateException

This, coupled with the fact that Tomcat recycles the HttpServletRequest and HttpServletResponse objects between requests means that if you write to the OutputStream of a response after it has been recycled, you pollute the state of the response and make Tomcat think that the response is already committed.

This is not normally a problem since you release your grasp on the OutputStream when the request is done, except in this particular case, where ImageIO holds on to the OutputStream until it is garbage collected, at which time it flushes the output buffer.

To reiterate the process: During the response-phase, your servlet hands an OutputStream to ImageIO.write. The user cancels the request. ImageIO starts writing to the now broken socket and throws an exception. The request/response object is recycled by Tomcat. The ImageWriter gets garbagecollected and pollutes the recycled HttpServletResponse. A new (Axis) request is formed, and Axis calls setBufferSize() on the HttpServletResponse, which Tomcat believes is already committed. Tomcat throws an IllegalStateException.

To work around this issue, I wrote the image to a temporary byte[] before streaming it to the end-user, so ImageIO never got the HttpServletResponse.OutputStream

While I do believe that this could be characterized as a bug in Java2D/ImageIO, I also believe that Tomcat would be improved by not insisting on recycling all objects, especially complex objects that are exposed directly to the user of the HttpServletRequest and HttpServletResponse objects.

Add new comment