Wednesday, 18 March 2009

Redefining Configuration Variables: StackOverflowException

The Problem


Today I made some changes to our CruiseControl.NET solution in order to get a project working that is slightly different from our general project structure. After making the changes I restarted the service but it wouldn't start. Looking in the Event Log I got a rather generic "There has been an error in the .NET 2.0 Framework" kind of exception which wasn't very helpful. What was going on?

Some Background


To understand the reason for the service termination, a decent understanding of CCNET's cb:define and cb:scope configuration declarations is needed. For those of you who may not be familiar with these declarations, I'll give you a brief run-down:

cb:define
This declaration allows you to define a variable in the CCNET configuration that can be used later. For example:

<cb:define workingDir="C:\example"/>


will create a variable called workingDir which can be used throughout the project like so:

<workingDir>$(workingDir)</workingDir>


cb:scope
The scope declaration allows you to override a previous definition for a certain scope. The following example shows this:

<cb:define workingDir="C:\example"/>
<!-- workingDir will equal C:\example -->
<workingDir>$(workingDir)</workingDir>
<cb:scope>
<cb:define workingDir="C:\different"/>
<!-- workingDir will now equal C:\different -->
<workingDir>$(workingDir)</workingDir>
</cb:scope>
<!-- workingDir will equal C:\example again -->
<workingDir>$(workingDir)</workingDir>


The Reason


Eventually I discovered why the service wasn't starting. I had kind of assumed that the 'define' and 'scope' declarations were similar to those in a programming language such as C#. In C# the following would work:

string workingDir = "C:\example";
Console.WriteLine(workingDir); // Writes 'C:\example'
workingDir = "C:\different";
Console.WriteLine(workingDir); // Now writes 'C:\different'


This, however, is not the case. My configuration, in an attempt to achieve "C:\example\anotherLevel" as a workingDir within the scope was:


<cb:define workingDir="C:\example"/>
<cb:scope>
<!-- Attempting to get C:\example\anotherLevel -->
<cb:define workingDir="$(workingDir)\anotherLevel"/>
</cb:scope>


This was resulting in a StackOverflowException which was killing the service when the configuration file was read and parsed.

The Solution


In the end I had to effectively redefine the complete path within the scope:

<cb:define workingDir="C:\example"/>
<cb:scope>
<!-- Redefine with full path -->
<cb:define workingDir="C:\example\anotherLevel"/>
</cb:scope>


While this results in a bit of duplication in the configuration file, I think that a working service is more important!

No comments:

Post a Comment