Pull to refresh

Интересная задачка: повышаем стабильность (robustness) приложений (ч. 3) — код под CER и прочие финты ушами

Reading time4 min
Views558
В продолжение темы устойчивых приложений и CER (начало тут) — рассмотрим, как подготовить произвольный (или почти произвольный) участок кода для выполнения в регионе Constrained Execution.

Зачем такое надо? Помните, я говорил, что в процессе подготовки к CER происходит инспекция стека и пре-jitt. Но проинспектировать стек, если вызов происходит через интерфейс или виртуальный метод заранее невозможно. Поэтому такой код:
    ...
    RuntimeHelper.PrepareConstrainedRegions();
     try {}
    finally {
         obj.SomeVirtualMethod();
    }
скомпилируется, но в run-time не достигнет желаемого.

Для того, чтобы подготовить виртуальные методы, необходимо воспользоваться еще одной возможностью класса RuntimeHelpers — методом PrepareMethod():
    ...
    RuntimeHelpers.PrepareMethod(obj.GetType().GetMethod("SomeVirtualMethod").MethodHandle);
    RuntimeHelpers.PrepareConstrainedRegions();
     try {}
    finally {
         obj.SomeVirtualMethod();
    }
Теперь регион будет подготовлен и отработает нормально.
Думаю, вы уже поняли, что в случае вызова через интерфейс следует подготовить методы всех классов, которые могут скрываться за интерфейсом. Sad but true.
Для делегатов процедура выглядит аналогично, однако следует помнить вот что — PrepareDelegate работает только по первому делегату в списке — это означает, что в MulticastDelegate будет обработан только первый из делегатов в списке — нужно будет вручную проходит по всем делегатам из списка и готовить их.
Если вы используете делегаты только для событий, вызывая события из CER, то ситуация несколько упрощается — использование кода, приведенного ниже, позволяет автоматически подготавливать делегаты:
public event EventHandler MyEvent {
  add {
    if (value == null) return;
    RuntimeHelpers.PrepareDelegate(value);
    lock(this) _myEvent += value;
  }
   remove { lock(this) _myEvent -= value; }
}
Понятно, что с большой силой приходит и большая ответственность. А возможность выполнить любой код без ограничения и выброса асинхронных исключений — это большая сила. Инфраструктура .net позволяет разработчику указать, какую ответственность принимает на себя код, будучи запущенным под CER. И делает он это при помощи аттрибута System.Runtime.ConstrainedExecution.ReliabilityContractAttribute. Этот аттрибут может быть применен как на уровне сборки или класса, так и на уровне метода, обеспечивая необходимый уровень детальности.
Если вы попробуете применить этот аттрибут, то вы увидите, что у него можно установить два свойства из допустимых перечислений: Cer и ConsistencyGuarantee. ConsistencyGuarantee отвечает за то, какие «разрушения» может нанести метод, если забить на асинхронные exceptions при исполнении его под Cer. Так выглядит его объявление (тут не нужно переводить, я думаю):
public enum Consistency {  MayCorruptProcess = 0, MayCorruptAppDomain = 1,  
MayCorruptInstance = 2, WillNotCorruptState = 3 }
А Cer отвечает за то, насколько успешным будет вызов метода. Он может принимать значения
{ None = 0, MayFail = 1, Success = 2 } 

Несмотря на большое количество сочетаний этих свойств, для методов, с которых начинается исполнение CER, валидны только 3 комбинации:
— [ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)] — «моя хата с краю, ничего не знаю»; при возникновении ошибки кроме инстанса объекта ничего не пострадает.
— [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] — «чистое» падение: даже если упал, можно продолжать работу;
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] — «метод точно не упадет»
По умолчанию все методы считаются имеющими аттрибут [ReliabilityContract(Consistency.WillCorruptProcess, Cer.None)].
Эти контракты на выполнение будут интерпретироваться хостинг-средой фреймворка, чтобы отреагировать на проблему соответствующим образом — например, «прибить» домен заранее, если уже произошло что-то фатальное, не давая программе повредить что-либо еще.

Напоследок вот что. acerv спрашивал про StackOverflow в прошлом посте. "- Спрашивали? Отвечаем!" — .net framework не гарантирует, что поток выполнит какой-либо блок finally в случае StackOverflowException. Тем не менне, существует не очень красивая, но простая возможность гарантированно выполнить некоторый clean-up код — все при помощи того же RuntimeHelpers. Много объяснять не буду:
try
{
    RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup( TryMethod, CleanupMethod, this);
}
catch(SomeException exc) { ... } // Handle unexpected failure

...

[MethodImpl(MethodImplOptions.NoInlining)]
void TryMethod(object userdata) { ... } 

[MethodImpl(MethodImplOptions.NoInlining)]
[ReliabilityContract(Cer.Success, Consistency.MayCorruptInstance)]
void CleanupMethod(object userdata, bool fExceptionThrown) { ... }


finally

Надеюсь, было интересно.
Однако ну и длинной выдалась тема! :) А еще есть что сказать про SafeHandles, MemoryBarries и FailFast. Ждите :)
Tags:
Hubs:
Total votes 12: ↑7 and ↓5+2
Comments3

Articles