WCF doesn't propagate properties of Trace.CorrelationManager when using a different thread for the same operation

StackOverflow https://stackoverflow.com/questions/10560675

Question

How do I get WCF to propagate the properties of Trace.CorrelationManager when it uses a different thread for the same operation?

I know that WCF doesn't guarantee thread affinity. So basically one thread can start a request and a different thread may finish it. When I reproed this behavior, I see that the first thread has the two properties Trace.Correlation.ActivityId and Trace.Correlation.LogicalOperationStack properly set. WCF finished the operation with a different thread but the properties were not propagated.

To work around this, I might have to abandon the use of the CorrelationManager and will probably have to store the ActivityId in the OperationContext which I know is propagated to the new thread (Please correct me if I am wrong here). I don't want to do this, since its more work of course and not as elegant as using that single property.

Any other ideas on how I can work around this? Can I tell WCF to propagte this for me somehow?

Thanks, Mohammed

Was it helpful?

Solution

To work around this, I abandoned the use of Trace.CorrelationManager properties. As a substitute to these properties, I now use custom properties that I added to an Extension class. I can now directly modify the ActivityId and LogicalOperationStack on this extension which can be accessed for the life of any operation request which is just as convenient as using the Trace.CorrelationManager properties.

As a plus, I can store any other custom properties I would like to use for the life of the request. A great example of this would be a customer id, or resource id, that can also be used in logging for better supportability.

using System;
using System.Collections;
using System.ServiceModel;

namespace MyNamespace
{
    /// <summary>
    /// Class that represents an extension used to store custom data for the life of a WCF OperationContext.
    /// </summary>
    public class OperationContextExtension : IExtension<OperationContext>
    {
        /// <summary>The activity id of the operation.</summary>
        private Guid activityId;

        /// <summary>The logical operation stack of the operation.</summary>
        private Stack logicalOperationStack;

        /// <summary>
        /// Initializes a new instance of the OperationContextExtension class.
        /// </summary>
        public OperationContextExtension()
        {
            this.logicalOperationStack = new Stack();
        }

        /// <summary>
        /// Gets the current OperationContextExtension extension from the OperationContext.
        /// </summary>
        public static OperationContextExtension Current
        {
            get
            {
                OperationContextExtension context;
                if (OperationContext.Current == null)
                {
                    context = null;
                }
                else
                {
                    context = OperationContext.Current.Extensions.Find<OperationContextExtension>();
                }

                return context;
            }
        }

        /// <summary>
        /// Gets or sets the activity id for the current operation.
        /// </summary>
        public Guid ActivityId
        {
            get { return this.activityId; }
            set { this.activityId = value; }
        }

        /// <summary>
        /// Gets the LogicalOperationStack for the current operation.
        /// </summary>
        public Stack LogicalOperationStack
        {
            get { return this.logicalOperationStack; }
        }

        /// <summary>
        /// Enables an extension object to find out when it has been aggregated. Called when the extension is added 
        /// to the System.ServiceModel.IExtensibleObject Extensions property.
        /// </summary>
        /// <param name="owner">The extensible object that aggregates this extension.</param>
        public void Attach(OperationContext owner)
        {
            // Use this method for request initialization if needed
        }

        /// <summary>
        /// Enables an object to find out when it is no longer aggregated. Called when an extension is removed 
        /// from the System.ServiceModel.IExtensibleObject Extensions property.
        /// </summary>
        /// <param name="owner">The extensible object that aggregates this extension.</param>
        public void Detach(OperationContext owner)
        {
            // Use this method for request cleanup if needed
        }
    }
}

You will now need add this extension to the OperationContext. First, you need to find a suitable hook into a WCF behavior extension. A popular option is to use a MessageInspector applied to an IEndpointBehavior. Here are some great reads on how to achieve this (A quick search will yield many useful examples if mine don't help):

Once you have your hook, you want to add your Extension to the OperationContext as soon as you can with the following line:

// Add an extension object to the current operation context to track custom state across all threads
// for the life of the operation
OperationContext.Current.Extensions.Add(new OperationContextExtension());

Now you can access your ActivityId property and LogicalOperationStack, or any other property you define in this class from virtually anywhere in your code work flow:

LogOperation(OperationContextExtension.Current.ActivityId, logMessage);
OperationContextExtension.Current.LogicalOperationStack.Push("Starting 'Nested Activity' 3...");

Hope this helps!

-Mohammed

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top