Reputation: 460
I have run into an issue which can be replicated in the following way (you need IIS8 so must be on Windows 8+ or Windows Server 2012 R2+):
Create a new website in IIS Manager, say TestWs on port 8881, pointing to a new folder, say C:\temp\testws, and add the following Web.config file in there
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation targetFramework="4.5"/>
<httpRuntime targetFramework="4.5"/>
</system.web>
</configuration>
Now add the following WsHandler.ashx file in the same folder
<%@ WebHandler Language="C#" Class="WsHandler" %>
using System;
using System.Threading;
using System.Web;
public class WsHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.AcceptWebSocketRequest(async webSocketContext =>
{
while (true)
{
await webSocketContext.WebSocket.ReceiveAsync(new ArraySegment<byte>(new byte[1024]), CancellationToken.None);
}
});
}
public bool IsReusable { get { return true; } }
}
Then create a websocket from within the developer toolbar in your browser like so
var ws = new WebSocket("ws://localhost:8881/wshandler.ashx");
ws.onclose = function() { console.log('closed'); };
In task manager you will see there is a w3wp.exe process for this application, if you kill it the client get the onclose event raised and the closed text will be printed.
However if you create a websocket as described above and go to IIS manager and recycle the application pool, the websocket will not be closed, and there will now be two w3wp.exe processes.
Closing the web socket ws.close();
or refreshing the browser will cause the original w3wp.exe process to be shut down.
It seems the presence of the open websocket is causing IIS to be unable to recycle the app pool correctly.
Can anyone figure out what to change in my code or what to change in IIS to get this to work?
Upvotes: 8
Views: 5045
Reputation: 31
As I had the same problem, here's the solution I figured out:
In your IHttpHandler you should have an static object which inherits IStopListeningRegisteredObject. Then use HostingEnvironment.RegisterObject(this) to get notified (via StopListening) when the Application Pool is recyled.
You'll also need a CancellationTokenSource (static, too), which you'll hand over in ReceiveAsync. In StopListening() you can then use Cancel() of the CancellationTokenSource to end the await. Then catch the OperationCanceledException and call Abort() on the socket.
Don't forget the Dispose() after the Cancel() or the App-Pool will still wait.
Upvotes: 3
Reputation: 2979
Try setting "Shutdown Time Limit" to 1 second (App Pool > Advanced Settings > Process Model) [PS: I don't have IIS8. I'm checking the properties in IIS7.]
This property defines the time given to worker process to finish processing requests and shutdown. If the worker process exceeds the shutdown time limit, it is terminated.
I can see default value in IIS7 is 90 seconds. If that's the same value in IIS8 too, then it might be giving that much time to earlier worker process to finish it's work. After 90 seconds (1.5 mins) it will terminate that process and your web socket will get closed. If you change it to 1 second it will terminate the earlier worker process will get terminated almost instantly (as soon as you recycle app pool) and you will get the expected behavior.
Upvotes: 3
Reputation: 561
As far as I know, while a WebSocket is open, IIS won't tear down the app domain, so you see this behaviour exhibited.
The best I can suggest is that you do some cross process signalling to force the old instance to shutdown. You could achieve this with an EventWaitHandle:
Create a named EventWaitHandle in your web application, and signal it at startup.
On a separate thread, wait on the wait handle
When it is signalled, call HostingEnvironment.InitiateShutdown to force any running old instance to shutdown.
Upvotes: 5