This project is read-only.
1
Vote

PEModule.MetadataReader property unreliably throws exceptions

description

In the PEModule.MetadataReader property the initialization sometimes throws one of various exceptions. The exception comes from the "peReaderOpt.HasMetadata" access.

This happens while loading a project and displaying a syntax highlighted source file. The Roslyn call during which this happens is during a "SemanticModel.GetDiagnostics" which I do to show errors in the current file.

I've observed this 5 times so far, out of about 150 runs, without changing the input, just rerunning the same tool over the same project. I've listed the different exceptions below, 3 of 5 where the "seek before start of file".
System.IO.IOException: Unable to read metadata file ---> System.Reflection.TargetInvocationException: Ein Aufrufziel hat einen Ausnahmefehler verursacht. ---> System.IO.IOException: Es wurde versucht, den Dateizeiger vor den Anfang der Datei zu bewegen.
 bei System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
 bei System.IO.FileStream.SeekCore(Int64 offset, SeekOrigin origin)
 bei System.IO.FileStream.FlushRead()
 bei System.IO.FileStream.FlushInternalBuffer()
 bei System.IO.FileStream.Flush(Boolean flushToDisk)
 bei System.IO.FileStream.Flush()
 bei System.IO.MemoryMappedFiles.MemoryMappedFile.CreateFromFile(FileStream fileStream, String mapName, Int64 capacity, MemoryMappedFileAccess access, MemoryMappedFileSecurity memoryMappedFileSecurity, HandleInheritability inheritability, Boolean leaveOpen)
 --- Ende der internen Ausnahmestapelüberwachung ---
 bei System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
 bei System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
 bei System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
 bei System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
 bei System.Reflection.Internal.MemoryMapLightUp.CreateMemoryMap(Stream stream)
 bei System.Reflection.Internal.StreamMemoryBlockProvider.TryCreateMemoryMapAccessor(Int32 start, Int32 size, IDisposable& accessor)
 --- Ende der internen Ausnahmestapelüberwachung ---
 bei System.Reflection.Internal.StreamMemoryBlockProvider.TryCreateMemoryMapAccessor(Int32 start, Int32 size, IDisposable& accessor)
 bei System.Reflection.Internal.StreamMemoryBlockProvider.GetMemoryBlockImpl(Int32 start, Int32 size)
 bei System.Reflection.Internal.MemoryBlockProvider.GetMemoryBlock(Int32 start, Int32 size)
 bei System.Reflection.PortableExecutable.PEReader.GetMetadataBlock()
 bei System.Reflection.PortableExecutable.PEReader.GetMetadata()
 bei System.Reflection.Metadata.PEReaderExtensions.GetMetadataReader(PEReader peReader, MetadataReaderOptions options, StringInterner stringInterner)
 bei Microsoft.CodeAnalysis.PEModule.get_MetadataReader()
System.BadImageFormatException: Missing data directory.
   bei System.Reflection.PortableExecutable.PEHeaders.CalculateMetadataLocation(Int64 peImageSize, Int32& start, Int32& size)
   bei System.Reflection.PortableExecutable.PEHeaders..ctor(Stream peStream)
   bei System.Reflection.PortableExecutable.PEReader.ReadPEHeadersNoLock(Stream stream)
   bei System.Reflection.PortableExecutable.PEReader.InitializePEHeaders()
   bei System.Reflection.PortableExecutable.PEReader.get_PEHeaders()
   bei System.Reflection.PortableExecutable.PEReader.get_HasMetadata()
   bei Microsoft.CodeAnalysis.PEModule.get_MetadataReader()
System.IndexOutOfRangeException: Beim Kopieren in den Speicher wurde eine mögliche E/A-Racebedingung festgestellt. Das E/A-Paket ist standardmäßig nicht threadsicher. In Multithread-Anwendungen muss mit einer threadsicheren Methode auf einen Datenstrom zugegriffen werden, z. B. einem von TextReader zurückgegebenen threadsicheren Wrapper oder synchronisierten TextWriter-Methoden. Dies gilt ebenfalls für StreamWriter- und StreamReader-Klassen.
   bei System.Buffer.InternalBlockCopy(Array src, Int32 srcOffsetBytes, Array dst, Int32 dstOffsetBytes, Int32 byteCount)
   bei System.IO.FileStream.Read(Byte[] array, Int32 offset, Int32 count)
   bei System.IO.BinaryReader.FillBuffer(Int32 numBytes)
   bei System.IO.BinaryReader.ReadInt32()
   bei System.Reflection.PortableExecutable.SectionHeader..ctor(BinaryReader reader)
   bei System.Reflection.PortableExecutable.PEHeaders.ReadSectionHeaders(BinaryReader reader)
   bei System.Reflection.PortableExecutable.PEHeaders..ctor(Stream peStream)
   bei System.Reflection.PortableExecutable.PEReader.ReadPEHeadersNoLock(Stream stream)
   bei System.Reflection.PortableExecutable.PEReader.InitializePEHeaders()
   bei System.Reflection.PortableExecutable.PEReader.get_PEHeaders()
   bei System.Reflection.PortableExecutable.PEReader.get_HasMetadata()
   bei Microsoft.CodeAnalysis.PEModule.get_MetadataReader()
When looking into the source of FileStream.FlushRead I believe a seek to a negative offset would be caused by a race condition. The last of the exceptions indicates the same.

My own code is not multithreaded (all running on the UI thread of a WinForms app) but I do await async Roslyn code. In particular when the last exception occurred I took a look at the debugger and there are 2 threads in Roslyn code at the time, both trying to access an assembly.

If I tracked it down correctly the calling code is as follows, one outstanding Classifier.GetClassifiedSpansAsync and one outstanding Document.GetSemanticModelAsync:
var markerTask = ScriptEditorUtilities.GetClassifiedSpansAsync(document, codeSpan);
var errorTask = ScriptEditorUtilities.GetDiagnosticsAsync(document);

// do other stuff ...

var markers = await markerTask.ConfigureAwait(true);
var errors = await errorTask.ConfigureAwait(true);

comments

Zarat wrote Sep 29, 2014 at 3:50 PM

Just got another one of these, it's the same as the first one (which I didn't write down before)
System.IO.IOException: Unable to read metadata file ---> System.Reflection.TargetInvocationException: Ein Aufrufziel hat einen Ausnahmefehler verursacht. ---> System.IO.IOException: Es wurde versucht, den Dateizeiger vor den Anfang der Datei zu bewegen.

   bei System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   bei System.IO.FileStream.SeekCore(Int64 offset, SeekOrigin origin)
   bei System.IO.FileStream.FlushRead()
   bei System.IO.FileStream.FlushInternalBuffer()
   bei System.IO.FileStream.Flush(Boolean flushToDisk)
   bei System.IO.FileStream.Flush()
   bei System.IO.MemoryMappedFiles.MemoryMappedFile.CreateFromFile(FileStream fileStream, String mapName, Int64 capacity, MemoryMappedFileAccess access, MemoryMappedFileSecurity memoryMappedFileSecurity, HandleInheritability inheritability, Boolean leaveOpen)
   --- Ende der internen Ausnahmestapelüberwachung ---
   bei System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   bei System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   bei System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   bei System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   bei System.Reflection.Internal.MemoryMapLightUp.CreateMemoryMap(Stream stream)
   bei System.Reflection.Internal.StreamMemoryBlockProvider.TryCreateMemoryMapAccessor(Int32 start, Int32 size, IDisposable& accessor)
   --- Ende der internen Ausnahmestapelüberwachung ---
   bei System.Reflection.Internal.StreamMemoryBlockProvider.TryCreateMemoryMapAccessor(Int32 start, Int32 size, IDisposable& accessor)
   bei System.Reflection.Internal.StreamMemoryBlockProvider.GetMemoryBlockImpl(Int32 start, Int32 size)
   bei System.Reflection.Internal.MemoryBlockProvider.GetMemoryBlock(Int32 start, Int32 size)
   bei System.Reflection.PortableExecutable.PEReader.GetMetadataBlock()
   bei System.Reflection.PortableExecutable.PEReader.GetMetadata()
   bei System.Reflection.Metadata.PEReaderExtensions.GetMetadataReader(PEReader peReader, MetadataReaderOptions options, StringInterner stringInterner)
   bei Microsoft.CodeAnalysis.PEModule.get_MetadataReader()

Zarat wrote Sep 29, 2014 at 4:05 PM

uhm ok that "add comment" didn't work well with the "insert code" feature (it previewed correctly before I submitted it)

I've updated the original issue post instead. Feel free to delete this and the broken comment, I can't seem to do that.

Zarat wrote Oct 6, 2014 at 2:36 AM

Looking a bit deeper into the first exception, it's an IOException complaining about seeking to a negative offset. When I look at the source of FileStream.FlushRead I can't help but think it most likely is a race condition, with the Flush happening while readPos and/or readLen is changing on another thread.

Zarat wrote Oct 6, 2014 at 8:04 AM

Yep, definitely a race condition. I have two outstanding async calls and Roslyn tries to load the same Assembly from two different threads. I've updated the issue with the details.

mattwar wrote Oct 7, 2014 at 7:29 PM

You are correct. This is a race condition caused by unintentionally sharing the same file stream. I've been assured that its already been fixed.

nguerrera wrote Dec 6, 2014 at 3:18 AM