So for the past few months I've been having some stability issues with one of our environments. We would periodically have the service gobble up all available JVM heap and die with the well known java.lang.OutOfMemory error. What was confusing me was that it was not necessarily load-related, it could hum along during peak usage (for this server that's about 5-6 requests per second) and not have any issues at all. Then, on other days, I'd watch the heap spike out of control, or rather jump 100-200mb of allocated objects and then level off... then maybe an hour later do the same. All the usual suspects (gc tuning, jrun.xml tuning, etc) were addressed, with no significant change in behavior. A graph of allocated heap would look something like:

Well last week I finally was able to correlate these spikes with a specific activity: FILE UPLOADS! We have a site that has always had steady usage of file uploads, but lately the content has contained a lot of podcasts at around 40-50mb a pop. It had never dawned on me that file uploads would be so devastating to the cf server. I even went as far as getting a heapdump off the server (different than a thread dump) - something you can do with a jvm newer than 1.4.2_12 by adding the -XX:+HeapDumpOnOutOfMemoryError flag to your config. With a heap dump in hand you can analyze it using the jhat utility provided with JDK6 updater 1. jhat will set up a little webserver for you on port 7000 that you can use to browse the heap dump (basically a snapshot of the heap at the time your server choked) to see what the heck was allocated. In my case it was about 900mb of byte arrays (byte[]) - all allocated by underlying cf request processing classes. I at least now knew my code was in the clear! You can read more about heap dumps here: http://blogs.sun.com/alanb/entry/heap_dumps_are_back_with

So - lets lay down some facts:

1) This is a fully patched 6.1 environment.

2) Yes, I'm aware there is a patch for file upload memory leaks recently released for 7.0.2 (http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=kb401239)

3) I wish it was as simple as upgrading the server, however, in my testing, only half the issue is really addressed.

Here's where it gets tricky - yes, a fully patched 7.0.2 cf box won't leak like a sieve during large fileuploads. However, it's still vulnerable from the perspective of file size and scalability - during a file upload, cf allocates almost 3x the size of the file in memory. Just because it now gives that memory back afterwards is not enough - if you have a few people uploading large files at the same time, and the server is running with a decent amount of heap already allocated to other applications, you could still get a OutOfMemory error. This is easy to test by simply capping the jvm heap at 250-300mb (-Xmx300m) and then uploading a file bigger than 200mb - you'll get a 500 null error and corresponding OutOfMemoryError in the logs. Most modern web browers (IE and Firefox) and webservers (IIS and Apache) support http multipart requests up to 2gb. Another interesting piece that you may not know is that this will happen with or without a <cffile action="upload".../> tag being executed in the request - the memory consumption happens way before any of your cfml is processed,  as file uploads are dealt with at the same time the "form" scope is populated. This to me also seems like a gaping Denial-Of-Service attack hole - fire a few large multipart requests at ANY cfm page and you can bring the server to its knees, whether the application attempts to "upload" the file or not. This is a little different than the security bulletin regarding a similar issue posted a while ago, in which file uploads that are stopped mid transfer were getting left on disk - theoretically could be used to exhaust the disk space on the server.

So of course I had to come up with a workaround (whether this issue gets fixed in Scorpio we'll have to see). These are the steps I took:

1) I wrote a servlet filter in java to intercept the request before it gets to your "cfmservlet" (basically the entry point for all CF requests). This needs to be installed and configured in your web.xml. To be safe, I only mapped this filter to the url that I knew would be dealing with fileuploads.

2) If the request is multipart encoded, I use the Apache commons-fileupload 1.2 (http://jakarta.apache.org/commons/fileupload/) to parse the request. You can tell the fileupload library that files over a certain size should be streamed to disk - I set this at 5mb. The common libaries are usually pretty easy to program with.

3) I then "wrap" the request using an HttpServletRequestWrapper and override some of the usual suspects that cf is probably using to determine if a file upload exists. I had to look at the source of the Oreilly multipart parser that cf uses to figure out what made it was looking for - it checks both the "content-type" request header and the alternate getContentType() ServletRequest method to see if either say "multipart/form-encoded" - instead my "wrapped" request always responds with "application/x-www-form-urlencoded" - which makes cf think that it's a regular form POST.

4) Unfortunately, this also prevents cf from properly populating the "form" scope because multipart requests are broken up differently than non-multipart ones and cf simply doesn't seem any POSTed parameters - there may be a way around this but I simply added some code in application.cfm to pull any values out of my "wrapped" request and place into the form scope.

5) I wrote a simple <cf_big_upload/> tag that returns the same stuff you would get in the "cffile." scope, however all the tag does is inspect the "wrapped" request object and if there is a file present under the field name you requested, it moves the file from the server's temp location to the destination of your choice - using DiskFileItem.write("/path/to/final/resting/place/"))

6) At the end of the request any uploaded files still in the temp location are deleted. This functionality is part of commons-fileupload.

Long story short - on the train tonight I successfully uploaded a 800mb file on a 6.1 server with -Xmx280m ... memory usage increased negligably and all of the other processing that needed to take place in addition to the file upload happened without issue.

If anyone is interested in the code or more detail in how this works let me know - I didn't want to post it without some proper load testing scenarios, but I'm still excited that I can work around this issue without a major upgrade (or even waiting for a "next" release).


05/12/2007 05:43 A - Matthew Lesko said...
Had some similar problems and suspected the commons solution would solve it (i.e., stream the upload), but never got to investigating. In a related note, transferring a file via cfhttp between two servers appears to stream automatically.

Would love to see the code.

05/13/2007 07:20 P - Jen Larkin said...
The only time that I have seen behavior like that, I have seen in on Windows where the C drive is full. The uploads are temporarily stored on the C drive temp directory and if an upload fails because there is not enough room to store the file temporarily on the C drive, the action fails, the file is not moved out of the C drive, and the memory is not released.

I'm not sure that I can blame that on ColdFusion though, since this temporary file storage method is used in Unix environments too. This is the way the operating system works and for ColdFusion to handle it, the operating system would have to inject an error into CF.

Make sure that you have tons of available drive space on the drive with the operating system's default temporary storage directory is. That is what fixed the problem for me.

05/14/2007 06:40 A - Dave Ross said...
Jen,

Trust me - we have plenty of space free on that box (I believe around 20gb free on C: right now - which is just for OS + CF).

I'm not sure how you're correlating file == memory, in reality the problem is that CF reads the entire multipart stream into memory before writing it to a file, and doesn't release all of the memory after writing to disk. This is a known issue with CF: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=kb401239

-Dave



05/14/2007 11:07 A - Edward Prevost said...
Nice to see that my adobe links were atleast accurate to some degree. You should totally post all of the code, or atleast link to the files for downloading. PS - Much better blog entry. =D

05/14/2007 05:13 P - Jen Larkin said...
When you encounter the problem that I described, as you said "CF reads the entire multipart stream into memory before writing it to a file" and when the write to disk fails, this memory is not released. None of it. So that is how I correlate file == memory. Snarky much?

05/14/2007 05:40 P - Dave Ross said...
Jen,

Sorry -  should have been more clear (and reading my comment back it does sound snooty - sorry about that).

I should have simply said "this condition occurs when the upload is successful".

thanks,

Dave

05/17/2007 06:11 A - Sana said...
Hi Dave,

Reading your blog post, seems that you have nail down potential memory leak issue. I would like to give a try to your solution.

Thanks

05/18/2007 02:29 A - Jen Larkin said...
Thanks. :) I analyzed our servers after I eliminated the original problem and didn't have any apparent memory leak issues. This was on a 6.1 box that had frequent large uploads, but we never had an out of memory error after we fixed the problem. However, we had tons of RAM on the box and without having out of memory errors it was only worthwhile to check periodically and make sure there were no impending problem. There never were.

I assume that this overallocation of memory is an attempt to keep from having to do frequent reallocation. I am not sure of problems that frequent reallocation would involve but it certainly seems like it could be problematic. If only we could know the filesize ahead of time!

It's good that you found a solution though! Thanks for sharing it.

05/21/2007 05:52 P - Ken Smith said...
I would like to see your code.

05/22/2007 11:33 A - Kathy Haines said...
I'm working with Ken - could you forward the code?

06/25/2007 05:23 P - Jo-Anne said...
Could you please send me the code too. We have a very similar problem on a website that allows users to upload files. Thanks in advance.

06/25/2007 05:44 P - Kathy Haines said...

Although I can't post my code, I think it's safe to generally explain the solution.

I wrote an intercepting filter, and this seems to capture the file before JRun gets to it. See more about these at http://java.sun.com/blueprints/corej2eepatterns/Patterns/InterceptingFilter.html

You have define where your filter is called in the web.xml file. In the case of our installation, the appropriate file was at C:\JRun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\web.xml. Example filter & filter mapping tags are available on the Intercepting Filter site. For example, I had something like all files in the /MyApplication/Attachment/ section go through the filter.

In reality, because of the nature of my problem, I had two filters, on for upload and one for download. The download filter just streams the file back to the user. The upload filter, which is what most people seem to be having problems with, was significantly harder. Like D Ross, I completed the rest of the design pattern, including extending HttpServletRequestWrapper.

Here's where my implementation differentiates from the one he describes. D Ross changes the request to the regular variety, where I needed to keep it as multipart. In a non-JRun world, the way you would do this is very simple. You would just override some of the easy methods of HttpServletRequestWrapper like getParameterNames() or getParameterMap() and your parameters are preserved. However, I discovered that at least my version of JRun does not use any of those easy methods. It only seems to use getInputStream().

To implement getInputStream(), you have read the white pages for an HTTP request, and JRun not surprisingly is pretty picky. You have to build all the headers to spec, basically, do the really low level stuff your browser normally does for you. Basically, it's really yucky. Unforutnately, it's not much better than his hacked solution, to just store the parameters in a different location since they're going to be lost anyways.

Even though this is probably not enough information, it's a start... What basically happens, when you use Apache FileUpload, or COS, which is a framework provided by the O'Reilly author, you will have "consumed" the files in the request. As in, they are not available for JRun. JRun really doesn't like this if you leave the request multipart, so you have to put them back. And the only way I was able to manage, was to override getInputStream().

Again I strongly recommend you read about Intercepting Filters to get the general idea about all of these things. There's also Javadoc available online for things like HttpServletRequestWrapper http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServletRequestWrapper.html

Best of luck... Ken Smith & I had had no luck contacting the author of this website...


07/01/2007 11:44 A - Dave Ross said...
To those that asked to see the code (and wondered if I'd ever respond) - I'm working on it. I don't want to distribute something that hasn't had real world use. Hope everyone understands (and it looks like Kathy was able to come up with a similar solution on her own).

07/11/2008 12:46 P - Charlie Arehart said...
Hey Dave, a couple of things. First, you mention a possible DOS concern. I realize you're referring to the problem remaining in 6.1 (since there was the fix in 7.02). But I'll point out that at least from 7.01 MM addressed the issue by adding a limiter for these sort of uploads: see the "maximum size of post data" option in the Admin's Settings page. I think it defaults to 100mb (but not positive).  I realize that doesn't help on 6.1, though.

You mentioned last July that you might post the code after a little more real world testing. Any chance of that? As you note, if one's on 6/6.1, this is an important problem to solve.

Finally, have you noticed you've been hit by a raft of blogspam (all the "thank you's above", which aren't real but are just ways spammers try to get their URLs listed on blogs to raise their search ranking). As can be seen, they're like weeds. If you don't pull them up, they spread and soon you're inundated. :-)

10/23/2008 09:09 P - Cool Gadgets Blog said...
Charlie is right Dave. You should put nofollow to your comment links or approve comments by moderation. Because you have a PR of 3, spammers will comment anything goes even they don't understand a single word about CF. I hope it helped :)

01/22/2009 03:20 A - Kozmetik-zayiflama said...
Thanks. :) I analyzed our servers after I eliminated the original problem and didn't have any apparent memory leak issues.

01/23/2009 03:22 A - K?rm?z? mantar said...
<a href="http://www.kirmizimantar.biz" title="kirmizi mantar reishi ölümsüzlük mantari">kirmizi mantar</a>

01/23/2009 03:25 A - K?rm?z? mantar said...
tankyou very mach its a very good knowlage

01/27/2009 03:18 A - Yetiskinlere Ozel Cinsel Saglik Urunleri said...
thanks for these codes.i will use them in my blogs

02/08/2009 10:30 A - best iphone cases said...
This information is very useful. I think many people had also such memory leaks. Thanks

02/09/2009 12:03 P - zrmbilisim katk?lar? ile 2009 seo yar??mas? said...
thanx for seo ;) <a href="http://dersnotlari.biz">zrmbilisim katk?lar? ile 2009 seo yar??mas?</a>
<a href="http://dersnotlari.biz/kitap-ozetleri/">kitap özetleri</a>

02/13/2009 05:59 A - seks shop said...
I am liking very much your site
very well said.

02/21/2009 01:01 P - french language suite said...
I have read about the solution but didn't understand the final conclusion. Should we assume that memory leaks can be solved with these techniques?

03/22/2009 04:05 P - ADAMS said...
Thanks. :) I analyzed our servers after I eliminated the original problem and didn't have any apparent memory leak issues.

03/31/2009 02:15 A - Kendall said...
In networks, uploading and downloading refer to the two canonical directions (corresponding to send and receive, respectively) that information can move, and further defines such data as being copied and compiled (indicated by the term "loading") to create a complete file, after a period of time. Downloading is distinguished from the related concept of streaming, which indicates a download in which the data is sequentially usable as it downloads, or "streams," and that (typically) the data is not stored. Meanwhile, You ever heard of synsepalum dulcificum?  Most people haven't heard of it, and most people don't even want to try and pronounce synsepalum dulcificum. The miracle fruit has an odd property, in that it temporarily confuses the taste buds into thinking that things sour are sweet, and things spicy are not. (We do not recommend using and then gorging on habanero peppers.)  Besides the obvious benefits – like getting the kiddies to eat their vegetables for once, which most people would get a cash advance to figure out how to do – the fruit also has a propensity for enabling cancer patients to eat during chemotherapy, which has a side effect of disabling taste buds.  It turns out that synsepalum dulcificum might be a miracle fruit after all.

12/29/2009 12:51 P - selülit kremi said...
This information is very useful. I think many people had also such memory leaks. Thanks

Post a comment:

(required, will not be displayed)
 


   You will be sent an email asking you to validate your comment.



Driven by Farcry Open Source CMS. Dressed in Aura.
Powered by ColdFusion MX.