-
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
Changes from all commits
3092d86
6d17edc
2cc7ecb
d9350ff
0de6947
dabec4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
using Microsoft.DotNet.RemoteExecutor; | ||
using Xunit; | ||
using System.Threading.Tasks; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace System.Diagnostics.Tests | ||
{ | ||
|
@@ -107,8 +108,8 @@ public void TestStartTimeProperty_OSX() | |
} | ||
} | ||
|
||
[Fact] | ||
[PlatformSpecific(TestPlatforms.Linux|TestPlatforms.Windows)] // OSX and FreeBSD throw PNSE from StartTime | ||
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] | ||
[PlatformSpecific(TestPlatforms.Linux | TestPlatforms.Windows)] // OSX and FreeBSD throw PNSE from StartTime | ||
public async Task TestStartTimeProperty() | ||
{ | ||
TimeSpan allowedWindow = TimeSpan.FromSeconds(2); | ||
|
@@ -148,15 +149,13 @@ public async Task TestStartTimeProperty() | |
await Task.Factory.StartNew(() => | ||
{ | ||
p.Refresh(); | ||
try | ||
{ | ||
var newest = p.Threads.Cast<ProcessThread>().OrderBy(t => t.StartTime.ToUniversalTime()).Last(); | ||
Assert.InRange(newest.StartTime.ToUniversalTime(), curTime - allowedWindow, DateTime.Now.ToUniversalTime() + allowedWindow); | ||
} | ||
catch (InvalidOperationException) | ||
{ | ||
// A thread may have gone away between our getting its info and attempting to access its StartTime | ||
} | ||
|
||
int newThreadId = GetCurrentThreadId(); | ||
|
||
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 commentThe 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 commentThe 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 commentThe 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. |
||
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); | ||
} | ||
} | ||
|
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.
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.
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
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:runtime/src/libraries/System.Threading.Thread/tests/ThreadTests.cs
Line 621 in 27776e2
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.
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.