/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Workspaces/Remote/ServiceHub/Services/KeepAlive/RemoteKeepAliveService.cs
85 строк
5 KB
Cyrus Najmabadi
infinite
06 дек 2025, 02:13
06 дек 2025, 02:13
2653602
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.Threading; namespace Microsoft.CodeAnalysis.Remote; internal sealed partial class RemoteKeepAliveService : BrokeredServiceBase, IRemoteKeepAliveService { internal sealed class Factory : FactoryBase<IRemoteKeepAliveService> { protected override IRemoteKeepAliveService CreateService(in ServiceConstructionArguments arguments) => new RemoteKeepAliveService(arguments); } /// <summary> /// Mapping from sessionId to the completion source that will be used to signal when that session's solution /// snapshot is fully hydrated on the OOP (this) side. The `bool` result of the TaskCompletionSource is unused, and /// is only there because TaskCompletionSource requires some type on netstandard2.0. /// </summary> private readonly ConcurrentDictionary<long, TaskCompletionSource<bool>> _sessionIdToCompletionSource = new(); public RemoteKeepAliveService(in ServiceConstructionArguments arguments) : base(arguments) { } private TaskCompletionSource<bool> GetSessionCompletionSource(long sessionId) => _sessionIdToCompletionSource.GetOrAdd(sessionId, static _ => new()); public async ValueTask KeepAliveAsync( Checksum solutionChecksum, long sessionId, CancellationToken cancellationToken) { // Ensure we have a completion source for this sessionId. Note: we are potentially racing with // WaitForSessionIdAsync on the host side, so it's possible that it will beat us to creating the entry in this // dictionary. That's fine though, as both calls will be guaranteed to get the same completionSource thanks to // GetOrAdd. var completionSource = GetSessionCompletionSource(sessionId); try { // First get the solution, ensuring that it is currently pinned. await RunServiceAsync(solutionChecksum, async solution => { // Now that we have the solution, we can trigger the completion source to let the host know it can // proceed with its work. completionSource.TrySetResult(true); // Wait for our caller to tell us to cancel. That way we can release this solution and allow it // to be collected if not needed anymore. // // This was provided by stoub as an idiomatic way to wait indefinitely until a cancellation token triggers. await Task.Delay(Timeout.Infinite, cancellationToken).ConfigureAwait(false); }, cancellationToken).ConfigureAwait(false); } finally { // Now that we've been cancelled, remove the completion source from our map. Note: this cancellationToken // is the one owned by RemoteKeepAliveSession, and only triggered in its Dispose method. We only get to // that point once the RemoteKeepAliveSession is fully created, returned back out to the caller, and then // eventually Disposed of. That means that by the time we get here, WaitForSessionIdAsync already must have // returned, so there is no concern about racing at this point, and we are certain to clear out the value // and not leave around stale entries. Contract.ThrowIfFalse(_sessionIdToCompletionSource.TryRemove(sessionId, out var foundSource)); Contract.ThrowIfFalse(foundSource == completionSource); } } public async ValueTask WaitForSessionIdAsync(long sessionId, CancellationToken cancellationToken) { // Ensure we have a completion source for this sessionId. Note: we are potentially racing with KeepAliveAsync // on the host side, so it's possible that it will beat us to creating the entry in this dictionary. That's // fine though, as both calls will be guaranteed to get the same completionSource thanks to GetOrAdd. var completionSource = GetSessionCompletionSource(sessionId); // Now, wait for KeepAliveAsync to finally signal us to proceed. We also listen for cancellation so that the // host can bail out if needed, without waiting on the full solution (or project cone) to sync over. await completionSource.Task.WithCancellation(cancellationToken).ConfigureAwait(false); } }