5

I'm making my first attempt at experimenting with Comet. I developed a very simple chat web app - basically a hello world of comet via c#. The problem i'm having is IIS will sometimes crash and by crash i mean it simply stops responding to HTTP requests. It then takes for ever to restart the app pool and sometimes the whole IIS service. I'm almost positive the culprit is the ManualResetEvent object I'm using to block comet requests threads until a signal to release (update) those threads is received. I tried writing an HTTP handler to get around this and set the reusable property to false (to put new requests threads on another instance of the ManualResetEvent object) but that didn't work. I'm also trying implementing the IRegisteredObject so I can release those theads when the app is shutting down but that doesn't seem to work either. It still crashes and there doesn't seem to be any pattern in when it crashes (that i've noticed). I'm almost sure it's a combination of static instances and the use of ManualResetEvent that's causing it. I just don't know for sure how or how to fix it for that matter.

Comet.cs (My simple comet lib)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Mail;
using System.Web.Hosting;

namespace Comet
{
    public class CometCore : IRegisteredObject
    {
        #region Globals
        private static CometCore m_instance = null;
        private List<CometRequest> m_requests = new List<CometRequest>();
        private int m_timeout = 120000; //Default - 20 minutes;
        #endregion

        #region Constructor(s)
        public CometCore()
        {
            HostingEnvironment.RegisterObject(this);
        }
        #endregion

        #region Properties
        /// <summary>
        /// Singleton instance of the class
        /// </summary>
        public static CometCore Instance
        {
            get
            {
                if (m_instance == null)
                    m_instance = new CometCore();
                return m_instance;
            }
        }

        /// <summary>
        /// In milliseconds or -1 for no timeout.
        /// </summary>
        public int Timeout { get { return m_timeout; } set { m_timeout = value; } }
        #endregion

        #region Public Methods
        /// <summary>
        /// Pauses the thread until an update command with the same id is sent.
        /// </summary>
        /// <param name="id"></param>
        public void WaitForUpdates(string id)
        {
            //Add this request (and thread) to the list and then make it wait.
            CometRequest request;
            m_requests.Add(request = new CometRequest(id));

            if (m_timeout > -1)
                request.MRE.WaitOne(m_timeout);
            else
                request.MRE.WaitOne();
        }

        /// <summary>
        /// Un-pauses the threads with this id.
        /// </summary>
        /// <param name="id"></param>
        public void SendUpdate(string id)
        {
            for (int i = 0; i < m_requests.Count; i++)
            {
                if (m_requests[i].ID.Equals(id))
                {
                    m_requests[i].MRE.Set();
                    m_requests.RemoveAt(i);
                    i--;
                }
            }
        }
        #endregion

        public void Stop(bool immediate)
        {
            //release all threads
            for (int i = 0; i < m_requests.Count; i++)
            {
                m_requests[i].MRE.Set();
                m_requests.RemoveAt(i);
                i--;
            }
        }
    }

    public class CometRequest
    {
        public string ID = null;
        public ManualResetEvent MRE = new ManualResetEvent(false);
        public CometRequest(string pID) { ID = pID; }
    }
}

My chat class and web service

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using Comet;

namespace CometTest
{
    /// <summary>
    /// Summary description for Chat
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
    [System.Web.Script.Services.ScriptService]
    public class Chat : System.Web.Services.WebService
    {

        [WebMethod]
        public string ReceiveChat()
        {
            return ChatData.Instance.GetLines();
        }

        [WebMethod]
        public string ReceiveChat_Comet()
        {
            CometCore.Instance.WaitForUpdates("chat");
            return ChatData.Instance.GetLines();
        }

        [WebMethod]
        public void Send(string line)
        {
            ChatData.Instance.Add(line);
            CometCore.Instance.SendUpdate("chat");
        }
    }

    public class ChatData
    {
        private static ChatData m_instance = null;
        private List<string> m_chatLines = new List<string>();
        private const int m_maxLines = 5;

        public static ChatData Instance
        {
            get
            {
                if (m_instance == null)
                    m_instance = new ChatData();
                return m_instance;
            }
        }

        public string GetLines()
        {
            string ret = string.Empty;
            for (int i = 0; i < m_chatLines.Count; i++)
            {
                ret += m_chatLines[i] + "<br>";
            }
            return ret;
        }

        public void Add(string line)
        {
            m_chatLines.Insert(0, line);
            if (m_chatLines.Count > m_maxLines)
            {
                m_chatLines.RemoveAt(m_chatLines.Count - 1);
            }
        }
    }
}

The test aspx file

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="CometTest.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">

        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Services>
                <asp:ServiceReference Path="~/Chat.asmx" />
            </Services>
        </asp:ScriptManager>

        <div id="lyrChatLines" style="height: 200px; width: 300px; border: 1px solid #cccccc; overflow: scroll">
        </div>

        <asp:Panel runat="server" DefaultButton="cmdSend">
            <asp:UpdatePanel runat="server">
                <ContentTemplate>
                    <asp:TextBox style="width: 220px" runat="server" ID="txtChat"></asp:TextBox>
                    <asp:Button runat="server" ID="cmdSend" Text="Send" OnClick="cmdSend_Click" />
                </ContentTemplate>
            </asp:UpdatePanel>
        </asp:Panel>

        <script type="text/javascript">

            function CometReceive()
            {
                CometTest.Chat.ReceiveChat_Comet(receive, commError, commError);
            }

            function ReceiveNow()
            {
                CometTest.Chat.ReceiveChat(receive, commError, commError);
            }

            function receive(str)
            {
                document.getElementById("lyrChatLines").innerHTML = str;
                setTimeout("CometReceive()", 0);
            }

            function commError()
            {
                document.getElementById("lyrChatLines").innerHTML =
                    "Communication Error...";
                setTimeout("CometReceive()", 5000);
            }

            setTimeout("ReceiveNow()", 0);
        </script>
    </form>
</body>
</html>

And the aspx code behind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace CometTest
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void cmdSend_Click(object sender, EventArgs e)
        {
            Chat service = new Chat();
            service.Send
            (
                Request.UserHostAddress + "> " +
                txtChat.Text
            );
            txtChat.Text = string.Empty;
            txtChat.Focus();
        }
    }
}

If anyone has a good theory on the cause and/or fix for the seemingly arbitrary crashes it would be much appreciated if you'ld post :)

Stewart Anderson
  • 333
  • 2
  • 14

1 Answers1

1

This question .NET Comet engine has some links that should point you in the right direction. You need to look at implementing a IHttpAsyncHandler to handle the long running comet request.

Community
  • 1
  • 1
snoopy-do
  • 605
  • 4
  • 16
  • Thanks! I'll give that a try as soon as i can. I do have an http handler i wrote to get around this problem but i implemented IHttpHandler (not the asnyc) so I'll definatley give that a try. Do you think it's possible to do this without creating an HTTP handler though? I ask because it would be nice to develop a simple lib that can be easily used in a web servie like i have above. – Stewart Anderson Jun 20 '11 at 04:39
  • 1
    I've not used a web service so I can't say. I have however developed a comet server using the IHttpAsyncHandler and it works great. IIS/http.sys uses a fixed number of threads to process incoming requests, if you tie them up with long running or blocking (.WaitOne) calls you very quickly kill iis. – snoopy-do Jun 20 '11 at 09:39
  • Does this http://msdn.microsoft.com/en-us/library/aa480516.aspx link from msdn help? It talks about Async WebMthods. – snoopy-do Jun 20 '11 at 09:41
  • I didn't know that, i thought it can use as many threads as resources would allow - good to know! I'll give the HttpAsyncHandler a try hopefully with in the next couple of days. Thanks again! – Stewart Anderson Jun 20 '11 at 15:19
  • I'll give the async web methods a try too. – Stewart Anderson Jun 20 '11 at 15:38
  • Async HTTP Handlers fixes this issue! I don't think async web methods can be invoked from the client side (via javascript) so that method wouldn't work but async http handlers do. The only issue with that is it makes sending data to the client a little less convenient. Oh well, it's on a comet communication mechanism and it works and doesn't seem to ever crash. If anyone figures out a way to make it work with async web methods please let me know! – Stewart Anderson Jul 14 '11 at 23:59
  • Scripts can call a page method if you mark the page method as script callable, same as you do for a service. In a page method, unlike a service, you can use Page.RegisterAsyncTask, which should do what you need. – eselk Apr 22 '13 at 16:22