A targetTimevariable is created, of type System.DateTime, for the WaitDelay Expression shape. This will be used as a timer:
targetTime = System.DateTime.Now.AddSeconds(120);
In our example, we will run only simple processing. In the AssignMessage shape:
msgInvoice = msgInvoiceIn;
msgInvoice(*) = msgInvoiceIn(*);
RefInvoice = xpath(msgInvoice, "string(/*[local-name()='Invoice']/*[local-name()='Reference'])");
Year = xpath(msgInvoice, "string(/*[local-name()='Invoice']/*[local-name()='Year'])");
RefInvoice = "Invoice_" + Year + "_" + RefInvoice;
xpath(msgInvoice, "/*[local-name()='Invoice']/*[local-name()='Reference']") = RefInvoice;
Then send the message processed using the SendPort.
We create a Boolean variable named vExistNextInvoice, which we set to True, as the condition for exiting the loop. In the ManageNextInvoiceloop and the left branch of the Listen shape, we will receive the next invoices, process then in the same way, and send them to the SendPort. At the same time, in the right branch, we run a timer in the shape:
When the timer ends, vExistNextInvoiceis set to False in order to exit the loop.
Here we deliberately add a shape Duration (OthersProcess) to create zombie messages. This shape creates a ‘duration’ to simulate the real-life situation of other processing that might follow.
Assume that the first invoice arrives in the Receive, which triggers the orchestration and initializes a correlation. The orchestration’s status will be Dehydrated, pending the receipt of new invoices, or the end of the duration that was set. This means that during the duration set, the following situations can arise:
- no message is sent subsequently;
- new messages with the same correlation sets are received, and the left branch is executed;
- new messages with different correlations sets are received, and new orchestrations are triggered.
At the end of the period, the right branch is executed, thevExistNextInvoicevariable is set to False, the receive loop is exited, and so is the processing. Now, any new message arriving will be a zombie message. It will not be processed and the orchestration will be suspended. The famous “The instance completed without consuming all of its messages”error is returned.
When the orchestration is run at the end of the duration and has just exited the loop, if messages arrive with the same correlation sets, this creates zombie messages.
We suggest two possible solutions to this issue:
- The simple suggestion is to increase the loop duration. This provides a larger window to receive messages.
- Another solution:
The idea is to create another loop that encompasses the ManageNextInvoiceloop, called ZombiesHandle. The condition to exit this loop is the same, vExistNextInvoice = False. When we exit the ManageNextInvoiceloop,
vExistNextInvoice = False
Continue with other processing (OtherProcess). To this end, we add a new Listen shape, with a very short duration, such as one second:
In our left branch, we copy the start of the orchestration, i.e. we receive an invoice, set the duration and run the invoice processing, and send them to terminate. At the end of the left branch of this Listen, we reactivate the ZombiesHandle loop. In the StartNewListen shape, we put:
vExistNextInvoice = True;
In this way, the ManageNextInvoice loop is reactivated and the next sequence of processing can start.
A very short duration is used to capture any new incoming messages. If any arrive, the loop is reactivated and the processes run. Otherwise, the orchestration instance (for the same correlation set) will end very quickly. Even if new messages have arrived in the meantime, a new instance of the orchestration will be triggered. The zombie message phenomenon is thus avoided.
After releasing the project into BizTalk and binding the ports, a few invoice messages can be sent as tests, for example:
In the send folder, we obtained processed invoice messages as follows:
We ran many tests without encountering any zombie messages.
This article therefore explained how zombie messages arise and offered a solution to minimize them.