December 3, 2006 Continuous Integration

AsyncExec and WaitForExit: Speeding Up The Build To Do More

I have been working to bring the build time for our developer facing build to something less than 5 minutes.  I recently got it to less than 5.  Currently it does none of the nice fancy stuff like run Simian, NDepend, or FXCop.  I have been experimenting with an extended exec task: asynexec.  It wraps the core exec task starting base.ExecuteTask in a new thread.  It has one additional attribute that must be set: taskname.  Taskname is used to track the thread.  This enables the other task that I created: waitforexit.  These two tasks can be used in concert like so:

<asyncexec taskname=UnitTests

      program=${NantProcess}

      failonerror=False

      resultproperty=UnitTestsResult>

  <arg line=-buildfile:.\Packages\MSTest\UnitTest.Target.xml/>

  <arg line=@”${Common.PropertiesFile}” />

  <arg line=UnitTest.RunTests/>

  <arg line=-logger:NAnt.Core.XmlLogger/>

  <arg line=-logfile:”${Common.ReportDirectory}\UnitTests.xml” />

</asyncexec>

 

<waitforexit>

  <tasknames>

    <string value=UnitTests/>

  </tasknames>

</waitforexit>

 

<if test=${int::parse(UnitTestsResult) != 0}>

  <fail message=Atleast one unit test failed!/>

</if>

Functionally this will behave the same as simply calling that task exec.  Things get interesting when you start doing more:

<asyncexec taskname=UnitTests program=${NantProcess} failonerror=False resultproperty=UnitTestsResult>

  <arg line=-buildfile:.\Packages\MSTest\UnitTest.Target.xml/>

  <arg line=@”${Common.PropertiesFile}” />

  <arg line=UnitTest.RunTests/>

  <arg line=-logger:NAnt.Core.XmlLogger/>

  <arg line=-logfile:”${Common.ReportDirectory}\UnitTests.xml” />

</asyncexec>

 

<asyncexec taskname=Simian program=${NantProcess} failonerror=False>

  <arg line=-buildfile:.\Packages\Simian\Simian.Target.xml/>

  <arg line=@”${Common.PropertiesFile}” />

  <arg line=Simian.Run/>

  <arg line=-logger:NAnt.Core.XmlLogger/>

  <arg line=-logfile:”${Common.ReportDirectory}\Simian.xml” />

</asyncexec>

 

<asyncexec taskname=NDepend program=${NantProcess} failonerror=False>

  <arg line=-buildfile:.\Packages\NDepend\NDepend.Target.xml/>

  <arg line=@”${Common.PropertiesFile}” />

  <arg line=nDepend.Calculate/>

  <arg line=-logger:NAnt.Core.XmlLogger/>

  <arg line=-logfile:”${Common.ReportDirectory}\NDepend.xml” />

</asyncexec>

 

<waitforexit>

  <tasknames>

    <string value=Simian/>

    <string value=UnitTests/>

    <string value=NDepend/>

  </tasknames>

</waitforexit>

 

<if test=${int::parse(UnitTestsResult) != 0}>

  <fail message=Atleast one unit test failed!/>

</if>

 This saved about 35 seconds on our build server from the old way:

<call target=UnitTest.RunTests/>

<call target=Simian.Run/>

<call target=nDepend.Calculate/>

I spent some time playing around with how best to use this.  We use CI Factory on this project.  I bring this up because it will give some background to how the build script is setup.  There is a target analogous to sub Main.  In this Main target targets from Packages are called.  The table below contains timing information about those targets:

Asynchronous Asynchronous Synchronous
Target Seconds Minutes Seconds Minutes Seconds Minutes
nDepend.Calculate 166.5 02:46
Simian.Run 118.17 01:58 53.81 00:53
Compile.CompileSource 95.89 01:35 81.47 01:21 84.98 01:24
UnitTest.RunTests 87.95 01:27 31.47 00:31 20.3 00:20
SourceControl.GetOfProductDirectory 14.47 00:14 15.78 00:15 15.11 00:15
Tracker.Queries 9.28 00:09 9.75 00:09 8.94 00:08
Tracker.MoveTrackersTo 8.67 00:08 5.39 00:05 5.95 00:05
Tracker.Report 2.89 00:02 3.83 00:03 3.97 00:03
SourceControl.GetOfThirdPartyDirectory 1.28 00:01 2.53 00:02 1.06 00:01
SetUp 1.59 00:01 1.64 00:01 0.89 00:00
SourceModificationReport.ConsolidateReports 0.97 00:00 1.16 00:01 0.95 00:00
TearDown 0.3 00:00 0.22 00:00 0.17 00:00
Actual Total 299.45 04:59 174.81 02:54 144.09 02:24
Total If Synchronous 507.96 08:27 207.05 03:27
Time Saved 208.51 03:28 32.24 00:32

Like colored targets are executed at the same time.  I ran the scripts many times to get a feel for any variance.  The data above are from mean runs.  Things looked pretty good until I added NDepend.  This could be because it is an expensive operation and or that I had the script executing the unit tests and Simian at the same time (it is only dual proc).

A few posts ago I talked about a post build nant script; one that CCNET ran after the log publisher task.  I have used the asyncexec task there too but in slightly different way.  It is not waiting for an exit.  There is no need to complete the work in the post build script before starting on a new build.  I think that NDepend could be moved to the post build script.  This would let us gain both NDepend and Simian for a sum total of 30+ seconds (note this is only accurate if another build does not start immediately).  Not a bad price.

Below is the nant script main target.  It has been color coded to match the table above.

<target name=Triggered depends=SetUps >

  <trycatch>

    <try>

      <description>Begin Main Build</description>

 

      <description>Begin Pre Build Actions</description>

      <call target=SourceModificationReport.ConsolidateReports />

      <call target=Tracker.Queries />

 

      <asyncexec taskname=TrackerReport program=${NantProcess} failonerror=False>

        <arg line=-buildfile:.\Packages\VisualSourceSafe\VSS.Target.xml/>

        <arg line=@”${Common.PropertiesFile}” />

        <arg line=Tracker.Report/>

        <arg line=-D:Tracker.QueryScrList=${Tracker.QueryScrList} />

        <arg line=-logger:NAnt.Core.XmlLogger/>

        <arg line=-logfile:”${Common.ReportDirectory}\TrackerReport.xml” />

      </asyncexec>

      <description>End Pre Build Actions</description>

 

      <description>Begin Clean Up Actions</description>

 

      <asyncexec taskname=GetOfThirdPartyDirectory program=${NantProcess} failonerror=False

                resultproperty=GetOfThirdPartyDirectoryResult>

        <arg line=-buildfile:.\Packages\VisualSourceSafe\VSS.Target.xml/>

        <arg line=@”${Common.PropertiesFile}” />

        <arg line=SourceControl.GetOfThirdPartyDirectory/>

        <arg line=-logger:NAnt.Core.XmlLogger/>

        <arg line=-logfile:”${Common.ReportDirectory}\GetOfThirdPartyDirectory.xml” />

      </asyncexec>

 

      <call target=SourceControl.GetOfProductDirectory/>

 

      <waitforexit>

        <tasknames>

          <string value=GetOfThirdPartyDirectory/>

        </tasknames>

      </waitforexit>

 

      <if test=${int::parse(GetOfThirdPartyDirectoryResult) != 0}>

        <fail message=Get latest of the Third Party directory failed!/>

      </if>

      <description>End Clean Up Actions</description>

 

      <description>Begin Compile Actions</description>

      <touch millis=1 verbose=true>

        <fileset>

          <include name=${ProductDirectory}\ProjectInfo.*/>

          <include name=${ProductDirectory}\**\AssemblyInfo.*/>

        </fileset>

      </touch>

 

      <delete dir=${Compile.Bin} if=${directory::exists(Compile.Bin)} />

 

      <call target=Compile.CompileSource />

 

      <description>End Compile Actions</description>

 

      <description>Begin Varification Actions</description>

 

      <asyncexec taskname=NDepend program=${BuildDirectory}\nAnt\bin\nant.exe failonerror=False>

        <arg line=-buildfile:.\Packages\NDepend\NDepend.Target.xml/>

        <arg line=@”${Common.PropertiesFile}” />

        <arg line=nDepend.Calculate/>

        <arg line=-logger:NAnt.Core.XmlLogger/>

        <arg line=-logfile:”${Common.ReportDirectory}\NDepend.xml” />

      </asyncexec>

 

      <asyncexec taskname=UnitTests program=${NantProcess} failonerror=False resultproperty=UnitTestsResult>

        <arg line=-buildfile:.\Packages\MSTest\UnitTest.Target.xml/>

        <arg line=@”${Common.PropertiesFile}” />

        <arg line=UnitTest.RunTests/>

        <arg line=-logger:NAnt.Core.XmlLogger/>

        <arg line=-logfile:”${Common.ReportDirectory}\UnitTests.xml” />

      </asyncexec>

 

      <asyncexec taskname=Simian program=${NantProcess} failonerror=False>

        <arg line=-buildfile:.\Packages\Simian\Simian.Target.xml/>

        <arg line=@”${Common.PropertiesFile}” />

        <arg line=Simian.Run/>

        <arg line=-logger:NAnt.Core.XmlLogger/>

        <arg line=-logfile:”${Common.ReportDirectory}\Simian.xml” />

      </asyncexec>

 

      <waitforexit>

        <tasknames>

          <string value=UnitTests/>

        </tasknames>

      </waitforexit>

 

      <if test=${int::parse(UnitTestsResult) != 0}>

        <fail message=Atleast one unit test failed!/>

      </if>

      <description>End Varification Actions</description>

 

      <description>Begin Post Build Actions</description>

      <call target=Tracker.MoveTrackersTo/>

      <waitforexit>

        <tasknames>

          <string value=Simian/>

          <string value=NDepend/>

          <string value=TrackerReport/>

        </tasknames>

      </waitforexit>

      <description>End Post Build Actions</description>

 

      <description>End Main Build</description>

    </try>

    <finally>

 

      <call target=TearDowns/>

 

    </finally>

  </trycatch>

</target>

Download these tasks and more in NAnt Stuff.

7,680 Total Views

5 to “AsyncExec and WaitForExit: Speeding Up The Build To Do More”

Trackbacks/Pingbacks

  1. AsyncExec and WaitForExit: Speeding Up The Build To Do More…

    You’ve been kicked (a good thing) – Trackback from DotNetKicks.com…

  2. [...] In a previous post I introduced the task AsyncExec.  For the way I was using it in that post it worked great.  I hinted at using it to execute a process, letting the child process continue after the parent had exited.  The task was not in fact capable of this.  The ancestor task ExternalProgramBase spawned two threads for collecting the Standard Out and Error Out streams.  These threads as well as the one spawned for calling the base method ExecuteTask were not background threads.  These three threads plus the call to WaitForExit on the System.Diagnostics.Process object were causing the parent process to hang around.  So four things needed to be addressed to allow for the desired behavior. [...]

  3. [...] JayFlowers > AsyncExec and WaitForExit: Speeding Up The Build To Do More: [...]

  1. DotNetKicks.com says...

    AsyncExec and WaitForExit: Speeding Up The Build To Do More…

    You’ve been kicked (a good thing) – Trackback from DotNetKicks.com…

  2. Jon says...

    This sounds exactly what I am looking for, as I need to build 32-bit (debug and release) and 64-bit (debug and release) for a total of 4 builds. It will be nice to be able to build in parallel vs. building serially which currently takes over 5 hours.

    The question is, any chance this will make nant or nantcontrib? I just don’t want to get dependent on this task, then not be able to upgrade nant later on.

    Thanks for the great work!
    Jon

  3. jflowers says...

    Someone else has asked me for the source and I think that would get you where you want to be. I don’t see that NAnt would change in a way that would break these two tasks. You should be able to upgrade without issue. For that matter it should work with older versions of NAnt as well. Here is a link to the source:

    Async.Tasks.zip

  4. JayFlowers > Update/Fix AsyncExec Task and Post Build Extension says...

    [...] In a previous post I introduced the task AsyncExec.  For the way I was using it in that post it worked great.  I hinted at using it to execute a process, letting the child process continue after the parent had exited.  The task was not in fact capable of this.  The ancestor task ExternalProgramBase spawned two threads for collecting the Standard Out and Error Out streams.  These threads as well as the one spawned for calling the base method ExecuteTask were not background threads.  These three threads plus the call to WaitForExit on the System.Diagnostics.Process object were causing the parent process to hang around.  So four things needed to be addressed to allow for the desired behavior. [...]

  5. JayFlowers > AsyncExec and WaitForExit: Speeding Up The Build To Do More « Hornet Dear Bernard says...

    [...] JayFlowers > AsyncExec and WaitForExit: Speeding Up The Build To Do More: [...]

Leave a comment

*

here