Flawed assembly caching in VS11 Beta

Note: This is based on Visual Studio 11 Beta

A custom MSBuild Task

Let's say we a custom MSBuild Task

public class CustomTask : Task
{
    public override bool Execute()
    {
        var message = "CustomTask version is " + GetType().Assembly.GetName().Version;
        var args = new BuildWarningEventArgs("", "", "", 0, 0, 0, 0, message, "", "CustomTask");
        BuildEngine.LogWarningEvent(args);
        return true;
    }
}

Fairly simple right, it will just write a warning with the current version of the assembly.

Multiple versions

Now lets say we are using two different versions of the MSBuild Task.

  • Version 1 is used in Project 1 that exists in Solution 1
  • Version 2 is used in Project 2 that exists in Solution 2

Building in Visual Studio

So my scenerio is

  1. Open and Build Solution 1
  2. Close Visual Studio
  3. Open and Build Solution 2

In Visual Studio 2010

  • Building Solution 1 outputs "CustomTask version is 1.0.0.0"
  • Building Solution 2 outputs "CustomTask version is 2.0.0.0"

In Visual Studio 11

  • Building Solution 1 outputs "CustomTask version is 1.0.0.0"
  • Building Solution 2 outputs "CustomTask version is 1.0.0.0"

Note that in Visual Studio 11 both outputted 1.0.0.0

Try something different

Obviously something went wrong with Visual Studio 11. Instead try the following in Visual Studio 11

  1. Open and Build Solution 1
  2. Close Visual Studio
  3. Open Task Manager
  4. Find MSBuild.exe and kill it
  5. Open and Build Solution 2

Now the result will be

  • Building Solution 1 outputs "CustomTask version is 1.0.0.0"
  • Building Solution 2 outputs "CustomTask version is 2.0.0.0"

So What Happened

Well it turns out a few things change between Visual Studio 2010 and Visual Studio 11

  • Builds no longer happen in the devenv.exe process. Builds now are shelled out to an MSBuild.exe process
  • Multiple MSBuild.exe may be used for parallel builds. See Tools > Options > Projects and Solutions > maximum number of parallel project builds
  • MSBuild.exe instances are re-used for performance reasons. The actually stay resident after the Visual Studio (that spawned them) has closed. This saves on the cost of re-loading assemblies into the AppDomain.

Also remember that assemblies cannot be unloaded from an AppDomain.

So with all that in mind what happened was

  1. Open Solution 1 in Visual Studio
  2. Build Solution
  3. An MSBuild.exe is spawned and loads Custom Task version 1
  4. Close Visual Studio. Note MSBuild.exe will not close
  5. Open Solution 2 in Visual Studio
  6. Build Solution
  7. Visual Studio will detect an unused MSBuild.exe and reuse it
  8. Since Custom Task version 1 is loaded in that AppDomain it will be reused

How this could affect you

Problems updating MSBuild task assemblies

The workflow for updating MSBuild task assemblies at the moment is

  • Close Visual Studio because the file will be locked
  • Overwrite the MSBuild task assembly
  • Re-start Visual Studio

This will no longer work because the MSBuild.exe will keep the file locked after Visual Studio has closed. You will need to explicitly kill the MSBuild.exe process.

Heisenbug when moving between solutions that use different versions of tasks

Consider the scenario where you have multiple solutions using different versions of MSBuild Tasks. In this case it is possible to have random and hard to diagnose issues since you will get random versions of MSBuild Task assembly being used based on the order that you build, open and close solutions.

Difficultly developing MSBuild Task assemblies

The locking nature of Visual Studio 11 makes it very hard to do integration testing when developing new MSBuild tasks. I found myself having to regularly kill MSBuild.exe so I could re-run my tests.

So I raised a bug.

Considering the above issues I was certain this was a bug in the Visual Studio 11 Beta. So I raised on MSConnect: Ghost MSBuild.exe in VS11 when using custom task. And of course, in under 24h hours, it was marked as "Closed as By Design" with the comment

Thanks for the report. This is by design, since MSBuild 3.5. The child processes persist until 15 minutes has passed without use (ie without a build). This gives some performance gains in some cases.

The (flawed) Workaround

In MSConnect they did propose a workaround.

If you're using Visual Studio, set an environment variable MSBUILDDISABLENODEREUSE=1. Everything will work functionally the same.

What this actual does is force Visual Studio to spawn a fresh instance of MSBuild.exe for every build. Now while this address the above issues it does so with a performance cost. It means that all assemblies required for build will need to be re-loaded into the AppDomain on every build. You builds will now be slower than when using Visual Studio 2010.

Also adding an environment variable to fix a problem like this is not "Falling Into The Pit of Success"

How Visual Studio 11 should work

While I like the idea of spawning builds to a different process they should not be reused in future Visual Studio instances. This would fix the above issue with minimal impact on performance.

What you can do

If you use MSBuild tasks, probably everyone reading this blog, then please vote for the Bug/Feature (Ghost MSBuild.exe in VS11 when using custom task) to be fixed on MSConnect.

Posted by: Simon Cropp
Last revised: 26 Mar, 2012 12:50 PM History

Comments

No comments yet. Be the first!

No new comments are allowed on this post.