#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

270 lines
7.2 KiB

// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using Microsoft.Build.Framework;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
using ICSharpCode.SharpDevelop.BuildWorker.Interprocess;
namespace ICSharpCode.SharpDevelop.BuildWorker
{
/// <summary>
/// Manages the list of running child worker processes.
/// </summary>
public static class WorkerManager
{
/// <summary>
/// Delegate executed on the host when an exception occurs.
/// </summary>
public static Action<Exception> ShowError { get; set; }
const int MaxWorkerProcessCount = 16;
static readonly object lockObject = new object();
static Queue<BuildRun> outstandingBuildRuns = new Queue<BuildRun>();
static int workerProcessCount;
static List<WorkerProcessHost> freeWorkerProcesses = new List<WorkerProcessHost>();
static WorkerProcessHost DequeueFreeWorkerProcess()
{
WorkerProcessHost h = freeWorkerProcesses[freeWorkerProcesses.Count - 1];
freeWorkerProcesses.RemoveAt(freeWorkerProcesses.Count - 1);
return h;
}
public static void StartBuild(BuildJob job, BuildSettings settings)
{
if (job == null)
throw new ArgumentNullException("job");
if (settings == null)
throw new ArgumentNullException("settings");
BuildRun buildRun = new BuildRun(job, settings);
lock (lockObject) {
if (freeWorkerProcesses.Count > 0) {
DequeueFreeWorkerProcess().StartBuild(buildRun);
} else {
outstandingBuildRuns.Enqueue(buildRun);
if (workerProcessCount < MaxWorkerProcessCount) {
workerProcessCount++;
(new WorkerProcessHost()).Start();
}
}
}
}
static Program inProcessBuildWorker;
public static void RunBuildInProcess(BuildJob job, BuildSettings settings)
{
if (job == null)
throw new ArgumentNullException("job");
if (settings == null)
throw new ArgumentNullException("settings");
lock (lockObject) {
if (inProcessBuildWorker == null)
inProcessBuildWorker = new Program();
}
inProcessBuildWorker.BuildInProcess(settings, job);
}
static readonly object timerLock = new object();
static Timer lastBuildDoneTimer;
static void SetLastBuildDoneTimer()
{
lock (timerLock) {
if (lastBuildDoneTimer != null) {
lastBuildDoneTimer.Dispose();
lastBuildDoneTimer = null;
}
lastBuildDoneTimer = new Timer(LastBuildDoneTimerCallback, null, 60000, 20000);
}
}
static void ClearLastBuildDoneTimer()
{
lock (timerLock) {
if (lastBuildDoneTimer != null) {
lastBuildDoneTimer.Dispose();
lastBuildDoneTimer = null;
}
}
}
static void LastBuildDoneTimerCallback(object state)
{
lock (lockObject) {
if (freeWorkerProcesses.Count > 0) {
Debug.WriteLine("WorkerManager: shutting down free worker");
DequeueFreeWorkerProcess().Shutdown();
} else {
ClearLastBuildDoneTimer();
}
}
}
sealed class BuildRun
{
internal BuildJob job;
internal BuildSettings settings;
EventSource eventSource = new EventSource();
public BuildRun(BuildJob job, BuildSettings settings)
{
this.job = job;
this.settings = settings;
foreach (ILogger logger in settings.Logger) {
logger.Initialize(eventSource);
}
}
public void RaiseError(string message)
{
Debug.WriteLine(message);
RaiseEvent(new BuildErrorEventArgs(null, null, null, -1, -1, -1, -1, message, null, "SharpDevelopBuildWorkerManager"));
}
public void RaiseEvent(BuildEventArgs e)
{
eventSource.RaiseEvent(e);
}
public void Done(bool success)
{
SetLastBuildDoneTimer();
try {
foreach (ILogger logger in settings.Logger) {
logger.Shutdown();
}
} finally {
if (settings.BuildDoneCallback != null)
settings.BuildDoneCallback(success);
}
}
}
sealed class WorkerProcessHost : IHostObject
{
WorkerProcess process;
internal void Start()
{
process = new WorkerProcess(this);
process.Ready += OnReady;
process.WorkerLost += OnWorkerLost;
process.Start(Program.CreateStartInfo());
}
BuildRun currentBuildRun;
// runs in lock(lockObject)
internal void StartBuild(BuildRun nextBuildRun)
{
Debug.Assert(currentBuildRun == null);
currentBuildRun = nextBuildRun;
process.CallMethodOnWorker("StartBuild", currentBuildRun.job);
}
void OnReady(object sender, EventArgs e)
{
BuildRun nextBuildRun = null;
lock (lockObject) {
if (outstandingBuildRuns.Count > 0)
nextBuildRun = outstandingBuildRuns.Dequeue();
else
freeWorkerProcesses.Add(this);
}
if (nextBuildRun != null) {
StartBuild(nextBuildRun);
}
}
void OnWorkerLost(object sender, EventArgs e)
{
lock (lockObject) {
workerProcessCount--;
freeWorkerProcesses.Remove(this);
if (workerProcessCount == 0 && currentBuildRun == null) {
// error starting worker => we must
// cancel all outstanding build runs to prevent them from waiting
// for a worker becoming ready when all workers are dead
while (workerProcessCount == 0 && outstandingBuildRuns.Count > 0) {
BuildRun r = outstandingBuildRuns.Dequeue();
Monitor.Exit(lockObject);
r.RaiseError("Error starting worker process.");
r.Done(false);
Monitor.Enter(lockObject);
}
}
}
BuildRun buildRun = Interlocked.Exchange(ref currentBuildRun, null);
if (buildRun != null) {
buildRun.RaiseError("Worker process lost during build");
buildRun.Done(false);
}
}
internal void Shutdown()
{
process.Shutdown();
}
// Called with CallMethodOnHost
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public void ReportEvent(BuildEventArgs e)
{
BuildRun buildRun = currentBuildRun;
if (buildRun != null) {
buildRun.RaiseEvent(e);
}
}
// Called with CallMethodOnHost
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public void BuildDone(bool success)
{
BuildRun buildRun = Interlocked.Exchange(ref currentBuildRun, null);
if (buildRun != null) {
// OnReady must be called before buildRun.Done - the callback
// might trigger another build, and if this worker process
// isn't marked as ready, a new process will be created even
// though this one could do the work.
OnReady(null, null);
buildRun.Done(success);
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public void ReportException(Exception ex)
{
// shutdown worker if it produced an exception
try {
process.Shutdown();
} catch {}
if (ShowError != null)
ShowError(ex);
else
Program.ShowMessageBox(ex.ToString());
}
}
}
public sealed class BuildSettings
{
List<ILogger> logger = new List<ILogger>();
public Action<bool> BuildDoneCallback { get; set; }
public ICollection<ILogger> Logger {
get { return logger; }
}
}
}