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

ProcessThreadTests.TestStartTimeProperty - Get spawned thread by ID from Process.Threads to avoid mismatch #104972

Merged
merged 6 commits into from
Jul 22, 2024

Conversation

adamsitnik
Copy link
Member

fixes #103448

@adamsitnik adamsitnik added area-System.Diagnostics.Process test-enhancement Improvements of test source code runtime-mono specific to the Mono runtime labels Jul 16, 2024
@adamsitnik adamsitnik added this to the 9.0.0 milestone Jul 16, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

@danmoseley
Copy link
Member

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.
@danmoseley
Copy link
Member

Hmm, is the likely reason for the failure that Task.Factory.StartNew(() => cannot be assumed to start a new thread, even with TaskCreationOptions.LongRunning ? If that's the case, I don't think your change will fix that problem. The fix would be to actually create a new thread (?)

@@ -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();
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member Author

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?

Copy link
Member

Choose a reason for hiding this comment

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

Based on this:

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))]

Copy link
Member

@kouvel kouvel Jul 17, 2024

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, () =>

Copy link
Member Author

Choose a reason for hiding this comment

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

@kouvel thank you!

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

@jozkee jozkee left a 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);
Copy link
Member

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?

Copy link
Member Author

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)

Copy link
Member

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.

@jozkee jozkee changed the title Increasing the tolerance for Mono Runtime for the flaky ProcessThreadTests.TestStartTimeProperty test ProcessThreadTests.TestStartTimeProperty - Get spawned thread by ID from Process.Threads to avoid mismatch Jul 19, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Diagnostics.Process runtime-mono specific to the Mono runtime test-enhancement Improvements of test source code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ProcessThreadTests.TestStartTimeProperty failed in CI
5 participants