Originally posted on: http://geekswithblogs.net/cyoung/archive/2014/06/02/using-the-bam-interceptor-with-continuation.aspxI’ve recently been resurrecting some code written several
years ago that makes extensive use of the BAM Interceptor provided as part of
BizTalk Server’s BAM event observation library. In doing this, I noticed an
issue with continuations. Essentially, whenever I tried to configure one or
more continuations for an activity, the BAM Interceptor failed to complete the
activity correctly. Careful inspection of my code confirmed that I was initializing
and invoking the BAM interceptor correctly, so I was mystified. However, I
eventually found the problem. It is a logical error in the BAM Interceptor
code itself.
The BAM Interceptor provides a useful mechanism for
implementing dynamic tracking. It supports configurable ‘track points’. These
are grouped into named ‘locations’. BAM uses the term ‘step’ as a synonym for
‘location’. Each track point defines a BAM action such as starting an
activity, extracting a data item, enabling a continuation, etc. Each step
defines a collection of track points.
Understanding Steps
The BAM Interceptor provides an abstract model for handling
configuration of steps. It doesn’t, however, define any specific configuration
mechanism (e.g., config files, SSO, etc.) It is up to the developer to decide
how to store, manage and retrieve configuration data. At run time, this
configuration is used to register track points which then drive the BAM
Interceptor.
The full semantics of a step are not immediately clear from
Microsoft’s documentation. They represent a point in a business activity where
BAM tracking occurs. They are named locations in the code. What is less
obvious is that they always represent either the full tracking work for a given
activity or a discrete fragment of that work which commences with the start of
a new activity or the continuation of an existing activity. The BAM
Interceptor enforces this by throwing an error if no ‘start new’ or ‘continue’
track point is registered for a named location.
This constraint implies that each step must marked with an
‘end activity’ track point. One of the peculiarities of BAM semantics is that
when an activity is continued under a correlated ID, you must first mark the
current activity as ‘ended’ in order to ensure the right housekeeping is done
in the database. If you re-start an ended activity under the same ID, you will
leave the BAM import tables in an inconsistent state. A step, therefore,
always represents an entire unit of work for a given activity or continuation
ID. For activities with continuation, each unit of work is termed a ‘fragment’.
Instance and Fragment State
Internally, the BAM Interceptor maintains state data at two
levels. First, it represents the overall state of the activity using a ‘trace
instance’ token. This token contains the name and ID of the activity together
with a couple of state flags. The second level of state represents a ‘trace
fragment’. As we have seen, a fragment of an activity corresponds directly to
the notion of a ‘step’. It is the unit of work done at a named location, and
it must be bounded by start and end, or continue and end, actions.
When handling continuations, the BAM Interceptor
differentiates between ‘root’ fragments and other fragments. Very simply, a
root fragment represents the start of an activity. Other fragments represent
continuations. This is where the logic breaks down. The BAM Interceptor loses
state integrity for root fragments when continuations are defined.
Initialization
Microsoft’s BAM Interceptor code supports the initialization
of BAM Interceptors from track point configuration data. The process starts by
populating an Activity Interceptor Configuration object with an array of track
points. These can belong to different steps (aka ‘locations’) and can be
registered in any order. Once it is populated with track points, the Activity Interceptor
Configuration is used to initialise the BAM Interceptor. The BAM Interceptor
sets up a hash table of array lists. Each step is represented by an array list,
and each array list contains an ordered set of track points.
The BAM Interceptor represents track points as ‘executable’
components. When the OnStep method of the BAM Interceptor is called for a
given step, the corresponding list of track points is retrieved and each track
point is executed in turn. Each track point retrieves any required data using
a call back mechanism and then serializes a BAM trace fragment object
representing a specific action (e.g., start, update, enable continuation, stop,
etc.). The serialised trace fragment is then handed off to a BAM event stream
(buffered or direct) which takes the appropriate action.
The Root of the Problem
The logic breaks down in the Activity Interceptor Configuration.
Each Activity Interceptor Configuration is initialised with an instance of a
‘trace instance’ token. This provides the basic metadata for the activity as a
whole. It contains the activity name and ID together with state flags
indicating if the activity ID is a root (i.e., not a continuation fragment) and
if it is completed. This single token is then shared by all trace actions for
all steps registered with the Activity Interceptor Configuration.
Each trace instance token is automatically initialised to
represent a root fragment. However, if you subsequently register a ‘continuation’
step with the Activity Interceptor Configuration, the ‘root’ flag is set to
false at the point the ‘continue’ track point is registered for that step. If
you use a ‘reflector’ tool to inspect the code for the ActivityInterceptorConfiguration
class, you can see the flag being set in one of the overloads of the
RegisterContinue method.
This makes no sense. The trace instance token is shared
across all the track points registered with the Activity Interceptor Configuration.
The Activity Interceptor Configuration is designed to hold track points for
multiple steps. The ‘root’ flag is clearly meant to be initialised to ‘true’
for the preliminary root fragment and then subsequently set to false at the
point that a continuation step is processed. Instead, if the Activity Interceptor
Configuration contains a continuation step, it is changed to ‘false’ before the
root fragment is processed. This is clearly an error in logic.
The problem causes havoc when the BAM Interceptor is used
with continuation. Effectively the root step is no longer processed correctly,
and the ultimate effect is that the continued activity never completes! This
has nothing to do with the root and the continuation being in the same
process. It is due to a fundamental mistake of setting the ‘root’ flag to
false for a continuation before the root fragment is processed.
The Workaround
Fortunately, it is easy to work around the bug. The trick
is to ensure that you create a new Activity Interceptor Configuration object
for each individual step. This may mean filtering your configuration data to
extract the track points for a single step or grouping the configured track
points into individual steps and the creating a separate Activity Interceptor Configuration
for each group. In my case, the first approach was required. Here is what the
amended code looks like:
// Because of a logic error in Microsoft's code, a
separate ActivityInterceptorConfiguration must be used
// for each location. The following code extracts only those track
points for a given step name (location).
var trackPointGroup = from ResolutionService.TrackPoint tp in
bamActivity.TrackPoints
where (string)tp.Location ==
bamStepName
select tp;
var bamActivityInterceptorConfig =
new Microsoft.BizTalk.Bam.EventObservation.ActivityInterceptorConfiguration(activityName);
foreach (var trackPoint in trackPointGroup)
{
switch (trackPoint.Type)
{
case TrackPointType.Start:
bamActivityInterceptorConfig.RegisterStartNew(trackPoint.Location,
trackPoint.ExtractionInfo);
break;
etc…
I’m using LINQ to filter a list of track points for those
entries that correspond to a given step and then registering only those track
points on a new instance of the ActivityInterceptorConfiguration class. As
soon as I re-wrote the code to do this, activities with continuations started
to complete correctly.