Exceptions in CruiseControl.NET revisited

The other day I wrote about configuring CruiseControl.NET to send email when an exception occurred at the project level, meaning outside of the actual “build” which technically equates to the <tasks> block. I’ve since spent some time trying to understand why the CCNet was changed to remove support for $g(PublishExceptions) and found revision 2567 to IntegrationRunner.cs where it was removed with a comment of “all exceptions during build break the build
deprecating PublishExceptions attribute”. That didn’t exactly provide the explanation I was looking for and AFAICT, this change has now left a gap in CruiseControl.NET where exceptions occurring outside of the <tasks> block can’t be published using a build publisher. So, unless you’re checking the log files regularly, or you’ve configured log4net using an SmtpAppender they’ll go unnoticed.

To figure this out I’ve been posting messages to the ccnet-devel group and thankfully I’m getting some response although it’s been a very slow conversation that’s had it’s share of missteps due, in part, to my incorrect phrasing of where exceptions were occurring. Originally, I was looking for guidance thinking I must be missing something. After a few more posts a change request and subsequent revision was made but the change addressed a situation I’m pretty sure can’t occur in the current code base short of a plug-in explicitly catching exceptions and setting the build status to “Exception” which isn’t what I was looking for.

I believe the necessary change to allow for integration exceptions to be published looks something like this (from IntegrationRunner.cs):

1 public IIntegrationResult Integrate(IntegrationRequest request)
2 {
3 IIntegrationResult result = resultManager.StartNewIntegration(request);
4 IIntegrationResult lastResult = resultManager.LastIntegrationResult;
5 try 6 {
7 CreateDirectoryIfItDoesntExist(result.WorkingDirectory);
8 CreateDirectoryIfItDoesntExist(result.ArtifactDirectory);
9 result.MarkStartTime();
10 result.Modifications = GetModifications(lastResult, result);
11 if (result.ShouldRunBuild())
12 {
13 Log.Info("Building: " + request);
14 Build(result);
15 PostBuild(result
16 Log.Info(string.Format("Integration complete: {0} - {1}",
17 result.Status, result.EndTime));
18 }
19 target.Activity = ProjectActivity.Sleeping;
20 return result;
21 }
22 catch (Exception ex)
23 {
24 result.Status = IntegrationStatus.Exception;
25 result.ExceptionResult = ex;
26 target.PublishResults(result);
27 return result;
28 }
29 }
30

I’ve added a try…catch to handle exceptions that occur during the integration process and call PostBuild() which calls FinishIntegration() and PublishResults(). That way any exception occurring during the integration process can be handled by any of the available publishers including the email publisher. Btw, the old PublishExceptions code looked similar. Of course, the code presented here isn’t the only, nor perhaps even the best way to handle this situation personally, I’m not fond of PostBuild() appearing twice but adding a finally block seemed to unnecessarily complicate the code. Since I don’t understand the reasoning behind removing the PublishExceptions tag it’s hard to know what the desired behavior here is but I’m pretty sure what’s checked in is probably not what was intended.

There is one other change I think is important as well which is to modify IntegrationResult.ShouldRunBuild() as follows (from IntegrationResult.cs):

1 public bool ShouldRunBuild()
2 {
3 return status == IntegrationStatus.Unknown &&
4 (BuildCondition.ForceBuild == BuildCondition || HasModifications());
5 } 

This change rather subtle but it allows for an ISourceControl plug-in to handle it’s own exceptions and still prevent the build from executing by setting IntegrationStatus to something other than “Unknown”. Without the check for IntegrationStatus.Unknown the build would run regardless of exceptions that occurred within the GetModifications() call. For example, here is a catch block you could use within your implementation of ISourceControl.GetModfications() to prevent the build from executing. However, this will only work if the above proposed changes are made to CCNet.

1 catch(Exception ex)
2 {
3 to.ExceptionResult = ex;
4 to.Status = ThoughtWorks.CruiseControl.Remote.IntegrationStatus.Exception;
5 return null;
6 }

In the event of an exception you’ll have the opportunity to have the exception published using your configured publishers. Additionally, the web dashboard will reflect the fact that the build is left in an Exception state.

Anyway, I’m going continue to work this issue and see if I can get either the above changes or some other solution integrated into CCNet though the thread on the groups seems to have died.

[UPDATE: Jan 4, 2008] Here is an SVN patch for these changes.
[UPDATE: Jan 9, 2008] Fix for IntegrationRunner.Integrate to simply publish the results in the event of an exception.

2 thoughts on “Exceptions in CruiseControl.NET revisited

  1. Hi Steve,
    i’ve added the following lines at the beginning of the catch clause because without them the dashboard was showing the last build time and last build label as "UNKNOWN" after an exception occured.

    result.MarkEndTime()
    result.Label = lastResult.LastSuccessfulIntegrationLabel;

    With those lines added it is working very nicely for me. I hope we can get this into the next release.
    regards,
    Daniel

Comments are closed.