1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 //-----------------------------------------------------------------------
4 // </copyright>
5 // <summary>Contains a set of data used for legacy threading semantics</summary>
6 //-----------------------------------------------------------------------
7 
8 using System;
9 using System.Threading;
10 using Microsoft.Build.BackEnd;
11 using Microsoft.Build.Shared;
12 using System.Collections.Generic;
13 using System.Threading.Tasks;
14 
15 namespace Microsoft.Build.Execution
16 {
17     /// <summary>
18     /// This class represents the data which is used for legacy threading semantics for the build
19     /// </summary>
20     internal class LegacyThreadingData
21     {
22         #region Fields
23         /// <summary>
24         /// Store the pair of start/end events used by a particular submission to track their ownership
25         /// of the legacy thread.
26         /// Item1: Start event, tracks when the submission has permission to start building.
27         /// Item2: End event, signalled when that submission is no longer using the legacy thread.
28         /// </summary>
29         private IDictionary<int, Tuple<AutoResetEvent, ManualResetEvent>> _legacyThreadingEventsById = new Dictionary<int, Tuple<AutoResetEvent, ManualResetEvent>>();
30 
31         /// <summary>
32         /// The current submission id building on the main thread, if any.
33         /// </summary>
34         private int _mainThreadSubmissionId = -1;
35 
36         /// <summary>
37         /// The instance to be used when the new request builder is started on the main thread.
38         /// </summary>
39         private RequestBuilder _instanceForMainThread;
40 
41         /// <summary>
42         /// Lock object for startNewRequestBuilderMainThreadEventsById, since it's possible for multiple submissions to be
43         /// submitted at the same time.
44         /// </summary>
45         private Object _legacyThreadingEventsLock = new Object();
46         #endregion
47 
48         #region Properties
49 
50         /// <summary>
51         /// The instance to be used when the new request builder is started on the main thread.
52         /// </summary>
53         internal RequestBuilder InstanceForMainThread
54         {
55             get
56             {
57                 return _instanceForMainThread;
58             }
59 
60             set
61             {
62                 ErrorUtilities.VerifyThrow(_instanceForMainThread == null || (_instanceForMainThread != null && value == null) || (_instanceForMainThread == value), "Should not assign to instanceForMainThread twice without cleaning it");
63                 _instanceForMainThread = value;
64             }
65         }
66 
67         /// <summary>
68         /// The current submission id building on the main thread, if any.
69         /// </summary>
70         internal int MainThreadSubmissionId
71         {
72             get
73             {
74                 return _mainThreadSubmissionId;
75             }
76 
77             set
78             {
79                 if (value == -1)
80                 {
81                     _instanceForMainThread = null;
82                 }
83 
84                 _mainThreadSubmissionId = value;
85             }
86         }
87         #endregion
88 
89         /// <summary>
90         /// Given a submission ID, assign it "start" and "finish" events to track its use of
91         /// the legacy thread.
92         /// </summary>
RegisterSubmissionForLegacyThread(int submissionId)93         internal void RegisterSubmissionForLegacyThread(int submissionId)
94         {
95             lock (_legacyThreadingEventsLock)
96             {
97                 ErrorUtilities.VerifyThrow(!_legacyThreadingEventsById.ContainsKey(submissionId), "Submission {0} should not already be registered with LegacyThreadingData", submissionId);
98 
99                 _legacyThreadingEventsById[submissionId] = new Tuple<AutoResetEvent, ManualResetEvent>
100                             (
101                                 new AutoResetEvent(false),
102                                 new ManualResetEvent(false)
103                             );
104             }
105         }
106 
107         /// <summary>
108         /// This submission is completely done with the legacy thread, so unregister it
109         /// from the dictionary so that we don't leave random events lying around.
110         /// </summary>
UnregisterSubmissionForLegacyThread(int submissionId)111         internal void UnregisterSubmissionForLegacyThread(int submissionId)
112         {
113             lock (_legacyThreadingEventsLock)
114             {
115                 ErrorUtilities.VerifyThrow(_legacyThreadingEventsById.ContainsKey(submissionId), "Submission {0} should have been previously registered with LegacyThreadingData", submissionId);
116 
117                 if (_legacyThreadingEventsById.ContainsKey(submissionId))
118                 {
119                     _legacyThreadingEventsById.Remove(submissionId);
120                 }
121             }
122         }
123 
124         /// <summary>
125         /// Given a submission ID, return the event being used to track when that submission is ready
126         /// to be executed on the legacy thread.
127         /// </summary>
GetStartRequestBuilderMainThreadEventForSubmission(int submissionId)128         internal WaitHandle GetStartRequestBuilderMainThreadEventForSubmission(int submissionId)
129         {
130             Tuple<AutoResetEvent, ManualResetEvent> legacyThreadingEvents = null;
131 
132             lock (_legacyThreadingEventsLock)
133             {
134                 _legacyThreadingEventsById.TryGetValue(submissionId, out legacyThreadingEvents);
135             }
136 
137             ErrorUtilities.VerifyThrow(legacyThreadingEvents != null, "We're trying to wait on the legacy thread for submission {0}, but that submission has not been registered.", submissionId);
138 
139             return legacyThreadingEvents.Item1;
140         }
141 
142         /// <summary>
143         /// Given a submission ID, return the event being used to track when that submission is ready
144         /// to be executed on the legacy thread.
145         /// </summary>
GetLegacyThreadInactiveTask(int submissionId)146         internal Task GetLegacyThreadInactiveTask(int submissionId)
147         {
148             Tuple<AutoResetEvent, ManualResetEvent> legacyThreadingEvents = null;
149 
150             lock (_legacyThreadingEventsLock)
151             {
152                 _legacyThreadingEventsById.TryGetValue(submissionId, out legacyThreadingEvents);
153             }
154 
155             ErrorUtilities.VerifyThrow(legacyThreadingEvents != null, "We're trying to track when the legacy thread for submission {0} goes inactive, but that submission has not been registered.", submissionId);
156 
157             return legacyThreadingEvents.Item2.ToTask();
158         }
159 
160         /// <summary>
161         /// Signal that the legacy thread is starting work.
162         /// </summary>
SignalLegacyThreadStart(RequestBuilder instance)163         internal void SignalLegacyThreadStart(RequestBuilder instance)
164         {
165             ErrorUtilities.VerifyThrow
166                 (
167                     instance != null &&
168                     instance.RequestEntry != null &&
169                     instance.RequestEntry.Request != null,
170                     "Cannot signal legacy thread start for a RequestBuilder without a request"
171                 );
172 
173             int submissionId = instance.RequestEntry.Request.SubmissionId;
174             this.InstanceForMainThread = instance;
175 
176             Tuple<AutoResetEvent, ManualResetEvent> legacyThreadingEvents = null;
177             lock (_legacyThreadingEventsLock)
178             {
179                 _legacyThreadingEventsById.TryGetValue(submissionId, out legacyThreadingEvents);
180             }
181 
182             ErrorUtilities.VerifyThrow(legacyThreadingEvents != null, "We're trying to signal that the legacy thread is ready for submission {0} to execute, but that submission has not been registered", submissionId);
183 
184             // signal that this submission is currently controlling the legacy thread
185             legacyThreadingEvents.Item1.Set();
186 
187             // signal that the legacy thread is not currently idle
188             legacyThreadingEvents.Item2.Reset();
189         }
190 
191         /// <summary>
192         /// Signal that the legacy thread has finished its work.
193         /// </summary>
SignalLegacyThreadEnd(int submissionId)194         internal void SignalLegacyThreadEnd(int submissionId)
195         {
196             this.MainThreadSubmissionId = -1;
197 
198             Tuple<AutoResetEvent, ManualResetEvent> legacyThreadingEvents = null;
199             lock (_legacyThreadingEventsLock)
200             {
201                 _legacyThreadingEventsById.TryGetValue(submissionId, out legacyThreadingEvents);
202             }
203 
204             ErrorUtilities.VerifyThrow(legacyThreadingEvents != null, "We're trying to signal that submission {0} is done with the legacy thread, but that submission has not been registered", submissionId);
205 
206             // The legacy thread is now idle
207             legacyThreadingEvents.Item2.Set();
208         }
209     }
210 }
211