-
Notifications
You must be signed in to change notification settings - Fork 391
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
fixing up some threading cases #2419
fixing up some threading cases #2419
Conversation
@dotnet/project-system for review |
@@ -78,7 +78,7 @@ internal abstract class CrossTargetSubscriptionHostBase : OnceInitializedOnceDis | |||
|
|||
protected async Task AddInitialSubscriptionsAsync() | |||
{ | |||
using (_tasksService.LoadedProject()) |
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.
So previously the object returned by LoadedProject
would have been disposed at the end of the block. Do we need to do something similar to the object returned by LoadedProjectAsync
?
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.
No _tasksService.LoadedProject()
blocked until it got a lock, releasing the lock when it was disposed. _tasksService.LoadedProjectAsync
asynchronously waits for a lock, executes the lambda, and then released the lock.
In reply to: 121492671 [](ancestors = 121492671)
@@ -249,7 +249,7 @@ await subscriber.Value.InitializeSubscriberAsync(this, _activeConfiguredProjectS | |||
|
|||
await _commonServices.ThreadingService.SwitchToUIThread(); | |||
|
|||
using (_tasksService.LoadedProject()) | |||
await _tasksService.LoadedProjectAsync(() => |
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.
ConfigureAwait(true)
for this await
?
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.
LoadedProjectAsync returns a JoinableTask which always resumes onto the same thread as its caller.
In reply to: 121514747 [](ancestors = 121514747)
Can we split this into 3 separate commits? |
e2bee26
to
dc4e581
Compare
@davkean split this into separate commits. |
@@ -262,10 +262,12 @@ await subscriber.Value.InitializeSubscriberAsync(this, _activeConfiguredProjectS | |||
|
|||
foreach (var subscriber in Subscribers) | |||
{ | |||
subscriber.Value.AddSubscriptionsAsync(newProjectContext); | |||
Shell.ThreadHelper.JoinableTaskFactory.Run(()=> subscriber.Value.AddSubscriptionsAsync(newProjectContext)); |
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.
Its unclear to me what the correct behavior is here. This section of code is in a lock yet previously we would call subscriber.Value.AddSubscriptionsAsync(newProjectContext)
and not observe the returned task or wait for it to complete. That seems to imply that we don't care about the lock, since we previously exited the lock statement without any guarantee that the async tasks had finished. Here I am making the assumption that this was an error and we only want to exit the lock once all subscriptions have been added.
@@ -262,10 +262,12 @@ await subscriber.Value.InitializeSubscriberAsync(this, _activeConfiguredProjectS | |||
|
|||
foreach (var subscriber in Subscribers) | |||
{ | |||
subscriber.Value.AddSubscriptionsAsync(newProjectContext); | |||
Shell.ThreadHelper.JoinableTaskFactory.Run(() => subscriber.Value.AddSubscriptionsAsync(newProjectContext)); |
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 problem are we solving here? You're adding a synchronous blocking call in a threadpool thread, which is the cause of all these issues here: #2297.
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.
Oh we're not observing it - why isn't this just an await?
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.
you cannot await inside a lock in C# (for obvious reasons).
Its unclear to me what the correct thing to do here is. Previously, we were in a lock but we did not wait for async operations to complete before leaving the lock.
either:
- This code does not need to be in the lock, in which case we should move it out of the lock
- This code does need to be in the lock, in which case we need to actually wait for the async operations to complete or we risk bugs from threading behavior.
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.
Which is why we're using semaphores elsewhere. Is this actually fixing a known race? if not, I'd leave it until I rewrite this logic of coordination between configured projects. CPS want us to approach it a different way.
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.
Is this actually fixing a known race
I am not aware of one,. Sounds like this is 2 and we should not exit the lock until this subscription is finished.
Which is why we're using semaphores elsewhere
Can you show me where else in the code we do that?
I'd leave it until I rewrite this logic of coordination between configured projects
Fair enough, do we have an issue # for how CPS wants us to do this?
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.
See _gate in LanguageServiceHost.
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.
Looks a lot better, any objections to just doing that?
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 have objectiins to doing in 15.3 unless we're solving an actual existing bug. This becomes easy to introduce other issues and other bugs such as deadlocks on shutdown.
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.
ie We've regressed so many times around this code - i'd prefer not to change it unless it's fixing a bug that we're running into right now.
@@ -266,14 +265,16 @@ private JoinableTask ExecuteWithinLockAsync(Func<Task> task) | |||
|
|||
_projectConfigurationsWithSubscriptions.Add(configuredProject.ProjectConfiguration); | |||
} | |||
} | |||
|
|||
return Task.CompletedTask; |
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.
Why is this needed?
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.
Ah, the differ confused me - I see it's for func, not the parent method
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.
You need to remove the JoinableTaskFactory.Run.
If this is not addressing any known perf regressions then let's retarget this for 15.6 |
b6e81da
to
a004d10
Compare
@davkean can you comment on whether you want the remaining changes or not? |
You pulled the semaphore? I'm okay with the other changes. |
@davkean yeah, the semaphore code definitely has problems but lets tackle that in a different PR. |
_tasksService.LoadedProject()
assumes that all logic run inside the using is correct and never causes a deadlock._tasksService.LoadedProjectAsync
explicitly uses JTF to ensure no deadlocks occur.GetService
blocks the calling thread where asGetServiceAsync
will try several techniques to acquire the service in a non-blocking way before falling back to a blocking approach.AddSubscriptionsAsync
was locking but then calling asynchronus methods and not waiting for them to complete. This could have led to races or interactions with the project after it had closed.@davkean to double check my understanding of this threading code
Ask mode template forthcoming