Question

we use a StateServer for handling Session for the known benefits (web farm, IIS recycling).

However I am trying to figure out how to make this fault tolerant. Nothing we store in the Session is critical, it is just used for performance. So if the StateServer is not available we are happy to reload from disk.

However there appears to be no way of detecting if the StateServer is online or not, so the following code all runs fine even if the StateServer is down

try
{
    //It is not NULL as it has been configured
    if (HttpContext.Current.Session != null)
        Session["Test"] = "value";
}
// No exception is thrown
catch (Exception)
{
    throw new Exception();
}

Now it makes sense to me that no exception is thrown. The Session handling would not be very performant if it had to check the status on every write. So I am guessing what happens is that it writes all the Session vaiables when the Response is written.

There lies the problem, when it tries to write the Session it fails with a 500 error and I do not know anyway to intercept this error and handle it.

Unable to make the session state request to the session state server. Please ensure that the ASP.NET State service is started and that the client and server ports are the same.

What I would like to happen is that the write just fails silently (or logs an error) and clients are not impacted. As it is write now the entire site goes down due to this single point of failure.

Any ideas - am I missing something obvious?

Was it helpful?

Solution 2

I would like to accept tgolisch answer as a solution that works for me.

  • In Global.asax we will look for the missing StateServer error in the Application_Error event
  • If we find it we will use Server.ClearError() and log the error
  • We will also use this to log the error and possibly send out an alert

Thanks all!

OTHER TIPS

well, it can be hard. Asp.net uses session tightly, so if session storage fails, it asp.net will also fails during initialization of session module. You can write own session state provider, that will wrap existing one, and in case of fail it will return empty session items, but it can be hard to use it, because session behavior can be unpredictable.

You can look into built in SQL session state provider, that has failover in case if your SQL server has replication.

UPDATE1

Here is example of wrapper for default session providers

public class SessionProviderWrapper : SessionStateStoreProviderBase
{
    private readonly SessionStateStoreProviderBase _provider;

    private static Func<SessionStateStoreProviderBase> _createProvider;

    static SessionProvider()
    {
        _createProvider = InitializerProvider();
    }

    private static Func<SessionStateStoreProviderBase> InitializerProvider()
    {
        if (_createProvider != null)
            return _createProvider;

        var sessionType = "stateserver"; // you can switch to another session provider

        Type type;
        switch (sessionType)
        {
            case "inproc":
                type = Type.GetType("System.Web.SessionState.InProcSessionStateStore, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
                break;

            case "sql":
                type = Type.GetType("System.Web.SessionState.SqlSessionStateStore, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
                break;

            case "stateserver":
                type = Type.GetType("System.Web.SessionState.OutOfProcSessionStateStore, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
                break;

            default:
                throw new ConfigurationErrorsException("Unknow session type: " + sessionType);
        }

        if (type == null)
        {
            throw new InvalidOperationException("Failed to find session provider for " + sessionType);
        }

        _createProvider = GenerateConstructorCall(type);

        return _createProvider;
    }

    private static Func<SessionStateStoreProviderBase> GenerateConstructorCall(Type type)
    {
        // we are searching for public constructor
        var constructor = type.GetConstructors().FirstOrDefault(c => c.GetParameters().Length == 0);
        if (constructor == null)
        {
            // otherwise for internal. SQL session provider has internal constructor, but we don't care
            constructor = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(c => c.GetParameters().Length == 0);
        }

        var node = Expression.New(constructor);
        var lambda = Expression.Lambda<Func<SessionStateStoreProviderBase>>(node, null);
        var func = lambda.Compile();
        return func;
    }

    public SessionProvider()
    {
        var createProvider = InitializerProvider();

        _provider = createProvider();
    }

    public override void Initialize(string name, NameValueCollection config)
    {
        _provider.Initialize(name, config);
    }

    public override string Name
    {
        get { return _provider.Name; }
    }

    public override string Description
    {
        get { return _provider.Description; }
    }

    public override void Dispose()
    {
        _provider.Dispose();
    }

    public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
    {
        return _provider.SetItemExpireCallback(expireCallback);
    }

    public override void InitializeRequest(HttpContext context)
    {
        _provider.InitializeRequest(context);
    }

    public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId,
                                         out SessionStateActions actions)
    {
        try
        {
            return _provider.GetItem(context, id, out locked, out lockAge, out lockId, out actions);
        }
        catch (Exception ex)
        {
            locked = false;
            lockAge = TimeSpan.Zero;
            lockId = null;
            actions = SessionStateActions.None;
            // log ex
            return new SessionStateStoreData(new SessionStateItemCollection(), new HttpStaticObjectsCollection(), 10);
        }
    }

    public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId,
                                                  out SessionStateActions actions)
    {
        return _provider.GetItemExclusive(context, id, out locked, out lockAge, out lockId, out actions);
    }

    public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
    {
        _provider.ReleaseItemExclusive(context, id, lockId);
    }

    public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
    {
        _provider.SetAndReleaseItemExclusive(context, id, item, lockId, newItem);
    }

    public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
    {
        _provider.RemoveItem(context, id, lockId, item);
    }

    public override void ResetItemTimeout(HttpContext context, string id)
    {
        _provider.ResetItemTimeout(context, id);
    }

    public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
    {
        return _provider.CreateNewStoreData(context, timeout);
    }

    public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
    {
        _provider.CreateUninitializedItem(context, id, timeout);
    }

    public override void EndRequest(HttpContext context)
    {
        _provider.EndRequest(context);
    }
}

Basically you can make try\catch on each method like in GetItem method, and in case of error, you can return empty session object. If it fails in try\catch application still will be alive. But performance will be decreased as for each request it will throw a couple of exceptions on Get\Release, that will be handled in catch section. But anyway these exceptions will decrease performance a bit

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top