Export (0) Print
Expand All

FAQ for BizTalk Orchestrations

Authors: Xuehong Gan and Paul Ringseth

Microsoft Corporation

April 2006

Applies to: Microsoft® BizTalk® Server 2004 and Microsoft® BizTalk® Server 2006

Summary: This whitepaper answers some frequently asked questions about orchestrations in Microsoft BizTalk Server 2004 and BizTalk Server 2006.

Prerequisite Knowledge: BizTalk Server 2004 or BizTalk Server 2006

BizTalk Server 2004 has no built-in mechanism for controlling the number of instances of a given orchestration. The BizTalk Server engine does its own throttling to maximize system throughput.

If you need to control the number of active instances because of external resource constraints (for example, availability of database connections), then the orchestration must do its own access control. Typically you do this by implementing a singleton or a fixed number of resource-controller orchestrations that dispense access to the resource.

Perhaps the simplest example of instance control is as follows: You want to control the number of instances of orchestration A that are doing work. Orchestration B is the singleton resource dispenser. On activation, A sends a request to B. B reads the request, determines if this new A can execute now, and returns a reply: yes or no. A reads the reply. If the reply is no, A delays for a period of time and then requests again. If the reply is yes, A executes. At completion, A sends another message to B telling B that it is finished.

As stated above, this is a simple example. In practice, you might need something more complex. More abstractly, you need to control the rate at which messages are activated to better match the rate that activated instances can complete their processing.

Additionally, if your resource-controller orchestrations are long running on BizTalk Server 2004, you need to have the following hotfix installed: http://go.microsoft.com/fwlink/?LinkId=65331.

There is no easy way to share global variables across all instances of an orchestration. If the global variables are static values, you might consider storing them in a persistent storage, such as a configuration file or SQL database, and allowing each instance to get the values from storage. You can also share a static state in user code as long as locks are placed correctly on access and modification of the shared state.

"Completed with Discarded Messages" is one of the states an orchestration instance may end with. A message that is discarded in this way is called a "zombie" and typically occurs when an extra message is routed after control flow in the executing orchestration has passed the Receive shape with the matching subscription. This can be due to a time-out on a long-running transaction or a time-out of a Delay branch on a Listen shape. It can also occur in a loop that contains a Receive shape that exits before consuming all routed messages.

You can use a Windows Management Instrumentation (WMI) method to listen to the suspend events, find the message associated with the orchestration instance, and resubmit the message.

A message is in the "Delivered Not Consumed" state when it is in the BizTalk Server internal work queue. It might be in this state because the engine is busy with other messages, or because the message destination is processing more slowly than expected.

You first need to determine where these messages are going: to the orchestration or to the send port. In HAT, you can figure this out by looking at what service the "delivered not consumed" references are associated with. Then you can check whether the destination orchestration or the send port is working correctly.

The XLANGs runtime may persist to the database (dehydrate) your orchestration, including all of its data, at any point (except in the atomic scope). When the orchestration dehydrates and rehydrates, user-defined variables are binary serialized and deserialized. In most cases, you need a serializable object. A non-serializable object can only be declared and used in an atomic scope. The XLANGs compiler gives you compilation errors if you try to use a non-serializable data type outside of an atomic scope.

Message definitions, whether with .NET types or with actual message types, require that the .NET types be XML serializable (for example, System.IntPtr cannot define a message), and that all the message parts (in the message type) be defined through either XML-serializable .NET types or schema types.

Interfaces cannot be marked serializable, nor can you assume that they are serializable, because you never know how they are implemented. Therefore, XLANGs treats interfaces as non-serializable. Non-serializable objects must run inside atomic scopes. This is why interface-based programming must be done inside an atomic scope. In fact, in .NET, you cannot declare an interface to be serializable.

XLANGs generates C# code. All user-declared XLANGs variables are generated as C# variables. There is no special behavior except in the case of atomic transactions. When a serviced component (that is, an instance of a class that implements System.EnterpriseServices.ServicedComponent) is declared in an atomic scope, then and only then does XLANGs generate and use a real DTC COM+ transaction.

If a variable is referenced as an L-value (that is, it is written to) in the atomic scope, but is declared in an outer scope, the variable is cloned to support rollback. However, an object (such as an XmlDocument) can be modified inside a .NET function call when passed as an in-parameter, and thus XLANGs will miss that the object is being written to and it will not roll back correctly. The workaround in this case is to pass such objects as ref parameters.

The bottom line is that components should behave as they do in other C# programs.

An atomic transaction retries when a RetryTransactionException is deliberately thrown by the user or when a PersistenceException is raised at the time that the atomic transaction tries to commit. The latter could happen if, for example, your atomic transaction was part of a distributed DTC transaction and some other participant in that transaction aborted the transaction. Likewise if there were database connectivity problems at the time when the transaction was trying to commit, a PersistenceException would be raised.

There can be several root causes for PersistenceException, depending on the situation, but what you generally observe is that all the XLANGs actions in your atomic scope seem to go through correctly, but then instead of committing, the scope fails. If that happens for an atomic scope that has Retry=True, then the atomic scope will retry up to 21 times. The delay between each retry is two seconds by default (but you can modify that value). After 21 retries, if the transaction is still unable to commit, the whole orchestration instance gets suspended. Then you can manually resume it and it will start over, with a fresh counter, from the beginning of the offending atomic scope.

Note that only two exception types can cause an atomic scope to retry: RetryTransactionException and PersistenceException. Any other exception raised in your atomic scope cannot cause it to retry, even if you have the Retry property set to True. If you're having trouble trying to figure out why an atomic scope isn't retrying, double-check the exception type.

You can override the two-second default delay between consecutive retries by using a public property on RetryTransactionException (if that's the exception you're using to cause the retries).

Parallel-activating Receive shapes do work under the following conditions:

  1. If one of the tasks of a parallel activation has an activatable Receive as the first shape, all of the tasks of that parallel activation must have an activatable Receive as the first shape, and all of those Receive shapes must initialize at least one correlation.
  2. If a particular correlation is initialized on more than one task of a parallel activation by a Receive shape, each of the concerned activating Receive shapes must initialize exactly the same correlation.

This is the parallel convoy case. It is the only parallel activation that XLANGs supports.

This is a race condition. Apparently you expect that the new orchestration will start in time to create the necessary subscriptions to receive the messages that will be sent to it. But you have no idea how long it will take to create the orchestration, because starting a new service is not a synchronous operation.

It would be better to have the newly created orchestration send a message back to its parent orchestration, reporting that it has started. Then have the new orchestration listen for its messages. In this scenario, the parent orchestration knows that there is a receiver for the messages before sending them. This also provides the necessary commit point to get the subscription to the MessageBox database.

In an Expression shape, you can use yourMessage(propertyName), for example, yourMessage(BTS.MessageID). However, if you want to access message properties from the XLANGMessage interface (for example, from user code), you would use the XLANGMessage methods SetPropertyValue and GetPropertyValue. You cannot use a context property for routing unless the property is in a correlation that is either initialized or followed in a Send shape.

Dynamic ports are not designed to inherit all attributes and characteristics of the port whose address is assigned to the dynamic port. A dynamic port just gets an address, which is analogous to casting a void* pointer in C/C++ to a more structured pointer type. The pointer that initialized that address has had all its information stripped away. The XLANGs runtime only listens for delivery notification if the port is statically set up that way.

You can use construct/transform code inside an Expression shape, to make it possible to assign the map dynamically. The code inside the Expression shape is similar to the following:

mapType = System.Type.GetType(mapName);construct Out_msg{  transform(Out_msg) = mapType(In_msg, In2_msg);}

In BizTalk Server, the messages are immutable. You can use a Construct shape to create a new message. Copy the original message to the new message and update the fields on the new message.

You can place the SOAP Send inside a scope with an exception handler and let the exception handler catch the resulting SoapException. Then you can retry the SOAP Send by nesting the scope inside a while loop.

Three parameters control dehydration in BTSNTSvc.exe.config: MaxThreshold, MinThreshold, and ConstantThreshold. These work as follows:

  • MaxThreshold is the maximum time that a dehydratable orchestration is retained in memory before being dehydrated.
  • MinThreshold is the minimum time that a dehydratable orchestration is retained in memory before it is considered for dehydration.
    Bb418739.note(en-US,BTS.10).gifNote
    MaxThreshold and MinThreshold are upper and lower bounds for time-to-dehydration. The true time-to-dehydration fluctuates between the MinThreshold and MaxThreshold values. It takes into account both the history of how long it took that subscription to dehydrate in the past and stress/throttling

  • ConstantThreshold is used to attempt to ignore the throttling implication in deciding when to dehydrate. Changing this parameter is not recommended. -1 is the default value, and it tells the engine not to use a constant threshold.

History is used as follows in determining time-to-dehydration: Recycling the BizTalk service process resets the history. The history is kept parameterized by subscription; therefore, the history of a given Receive is independent of orchestration instance. The first time a subscription is seen, there is no history, so BizTalk Server chooses to dehydrate immediately. After the first dehydration, BizTalk Server uses the history.

The default for MaxThreshold is 30 minutes and the default for MinThreshold is one second. The default dehydration threshold is 30 minutes. With resource pressure this scales down to as little as MinThreshold. When a Receive blocks, BizTalk Server looks up past blocking times of this Receive for every instance of this orchestration and creates an estimate of how long it will take to receive a message to unblock. If this estimate is greater than the dehydration threshold, then BizTalk Server dehydrates. The first time through the loop with a single instance, there is no history so BizTalk Server dehydrates immediately.

BizTalk Server 2004 and BizTalk Server 2006 differ as follows:

  • In BizTalk Server 2004 only: If the history of a subscription across other instances has satisfied the subscription in less time than MaxThreshold, then BizTalk Server waits without dehydrating at that subscription forever or until it is satisfied.
  • In BizTalk Server 2006 only: BizTalk Server dehydrates if it has been waiting at any subscription with a non-empty history for more than 2*MaxThreshold.

Guaranteed persistence is at end-of-transaction (EOT), Send (unless inside an atomic scope), Start Orchestration, and Suspend. Delay or Receive only persists if dehydration occurs. When end-of-service (EOS) or Terminate occurs, there is not a commit of any pending database operations, but the instance state is not persisted.

Commit points are optimized away in the following patterns:

  • Two or more EOTs collapses to a single commit.
  • One or more EOT followed by EOS collapses to a single commit.
  • A Send followed by EOT or EOS collapses to a single commit. For example, Send immediately followed by EOT immediately followed by EOT immediately followed by EOS is a single commit. However, some potential optimizations are missed, for example, "Send, Send, Send, EOT" is three commits because we only coalesce a single "Send, EOT". Similarly, "Send, EOT, Send, EOT" is two commits.

On graceful shutdown BizTalk Server attempts to dehydrate and thus persist all orchestrations in memory. This is not always successful; sometimes orchestrations are not dehydratable and so the process ends without performing all the dehydrations. If this happens, the orchestration is restored on restart from the last persisted point.

To update an orchestration

  1. Change the AssemblyInfo version number of the orchestrations, and then build and deploy the new assembly.
  2. Bind the new orchestrations.
  3. Manually perform the following steps:
    1. Disable the receive location to temporarily prevent new messages from arriving. Of course, this implies that your service is unavailable to the outside world.
    2. Unenlist the old version using BizTalk Explorer (do not stop it). This prevents new instances from starting while allowing old ones to finish.
    3. Enlist the new version and start it. All new instances will be created using this one.
    4. Enable the receive location.

When constructing an XLANGMessage with a stream, the stream type must either implement IStreamFactory or else be a MemoryStream. The following code sample shows how to construct an XLANGMessage:

public class FileStreamFactory : IStreamFactory
{
    string _fname;
 
    public FileStreamFactory(string fname)
    {
        _fname = fname;
    }
 
    public Stream CreateStream()
    {
        return new FileStream
        (
            _fname,
            FileMode.Open,
            FileAccess.Read,
            FileShare.Read
        );
    }
}
 
public static void AssignStreamFactoryToPart(XLANGMessage msg)
{
    IStreamFactory sf = new FileStreamFactory( @”c:\data.xml” );
    msg[0].LoadFrom( sf );
}

An orchestration always wraps the System.String string as an XML document when sending it out. For example, the following is the output of the string "mystring: from an orchestration:

<?xml version=”1.0” encoding=”utf-8”?><string>mystring</string>

To get a simple string without any XML tags, you need to define a custom-formatted part that serializes to raw text.

The following code shows an example of how to define a type that can be used in place of System.String to define such an XLANGs message part:

    public abstract class BaseFormatter : IFormatter
    {
        public virtual SerializationBinder Binder
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public virtual StreamingContext Context
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public virtual ISurrogateSelector SurrogateSelector
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public abstract void Serialize( Stream stm, object obj );
        public abstract object Deserialize( Stream stm );
    }

    public class RawStringFormatter : BaseFormatter
    {
        public override void Serialize(Stream s, object o)
        {
            RawString rs = (RawString)o;
            byte[] ba = rs.ToByteArray();
            s.Write( ba, 0, ba.Length );
        }

        public override object Deserialize(Stream stm)
        {
            StreamReader sr = new StreamReader( stm, true );
            string s = sr.ReadToEnd();
            return new RawString( s );
        }
    }

    [CustomFormatter(typeof(RawStringFormatter))]
    [Serializable]
    public class RawString
    {
        [XmlIgnore]
        string _val;

        public RawString(string s )
        {
            if (null==s) 
                throw new ArgumentNullException();
            _val = s;
        }

        public RawString()
        {
        }

        public byte[] ToByteArray()
        {
            return Encoding.UTF8.GetBytes( _val );
        }

        public override string ToString()
        {
            return _val;
        }
    }

Whenever a message is suspended after retries to a send port, the messaging engine generates a NACK. This NACK is translated into an exception in the orchestration. You can set the DeliveryNotification property to Transmitted on your send port, and add a catch handler to your scope to handle a DeliveryFailureException. However, in addition to generating the NACK, the messaging engine also suspends the message. There is no way to disable this behavior.

You need your own logic to clean up the suspended send port instance (message). You can create another orchestration that subscribes to NACKs. Both ACKs and NACKs have the following system context properties promoted, which can be used in filter expressions for routing.

After you have written an error-handling orchestration to subscribe to NACKs, you can use BizTalk Server WMI methods to terminate the suspended instance. You will get the instanceID of the suspended send port instance in the NACK.

Some of the context properties you can use in subscribing are:

  • BTS.AckType. Set to ACK or NACK.
  • AckID. Set to the message ID of the message that this ACK/NACK is for.
  • AckOwnerID. Set to the instance ID that this ACK/NACK is for.
  • AckSendPortName. The name of the send port that this message was being sent over.
  • AckOutboundTransportLocation. The outbound URL that this message was being sent to.

A Send on a Delivery Notification (ACK/NACK) can appear at any point in the orchestration. The Receive for the ACK/NACK occurs at the end of the enclosing scope, unless the enclosing scope is atomic. If the scope is atomic, the Receive for the ACK/NACK occurs at the end of the atomic scope's enclosing scope (for example, you do not get the delivery failure exception until all shapes in the enclosing scope have executed).

This error usually means the BizTalk orchestration engine cannot locate the custom component. You must install all assemblies included in a BizTalk application in the global assembly cache of the computer that hosts the application.

When you look at an orchestration in the debugger, you only see its state at the last commit point (in this case the send).

To see the current state of the orchestration, you can suspend the orchestration instance from HAT and open the Orchestration Debugger on this instance. From the Debug menu of the Orchestration Debugger, you can resume the instance in debug mode and attach to the instance to see its state.

Show:
© 2014 Microsoft