I'm reading through Constrained Execution Regions and other errata [Brian Grunkemeyer] in an attempt to understand constrained execution regions, however I'm having some problems understanding the following sample:
RuntimeHelpers.PrepareConstrainedRegions();
try {
// Prepare my backout code
MethodInfo m = _list.GetType().GetMethod("RemoveAt", new Type[] { typeof(int) });
RuntimeHelpers.PrepareMethod(m.MethodHandle);
IEnumerator en = c.GetEnumerator();
while(en.MoveNext()) {
_list.Insert(index++, en.Current);
// Assuming that these lines aren't reordered.
numAdded++;
}
_version++;
}
catch(Exception) {
// Reliable backout code
while(numAdded > 0) {
_list.RemoveAt(index--);
numAdded--;
}
throw;
}
My understanding is that the try
block is not constrained, only the finally and catch blocks are constrained. This means that during the try
block an asynchronous exception (e.g. ThreadAbortException) can be thrown at any time, in particular it could be thrown before numAdded++
but after _list.Insert
. In this case the backout code would remove one item too few from _list
.
Given this I'm struggling to understand the purpose of the constrained execution region in this example.
Is my understanding of this correct or have I missed something?
The documentation and the actual behavior of CERs do not match exactly based on what I observe. The issue you describe where a
ThreadAbortException
gets injected betweenInsert
andnumAdded++
is not possible with any of the .NET Framework versions I have tested. There are two possible reasons for this.PrepareConstrainedRegions
does, despite what the documentation says, have an observable effect on thetry
block. It will delay certain abort injections; specifically those that do not come while the thread is in an alertable state.PrepareConstrainedRegions
call the abort still will not get injected into that location. Based on the SSCLI code the abort will be injected at the backward jump to spin thewhile
loop.I figured some of this out while answering my own related question here and then attempting to answer a question about how
Thread.Abort
actually works here.Point #2 is not legit. It is an implementation detail of the SSCLI that may not carry over to the official distributions (though I suspect it actually does). Furthermore, it ignores the possibility of having the abort injected at some point during the execution of
Insert
. I suppose it is possible that the crucial bits ofInsert
could use a CER internally though.Point #1 may be the one that matters, but that begs the questions why did Microsoft not document it and why did the article you cited not mention it either. Surely the author of the article knew of this fact. Otherwise, I too am not understanding how the code presented could possibly be safe. In other words, it seems safe only by accident right now.
If I had to take a guess as to what
PrepareConstrainedRegions
is doing behind the scenes I would say that it sets a flag in the JIT engine that tells it not to inject the GC poll hook that gets placed strategically at backward branch jumps for code inside a CERtry
block. This GC poll hook is where the asynchronous abort would typically be injected (in addition to its main purpose related to garbage collection).