Obtaining Build Stop in Long-Run Activity
Sometime you need to perform long running operations during your build process. I've created long-run activities more than once i.e. a deploy activity that copies the drop location (or part of it) to a multiple distance locations.
Another example, I have created a Flash activity that compiles flash code (FLA files) and creates SWF. According to Adobe, there is no MSBuild-like command line so the only option I had is to run Flash.exe using JSFL code (Flash 'batch' file). We have a very large amount of Flash files (~500 FLAs), so I created a chunks of JSFL batch files and run them one after another using Process.Starts and Process.WaitForExit methods. Between the runs I checked the results.
The flash activity runs between 0.5-2 hours depending on a new build parameter I've created.
During the execution of the activity, I found 2 problems that can arise:
Refresh of the build log during the run – if you try to update the build log you might find that the messages are not always being logged \ displayed in the build log exactly when they are logged in code. That's because the WF thread is busy executing your long-running activity or even suspended in Process.WaitForExit method or so.
Unresponsiveness of the build controller \ agent – have you found yourself stopping a build from Visual Studio but then the IDE stacks? Or after period of 1-2 minutes the build log contains a nasty exception like index-was-outside-the-bounds-of-the-array exception? Well, when trying to stop the build during a long-run activity, the WF thread is busy running this activity. After the timeout, the build controller just forcedly kills the agent process. This can caused the build machine to be in unstable state: I found the build controller restarting itself (and sometimes failed on that too). In my case, the Flash.exe process was still in memory because the agent did not shut it down (FYI Flash.exe process is 'singleton'- cannot run more than one instance of the IDE in the same time, so running new build will fail to start the Flash).
Send a question to the MS forum, 'How can I know if the build stops during my WF\ activity exaction'. The answer I got was simply: You can't
At first, this is not what I need, right?
I mean I don't want the agent to run my Flash activity and continue to the next one before finishing.
But its methods are so interesting I just had to check that:
How Does the Build Run AsyncCodeActivity
Let's begin with Parallel activity. Usually when you want your build to do few activities at a time, you use ParallelActivity. But, believe it or not, this activity uses only one thread.
So it’s not really parallel. Let say you have 2 activities that run in parallel:
- Long run activity (that inherits from CodeActivity)
- For each with MSBuild activity
If the long run activity waits for an operation to finish and blocks the thread (i.e. Process.WaitForExit), you'll notice that MSBuild activity also blocked – that's because they use the same thread.
That's where AsyncCodeActivity come. The Build Agent will run the AsyncCodeActivity in a new thread so the WF thread can continue to the parallel tasks.
If you use AsyncCodeActivity without Parallel activity the WF won't continue to the next activity but waits for that one to complete without blocking the WF thread – so if a stop build occurs, the agent will activates Cancel method and stop the build with no timeout exceptions.
Create Demo Activity
I created a very simple AsyncDemoActivity that does not do much:
You just set a task you want to run asynchronously, in my case the DoSomething()
The method is being called when the DoSomething is finished.
Occurs when the WF is being cancelled. In my case I need to abort the Flash.exe process.
If I stop the build, the agent invokes cancel method as expected and the EndExecute right after. The stop operation in Visual Studio returns almost immediately – no more timeouts!
One Last Problem – The Log
I replaced all my long-run custom activities to inherit from AsyncCodeActivity instead of CodeActivity. It was pretty simple BUT the build failed with ActivityContext-can-only-be-accessed-within-the-scope-of-the-function-it-was-passed-into message L
It appears I cannot log information via CodeActivityContext from DoSomething method because this method runs asynchronously on another thread – not the WF thread, so the context is unavailable, even if I save it as a field in the activity, I cannot use it.
The context object is only available in BeginExecute ,EndExecute and Cancel methods
So how can I log all the information in the build log?
Obviously, logging all information in EndExecute at the end of the activity is unacceptable.
So if the context is unavailable, just use BuildStep instead J
You can read the Ewald's great post here for further details