Problems With Network Speed and Images and System.OutOfMemoryException

bigfoot3d

Well-known member
Joined
Mar 11, 2007
Messages
56
Location
New Orleans, LA
Programming Experience
3-5
When I run my code with no limitation on the network side, either by running from a localhost or running on a Local Area Network Connection, i get no problems whatsoever and it all just works.

However when I go ahead and put limitations on the speed either by running it across the internet or by setting a limitation on my network speed using a bandwidth manager, I then run into System.OutofMemoryExceptions.

I was wondering if there is anyway around this. I am thinking it has something to do with the buffer on the receive side that I am not clearing out correctly because the limitation is usually on the upload side. This is for the server part of my application. So the download speed is at least 200k and the upload is only 45 - 70k depending on the ISP's mood that day.

I figured that the receiving side is getting backed up by the sending side because of the limited upload speed. So the received side's buffer is just running out of memory because of the queuing its doing waiting to send to the upload side.

I am using this to transfer screenshots, so I am not really worried about losing screenshots that are taken while the uploading stream is in use.

I was thinking about having the receiving computer send a message to the sending computer through the server to send the next image once it has completely received the current image off of the stream, that way only one image would be transferred at a time. However that just seems as a workaround to me, and not a true solution.

Here is server side code for receiving the images:
VB.NET:
    Private Sub HFHClientSendScreenLink()
        Try
            While Connected
                If ConnectedtoTech Then
                    Dim br As New IO.BinaryReader(HFHClientSendScreen)
                    ThisClientsTech.HFHTechRecScreenLink(New IO.MemoryStream(br.ReadBytes(br.ReadInt32)))
                End If
            End While
        Catch e As Exception
            If e.Message = "Unable to read beyond the end of the stream." Then ' add here
                Exit Sub
            ElseIf e.Message = "Unable to read data from the transport connection: A blocking operation was interrupted by a call to WSACancelBlockingCall." Then
                Exit Sub
            Else
                MessageBox.Show("Check Me", "Check Me", MessageBoxButtons.OK)
            End If
        End Try
    End Sub

and here is the server side code with some additional tweaking to try to get rid of the extra buffered memory, but no luck.
VB.NET:
Private Sub HFHClientSendScreenLink()
     SendScreenToTech = True
     While Connected
          Try
               If ConnectedtoTech Then
                    Dim br As New IO.BinaryReader(HFHClientSendScreen)
                    Dim ScreenMemory As New IO.MemoryStream(br.ReadBytes(br.ReadInt32))
                    If SendScreenToTech Then
                         SendScreenToTech = False
                         ScreenMemorytoSend = ScreenMemory
                         Dim SendScreenTech As New Thread(New ThreadStart(AddressOf ShipScreentoTech))
                         SendScreenTech.Start()
                    End If
                    ScreenMemory.Dispose()
                    br = Nothing
               End If
          Catch e As Exception
               If e.Message = "Unable to read beyond the end of the stream." Then ' add here
                    Exit Sub
               ElseIf e.Message = "Unable to read data from the transport connection: A blocking operation was interrupted by a call to WSACancelBlockingCall." Then
                    Exit Sub
               ElseIf e.Message = "Non-negative number required." & vbNewLine & "Parameter name: count" Then
                    HFHClientSendScreen.Flush
               ElseIf e.Message = "Unable to read data from the transport connection: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full." Then
                    HFHClientSendScreen.Flush()
               ElseIf e.Message = "Stream was not writable." Then
                    Exit Sub
               Else
                    MessageBox.Show("Check Me " + vbNewLine + vbNewLine + e.Message + vbNewLine + vbNewLine + e.StackTrace + " " + e.Source + vbNewLine + e.ToString + vbNewLine + e.ToString, "Check Me", MessageBoxButtons.OK)
               End If
          End Try
     End While
End Sub

Any ideas or suggestions to over come this the proper way? I mean i can do a workaround like i mentioned above, but I would rather have something properly done rather than a workaround.

Thanks in advance for any comments or suggestions!
 
The continuous creation of new BinaryReaders that is not closed could be discussed. The BinaryReader implements IDisposable, which indicates there is a need for it, but it doesn't expose the Dispose method which is curious, one would think if it was important to free resouces used by this stream object there would be a public Dispose method willingly available. From the docs it says the call to Close will close the underlying stream (which may not be desirable) and call its protected Dispose to clean up unmanaged resources. This is as far I can find in docs anything related to this classes resource use explained, nowhere is it stated that you are required to close, but if you don't there could be built up unknown resource use that could take ages before is cleaned up. Setting the reader variable to Nothing solves nothing, the local variable assignment is dissolved next loop iteration anyway, and doing this only leave the object resource floating in memory for GC to collect sometime. Anyway there is no need for you to play with this, you can easily declare the reader outside the loop only once and Close it cleanly in Finally or after the Try block or after the loop depending on how you choose to arrange the code (you have both loop-try and try-loop in the posted options).

Other than that, is it the first or second or both these alternates you get OutOfMemory? (meaning, could it be not this code, but the ShipScreentoTech method?)

Making the receiver request a new image after it has finished reading one sounds like a good idea, it wouldn't cost much time delay to write for example a simple integer confirmation back the open stream before the sender sends next image.

The Stream class docs say this that may be related:
VB.NET:
A call to Close is required for proper operation of a stream.
This is also what you get when checking the MemoryStream.Close, so in this case you do have to call close. Note that MemoryStream class inherits Stream. BinaryReader does not, it just uses the stream. The Dispose() method of Stream is "not intended to be used directly from your code", using Close method will make appropriate disposing.
 
JohnH,

It happens with both of them! They both work fine until I limit the bandwidth, and as soon as I do that I get System.OutOfMemoryException. So i believe it may be what you have said, the creation of multiple, unused binary readers that are just adding up taking use of the system memory.

Let me check out what happens when I declare the before the loop and close it after the loop.

I shall let you know!

Thanks for the input!
 
So i took your suggestions and implemented it, this time it runs a few frames long(number of images sent).

This is what I have now for my code with the creation of the binary reader outside the loop, and also closing the streams inside the loop.

VB.NET:
    Private Sub HFHClientSendScreenLink()
        SendScreenToTech = True

        Dim br As New IO.BinaryReader(HFHClientSendScreen)
        While Connected
            Try
                If ConnectedtoTech Then
                    Dim ScreenMemory As New IO.MemoryStream(br.ReadBytes(br.ReadInt32))
                    If SendScreenToTech Then
                        SendScreenToTech = False
                        ScreenMemorytoSend = ScreenMemory
                        Dim SendScreenTech As New Thread(New ThreadStart(AddressOf ShipScreentoTech))
                        SendScreenTech.Start()
                    End If
                    ScreenMemory.Close()
                End If
            Catch e As Exception
                If e.Message = "Unable to read beyond the end of the stream." Then ' add here
                    Exit Sub
                ElseIf e.Message = "Unable to read data from the transport connection: A blocking operation was interrupted by a call to WSACancelBlockingCall." Then
                    Exit Sub
                ElseIf e.Message = "Non-negative number required." & vbNewLine & "Parameter name: count" Then
                    HFHClientSendScreen.Flush()
                ElseIf e.Message = "Unable to read data from the transport connection: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full." Then
                    HFHClientSendScreen.Flush()
                    MessageBox.Show("You Right", "You Right", MessageBoxButtons.OK)
                ElseIf e.Message = "Stream was not writable." Then
                    Exit Sub
                Else
                    MessageBox.Show("Check Me " + vbNewLine + vbNewLine + e.Message + vbNewLine + vbNewLine + e.StackTrace + " " + e.Source + vbNewLine + e.ToString + vbNewLine + e.ToString, "Check Me", MessageBoxButtons.OK)
                End If
            End Try
        End While
        br.Close()
    End Sub

Here is the ShipScreentoTech method:
VB.NET:
    Public Sub ShipScreentoTech()
        ThisClientsTech.HFHTechRecScreenLink(ScreenMemorytoSend)
        ScreenMemorytoSend.Close()
    End Sub

Here is the code that finally ships the memory stream to the reciever:
VB.NET:
    Public Sub HFHTechRecScreenLink(ByVal ScreenMem As IO.MemoryStream)
        Dim buffer As Byte() = ScreenMem.ToArray
        Dim bw As New IO.BinaryWriter(HFHTechRecScreen)
        bw.Write(buffer.Length)
        bw.Write(buffer, 0, buffer.Length)
        mem = Nothing
        bw = Nothing
        'ThisTechsClient.SendScreenToTech = True
    End Sub

The thing is the line of code that always seems to throw the System.OutofMemoryException is:
VB.NET:
Dim ScreenMemory As New IO.MemoryStream(br.ReadBytes(br.ReadInt32))

Here is the exception context:
VB.NET:
e.Message "Exception of type 'System.OutOfMemoryException' was thrown." String
e.StackTrace "   at System.IO.BinaryReader.ReadBytes(Int32 count)
   at HFHRemoteSupportServerApp.clsServerClient.HFHClientSendScreenLink() in C:\Documents and Settings\craig.denny\Desktop\HFHRemoteSupportServerApp\HFHRemoteSupportServerApp\clsServerClient.vb:line 230" String
e.Source "mscorlib" String
 
e.TargetSite {System.Reflection.RuntimeMethodInfo} System.Reflection.MethodBase
e.DeclaringType {Name = "BinaryReader" FullName = "System.IO.BinaryReader"} System.Type
e.Name "ReadBytes" String
e.ReflectedType {Name = "BinaryReader" FullName = "System.IO.BinaryReader"} System.Type

I think if the answer was in front of me it would jump out and bite me. After looking at this code long enough, its better to have another set of eyes take a glance at it.

Thanks for the input!
 
ReadBytes isn't documented to throw OutOfMemoryException for special cases so this is probably something building up elsewhere.

Even if you don't do anything useful with the Memorystream here (you just read a byte array and pass it around), I don't see any overhead problem with it. But what you do with "ScreenMemorytoSend = ScreenMemory" looks a little disturbing, this won't create a copy of the stream, it will just point two different variables to the the same instance reference, then you pass it to a sub method call byval which will create a copy of the reference. Still it is the same object you Close from calling sub. My test with doing Close on the MemoryStream and then read the bytes from its ToArray did work without problem though, the array weren't reset or anything.

You do the same as discussed above with the BinaryWriter, I looked at the source and its Close/Dispose(True) doesn't do anything but closing the underlying stream, so that shouldn't be a problem here. Actually a closer look at the BinaryReader.Close looks as it doesn't do much more either.

So this "set of eyes" don't see any problems here.

What about the original sender, is the data type of the byte length written certainly int32? Perhaps if you were trying to read a misaligned stream elsewhere into an image the memory problem could occur, you never know.. :)
 
I know its just something interesting.

What gets me is that it runs fine when there is no limitation on the network speed that the network streams use. However, once you add the limitation of the network speed, and the network stream can only transfer as fast as the network will allow it to, is when I see the trouble occur each and every time.

An interesting point I forgot to bring up is that the following exception occurs each and every time before the System.OutofMemoryException.
VB.NET:
e.Message = "Unable to read data from the transport connection: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full."

Here is the lines that sends the network stream from the sender to the server.
VB.NET:
                bw.Write(zipbuffer.Length)
                bw.Write(zipbuffer, 0, zipbuffer.Length)

Just to throw my 2 sense in, what I think is happening is that too much is building up on the receiving side of the server while it waits to transmit the image to the receiver from the sending side of the server. The upload speed is much more limited so the the servers receiver is waiting on the sender to send its next image, so I am not sure if the images are building up on the stream on the receive side or what.

Diagram of the Network Connection Path & Speed :)

Sender -> 300kBs Pipe -> ServerReceiver -> ServerSender -> 30kBs Pipe -> Receiver

So i think the Server Receiver may be causing the System.OutOfMemoryException because of the data it is receiving and trying to hold. However, I don't want it to hold it, i just want it to send what is available at the time the ServerSender is ready to send the next image.

Just a thought!

Sparks any thoughts or comments? Thanks!
 
Sending 100KB or so should not be a problem, and really not causing out of memory because of buffer size ;) Receiver will get it in a few seconds with that speed. So did you implement the confirmation, and let the sender wait to to send next image until allowed? It also still could indicate app has run out of resources because of leaks elsewhere.
 
I believe that is the next thing I will implement. I was trying to see if there were any other possible fixes first. I think of it as kind of a workaround, but I am sure it will work. If it works then I know it has something to do with the Server Receiver Buffer and one of those aspects overflowing, if not then I shall try to tear it apart more at that point.

I believe it will work. I just thing everything is getting hung up waiting to transfer from the server to the receiver.

I will let you know if that works.

Thanks for everything!
 
That was the idea, because theoretically this part of application will eat 270KB memory per second ad infinitum.

What about the threads used for sending here, won't that mean 25 threads will write bytes intermixing to the same stream, making one big garble? I think it doesn't serve a purpose also, because you don't need to stack up images in memory that can't sent anyway. So you won't need to read a new image here until you are ready to send next (when receiving end signals it want another)
 
So it all works fine when I do it with the receiver telling the sender to send the next image. However, I would like to clear up another problem I run into. I have found away around it, but I am not sure if I can clear it up without using my workaround.

The work around that I use is a try catch block.

What happens is that the first iteration of the loop works, but every other iteration after that does not without the try statement. The error exception that is thrown is
VB.NET:
{"Input string was not in a correct format."}

However when I step through the program, I know that it does not read anything new from the stream. I am not sure if it is reading something that was left on the stream or what, but at this point that it reads, I know nothing new has been sent on the stream from the receiver yet.

VB.NET:
 Public Sub TechSendScreenshot()
        Screenshots()
        While Not StopCapture
            Try
                Dim TechSendScreenBytes(HFHTechSendNextScreenTCP.ReceiveBufferSize) As Byte
                HFHTechSendNextScreen.Read(TechSendScreenBytes, 0, CInt(HFHTechSendNextScreenTCP.ReceiveBufferSize))
                Dim SendStr As Integer = Convert.ToInt32(Encoding.ASCII.GetString(TechSendScreenBytes))
                If SendStr = -2 Then
                    Screenshots()
                End If
            Catch e As Exception
            End Try
        End While
    End Sub

I have this same type of situation at 2 other places in my code as well, so I am just trying to clear it up at all places.

Thanks in advance!
 
That doesn't look right. You are requesting read of a full ReceiveBufferSize number of bytes, take whatever was read and convert it to a ascii string then convert this string to an int32. An int32 is 4 bytes, the sender should write an int32 or the byte representation of it, and your reader here should get that exactly. Also here you can probably use the same binarywriter/reader.
 
I fixed my problem.

The incorrect line of code was:
VB.NET:
HFHTechSendNextScreen.Read(TechSendScreenBytes, 0, CInt(HFHTechSendNextScreenTCP.ReceiveBufferSize))

The correct line of code is:
VB.NET:
HFHTechSendNextScreen.Read(TechSendScreenBytes, 0, TechSendScreenBytes.Length)

Works like a charm now :)

Thanks!
 
Back
Top