-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Fix flaky compiler server test #23518
Conversation
file.WriteAllText(pair.Value); | ||
foreach (var pair in files) | ||
{ | ||
TempFile file = currentDirectory.CreateFile(pair.Key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since CreateFile is the not thread safe line, could you simply lock around it, and return the pointer to the file, and then do the IO to the file? (Locking inside the loop, instead of around it.)
Locking the WriteAllText seems like the lock would be held for longer than required, if the various tasks would all be writing to different files.
Main cost of locks is waiting for them to free, not actually taking/releasing them, therefore taking them often, but locking for the minimal amount of time often gives the most benefits from having parallel execution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's theoretically possible, yes. However, this is in a test, not in production code, and that level of optimization isn't really needed, and just complicates maintainability. (Files being written here are also probably <1KB on average)
If we really cared about performance here, a better solution would be to make TempRoot
use a collection designed for concurrency and performance.
@@ -109,14 +109,18 @@ private static void ReferenceNetstandardDllIfCoreClr(List<string> arguments) | |||
#endif | |||
} | |||
|
|||
private static readonly object CreateFilesLock = new object(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use s_createFilesLock
for this variable.
MultipleSimultaneousCompiles calls RunCommandLineCompiler on multiple threads (using Task.Run), which in turn calls CreateFiles on multiple threads, which in turn goes through TempDirectory and TempRoot and eventually boils down to calling List<T>.Add(), which is not thread-safe, and so occasionally crashes. Solution: hit the problem with a hammer by throwing a lock around CreateFiles.
57f428f
to
23ca701
Compare
@@ -109,14 +109,18 @@ private static void ReferenceNetstandardDllIfCoreClr(List<string> arguments) | |||
#endif | |||
} | |||
|
|||
private static readonly object s_createFilesLock = new object(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this lock be inside TempDirectory so we don't get bit by this at some later point? We have plenty of other tests in the IDE layer at least that do multithreading that I now wonder if they're also broken.
I guess?? Done 😊 |
using System.IO; | ||
using System.Runtime.CompilerServices; | ||
|
||
namespace Microsoft.CodeAnalysis.Test.Utilities | ||
{ | ||
public sealed class TempRoot : IDisposable | ||
{ | ||
private readonly List<IDisposable> _temps = new List<IDisposable>(); | ||
private readonly ConcurrentBag<IDisposable> _temps = new ConcurrentBag<IDisposable>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the earlier version of this change. This type was not designed with thread safety in mind and I'm skeptical of making it more free threaded. In particular I'm always skeptical of types which are a) disposable and b) writable from multiple threads.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, then you and @jasonmalinowski need to talk. Both of you are giving conflicting feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My major objection comes down to Dispose. Mutation after Dispose should be an error. Enforcing that contract though adds a bit more state and complexity to the TempRoot object. Enough that I question if it's worth it to support the single case where mutate TempRoot from multiple threads vs. just adding the lock in that one case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jaredpar If you can prove that was the only test in our entire codebase using this in a multi-threaded way, I'll be OK with the original fix. Keep in mind this tool is used beyond just the compiler tests, so don't restrict your statements to that. Otherwise, let's just fix it for good before we create another flaky test down the road.
I don't want perfect to be the enemy of "leaving traps for the next person" here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, but what about the IDE tests Jason is talking about? Discussions about IDisposable aren't really relevant (it's simple enough to add tracking vs. finding/fixing all the IDE tests, and/or validating the IDE tests aren't broken)
Edit: Ack, didn't see that Jason submitted a comment at the same time.
private static void DisposeAll(IEnumerable<IDisposable> temps) | ||
{ | ||
foreach (var temp in temps) | ||
while (_temps.TryTake(out var temp)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general Dispose is a well known state transition for an object from usable to dead. This code though is allowing mutations, after dispose, to silently work.
MultipleSimultaneousCompiles
callsRunCommandLineCompiler
on multiplethreads (using
Task.Run
), which in turn callsCreateFiles
on multiplethreads, which in turn goes through
TempDirectory
andTempRoot
andeventually boils down to calling
List<T>.Add()
, which is notthread-safe, and so occasionally crashes.
Solution: hit the problem with a hammer by throwing a lock around CreateFiles.
Fixes #22913