9

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)

  • 5
    For what it's worth, the error, "System.InvalidOperationException: Collection was modified; enumeration operation may not execute." very rarely has anything to do with threading. Generally it's because the collection has been modfiied while in the midst of being enumerated, which is as hard to repro as `foreach (var item in list) list.Remove(item);`. – Kirk Woll Oct 23 '12 at 04:02
  • What is your enumeration? I have encountered this same error if my enumeration is using a resource that gets disposed before I was done serializing it. – twreid Oct 23 '12 at 04:03
  • 1
    @Kirk, if that is the case, wouldn't the stack trace suggest that it's being done within the serializer, and therefore be a bug in WCF? I'm relatively certain our code doesn't do that anywhere as I've done it many times myself in the past, so am (finally) aware to watch out for it – user1766568 Oct 23 '12 at 04:41
  • @twreid unfortunately I'm having a heck of a time tracking it down, so I don't know where the enumeration is. But from the stack trace, it looks to me like it's the serializer looping through a List<>. We don't explicitly dispose of anything, but is it possible the garbage collector would dispose of an object mid-serialization? (sorry, don't know much about the gc's) – user1766568 Oct 23 '12 at 04:44

2 Answers2

5

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).
Community
  • 1
  • 1
Dennis
  • 37,026
  • 10
  • 82
  • 150
  • Thanks for the example and detailed answer. Most services are InstanceContextMode.PerCall and ConcurrencyMode.Mulitple, so I'll probably go with a thread-safe collection (which I was unaware of) depending on what WCF tracing shows me -- in my confusion, I'd totally forgotten about tracing, though without your elucidation, still wouldn't have known the best solutions. Thanks! – user1766568 Oct 23 '12 at 14:04
  • @user1766568: I hope, that you won't replace every collection into its thread-safe analogue, because it is a way to hell. – Dennis Oct 23 '12 at 16:53
1

If Dennis's hypothesis is correct then the cleanest way to resolve this is to copy the collection and send the copy over the wire. At that point it doesn't matter if the original is modified during serialization

Richard Blewett
  • 6,089
  • 1
  • 18
  • 23
  • Thanks for the response. Might be the easiest, but the object graphs can be pretty deep, so I'm not sure it would be the fastest in this particular case. I would be returning an object which could have a collection, or have a collection of objects with collections, etc.etc. so think I may have to go with thread-safe collections. – user1766568 Oct 23 '12 at 14:09
  • It isn't a hypothesis. :) You can compile code sample to make sure of it. – Dennis Oct 23 '12 at 16:55
  • I'm not saying that your code doesn't demonstrate the issue -what I'm saying is just because your code has the same symptom doesn't mean it is the same cause as the OP. However if your code is the same cause then copying is a clean way to solve the issue – Richard Blewett Oct 26 '12 at 23:26