Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Premature disposing a MessageBodyStream in WCF

I am using WCF to download a really long file. It is a self hosted service on net-tcp binding. On client i am reading the stream and writing it to a disk in a background thread. On UI, there is a cancel button. I cancel the read-write loop using a CancellationToken.

Problem is,

if the Stream is premature (not EOF) then it takes too long to dispose it.

at Server (c#):

IO.Stream getFile(string filePath) {
    return new IO.FileStream(filePath);
}

at client (vb):

using proxy as new ServiceReference1.TestServer
    using wcfStrm = proxy.getFile("c:\100MB.dat")
        using fileStrm = new FileStream("d:\destination\100MB.dat")

            dim buff(256) as new Byte

            while true
                cancellationToken.ThrowIfCancellationRequested

                Dim len = wcfStrm.Read(buff, 0, buff.Length)

                if len > 0 then
                    fileStrm.write(buff, 0, len)
                else
                    exit while
                end if
             end while

         end using      
    end using ' <-------------- this hangs for 10Mins
end using

When the CancellationToken throws OperationCancelledException, all the three using blocks try to dispose their resources. Now when the second using block tries to dispose the MessageBodyStream, then it hangs for 10 minutes. but if the stream is completely read, then it exits quickly.

i suspected, it had something to do with the ReceiveTimeout of 10 mins. so i changed it to 30 seconds and viola! the dispose now takes 30 seconds.

One more thing. The Dispose operation actually timeouts. It eats my OperationCancelledException and produces a TimeoutException saying The sockket transfer timed out after 00:00:00... bla bla bla

following is the stack trace

System.TimeoutException: The socket transfer timed out after 00:00:00. You have exceeded the timeout set on your binding. The time allotted to this operation may have been a portion of a longer timeout.
   at System.ServiceModel.Channels.SocketConnection.SetReadTimeout(TimeSpan timeout, Boolean synchronous, Boolean closing)
   at System.ServiceModel.Channels.SocketConnection.ReadCore(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout, Boolean closing)
   at System.ServiceModel.Channels.SocketConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
   at System.ServiceModel.Channels.DelegatingConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
   at System.ServiceModel.Channels.PreReadConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
   at System.ServiceModel.Channels.SingletonConnectionReader.SingletonInputConnectionStream.ReadCore(Byte[] buffer, Int32 offset, Int32 count)
   at System.ServiceModel.Channels.SingletonConnectionReader.SingletonInputConnectionStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.ServiceModel.Channels.MaxMessageSizeStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.ServiceModel.Channels.SingletonConnectionReader.Close(TimeSpan timeout)
   at System.ServiceModel.Channels.SingletonConnectionReader.SingletonInputConnectionStream.Close()
   at System.ServiceModel.Channels.DelegatingStream.Close()
   at System.Xml.XmlBufferReader.Close()
   at System.Xml.XmlBaseReader.Close()
   at System.Xml.XmlBinaryReader.Close()
   at System.ServiceModel.Dispatcher.StreamFormatter.MessageBodyStream.Close()
   at System.IO.Stream.Dispose()
   at ...somewhere in my code...

I am not convinced that once cannot cancel a stream without fully reading it. On the other hand I cannot just forget the stream and let it go without disposing. It has to be a blocking wait-until-safely-released call.

Can someone please help me out here?

Edit

The stack trace shows:

'                                                         this is interesting
   at System.Xml.XmlBinaryReader.Close() '                    VVVVVVVVVVVVV
   at System.ServiceModel.Dispatcher.StreamFormatter.MessageBodyStream.Close()
   at System.IO.Stream.Dispose()

So i changed the using block to a try-finally block. there i put wcfStrm.close followed by wcfStrm.Dispose. to my astonishment, the close statement passed swiftly and the dispose timed out. Now if inside dispose the actual culprit was Close then why did explicit close didnt hang? and then again the dispose hanged even when the stream was closed?

like image 964
inquisitive Avatar asked Oct 07 '13 14:10

inquisitive


1 Answers

To clarify, the implementation of Stream.Dispose() is to call Stream.Close(). The base implementation of Stream.Close() is to call Stream.Dispose(bool). It's counter to the guidelines of how you would typically implement IDisposable, so it's worth noting.

The MessageBodyStream.Close() method is implemented to first close the Message being read, then to close the XmlDictionaryReader associated with the stream.

Looking at your full stack trace, the problem seems to be that this reader eventually calls into SingletonConnectionReader.Close(TimeSpan). This takes a TimeSpan as a timeout, and this is the source of your TimeoutException replacing the OperationCancelledException.

This method attempts to read the rest of the stream to complete the close operation. I can't explain the rationale behind that, but it how it is.


To fix your problem, you must stop using your proxy class as you are. Despite being IDisposable, it's not safe to use any WCF proxy in a using block, because calling Dispose() calls Close(), and in the event of an exception, that's not what you mean.

In this case, calling Abort() on the proxy will fix the problem completely, because it's what you mean: abort the operation.

using proxy as new ServiceReference1.TestServer
    dim wcfStrm = proxy.getFile("c:\100MB.dat")

    try
        using fileStrm = new FileStream("d:\destination\100MB.dat")

            dim buff(256) as new Byte

            while true
                cancellationToken.ThrowIfCancellationRequested

                Dim len = wcfStrm.Read(buff, 0, buff.Length)

                if len > 0 then
                    fileStrm.write(buff, 0, len)
                else
                    exit while
                end if
             end while

         end using      
    end try
    catch
        proxy.Abort()
    end catch
    finally
        wcfStrm.Dispose()
    end finally
end using

I'm not a VB developer, so I apologise if my syntax is terrible.

like image 140
Paul Turner Avatar answered Nov 17 '22 18:11

Paul Turner