| File: BackEnd\BuildManager\LegacyThreadingData.cs | Web Access |
| Project: src\msbuild\src\Build\Microsoft.Build.csproj (Microsoft.Build) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.Build.BackEnd; using Microsoft.Build.Shared; #nullable disable namespace Microsoft.Build.Execution { /// <summary> /// This class represents the data which is used for legacy threading semantics for the build /// </summary> internal class LegacyThreadingData { #region Fields /// <summary> /// Store the pair of start/end events used by a particular submission to track their ownership /// of the legacy thread. /// Item1: Start event, tracks when the submission has permission to start building. /// Item2: End event, signalled when that submission is no longer using the legacy thread. /// </summary> private readonly IDictionary<int, Tuple<AutoResetEvent, ManualResetEvent>> _legacyThreadingEventsById = new Dictionary<int, Tuple<AutoResetEvent, ManualResetEvent>>(); /// <summary> /// The current submission id building on the main thread, if any. /// </summary> private int _mainThreadSubmissionId = -1; /// <summary> /// The instance to be used when the new request builder is started on the main thread. /// </summary> private RequestBuilder _instanceForMainThread; /// <summary> /// Lock object for startNewRequestBuilderMainThreadEventsById, since it's possible for multiple submissions to be /// submitted at the same time. /// </summary> private readonly LockType _legacyThreadingEventsLock = new(); #endregion #region Properties /// <summary> /// The instance to be used when the new request builder is started on the main thread. /// </summary> internal RequestBuilder InstanceForMainThread { get => _instanceForMainThread; set { Assumed.True(_instanceForMainThread == null || (_instanceForMainThread != null && value == null) || (_instanceForMainThread == value), "Should not assign to instanceForMainThread twice without cleaning it"); _instanceForMainThread = value; } } /// <summary> /// The current submission id building on the main thread, if any. /// </summary> internal int MainThreadSubmissionId { get => _mainThreadSubmissionId; set { if (value == -1) { _instanceForMainThread = null; } _mainThreadSubmissionId = value; } } #endregion /// <summary> /// Given a submission ID, assign it "start" and "finish" events to track its use of /// the legacy thread. /// </summary> [SuppressMessage("Microsoft.Dispose", "CA2000:Dispose objects before losing scope", Justification = "The events are disposed in UnregisterSubmissionForLegacyThread")] internal void RegisterSubmissionForLegacyThread(int submissionId) { lock (_legacyThreadingEventsLock) { Assumed.False(_legacyThreadingEventsById.ContainsKey(submissionId), $"Submission {submissionId} should not already be registered with LegacyThreadingData"); _legacyThreadingEventsById[submissionId] = new Tuple<AutoResetEvent, ManualResetEvent>( new AutoResetEvent(false), new ManualResetEvent(false)); } } /// <summary> /// This submission is completely done with the legacy thread, so unregister it /// from the dictionary so that we don't leave random events lying around. /// </summary> internal void UnregisterSubmissionForLegacyThread(int submissionId) { lock (_legacyThreadingEventsLock) { Assumed.True(_legacyThreadingEventsById.ContainsKey(submissionId), $"Submission {submissionId} should have been previously registered with LegacyThreadingData"); // Dispose the events _legacyThreadingEventsById[submissionId].Item1?.Dispose(); _legacyThreadingEventsById[submissionId].Item2?.Dispose(); _legacyThreadingEventsById.Remove(submissionId); } } /// <summary> /// Given a submission ID, return the event being used to track when that submission is ready /// to be executed on the legacy thread. /// </summary> internal WaitHandle GetStartRequestBuilderMainThreadEventForSubmission(int submissionId) { Tuple<AutoResetEvent, ManualResetEvent> legacyThreadingEvents; lock (_legacyThreadingEventsLock) { _legacyThreadingEventsById.TryGetValue(submissionId, out legacyThreadingEvents); } Assumed.NotNull(legacyThreadingEvents, $"We're trying to wait on the legacy thread for submission {submissionId}, but that submission has not been registered."); return legacyThreadingEvents.Item1; } /// <summary> /// Given a submission ID, return the event being used to track when that submission is ready /// to be executed on the legacy thread. /// </summary> internal Task GetLegacyThreadInactiveTask(int submissionId) { Tuple<AutoResetEvent, ManualResetEvent> legacyThreadingEvents; lock (_legacyThreadingEventsLock) { _legacyThreadingEventsById.TryGetValue(submissionId, out legacyThreadingEvents); } Assumed.NotNull(legacyThreadingEvents, $"We're trying to track when the legacy thread for submission {submissionId} goes inactive, but that submission has not been registered."); return legacyThreadingEvents.Item2.ToTask(); } /// <summary> /// Signal that the legacy thread is starting work. /// </summary> internal void SignalLegacyThreadStart(RequestBuilder instance) { Assumed.NotNull(instance?.RequestEntry?.Request, "Cannot signal legacy thread start for a RequestBuilder without a request"); int submissionId = instance.RequestEntry.Request.SubmissionId; InstanceForMainThread = instance; Tuple<AutoResetEvent, ManualResetEvent> legacyThreadingEvents; lock (_legacyThreadingEventsLock) { _legacyThreadingEventsById.TryGetValue(submissionId, out legacyThreadingEvents); } Assumed.NotNull(legacyThreadingEvents, $"We're trying to signal that the legacy thread is ready for submission {submissionId} to execute, but that submission has not been registered"); // signal that this submission is currently controlling the legacy thread legacyThreadingEvents.Item1.Set(); // signal that the legacy thread is not currently idle legacyThreadingEvents.Item2.Reset(); } /// <summary> /// Signal that the legacy thread has finished its work. /// </summary> internal void SignalLegacyThreadEnd(int submissionId) { MainThreadSubmissionId = -1; Tuple<AutoResetEvent, ManualResetEvent> legacyThreadingEvents; lock (_legacyThreadingEventsLock) { _legacyThreadingEventsById.TryGetValue(submissionId, out legacyThreadingEvents); } Assumed.NotNull(legacyThreadingEvents, $"We're trying to signal that submission {submissionId} is done with the legacy thread, but that submission has not been registered"); // The legacy thread is now idle legacyThreadingEvents.Item2.Set(); } } }