Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vstest.console.exe grabs exclusive read access to its test container #1638

Closed
pvvl opened this issue Jun 8, 2018 · 8 comments
Closed

vstest.console.exe grabs exclusive read access to its test container #1638

pvvl opened this issue Jun 8, 2018 · 8 comments
Assignees
Labels

Comments

@pvvl
Copy link

pvvl commented Jun 8, 2018

Description

vstest.console.exe uses .NET's System.IO.File.Open() with a default ShareMode argument. As a consequence, attempts by other processes to read the DLL simultaneously will fail.

We use several vstest.console instances on the same DLL for maximal execution speed. This fails occasionally because of this race condition.
vstest.console has its own parallel execution mechanism, but the latter is still slower, offers less isolation, and does not allow distributed execution.

The problem is actually due to an ill-chosen default in the .NET framework. I filed a detailed description about this problem (in combination with vstest.console) entitled: ".NET's System.IO.File.Open() variants have default FileShare.None"

Steps to reproduce

  • create a DLL containing a unit test that sleeps for a minute or so.
  • start vstest.console to execute that test
  • while one test is running, start another vstest.console with the same arguments.

Expected behavior

There is no reason why running two tests in parallel would require exclusive access to the shared test container. In a concurrent environment, this behavior is a recipe for disaster.

Actual behavior

Occasional race conditions occur when executing tests in parallel from the same container.

Diagnostic logs

Whenever the race condition causes a collision, vstest.console produces an error message like this:
Could not load file or assembly 'file:///.dll' or one of its dependencies. The process cannot access the file because it is being used by another process. (Exception from HRESULT: 0x80070020).
I was able to get the call stack where the exclusive access is being requested: VSTEST.CONSOLE is an attemptingto open a test container with the (implicit!) share mode = none.
In the end I caught the problem using a conditional breakpoint on CreateFileW, checking for dwShareMode == 0x0.

I hit this conditional breakpoint with the following call stack:

KernelBase.dll!_CreateFileW@28�() Unknown Symbols loaded.
kernel32.dll!CreateFileWImplementation@28�() Unknown Symbols loaded.
mscorlib.ni.dll!79a7d2f8() Unknown No symbols loaded.
[Frames below may be incorrect and/or missing, native debugger attempting to walk managed call stack] Annotated Frame
mscorlib.ni.dll!79a7d2f8() Unknown No symbols loaded.
[Managed to Native Transition] Annotated Frame
mscorlib.dll!Microsoft.Win32.Win32Native.SafeCreateFile(string lpFileName, int dwDesiredAccess, System.IO.FileShare dwShareMode, Microsoft.Win32.Win32Native.SECURITY_ATTRIBUTES securityAttrs, System.IO.FileMode dwCreationDisposition, int dwFlagsAndAttributes, System.IntPtr hTemplateFile) Line 977 C# Symbols loaded.
mscorlib.dll!System.IO.FileStream.Init(string path, System.IO.FileMode mode, System.IO.FileAccess access = Read, int rights, bool useRights = false, System.IO.FileShare share, int bufferSize = 4096, System.IO.FileOptions options, Microsoft.Win32.Win32Native.SECURITY_ATTRIBUTES secAttrs, string msgPath = "Lms_UnitTests_GPKManagedTestStubs.dll", bool bFromProxy = false, bool useLongPath, bool checkHost) Line 843 C# Symbols loaded.
mscorlib.dll!System.IO.FileStream.FileStream(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) Line 527 C# Symbols loaded.
mscorlib.dll!System.IO.File.Open(string path, System.IO.FileMode mode, System.IO.FileAccess access) Line 474 C# Symbols loaded.
vstest.console.exe!Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities.AssemblyMetadataProvider.GetFrameWork(string filePath = "n:/TestLab/GPK/output/bin/Win32/Release/Lms_UnitTests_GPKManagedTestStubs.dll") Line 30 C# Symbols loaded.
vstest.console.exe!Microsoft.VisualStudio.TestPlatform.CommandLineUtilities.InferHelper.DetermineFrameworkName(System.Collections.Generic.IEnumerable sources = Count = 1, System.Collections.Generic.IDictionary<string, Microsoft.VisualStudio.TestPlatform.ObjectModel.Framework> sourceFrameworkVersions = Count = 0, out bool conflictInFxIdentifier = false) Line 134 C# Symbols loaded.
vstest.console.exe!Microsoft.VisualStudio.TestPlatform.CommandLineUtilities.InferHelper.AutoDetectFramework(System.Collections.Generic.List sources = Count = 1, System.Collections.Generic.IDictionary<string, Microsoft.VisualStudio.TestPlatform.ObjectModel.Framework> sourceFrameworkVersions = Count = 0) Line 101 C# Symbols loaded.
vstest.console.exe!Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers.TestRequestManager.UpdateRunSettingsIfRequired(string runsettingsXml = "\r\n\r\n \r\n 1\r\n n:\TestLab\GPK\TestResults\r\n x86\r\n Framework45\r\n \r\n \r\n \r\n \r\n \r\n \r\n True\r\n false\r\n True\r\n False\r\n \r\n <Directory Path="%SolutionDir%\output\bin\%Platform_Actual%\%Configuration%" includeSubDirectories="false" />\r\n \r\n \r\n \r\n 1\r\n \r\n n:\TestLab\GPK\r\n", System.Collections.Generic.List sources = Count = 1, out string updatedRunSettingsXml = "\r\n\r\n \r\n 1\r\n n:\TestLab\GPK\TestResults\r\n x86\r\n Framework45\r\n \r\n \r\n \r\n \r\n \r\n \r\n True\r\n false\r\n True\r\n False\r\n \r\n <Directory Path="%SolutionDir%\output\bin\%Platform_Actual%\%Configuration%" includeSubDirectories="false" />\r\n \r\n \r\n \r\n 1\r\n \r\n n:\TestLab\GPK\r\n") Line 367 C# Symbols loaded.
vstest.console.exe!Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers.TestRequestManager.DiscoverTests(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryRequestPayload discoveryPayload = {Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryRequestPayload}, Microsoft.VisualStudio.TestPlatform.Common.Interfaces.ITestDiscoveryEventsRegistrar discoveryEventsRegistrar = {Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.RunSpecificTestsArgumentExecutor.DiscoveryEventsRegistrar}, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ProtocolConfig protocolConfig = {Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ProtocolConfig}) Line 147 C# Symbols loaded.
vstest.console.exe!Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.RunSpecificTestsArgumentExecutor.DiscoverTestsAndSelectSpecified(System.Collections.Generic.IEnumerable sources = Count = 1) Line 238 C# Symbols loaded.
vstest.console.exe!Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.RunSpecificTestsArgumentExecutor.Execute() Line 214 C# Symbols loaded.
vstest.console.exe!Microsoft.VisualStudio.TestPlatform.CommandLine.Executor.ExecuteArgumentProcessor(Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.IArgumentProcessor processor = {Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.RunSpecificTestsArgumentProcessor}, ref int exitCode = 0) Line 328 C# Symbols loaded.
vstest.console.exe!Microsoft.VisualStudio.TestPlatform.CommandLine.Executor.Execute(string[] args = {string[3]}) Line 131 C# Symbols loaded.
vstest.console.exe!Microsoft.VisualStudio.TestPlatform.CommandLine.Program.Main(string[] args = {string[3]}) Line 39 C# Symbols loaded.
[Native to Managed Transition] Annotated Frame
mscoreei.dll!CorExeMain@0�() Unknown Symbols loaded.
mscoree.dll!ShellShimCorExeMain@0�() Unknown Symbols loaded.
mscoree.dll!CorExeMain_Exported@0�() Unknown Symbols loaded.
ntdll.dll!RtlUserThreadStart@8�() Unknown Symbols loaded.
ntdll.dll!_RtlUserThreadStart@8�() Unknown Symbols loaded.

Environment

File version of vstest.console.exe: 15.0.27617.1. This problem is reproducible in Version 15.8.0-dev of VSTEST.CONSOLE.

@pvvl
Copy link
Author

pvvl commented Jun 8, 2018

IMHO the problem can be solved by modifying line 30 of VSTest\vstest-master\src\vstest.console\CommandLine\AssemblyMetadataProvider.cs:
changing
using (var assemblyStream = File.Open(filePath, FileMode.Open, FileAccess.Read))
into
using (var assemblyStream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))

Our organization needs these changes to end up on an official Microsoft installation, so a locally patched version of vstest.console does not cut it.

@smadala
Copy link
Contributor

smadala commented Jun 8, 2018

@pvvl Did you tried that the fix works for your scenario? We are happy to accept your contribution 😄

@smadala smadala added the bug label Jun 8, 2018
@smadala smadala self-assigned this Jun 8, 2018
@pvvl
Copy link
Author

pvvl commented Jun 8, 2018 via email

@smadala
Copy link
Contributor

smadala commented Jun 8, 2018

@pvvl It makes sense to me. Feel free to raise a PR. We will ship in next release(15.8).

@pvlakshm
Copy link
Contributor

@pvvl, yo mention "We use several vstest.console instances on the same DLL for maximal execution speed."

Have you tried vstest's parallel test execution?
If you are using MSTest V2 based tests, you then try in-assembly parallel execution?

These can significantly reduce the overall time taken to complete a test run.

@pvvl
Copy link
Author

pvvl commented Jun 11, 2018 via email

@smadala
Copy link
Contributor

smadala commented Jun 25, 2018

@pvvl Create a #1660 to address this. Please review it. If possible test on your scenario.

@qingjuntian you might well interested on this issue.

@smadala
Copy link
Contributor

smadala commented Jun 27, 2018

Fix for this will be available in VS 15.8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants