Merge pull request #2768 from lambdageek/dev/monoerror-cominterop
[mono.git] / mcs / class / Microsoft.Build / Microsoft.Build.Internal / BuildNodeManager.cs
1 //
2 // BuildNodeManager.cs
3 //
4 // Author:
5 //   Atsushi Enomoto (atsushi@xamarin.com)
6 //
7 // Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.Collections.Generic;
30 using System.Linq;
31 using Microsoft.Build.Execution;
32 using Microsoft.Build.Framework;
33 using System.Threading.Tasks;
34 using System.Threading;
35 using System.Collections.Concurrent;
36
37 namespace Microsoft.Build.Internal
38 {
39         class BuildNodeManager
40         {
41                 public BuildNodeManager (BuildManager buildManager)
42                 {
43                         BuildManager = buildManager;
44                         new Thread (RunLoop) {
45                                 IsBackground = true,
46                                 Name = "xbuild request handler"
47                         }.Start ();
48                 }
49
50                 ~BuildNodeManager ()
51                 {
52                         run_loop = false;
53                         queue_wait_handle.Set ();
54                 }
55                 
56                 public BuildManager BuildManager { get; private set; }
57                 
58                 List<BuildNode> in_proc_nodes = new List<BuildNode> ();
59                 List<BuildNode> out_proc_nodes = new List<BuildNode> ();
60                 AutoResetEvent queue_wait_handle = new AutoResetEvent (false);
61                 ConcurrentQueue<BuildSubmission> queued_builds = new ConcurrentQueue<BuildSubmission> ();
62                 // FIXME: currently it is not in use but it should be stored somewhere for cancellation.
63                 Dictionary<BuildSubmission,Task> ongoing_builds = new Dictionary<BuildSubmission, Task> ();
64                 bool run_loop = true;
65
66                 readonly TaskFactory<BuildResult> task_factory = new TaskFactory<BuildResult> ();
67                 internal TaskFactory<BuildResult> ThreadTaskFactory {
68                         get { return task_factory; }
69                 }
70                 
71                 void RunLoop ()
72                 {
73                         while (run_loop) {
74                                 try {
75                                         if (queued_builds.Count == 0)
76                                                 queue_wait_handle.WaitOne ();
77                                         if (!run_loop)
78                                                 break;
79                                         BuildSubmission build;
80                                         if (!queued_builds.TryDequeue (out build))
81                                                 continue;
82                                         StartOneBuild (build);
83                                 } catch (Exception ex) {
84                                         // FIXME: I guess INodeLogger should be used instead.
85                                         Console.Error.WriteLine ("Uncaught build node exception occured");
86                                         Console.Error.WriteLine (ex);
87                                 }
88                         }
89                 }
90
91                 public void Stop ()
92                 {
93                         run_loop = false;
94                         queue_wait_handle.Set ();
95                 }
96
97                 public void ResetCaches ()
98                 {
99                         in_proc_nodes.Clear ();
100                         out_proc_nodes.Clear ();
101                 }
102                 
103                 public void Enqueue (BuildSubmission build)
104                 {
105                         queued_builds.Enqueue (build);
106                         queue_wait_handle.Set ();
107                 }
108                 
109                 void StartOneBuild (BuildSubmission build)
110                 {
111                         var node = TakeNode (build);
112                         // FIXME: Task (non-generic) here causes NotImplementedException in somewhere in Interlocked. It does not make sense.
113                         ongoing_builds [build] = task_factory.StartNew (node.ExecuteBuild);
114                         //new Thread (() => { node.ExecuteBuild (); }).Start ();
115                 }
116                 
117                 void EndOneBuild (BuildNode node)
118                 {
119                         var task = ongoing_builds [node.Build];
120                         ongoing_builds [node.Build] = null;
121                         node.Release ();
122                 }
123                 
124                 // FIXME: take max nodes into account here, and get throttling working.
125                 BuildNode TakeNode (BuildSubmission build)
126                 {
127                         var host = BuildManager.OngoingBuildParameters.HostServices;
128                         NodeAffinity affinity;
129                         if (host == null)
130                                 affinity = NodeAffinity.Any;
131                         else
132                                 affinity = host.GetNodeAffinity (build.BuildRequest.ProjectFullPath);
133                         BuildNode n = GetReusableNode (affinity);
134                         if (n != null)
135                                 n.Assign (build);
136                         else {
137                                 n = new BuildNode (this, affinity == NodeAffinity.Any ? NodeAffinity.InProc : affinity);
138                                 n.Assign (build);
139                                 if (n.Affinity == NodeAffinity.InProc)
140                                         in_proc_nodes.Add (n);
141                                 else
142                                         out_proc_nodes.Add (n);
143                         }
144                         return n;
145                 }
146                 
147                 BuildNode GetReusableNode (NodeAffinity affinity)
148                 {
149                         if (!BuildManager.OngoingBuildParameters.EnableNodeReuse)
150                                 return null;
151                         
152                         if (affinity != NodeAffinity.OutOfProc)
153                                 foreach (var n in in_proc_nodes)
154                                         if (n.IsAvailable && (n.Affinity & affinity) != 0)
155                                                 return n;
156                         if (affinity != NodeAffinity.InProc)
157                                 foreach (var n in out_proc_nodes)
158                                         if (n.IsAvailable && (n.Affinity & affinity) != 0)
159                                                 return n;
160                         return null;
161                 }
162         
163                 internal class BuildNode
164                 {
165                         static Random rnd = new Random ();
166                         
167                         public BuildNode (BuildNodeManager manager, NodeAffinity affinity)
168                         {
169                                 Manager = manager;
170                                 Affinity = affinity;
171                                 Id = rnd.Next ();
172                         }
173                         
174                         public bool IsAvailable { get; private set; }
175                         public int Id { get; private set; }
176                         public BuildNodeManager Manager { get; set; }
177                         public NodeAffinity Affinity { get; private set; }
178                         public BuildSubmission Build { get; private set; }
179                         
180                         public void Assign (BuildSubmission build)
181                         {
182                                 IsAvailable = false;
183                                 Build = build;
184                         }
185                         
186                         public void Release ()
187                         {
188                                 Build = null;
189                                 IsAvailable = true;
190                         }
191                         
192                         public BuildResult ExecuteBuild ()
193                         {
194                                 BuildResult result;
195                                 try {
196                                         // FIXME: depending on NodeAffinity, build it through another MSBuild process.
197                                         if (Affinity == NodeAffinity.OutOfProc)
198                                                 throw new NotImplementedException ();
199                                         result = Build.InternalExecute ();
200                                 } catch (Exception ex) {
201                                         // FIXME: I guess INodeLogger should be used instead.
202                                         Console.Error.WriteLine ("Uncaught build node exception occured");
203                                         Console.Error.WriteLine (ex);
204                                         result = null;
205                                 } finally {
206                                         Manager.EndOneBuild (this);
207                                 }
208                                 return result;
209                         }
210                 }
211         }
212 }