For a number of weeks, we had been encountering an odd exception on rare occasions. Typically, our point of sale would run flawlessly up until a very busy day where it would not want to render some of our cached images.

System.Web.HttpUnhandledException: Exception of type 'System.Web.HttpUnhandledException' was thrown. ---> System.ArgumentException: Parameter is not valid.
at System.Drawing.Image.Save(Stream stream, ImageCodecInfo encoder, EncoderParameters encoderParams)
at System.Drawing.Image.Save(Stream stream, ImageFormat format)
[...]

We investigated on Google for the possible cause of this error and found a bunch of irrelevant posts from people who get this error message every time they execute their code. In addition, we discovered that it was a generic message that could mean a null image, an inappropriate image format, etc. We figured it must have something to do with memory usage because of the time it took before it occurred in production. However, we knew that the ASP.NET worker process had not restarted because of excessive memory usage.

We ran stress tests on our machines, and never managed to replicate the error. In one session, we loaded all the birth certificates that a store had ever created, hundreds times more than what they would do on their busiest day. We were unfortunately unable to replicate this issue. (Here at LavaBlast, we mostly use NUnit and NUnitASP for our unit testing of ASP.NET applications).

Then we found a post saying that you might want to copy from the image, to a new MemoryStream instead of directly outputting to the Response.OutputStream of an ASP.NET application.

The relevant source code looks like this:

public static void CopyToStream(Bitmap image, Stream outputStream, ImageFormat format)
{
    using (MemoryStream stream = new MemoryStream())
    {
        image.Save(stream, format);
        stream.WriteTo(outputStream);
    }
}

The code is accessed this way in one of our ASP.NET handlers (ASHX file):

Bitmap image = null;
// load the image from cache
if (image != null)
{

HttpResponse response = context.Response;
response.ClearHeaders();
response.ClearContent();
response.Clear();
response.ContentType = "image/jpg";
CachedImageGenerator.CopyToStream(image,response.OutputStream,ImageFormat.Jpeg);
}

The end result is that this makes no difference at all. Bummer! Because we had to wait a week to get the results in production, we needed to replicate this error on our development machines. We quickly realized that we had overlooked the most obvious of solutions in the first place. We knew the image was not null and we knew it was still in the cache, but we had never checked to see if it was disposed! Yes, somehow the images in our cache were disposed by some external process, but not by the cache itself, which would have removed it from the cache beforehand. Once a System.Drawing.Image is disposed, all of its properties return the Parameter is not valid error. In our image cache, we coded a quick hack that would test the image.Height property was throwing this error: if it was, we reloaded the image from the database. (Note: Images do not have an IsDisposed property).

Obviously, this hack was not very reassuring. While Etienne took on the task of refactoring the image cache to store a byte array instead of a System.Drawing.Image, I took out the heavy artilery to find the root cause of this exception. By using JetBrains dotTrace 3.0, a superb tool for profiling .NET applications (Both WinForms and Web Applications), I discovered a memory leak in our application. I cannot overstress how glorious this tool is. It is simply excellent and it saved me tons of time.

In any case, before fixing the memory leak, I reduced the maximum memory IIS allows to my worker process to 16mb. (My machine has 4GB of RAM; that's why we never discovered the flaw in the first place. We should have tested on our sample production hardware instead … but that's another story). With such low memory, I was quickly able to cause the worker process to restart when trying to load too many images (all the images the store had ever produced, once again). Between worker process restarts, I managed to replicate the elusive Parameter is not valid exception. Debugging under this scenario with scare resources, I discovered that the image was being Dispose in the short lapse of time between its creation and its output, revealing that no amount of quick hacks would have solved this issue.

Returning to the memory leaks with JetBrains dotTrace, we found them quickly and the application managed to run our nasty stress test with 32mb assigned to the worker process.

In conclusion, there are no real miracle solutions for solving this problem except ensuring you don't use up too much memory! I just wanted to write this post to help people who are encountering intermittent "Parameter is not valid" exceptions figure out what is going on!

Shameless plug: LavaBlast creates industry-specific interactive kiosks integrated with tailor-made point of sale software, and a variety of other software solutions for franchisors.