Как защитить пулы приложений от исключений сериализации сеансов?

Мы используем Out-of-Process Session Provider (ScaleOut) для приложения ASP.NET, и мы заметили, что когда объект, который неправильно настроил для де-сериализации, непреднамеренно делает его путь в сеанс будет в конечном итоге привести к завершению всего процесса.

Воспроизведение и обработка этого сценария - это то, где он становится еще интереснее.

Исключение, которое завершает процесс, возникает в AnyStaObjectsInSessionState, реализация которого довольно проста:

internal static bool AnyStaObjectsInSessionState(HttpSessionState session)
{
    if (session != null)
    {
        int count = session.Count;
        for (int i = 0; i < count; i++)
        {
            object obj2 = session[i];
            if (((obj2 != null) && (obj2.GetType().FullName == "System.__ComObject"))
                && (UnsafeNativeMethods.AspCompatIsApartmentComponent(obj2) != 0))
            {
                return true;
            }
        }
    }
    return false;
}

Здесь трассировка стека, которая показывает, как здесь заканчиваются исключения:

An unhandled exception occurred and the process was terminated.

Application ID: /LM/W3SVC/1/ROOT

Process ID: 4208

Exception: System.Runtime.Serialization.SerializationException

Message: The constructor to deserialize an object of type 'Lucene.Net.QueryParsers.ParseException' was not found.

StackTrace:    at System.Runtime.Serialization.ObjectManager.CompleteISerializableObject(Object obj, SerializationInfo info, StreamingContext context)
   at System.Runtime.Serialization.ObjectManager.FixupSpecialObject(ObjectHolder holder)
   at System.Runtime.Serialization.ObjectManager.DoFixups()
   at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   at System.Web.Util.AltSerialization.ReadValueFromStream(BinaryReader reader)
   at System.Web.SessionState.SessionStateItemCollection.ReadValueFromStreamWithAssert()
   at System.Web.SessionState.SessionStateItemCollection.DeserializeItem(String name, Boolean check)
   at System.Web.SessionState.SessionStateItemCollection.DeserializeItem(Int32 index)
   at System.Web.SessionState.SessionStateItemCollection.get_Item(Int32 index)
   at System.Web.SessionState.HttpSessionStateContainer.get_Item(Int32 index)
   at System.Web.Util.AspCompatApplicationStep.AnyStaObjectsInSessionState(HttpSessionState session)
   at System.Web.HttpApplicationFactory.FireSessionOnEnd(HttpSessionState session, Object eventSource, EventArgs eventArgs)
   at System.Web.SessionState.SessionOnEndTargetWorkItem.RaiseOnEndCallback()
   at System.Web.Util.WorkItem.CallCallbackWithAssert(WorkItemCallback callback)
   at System.Threading.ExecutionContext.runTryCode(Object userData)
   at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)

InnerException: System.Runtime.Serialization.SerializationException

Message: The constructor to deserialize an object of type 'Lucene.Net.QueryParsers.ParseException' was not found.

StackTrace:    at System.Runtime.Serialization.ObjectManager.GetConstructor(Type t, Type[] ctorParams)
   at System.Runtime.Serialization.ObjectManager.CompleteISerializableObject(Object obj, SerializationInfo info, StreamingContext context)

Мы хотели бы понять две вещи:

  • Когда работает FireSessionOnEnd для поставщика вне процесса и, что более важно, как мы можем имитировать это в среде разработки, которая не находится под нагрузкой? Я экспериментировал с пониженным тайм-аутом сеанса (установленным на минуту), вручную вызывая Abandon() и вручную вызывая GC.Collect(), но все это не помогло.

  • Можем ли мы уловить ошибки, которые происходят на этом этапе для защиты пула приложений? Вышеприведенные исключения записываются в w/ Source = ASP.NET 2.0.50727.0 и не доходят до обработчиков ошибок приложения в global.asax. Что мы можем сделать для защиты от этого сценария, даже если соответствующие привязки и привязки применяются к объектам, связанным с сеансом?

Любые идеи будут оценены.

Ответ 1

Мы смогли решить эту проблему с помощью технической поддержки SOSS - они были чрезвычайно полезны - вот подробности:

  • По истечении срока действия сеанса SOSS вызывает событие истечения срока действия в своих клиентских библиотеках, что, в свою очередь, отвечает за запуск Session_End в Global.asax(NB:). Нагрузка ScaleOut выравнивает сроки истечения срока действия на всех клиентах, поэтому сеть сервер, создавший сеанс, может не обязательно получать свое истечение - это важно для воспроизведения этих проблем).
  • Поскольку это происходит вне контекста запроса, исключение необработанно и убивает пул приложений;
  • Это чрезвычайно необычный сценарий, но тот, который они тем не менее обратятся в будущих выпусках обслуживания;
  • Средства защиты заключаются в следующем:

    • Исправьте System.Exception -разложенный тип (сериализуемый, но не unserializable);

    • Удалить Session_End события в Global.asax или отключить события истечения срока действия ( max_event_retries установлен в 0 в файле soss_params.txt);

    • В этих сценариях вероятность того, что пользователь сталкивается с СериализацияException на одном из их запросы, то есть Application_Error; здесь вы можете очистить ключи сеанса (необходимо очистить все их) или отказаться от сеанса наповал;

    • Подпишитесь на AppDomain.UnhandledException, чтобы быть уведомление о необработанных исключениях, если они произойдут (здесь нет регресса, только регистрация); они также могут быть отключен через legacyUnhandledExceptionPolicy (не рекомендованная);

Ответ 2

Можем ли мы ловить ошибки, которые происходят при этом шаг для защиты пула приложений? исключения, поднятые здесь, регистрируются w/ Source = ASP.NET 2.0.50727.0 и не достичь обработчиков ошибок приложения в global.asax. Что мы можем сделать для защищать от этого сценария, даже после надлежащих сдержек и противовесов применяются к объектам, связанным с сеансом?

Я не знаю, будет ли это, но вы можете сделать снимок

Ответ 3

Я исправил это, полностью удалив методы SessionEnd. Недостаточно удалить содержимое методов, поскольку Asp.net ищет существование метода с использованием отражения, а затем запускает код нарушения.