Ending a Response without Response.End() Exceptions?
I'm re-working some of my internal banner manager code and one thing I'd like to do is optimize the banner serving as much as possible. Banners are served from the database by default, but the banners can be cached optionally in memory so there's no data access.
Problem is though the banner click tracking still has to hit the database and while this isn't exactly slow it's slower and on occasion fires into some cleanup code. What I really want to do is something like this:
string output = "document.write(" +
wwWebUtils.EncodeJsString(banner.RenderLink()) + ");";context.Response.Write(output);
Response.End();
// *** Update the hit counter - plus potentially other 'stuff' // that's potentially slowmanager.BannerHit(banner.BannerId);
Of course you probably already know that this won't work because Response.End() REALLY means Response.End() as it throws an exception and finishes out the request so any code following a Response.End() code doesn't ever fire. BannerHit above never runs. Instead a ThreadAbort exception is fired which kills the current request.
There's also context.ApplicationInstance.CompleteRequest() which also closes out the request, but it doesn't properly shut down the Response object and so the request still hangs.
So, I started playing around with a few other ways to get this to work. One obvious though is to turn off Response buffering and just .Flush() the content when done:
HttpResponse Response = context.Response;Response.Clear();
Response.BufferOutput = false;Response.ContentType = "text/html";string output = "document.write(" +
wwWebUtils.EncodeJsString(banner.RenderLink()) + ");";context.Response.Write(output);
Response.Flush();
// *** Update the hit counter - plus potentially other 'stuff' // that's potentially slowmanager.BannerHit(banner.BannerId);
By turning off Response buffering you're supposed to be able to flush content back to the client, but in some quick tests throwing in some Thread.Sleep() breaks I clearly failed to get the Response to flush output all the way to the client. It looks like the headers get sent with Chunked output headers provided, but it doesn't actually 'finish' the HTTP request so that the client knows the request has completed. Still doesn't work.
The first 'buffer' sent to the client (as captured with Fiddler) looks like this and includes only the headers:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Expires: -1
Server: Microsoft-IIS/7.0
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
Date: Sun, 25 May 2008 15:52:20 GMT
Connection: close
Then when the request finishes 5 seconds later I get the second chunk:
105document.write("<a href='/WestWindWebToolkit/wwBanner.ashx?a=c&id=b628a170&t=633473275403436000&u=http%3a%2f%2frasnote%2fWestWindWebToolkit%2fBannerTest.aspx' target='_top'><img src='http://www.west-wind.com/banners/webconnectionBanner50.gif' border='0'></a>");
0
Both IE and Firefox wait until the final chunk arrives to actually display the data. So this makes this mechanism fairly worthless for small content. Both IE and FireFox render content eventually when the chunked data accumulated gets longer, but for small bits like the above nothing happens until there's enough data. Not sure if this is IIS holding off on sending back the chunked data or the browsers trying to cache the content first.
Really what I need to do is Response.End() but continue running my code in the current method. Looking at the Response.End code with Reflector yields:
public void End()
{if (this._context.IsInCancellablePeriod)
{InternalSecurityPermissions.ControlThread.Assert();
Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));
}
else if (!this._flushing)
{ this.Flush();this._ended = true;
if (this._context.ApplicationInstance != null)
{ this._context.ApplicationInstance.CompleteRequest();}
}
}
so it really looks like there's no clean way to do this.This clearly shows that ASP.NET is bascially throwing the entire thread, which is pretty brute force. The else block looks promising except that _ended isn't accessible. I'm not quite sure where the actual ThreadAbortException comes from as neither CancelModuleException() or the CompleteRequest fire it.
After searching around for a while I gave up and just did the obvious:
string output = "document.write(" +
wwWebUtils.EncodeJsString(banner.RenderLink()) + ");";context.Response.Write(output);
// *** Ignore Response.End() exceptiontry{Response.End();
}
catch { }Thread.Sleep(5000);// *** Update the hit countermanager.BannerHit(banner.BannerId);
and this surprisingly works! Given the code above and the CancelModuleException() signature and implementation I'm not even sure why this even works at all, but when run the above the banner HTML is immediately pushed out and rendered.
In this handler scenario I'm working with the behavior is probably fine because there won't be other modules or end handlers that need to fire. But inside of an ASP.NET page or other more complex handler I think I'd be worried about side effects for this behavior. For now this does the trick in this scenario.
So, what are you doing to handle an 'early exit' scenario in ASP.NET when you need additional processing? I suppose another option would be to fire an asynchronous delegate and let it go off on a separate thread, but that seems like overkill as well.
It seems there should be an easier solution to this possibly common scenario.
Other Posts you might also like
- Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application
- Resolving Paths To Server Relative Paths in .NET Code
- Map Physical Paths with an HttpContext.MapPath() Extension Method in ASP.NET
- Getting the ASP.NET Core Server Hosting Urls at Startup and in Requests
- Back to Basics: Rewriting a URL in ASP.NET Core
The Voices of Reason
# re: Ending a Response without Response.End() Exceptions?
# re: Ending a Response without Response.End() Exceptions?
try { Response.End(); } finally { // *** Update the hit countermanager.BannerHit(banner.BannerId); }
# re: Ending a Response without Response.End() Exceptions?
# re: Ending a Response without Response.End() Exceptions?
http://msdn.microsoft.com/en-us/library/system.threading.threadabortexception.aspx
"When a call is made to the Abort method to destroy a thread, the common language runtime throws a ThreadAbortException. ThreadAbortException is a special exception that can be caught, but it will automatically be raised again at the end of the catch block. When this exception is raised, the runtime executes all the finally blocks before ending the thread. Since the thread can do an unbounded computation in the finally blocks, or call Thread..::.ResetAbort to cancel the abort, there is no guarantee that the thread will ever end. If you want to wait until the aborted thread has ended, you can call the Thread..::.Join method. Join is a blocking call that does not return until the thread actually stops executing."
# re: Ending a Response without Response.End() Exceptions?
In my site I just collect the data needed for sending the email(s) and queue using the ReaderWriterGate class of that library. Because it recycles threads it is very performant and I found out that without load the delegate queued in the RWG lock is executed immediately after the response has ended - using the same thread. This also explains why Response.Close() doesn't work correctly for subsequent requests: Response.End() resp. the ThreadAbortException thrown by it doesn't do anythiong else than putting the current thread back into the ASP.NET thread pool. If it is not put back and you have some load, the thread pool maybe becomes empty and there are no threads available for processing the waiting requests. I didn't have much load yet so I can't really say how it is handled then, but I guess it will let process the ASP.NET client requests and execute the queued calls between them if there is not so much load for a moment.
http://www.wintellect.com/PowerThreading.aspx
# re: Ending a Response without Response.End() Exceptions?
# re: Ending a Response without Response.End() Exceptions?
I'm talking about using it in a major web app, not a small one-off like is outlined in the example here.
It is troubling that there is no simple solution for this, and that the issue is not even widely discussed. (Thank you Rick for bring up the issue.)
In the help docs there is some brief discussion about the fact that an issue exists, and some hints at how to solve it, but it is all completely useless to try to use in a consistent, global fashion throughout a major web app.
The same exact issue exists for Server.Transfer(), which halts the current page executing and transfers control to another one. The same exception is thrown, which is pretty bad.
The worst scenario I came across was on a complex page that has code for several events, including Init, Load, PreRender, etc., and is also testing in various events for postbacks, AJAX partial postbacks, etc. I tried to gracefully do a Server.Transfer() to another page early in the page life cycle, and quickly realized that it was all but impossible, if I wanted to maintain my sanity.
Rick, hopefully your blog post will get noticed by someone at MS, who is driven to try to fix the situation.
# re: Ending a Response without Response.End() Exceptions?
http://msdn.microsoft.com/en-us/library/22t547yb.aspx
# re: Ending a Response without Response.End() Exceptions?
The following DOES NOT work:
try
{
Response.End();
}
finally
{
Thread.Sleep(5000);
// *** Update the hit counter
manager.BannerHit(banner.BannerId);
}Same scenario as if just letting the code run - banners wait 5 seconds before displaying even though Response.End() has fired.
Using an anonymous delegate also works:
string output = "document.write(" + wwWebUtils.EncodeJsString(banner.RenderLink()) + ");"; context.Response.Write(output); Action<object> d = delegate(object val) { Thread.Sleep(5000); // *** Update the hit counter manager.BannerHit(banner.BannerId); }; d.BeginInvoke(null,null,null); Response.End();
# re: Ending a Response without Response.End() Exceptions?
1. The ThreadAbortException obviously comes from the Thread.Current.Thread.Abort() call. You must've missed that. ;)
2. Since ThreadAbortExceptions are actually uncatchable (as mentioned above, they are simply reraised by the CLR after your catch{} ends) - I think what's going on is that you're tripping into the else{} block - which means that you've tripped HttpContext.IsInCancellablePeriod to be false. Quickly reflecting on that, though, I don't see any obvious clues *how* you're tripping that - so I'm going to guess that it's simply because you're in a try{} block.
3. According to MS http://support.microsoft.com/kb/312629, and seemingly verified by reflection into HttpResponse.End(), the correct way to do this is to call HttpContext.Current.ApplicationInstance.CompleteRequest() http://msdn.microsoft.com/en-us/library/system.web.httpapplication.completerequest.aspx. This looks to finish out your page, but raise the EndRequest event for any interested modules.
4. I wonder what happens in both your scenario, CompleteRequest(), and a delegate if an Exception occurs?
5. Should this type of logic be moved into an HttpModule? That'd certainly look cleaner to me - though you'd still have to fire off a new thread to avoid blocking, I guess....
# re: Ending a Response without Response.End() Exceptions?
... this.Page.Response.AddHeader("Content-Disposition", "attachment; filename=" + filename); this.Page.Response.WriteFile(fullPath); this.Page.Response.End(); File.Delete(fullPath);
The above works out in Firefox 2 and 3 but not in IE7.
# re: Ending a Response without Response.End() Exceptions?
# re: Ending a Response without Response.End() Exceptions?
But that's not really the point. In this simple scenario it really doesn't matter, but it's one of those things that I've run into in a few occasions, and there's no clear way to do the Response.End() code and continue on, apparently short of offloading to a delegate or separate thread.
# re: Ending a Response without Response.End() Exceptions?
cscript adsutil.vbs set /W3SVC/AspEnableChunkedEncoding "FALSE"
# re: Ending a Response without Response.End() Exceptions?
If u r trying to avoid extra cycles caused by db why not use msmq? This way u delegate all non asp request code to a different thread.
More work 4 sure, but u could use the same idea for other tasks
enterprise library already comes with plumbing to do this
# re: Ending a Response without Response.End() Exceptions?
# re: Ending a Response without Response.End() Exceptions?
Response.TransmitFile(objFileInfo.FullName)
Response.Flush()
' Used to be Response.Close()
HttpContext.Current.ApplicationInstance.CompleteRequest()# re: Ending a Response without Response.End() Exceptions?
The way to do "custom Ajax" stuff (with e.g. jQuery in client-layer) is to use Response.Filter...
You can see a solution for that in our library (which is Open Source and all code is embedded in download)
.t
# re: Ending a Response without Response.End() Exceptions?
Exceptions thrown in the delegate does not trigger this page, actually, I don't really know where do they go.
# re: Ending a Response without Response.End() Exceptions?
Then you can call it async from your .aspx!?
So the aspx renders out the html, starts the webservice and ends it self on a normal way.
Haven't tested, but should work?!
# re: Ending a Response without Response.End() Exceptions?
Response.Write(new string(" ".Chars(0), 255));
After doing that, subsequent calls to flush should get the content to appear in the browser.
# re: Ending a Response without Response.End() Exceptions?
What I've done is working fine for me :
//This is my extension method to make things easier
public static class ExtensionMethods {
public static void WriteJSONObject(this HttpResponse response, object content) {
response.ContentType = "application/json";
response.Write(new JavaScriptSerializer().Serialize(content));
response.End();
}
}
and here I do my logic :
try { Response.WriteJSONObject(new { Result = "See hello" }); } catch (Exception err) { if (err.Message != "Thread was being aborted.") Response.WriteJSONObject(new { Result = err.Message }); else { Response.End(); } }
# re: Ending a Response without Response.End() Exceptions?
What solution did you end up using?
I was using Response.Close() for years, and found that it doesn't work any more under IIS 7.5
Thanks
Andrew
# re: Ending a Response without Response.End() Exceptions?
One way to do deal with the thread exceptions is to trap for them specifically in a Application_Error handler and ignore them (or handle them as you see fit).
# re: Ending a Response without Response.End() Exceptions?
If a specific parameter is passed in for "async" mode, then I would Response.Write some XML with a GUID in it, call Response.Close(), and continue processing.
Then when processing was complete, I would stick the result in a cache, using the guid as a key.
The client code can poll the cache to find the result.
However in IIS 7.5, Response.Close won't close the connection. So I was looking into some alternate ideas. Perhaps the delegate idea mentioned above will work.
# re: Ending a Response without Response.End() Exceptions?
private void EndResponse()
{
try
{
Context.Response.End();
}
catch (System.Threading.ThreadAbortException err)
{
System.Threading.Thread.ResetAbort();
}
catch (Exception err)
{
}
}
# re: Ending a Response without Response.End() Exceptions?
I think this might do the trick...
Response.Flush(); Response.Close();Cheers,
James