2

I've written an exchange transport agent that uploads the body of an email message to a WCF service. The service sits on the same box as Exchange, listening on localhost:1530 to receive incoming TCP connections. The transport agent is implemented with the .NET 3.5 Framework, and the service is self-hosted in a Windows Service implemented on the .NET 4.0 framework.

My question: why is the stream terminated before reading is complete, and how do I fix it?

The service contract is defined like this:

[ServiceContract]
public interface IReceiveService
{
    [OperationContract]
    Guid ImportMessage(DateTime dateTimeReceived, string from, IList<string> to, string subject, int attachmentCount, bool bodyEmpty, Guid clientID);

    [OperationContract]
    void ImportMessageBody(IMessageBody body);

    [OperationContract]
    void ImportMessageAttachment(IMessageAttachment attachment);
}

Update: I've re-ordered this to make it easier for someone to quickly read the problem without necessarily having to read the rest of my description, which is long. This first bit showing how I kick off a Task to process requests seems to be the problem. Indeed, if I comment out the Task.Factory.StartNew() part, the Stream.CopyTo works.

In my service implementation, I try to use Stream.CopyTo to copy the incoming stream to a temp file like this:

public void ImportMessageBody(IMessageBody body)
{
    Task.Factory.StartNew(() =>
        {
            string fileName = GetFileNameFromMagicalPlace();
            using (FileStream fs = new FileStream(fileName, FileMode.Append))
            {
                body.Data.CopyTo(fs); // <---- throws System.IOException right here
            }
        });
}

The exception error is: "error: An exception has been thrown when reading the stream." The stack trace:

   at System.ServiceModel.Dispatcher.StreamFormatter.MessageBodyStream.Read(Byte[] buffer, Int32 offset, Int32 count) 
   at System.IO.Stream.CopyTo...

There is an inner exception:

System.Xml.XmlException: Unexpected end of file. Following elements are not closed: Address, IMessageBody, Body, Envelope. 
    at System.Xml.XmlExceptionHelper.ThrowXmlException(XmlDictionaryReader reader, String res, String arg1, String arg2, String arg3) 
    at System.Xml.XmlBufferReader.GetByteHard() 
    at System.Xml.XmlBufferReader.ReadMultiByteUInt31() 
    at System.Xml.XmlBinaryReader.ReadName(StringHandle handle) 
    at System.Xml.XmlBinaryReader.ReadNode() 
    at System.ServiceModel.Dispatcher.StreamFormatter.MessageBodyStream.Read(Byte[] buffer, Int32 offset, Int32 count) 

Other details follow.


IMessageBody is defined like this:

[MessageContract]
public class IMessageBody
{
    [MessageHeader]
    public Guid MessageID { get; set; }

    [MessageHeader]
    public string Format { get; set; }

    [MessageBodyMember]
    public System.IO.Stream Data { get; set; }
}

An abbreviated version of the transport agent showing the relevant bits (I hope):

public class Agent : RoutingAgent
{
    public delegate void PostingDelegate(MailItem item);
    IReceiveService service;    

    public Agent()
    {
        string tcpServiceUri = "net.tcp://localhost:1530";

        NetTcpBinding endpointBinding = new NetTcpBinding();
        endpointBinding.TransferMode = TransferMode.Streamed;

        ServiceEndpoint serviceEndpoint = new ServiceEndpoint(
            ContractDescription.GetContract(typeof(IReceiveService)),
            endpointBinding,
            new EndpointAddress(tcpServiceUri));
        ChannelFactory<IReceiveService> factory = new ChannelFactory<IReceiveService>(serviceEndpoint);
        service = factory.CreateChannel();

        this.OnSubmittedMessage += new SubmittedMessageEventHandler(Agent_OnSubmittedMessage);
    }

    void Agent_OnSubmittedMessage(SubmittedMessageEventSource source, QueuedMessageEventArgs e)
    {
        if (TheseAreTheDroidsImLookingFor(e))
        {
            PostingDelegate del = PostData;
            del.BeginInvoke(e.MailItem, CompletePost, GetAgentAsyncContext());
        }
    }

    void PostData(MailItem item)
    {
        // Body class is basically direct implementation of IMessageBody 
        // with a constructor to set up the public properties from MailItem.
        var body = new Body(item); 
        service.ImportMessageBody(body);
    }

    void CompletePost(IAsyncResult ar)
    {
        var context = ar.AsyncState as AgentAsyncContext;
        context.Complete();
    }
}

Lastly, the service implementation is hosted like this:

string queueUri = String.Format("net.tcp://localhost:{0}/{1}", port, serviceName);
            try
            {
                host = new ServiceHost(typeof(ReceiveService), new Uri(queueUri));
                host.AddDefaultEndpoints();
                var endpoint = host.Description.Endpoints.First();
                ((NetTcpBinding)endpoint.Binding).TransferMode = TransferMode.Streamed;                 

                trace.Log(Level.Debug,String.Format("Created service host: {0}", host));                

                host.Open();                
                trace.Log(Level.Debug,"Opened service host.");
            }
            catch (Exception e)
            {
                string message = String.Format("Threw exception while attempting to create ServiceHost for {0}:{1}\n{2}", queueUri, e.Message, e.StackTrace);
                trace.Log(Level.Debug,message);
                trace.Log(Level.Error, message);
            }
Ben Collins
  • 20,538
  • 18
  • 127
  • 187

2 Answers2

3

Well it is too much code. You should try to minimize code to smallest possible reproducible example. I guess the problem can be here:

public void ImportMessageBody(IMessageBody body)
{
    Task.Factory.StartNew(() =>
        {
            string fileName = GetFileNameFromMagicalPlace();
            using (FileStream fs = new FileStream(fileName, FileMode.Append))
            {
                body.Data.CopyTo(fs); // <---- throws System.IOException right here
            }
        });
}

There is OperationBehavior applied by default this behavior contains AutoDisposeParameter set by default to true which disposes all disposable parameters when the operation ends. So I expect that operation ends before your Task is able to process the whole stream.

Community
  • 1
  • 1
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • Ladislav, thanks for your answer (and the link to your other answer!). I posted this much code because not knowing where the problem was, this *was* the smallest possible reproducible example as far as I was concerned. – Ben Collins Jul 13 '11 at 18:27
  • 1
    This turns out not to be the problem, after all. I have now decoraded my `ImportMessageBody` operation with `[OperationBehavior(AutoDisposeparameters=false)]` and implemented an `OperationCompleted` event handler, but `Stream.CopyTo` still fails in the same way as before. – Ben Collins Jul 13 '11 at 19:21
  • Looks like you were on the right track, though, because when I do the `Stream.CopyTo` synchronously in the `ImportMessageBody` implementation (i.e., no `Task`), it works. – Ben Collins Jul 13 '11 at 19:28
  • 1
    ....which makes all kinds of sense, because there's no need to manually effect concurrency like this. WCF does concurrency for you. I'll mark this as the answer because it pointed me in the right direction, even if it wasn't ultimately the answer. – Ben Collins Jul 13 '11 at 20:56
1

The issue was essentially that I was doing unnecessary concurrency by invoking Task.Factory.StartNew instead of making sure my service was properly configured for concurrency through the ServiceBehaviorAttribute. Thanks to Ladislav for pointng me in the right direction.

Ben Collins
  • 20,538
  • 18
  • 127
  • 187