and how Orchestration really works
My voyage of BizTalk exploration goes on. XLANG/s is not, in the strictest sense, a new .NET language (i.e., it doesn't compile directly to IL). When you compile a BizTalk Visual Studio project, a whole bunch of temporary C# files are generated for orchestrations, pipelines, messages, schemas, maps, etc. XLANG/s simply scripts the output of the some of this generated source code.
You have to be quick to spot these C# files. They are generated in the current user's 'Local Settings/temp' folder, but are deleted automatically as soon as compilation is complete. Being nosy, I wrote a simple little utility using the FileSystemWatcher class to dump copies of these files before they get deleted. All good fun, and it's much easier reading the C# source than the IL!
I first saw a preview of XLANG/s at the 2001 PDC in LA. As I recall, you could, at that time, embed in-line sections of real C# within your XLANG/s. As far as I know, this is not supported in BizTalk 2004 (who knows, perhaps this is an 'undocumented' feature!). I should have realised, on the strength of that presentation, that XLANG/s was merely scripting a C# code generator. However, the presenter told us that 'XLANG/s' was possibly only a temporary name, and that the language might be launched as a fully-fledged .NET language called 'X#'. As some readers may know, the term 'X#' was later associated with a language developed by Microsoft Research that extended C# syntax with direct handling for relational and XML data. This X# is also known as 'Xen', and I don't think it is directly associated with XLANG/s at all. It's current status is very unclear. Some say it is dead, others that it is the shape of programming to come.
OK. So here is the low-down on how an orchestration works
Each orchestration is a single .NET class derived from the BTXService class. It contains inner context classes, including an overall context for the service as a whole, and additional contexts for each scope / transaction level within the orchestration (you set transaction levels on scope shapes, so the two are synonymous). There are four base context classes - ServiceContext, ExceptionHandlingContext, LongRunningTransaction and AtomicTransaction. The 'ServiceContext’ class provides the root context for the entire orchestration and is not reflected by any feature within the Orchestration Designer. The other context objects provide internal fields for scoped variables, messages and correlation sets.
ExceptionHandlingContext classes have an additional internal field (__exv__) used to store exception objects. All context objects have an OnError() method. This method is generated to do different things depending on the context type. For ExceptionHandlingContext classes, it inspects the type of error and then invokes the exception handling code created by the orchestration designer for that method. For atomic transactions, this method invokes compensation code. In all cases, the OnError() method invokes a Finally() method. I assume this method is also called whenever a segment (see below) completes its work without throwing an exception. As far as I can tell, there is currently no support for creating your own 'finally' code.
The generated Finally() methods clean up your code. There is an interesting aspect to this. If you declare an orchestration variable (including messages and correlation sets) in an outer scope, and that variable is used within an inner scope, the inner scope class maintains its own local copy of that orchestration variable. Locks are used to synchronise access to the local copy. The Finally() method copies these variables back to the outer context when processing is complete for the inner context. This is the explanation for the strange variable scoping rules in XLANG/s which do not allow you to 'hide' outer variables by using inner variables of the same name. Locally scoped orchestration variables are nulled by the Finally() method.
Each context is linked to one or more segments. A segment is a BizTalk object that holds a delegate reference to a method within the orchestration class. At runtime the orchestration object creates an array of segments and hooks them up to the respective methods. There is one method in the orchestration class for each segment. The root context is associated with segment 0, and the next scope is associated with segment 1. Each additional inner scope is associated with one or more segments, and every context class provides 'InitialSegment' and 'FinalSegment' properties to define a sequence of segments.
Segments relate to various shapes within the Orchestration Designer such as ConstructMessage and Expression shapes (the Orchestration Designer uses ‘bold’ dotted lines to represent segments). They form the breakpoint 'boundaries' when using the Orchestration Debugger. If you can't set a breakpoint at a specific point in you orchestration, it is because that point is embedded within a segment, and does not mark the start of a segment. Another way to think of segments is in terms of the MessageBox. After the processing of each segment, the message data in the MessageBox is left in a consistent state. Read and write context locks are acquired on messages, variables etc., at the level of each segment. Again, you can see this in the Orchestration Debugger.
Each segment is further divided into specific, instrumented, code sections (I shall call them 'steps'). The generated code actually uses a C# switch statement to define these steps. Each step is a 'case' within the switch, and the sequence of step processing is handled by using goto statements. The instrumentation in each step interacts with the BizTalk engine. As the code enters the step, it attempts to increment a 'progress' counter via a call to a Service method (in fact, successful calls may not actually result in the incrementation of the progress counter. A single progress number can cover a number of steps.). This method indicates through a boolean return value whether or not processing should continue, allowing the engine to indicate to the orchestration that processing should be suspended (paused). Some steps run additional tests, and may suspend processing if, for example, locks cannot be acquired, or if the engine indicates that a breakpoint has been reached. In each case, the step will return processing to the engine, via the segment object, with a flag indicating the reason for the suspension.
Expression and message assignment code is represented by generated code within the appropriate steps. In many cases, the entire contents of an expression may be included within a single step, but if you use, for example, 'if' statements within your expressions, these are broken up into multiple steps.
For the most part, steps are executed in a sequential manner. However, for conditional logic, the code within a step may use the goto statement to jump to non-adjacent steps. In addition, a step may use the StartContext() method of its current context to effectively call out to the initial segment of another context. In this way, the segments within an orchestration are chained in a fashion that reflects the process flow captured in the Orchestration Designer.
This is the basic model for orchestrations. Quite straightforward, really, though there is clearly a lot of complexity in the BizTalk engine itself.