Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is WCF's DataContractSerilaizer thread safe?

I've been converting a fairly large system from Remoting to WCF and everything seems to be running well, except we frequently get the following exception: "System.InvalidOperationException: Collection was modified; enumeration operation may not execute." I haven't had any luck tracking it down because it only happens when there are hundreds of calls getting passed through, and I can only assume it's because an object is being modified as it's being serialized.

The classes all use: [DataContract(IsReference=true)].

There were no similar exceptions when using remoting, so I'm wondering if anyone has had a similar problem in WCF or can let me know that it probably is the serializer -- in which case I assume I have to write my own serializers to perform locks where necessary (which is a big undertaking I'd prefer to avoid).

The following is the stack trace:

WCF Error: at System.Collections.Generic.List1.Enumerator.MoveNextRare() at WriteArrayOfLineToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , CollectionDataContract ) at System.Runtime.Serialization.CollectionDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle) at WriteLineGroupToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract ) at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle) at WriteLineToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract ) at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiTypeAtTopLevel(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle originalDeclaredTypeHandle, Type graphType) at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(XmlDictionaryWriter writer, Object graph) at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeParameterPart(XmlDictionaryWriter writer, PartInfo part, Object graph) at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeParameter(XmlDictionaryWriter writer, PartInfo part, Object graph) at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.SerializeBody(XmlDictionaryWriter writer, MessageVersion version, String action, MessageDescription messageDescription, Object returnValue, Object[] parameters, Boolean isRequest) at System.ServiceModel.Dispatcher.OperationFormatter.SerializeBodyContents(XmlDictionaryWriter writer, MessageVersion version, Object[] parameters, Object returnValue, Boolean isRequest) at System.ServiceModel.Dispatcher.OperationFormatter.OperationFormatterMessage.OperationFormatterBodyWriter.OnWriteBodyContents(XmlDictionaryWriter writer) at System.ServiceModel.Channels.BodyWriter.WriteBodyContents(XmlDictionaryWriter writer) at System.ServiceModel.Channels.BodyWriterMessage.OnWriteBodyContents(XmlDictionaryWriter writer) at System.ServiceModel.Channels.Message.OnWriteMessage(XmlDictionaryWriter writer) at System.ServiceModel.Channels.Message.WriteMessage(XmlDictionaryWriter writer) at System.ServiceModel.Channels.BufferedMessageWriter.WriteMessage(Message message, BufferManager bufferManager, Int32 initialOffset, Int32 maxSizeQuota) at System.ServiceModel.Channels.BinaryMessageEncoderFactory.BinaryMessageEncoder.WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset) at System.ServiceModel.Channels.FramingDuplexSessionChannel.EncodeMessage(Message message) at System.ServiceModel.Channels.FramingDuplexSessionChannel.OnSend(Message message, TimeSpan timeout) at System.ServiceModel.Channels.OutputChannel.Send(Message message, TimeSpan timeout) at System.ServiceModel.Dispatcher.DuplexChannelBinder.DuplexRequestContext.OnReply(Message message, TimeSpan timeout) at System.ServiceModel.Channels.RequestContextBase.Reply(Message message, TimeSpan timeout) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.Reply(MessageRpc& rpc)

like image 533
user1766568 Avatar asked Oct 23 '12 03:10

user1766568


1 Answers

Indeed, this error can be easily reproduced with DataContractSerializer. It doesn't refer to thread safety of DataContractSerializer, it is about thread safety of some collection, used in your data contracts:

[DataContract]
public class C
{
    [DataMember]
    public List<int> Values { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var c = new C
        {
            Values = new List<int>()
        };

        var serializer = new DataContractSerializer(typeof(C));

        Task
            .Factory
            .StartNew(() => 
            {
                while (true)
                {
                    Console.WriteLine("Trying to add new item.");
                    c.Values.Add(DateTime.Now.Millisecond);
                }
            }, 
            TaskCreationOptions.LongRunning);

        Task
            .Factory
            .StartNew(() =>
            {
                while (true)
                {
                    using (var stream = new MemoryStream())
                    {
                        Console.WriteLine("Trying to serialize.");
                        serializer.WriteObject(stream, c);
                    }
                }
            },
            TaskCreationOptions.LongRunning);

        Console.ReadLine();
    }

After short execution time, you'll get the IOE with similar stack trace:

   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)    at System.Collections.Generic.List`1.Enumerator.MoveNextRare()    at System.Collections.Generic.List`1.Enumerator.MoveNext()    at WriteArrayOfintToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , CollectionDataContract )    at System.Runtime.Serialization.CollectionDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)

Looks like you're continuing to modify some shared data, which being serialized the same time. You can turn on WCF tracing (see this question) to find out which operation causes this error, and look carefully, which data being used by this operation.

Then, depending on values of InstanceContextMode and ConcurrencyMode properties of current service behavior, you can choose which way to go:

  • either to use some locking;
  • or to use any thread-safe collection;
  • or to change service behavior;
  • or to change service itself (e.g., make it stateless).
like image 156
Dennis Avatar answered Oct 08 '22 06:10

Dennis