GCStress: try to reduce races and tolerate races better (#17330)
authorAndy Ayers <andya@microsoft.com>
Thu, 19 Apr 2018 17:17:11 +0000 (10:17 -0700)
committerGitHub <noreply@github.com>
Thu, 19 Apr 2018 17:17:11 +0000 (10:17 -0700)
commit571b1a7c84aa264afe6a33bd58eca8c9c10052ff
tree238ff06b6a076af59bd9e1a28f115c6c41a32adf
parent204c11b8309bf243b9255ddf7f17820cd320cf4d
GCStress: try to reduce races and tolerate races better (#17330)

This change addresses races that cause spurious failures in when running
GC stress on multithreaded applications.

* Instruction update race

Threads that hit a gc cover interrupt where gc is not safe can race to
overrwrite the interrupt instruction and change it back to the original
instruction.

This can cause confusion when handling stress exceptions as the exception code
raised by the kernel may be determined by disassembling the instruction that
caused the fault, and this instruction may now change between the time the
fault is raised and the instruction is disassembled. When this happens the
kernel may report an ACCESS_VIOLATION where there was actually an attempt to
execute a priveledged instruction.

x86 already had a tolerance mechanism here where when gc stress was active
and the exception status was ACCESS_VIOLATION the faulting instruction would
be retried to see if it faults the same way again. In this change we extend
this to tolerance to cover x64 and also enable it regardless of the gc mode.
We use the exception information to further screen as these spurious AVs look
like reads from address 0xFF..FF.

* Instrumentation vs execution race

The second race happens when one thread is jitting a method and another is
about to call the method. The first thread finishes jitting and publishes the
method code, then starts instrumenting the method for gc coverage. While this
instrumentation is ongoing, the second thread then calls the method and hits
a gc interrupt instruction. The code that recognizes the fault as a gc coverage
interrupt gets confused as the instrumentation is not yet complete -- in
particular the m_GcCover member of the MethodDesc is not yet set. So the second
thread triggers an assert.

The fix for this is to instrument for GcCoverage before publishing the code.
Since multiple threads can be jitting a method concurrently the instrument and
public steps are done under a lock to ensure that the instrumentation and code
are consistent (come from the same thread).

With this lock in place we have removed the secondary locking done in
SetupGcCoverage as it is no longer needed; only one thread can be instrumenting
a given jitted method for GcCoverage.

However we retain a bailout` clause that first looks to see if m_GcCover is
set and if so skips instrumentation, as there are prejit and rejit cases where we
will retry instrumentation.

* Instruction cache flushes

In some cases when replacing the interrupt instruction with the original the
instruction cache was either not flushed or not flushed with sufficient length.
This possibly leads to an increased frequency of the above races.

No impact expected for non-gc stress scenarios, though some of the code changes
are in common code paths.

Addresses the spurious GC stress failures seen in #17027 and #17610.
14 files changed:
src/inc/CrstTypes.def
src/inc/crsttypes.h
src/inc/switches.h
src/vm/ceemain.cpp
src/vm/dynamicmethod.cpp
src/vm/excep.cpp
src/vm/excep.h
src/vm/exceptionhandling.cpp
src/vm/gccover.cpp
src/vm/i386/excepx86.cpp
src/vm/method.hpp
src/vm/prestub.cpp
src/vm/threads.cpp
src/vm/threads.h