Skip to content

Added a multithreaded hydration functional test#212

Merged
sanoursa merged 3 commits intomicrosoft:masterfrom
sanoursa:hydration-race
Aug 24, 2018
Merged

Added a multithreaded hydration functional test#212
sanoursa merged 3 commits intomicrosoft:masterfrom
sanoursa:hydration-race

Conversation

@sanoursa
Copy link
Contributor

There was a previously existing functional test, but it would hydrate a file on a single thread, and then attempt to repeatedly read from it from 4 parallel threads. That test was ensuring that it's possible to read from a hydrated placeholder, but it did not cover the races that we are currently investigating on Mac.

I added a new test that uses 32 threads to concurrently read from a path that was previously completely virtual, all the way to the root of the repo. On my MacBook Pro, this test has failed every single time with 32 threads (and about 80% with 16 threads, 50% with 4 threads). I'm posting the test now to ensure that it is failing on our build servers, and anyone else's machines if you have time to test it out.

Before completing this PR, I will need to either mark the test as Windows-only, or else fix the races on Mac, though I suspect that will take longer.

…attempting to concurrently read a previously unhydrated file from several threads
@sanoursa
Copy link
Contributor Author

@nickgra @wilbaker It would be really helpful if you two have time to run this new test a few times and confirm that it fails reliably for you. I'd like to make sure the failure is reliable, so we then have confidence in the fix(es) as well.

@wilbaker
Copy link
Member

On my first run I hit the failure:

1) Failed : GVFS.FunctionalTests.Tests.EnlistmentPerFixture.MultithreadedReadWriteTests.CanReadVirtualFileInParallel()
  At least one of the reads failed
  Expected: null
  But was:  <System.IO.FileNotFoundException: Could not find file '/GVFS.FT/test/abccb9029e3642258ab2/src/GVFS/GVFS.FunctionalTests/Tests/LongRunningEnlistment/GitMoveRenameTests.cs'.
File name: '/GVFS.FT/test/abccb9029e3642258ab2/src/GVFS/GVFS.FunctionalTests/Tests/LongRunningEnlistment/GitMoveRenameTests.cs'
   at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirectory, Func`2 errorRewriter)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
   at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize)
   at System.IO.File.InternalReadAllText(String path, Encoding encoding)
   at System.IO.File.ReadAllText(String path)
   at GVFS.FunctionalTests.FileSystemRunners.SystemIORunner.ReadAllText(String path) in /Users/wilbaker/Repos/VFSForGit/src/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs:line 68
   at GVFS.FunctionalTests.Tests.EnlistmentPerFixture.MultithreadedReadWriteTests.<>c__DisplayClass0_0.<CanReadVirtualFileInParallel>b__0() in /Users/wilbaker/Repos/VFSForGit/src/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MultithreadedReadWriteTests.cs:line 38>
   at GVFS.Tests.Should.ValueShouldExtensions.ShouldBeNull[T](T obj, String message) in /Users/wilbaker/Repos/VFSForGit/src/GVFS/GVFS.Tests/Should/ValueShouldExtensions.cs:line 76
   at GVFS.FunctionalTests.Tests.EnlistmentPerFixture.MultithreadedReadWriteTests.CanReadVirtualFileInParallel() in /Users/wilbaker/Repos/VFSForGit/src/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MultithreadedReadWriteTests.cs:line 54

I'll run some more iterations tomorrow.

@nickgra
Copy link
Member

nickgra commented Aug 24, 2018

On my tiny Mac Mini, I have reproed the failure 5 times in a row.

1) Failed : GVFS.FunctionalTests.Tests.EnlistmentPerFixture.MultithreadedReadWriteTests.CanReadVirtualFileInParallel()
  At least one of the reads failed
  Expected: null
  But was:  <System.IO.DirectoryNotFoundException: Could not find a part of the path '/GVFS.FT/test/a1b2d1730a1d43eab942/src/GVFS/GVFS.FunctionalTests/Tests/LongRunningEnlistment/GitMoveRenameTests.cs'.
   at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirectory, Func`2 errorRewriter)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
   at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize)
   at System.IO.File.InternalReadAllText(String path, Encoding encoding)
   at System.IO.File.ReadAllText(String path)
   at GVFS.FunctionalTests.FileSystemRunners.SystemIORunner.ReadAllText(String path) in /Users/nickgra/VFSForGit/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs:line 68
   at GVFS.FunctionalTests.Tests.EnlistmentPerFixture.MultithreadedReadWriteTests.<>c__DisplayClass0_0.<CanReadVirtualFileInParallel>b__0() in /Users/nickgra/VFSForGit/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MultithreadedReadWriteTests.cs:line 38>
   at GVFS.Tests.Should.ValueShouldExtensions.ShouldBeNull[T](T obj, String message) in /Users/nickgra/VFSForGit/GVFS/GVFS.Tests/Should/ValueShouldExtensions.cs:line 76
   at GVFS.FunctionalTests.Tests.EnlistmentPerFixture.MultithreadedReadWriteTests.CanReadVirtualFileInParallel() in /Users/nickgra/VFSForGit/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MultithreadedReadWriteTests.cs:line 54

I'll try on my Mac Pro and the iMac Pro tomorrow.

Code LGTM though.

}
catch (Exception e)
{
readException = e;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the number of failed reads provide any interesting signal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered about that too, but I'm not sure that it would. The number needs to be 0, always. And if it's ever non-zero, it signals that there's at least one race, somewhere, but gives no more signal than that. So I think this is a binary test, unless you can think of more we can get out of it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think this is a binary test, unless you can think of more we can get out of it?

@sanoursa, have you checked if the thread(s) that are failing are all failing with the same exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every time I've seen it fail, it has been one of the two failures that you and Nick reported. One of the directories or the file appears missing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no expectation that they will all fail with the same exception. What's happening is that they are all racing on creating the placeholder directories, expanding those directories, then creating the placeholder file, and finally hydrating it. Since we currently have races in our enumeration of directories, one of the directories or the leaf file will sometimes appear to be missing.

That said, I can definitely update it to print out every exception we get, if you think it'll add more diagnostic value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, I can definitely update it to print out every exception we get, if you think it'll add more diagnostic value.

I think we can start with just printing one. I agree the most important\interesting part of this test is simply whether or not it succeeds.

threads[i].Join();
}

readException.ShouldBeNull("At least one of the reads failed");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you print the Exception callstack here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It already is - see your test output up above :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should have checked the output first :) You're right I can see the exception, thanks!

}
catch (Exception e)
{
readException = e;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think this is a binary test, unless you can think of more we can get out of it?

@sanoursa, have you checked if the thread(s) that are failing are all failing with the same exception?

@sanoursa
Copy link
Contributor Author

sanoursa commented Aug 24, 2018

Confirmed that the Mac build agent also fails on this test:

2018-08-24T15:52:34.7491020Z => GVFS.FunctionalTests.Tests.EnlistmentPerFixture.MultithreadedReadWriteTests.CanReadVirtualFileInParallel()
2018-08-24T15:52:34.7508480Z Test GVFS.FunctionalTests.Tests.EnlistmentPerFixture.MultithreadedReadWriteTests.CanReadVirtualFileInParallel()
2018-08-24T15:52:34.7525560Z Failed at 3:52:34 PM taking 00:00:00.9360170
2018-08-24T15:52:34.7542200Z   At least one of the reads failed
2018-08-24T15:52:34.7558830Z   Expected: null
2018-08-24T15:52:34.7590470Z   But was:  <System.IO.DirectoryNotFoundException: Could not find a part of the path '/GVFS.FT/test/b0d29b4197074782bb10/src/GVFS/GVFS.FunctionalTests/Tests/LongRunningEnlistment/GitMoveRenameTests.cs'.
2018-08-24T15:52:34.7635860Z    at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirectory, Func`2 errorRewriter)
2018-08-24T15:52:34.7664860Z    at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode)
2018-08-24T15:52:34.7688230Z    at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
2018-08-24T15:52:34.7708560Z    at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize)
2018-08-24T15:52:34.7727550Z    at System.IO.File.InternalReadAllText(String path, Encoding encoding)
2018-08-24T15:52:34.7753650Z    at System.IO.File.ReadAllText(String path)
2018-08-24T15:52:34.7772920Z    at GVFS.FunctionalTests.FileSystemRunners.SystemIORunner.ReadAllText(String path) in /Users/vsts/agent/2.139.0/work/1/s/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs:line 68
2018-08-24T15:52:34.7793560Z    at GVFS.FunctionalTests.Tests.EnlistmentPerFixture.MultithreadedReadWriteTests.<>c__DisplayClass0_0.<CanReadVirtualFileInParallel>b__0() in /Users/vsts/agent/2.139.0/work/1/s/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MultithreadedReadWriteTests.cs:line 38>
2
2018-08-24T15:56:52.9206430Z    at GVFS.Tests.Should.ValueShouldExtensions.ShouldBeNull[T](T obj, String message) in /Users/vsts/agent/2.139.0/work/1/s/GVFS/GVFS.Tests/Should/ValueShouldExtensions.cs:line 76
2018-08-24T15:56:52.9223800Z    at GVFS.FunctionalTests.Tests.EnlistmentPerFixture.MultithreadedReadWriteTests.CanReadVirtualFileInParallel() in /Users/vsts/agent/2.139.0/work/1/s/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MultithreadedReadWriteTests.cs:line 54

Thread[] threads = new Thread[32];
for (int i = 0; i < threads.Length; ++i)
{
int myIndex = i;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this index, it's not being used anywhere

{
try
{
FileSystemRunner.DefaultRunner.ReadAllText(virtualPath).ShouldBeNonEmpty();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there would be value in using all of our runners here, since they may have slightly different timing or system calls that trigger hydration in different ways.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And that implies a need to make this class create an enlistment per test case, since the test is guaranteed to pass the second time around, regardless of races

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly enough, I can't repro the failures with the BashRunner, so I'm not going to add the overhead of a new enlistment per test case for now. It must be that having to spawn a new bash process per request slows things down enough to avoid hitting these races, because the SystemIORunner fails completely reliably and the BashRunner passes completely reliably, on my MacBook Pro.

[TestCase]
public void CanReadUnhydratedFileInParallelWithoutTearing()
[TestCase, Order(1)]
[Category(Categories.Windows)]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marked as Windows-only for now. I'm rerunning the tests on both platforms now, and will merge in once those pass. I'll then remove this category in my other PR for reproing and prototyping fixes for the races.

@sanoursa sanoursa merged commit 0449ca0 into microsoft:master Aug 24, 2018
@sanoursa sanoursa deleted the hydration-race branch August 24, 2018 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants