-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
ProcessThreadTests.TestStartTimeProperty - Get spawned thread by ID from Process.Threads to avoid mismatch #104972
Conversation
…Tests.TestStartTimeProperty test, fixes dotnet#103448
Tagging subscribers to this area: @dotnet/area-system-diagnostics-process |
Although, I think @jozkee has a point in his comment in the issue, that tolerating such wrong timestamps is probably masking a real bug rather than just accommodating test noise. |
…atch block: - it can hide LINQ issues as LINQ may throw InvalidOperationException - it's not needed for the current thread, because the code is still running so we know the thread is alive
…ssThreadTests.TestStartTimeProperty test, fixes dotnet#103448" This reverts commit 3092d86.
Hmm, is the likely reason for the failure that |
src/libraries/System.Diagnostics.Process/tests/ProcessThreadTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/ProcessThreadTests.cs
Outdated
Show resolved
Hide resolved
@@ -148,17 +149,22 @@ public async Task TestStartTimeProperty() | |||
await Task.Factory.StartNew(() => | |||
{ | |||
p.Refresh(); | |||
try | |||
{ | |||
var newest = p.Threads.Cast<ProcessThread>().OrderBy(t => t.StartTime.ToUniversalTime()).Last(); |
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.
Was it wrongly assumed that Process.Threads contained the threads ordered with the most recently created as the Last()
one?
Is also possible that the property does have the threads ordered but StartNew wasn't creating a new thread but reusing an existing one as Dan mentioned.
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.
Was it wrongly assumed that Process.Threads contained the threads ordered with the most recently created as the Last() one?
There are other tests running in parallel and each of them can create a new thread. Even if we disable that, the ThreadPool
can still start a new thread on it's own. So to get 100% deterministic behavior we should IMO perform an ID match.
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.
StartNew wasn't creating a new thread but reusing an existing one as Dan mentioned.
In theory CLR and Mono are sharing the same thread pool implementation, so I would expect TaskCreationOptions.LongRunning
to always create a new thread, but I might be wrong.
@kouvel is it safe to assume that Task.Factory.StartNew(/**/, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
executed from an xUnit test runner will always spawn a new thread?
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.
Based on this:
runtime/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs
Lines 45 to 53 in 27776e2
if (Thread.IsThreadStartSupported && (options & TaskCreationOptions.LongRunning) != 0) | |
{ | |
// Run LongRunning tasks on their own dedicated thread. | |
new Thread(s_longRunningThreadWork) | |
{ | |
IsBackground = true, | |
Name = ".NET Long Running Task" | |
}.UnsafeStart(task); | |
} |
It looks like when thread start is supported, a new thread is created for each long-running task. The threads used for long-running tasks are not pooled currently, though they could be. Probably creating a new thread would be more explicit in intent.
Wonder if the failure is happening when thread start is not supported. In the threading tests that create new threads, the following condition is used, which appears to be the same condition as Thread.IsThreadStartSupported
:
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
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.
Although using a long-running task would avoid an unhandled exception/crash on assertion failure in the new thread. ThreadTestHelpers.CreateGuardedThread
could be used for that instead, as in:
ThreadTestHelpers.CreateGuardedThread(out waitForThread, () => |
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.
@kouvel thank you!
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.
The threads used for long-running tasks are not pooled currently, though they could be.
TIL - I should have checked the code. @kouvel is that worth an issue, as a suggestion? Basically pull a thread from the pool if available, and trigger a replacement to start. I have no idea.
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.
What's done currently is simple and may be sufficient if most long-running work items rarely or never complete. If there's a need where work items would complete frequently enough, and are not short enough and need to be classified as long-running, then it may be worthwhile to pool those 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.
LGTM, assuming CI passes, thanks!
ProcessThread[] processThreads = p.Threads.Cast<ProcessThread>().ToArray(); | ||
ProcessThread newThread = Assert.Single(processThreads, thread => thread.Id == newThreadId); | ||
|
||
Assert.InRange(newThread.StartTime.ToUniversalTime(), curTime - allowedWindow, DateTime.Now.ToUniversalTime() + allowedWindow); |
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.
Now that we get the correct thread, could we get rid of the tolerance?
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've reverted the increase of the tolerance. I think it's safer to keep the current one (I am just afraid it would make the test even more flaky)
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 think with your fix the test won't be flaky at all 😆 but I might be wrong.
…x the build for arm64)
fixes #103448