1

I need to POST a 5MB file from within a ResourceIntensiveTask, where the OS sets a max memory usage of 5MB. So trying to stream the file directly from storage, but the Stream associated to the HttpWebRequest keeps growing in size. This is the code:

        public void writeStream(Stream writer, string filesource, string filename)
        {
            var store = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication();
            var f = store.OpenFile(filesource, FileMode.Open, FileAccess.Read);
            store.Dispose();

            byte[] buffer = Encoding.UTF8.GetBytes(String.Format(@"Content-Disposition: form-data; name=""file""; filename=""{0}""\n", filename));
            writer.Write(buffer, 0, buffer.Length);

            buffer = Encoding.UTF8.GetBytes("Content-Type: application/octet-stream\n");
            writer.Write(buffer, 0, buffer.Length);

            long initialMemory = Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage;

            buffer = new byte[2048]; 
            int DataRead = 0;
            do
            {
                DataRead = f.Read(buffer, 0, 2048);
                if (DataRead > 0)
                {
                    writer.Write(buffer, 0, DataRead);
                    Array.Clear(buffer, 0, 2048);
                }
            } while (DataRead > 0);

            double increasedMemory = ((double)Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage - initialMemory) / 1000000;                

            buffer = Encoding.UTF8.GetBytes("\n--" + boundary + "\n--");
            writer.Write(buffer, 0, buffer.Length);
            writer.Flush();
        }

increasedMemory debug variable is used to get the differential memory before and after the file is read and streamed to the HttpWebRequest, and it gives almost the exact size of the file (5MB) which means the process memory is increasing 5MB.

I am also setting AllowReadStreamBuffering=false to the HttpWebRequest.

How to keep memory low? How to upload large files when memory usage limit is 5MB?

Jandieg
  • 88
  • 8
  • I have the same problem - `OutOfMemoryException` while uploading large files (but from foreground application). Maybe someone can help. Started a `bounty` – Ku6opr Mar 23 '12 at 15:37
  • Just a guess at this point (I haven't tested this) but have you tried adding a `writer.Flush();` after the `writer.Write()` in the loop? – Nomad101 Mar 23 '12 at 17:53
  • Yes. Also, tried `Thread.Sleep` in the loop to wait until data is uploaded – Ku6opr Mar 23 '12 at 18:04
  • Also, I write the same data from one byte array, so problem is not in `reading` – Ku6opr Mar 23 '12 at 18:06

2 Answers2

2

The problem is that without being able to turn off write buffering, the connection to the server is not even made until BeginGetResponse() is called after closing the request stream (verified with WireShark).

The only way I can think of to get around this would be to use sockets directly (although that will be way more complicated if using an SSL connection).

This code works for me and doesn't increase memory usage while sending data to the server. I haven't tested it in a background task but don't see any reason it wouldn't work.

Socket _socket;
const int BUFFERSIZE = 4096;
byte[] writebuffer = new byte[BUFFERSIZE];
string hostName = "www.testdomain.com";
string hostPath = "/test/testupload.aspx";
IsolatedStorageFileStream isoFile;


public void SocketPOST(string hostName, string filesource)
{
    using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
    {
        if (store.FileExists(filesource))
        {
            isoFile = store.OpenFile(filesource, FileMode.Open, FileAccess.Read);
        }
    }

    _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    _socket.SetNetworkRequirement(NetworkSelectionCharacteristics.NonCellular);

    SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
    socketEventArg.RemoteEndPoint = new DnsEndPoint(hostName, 80);
    socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(Socket_Completed);

    _socket.ConnectAsync(socketEventArg);
}


private void Socket_Completed(object sender, SocketAsyncEventArgs e)
{
    if (e.SocketError == SocketError.Success)
    {
        switch (e.LastOperation)
        {
            case SocketAsyncOperation.Connect:   // Connected so started sending data, headers first

                if (e.ConnectSocket.Connected)
                {
                    StringBuilder sbHeaders = new StringBuilder("POST " + hostPath + " HTTP/1.1\r\n");
                    sbHeaders.Append("HOST: " + hostName + "\r\n");
                    sbHeaders.Append("USER-AGENT: MyWP7App/1.0\r\n");
                    sbHeaders.Append("Content-Type: text/plain; charset=\"utf-8\"\r\n");
                    sbHeaders.Append("Content-Length: " + isoFile.Length.ToString() + "\r\n\r\n");

                    byte[] headerBuffer = Encoding.UTF8.GetBytes(sbHeaders.ToString());
                    e.SetBuffer(headerBuffer, 0, headerBuffer.Length);

                    if (!e.ConnectSocket.SendAsync(e)) Socket_Completed(e.ConnectSocket, e);
                }

                break;

            case SocketAsyncOperation.Send:
            case SocketAsyncOperation.SendTo:   // Previous buffer sent so send next one if stream not finished

                Array.Clear(writebuffer, 0, BUFFERSIZE);

                int DataRead = 0;

                DataRead = isoFile.Read(writebuffer, 0, BUFFERSIZE);
                if (DataRead > 0)
                {
                    e.SetBuffer(writebuffer, 0, DataRead);
                    if (!_socket.SendAsync(e)) Socket_Completed(e.ConnectSocket, e);
                }
                else
                {
                    isoFile.Dispose();
                    if (!_socket.ReceiveAsync(e)) Socket_Completed(e.ConnectSocket, e);
                }

                break;

            case SocketAsyncOperation.Receive:
            case SocketAsyncOperation.ReceiveFrom:

                if (e.BytesTransferred > 0)
                {
                    string response = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred).Trim('\0');

                    // Check response if necessary 

                    e.ConnectSocket.Shutdown(SocketShutdown.Both);
                    e.ConnectSocket.Dispose();
                }

                break;

            default:
                break;
        }

    }
}

Note: I've left a lot of the error handling out to keep the example short.

SSL Note: Because SSL works at the TCP level and WP7 doesn't currently support SSL sockets (SslStream) you would need to handle the certificate handshake, cipher exchange, etc yourself to set up the SSL connection on the socket and then encrypt everything being sent (and decrypt everything received) with the agreed algorithms. There has been some success using the Bouncy Castle API so that could be possible (see this blog post).

Nomad101
  • 302
  • 1
  • 5
  • Very interesting... Bug disadvantage is it doesn't will work under lock screen. :( – Ku6opr Mar 26 '12 at 12:25
  • True, I remember your previous post about that issue. Do you know if that affects background agents too (the socket not working under lock screen)? – Nomad101 Mar 26 '12 at 12:53
  • `Background agents` is not the same as running under lock screen :) . I think that there is no restriction for that and this code should run smoothly with `Background Agents` – Ku6opr Mar 26 '12 at 13:05
  • Yep, I know they're not the same - that's why I asked whether BG agents were also affected by the issue. I wasn't sure if sockets just generally didn't work unless running in the foreground. :) – Nomad101 Mar 26 '12 at 13:22
  • Interesting, any hints on what should be required to make it work with SSL? – Jandieg Mar 26 '12 at 16:56
  • Added some info on SSL to my answer. It would be quite complex to do from scratch (handling the SSL handshake) and encryption/decryption. I haven't tried Bouncy Castle myself but it might help. – Nomad101 Mar 26 '12 at 17:33
0

One thing I noticed: you forgot to dispose f!

I personally would use the code like this:

public void writeStream(Stream writer, string filesource, string filename)
{
    using (var store = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication())
    {
        long initialMemory = Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage;

        using (var f = store.OpenFile(filesource, FileMode.Open, FileAccess.Read))
        {
            byte[] buffer = Encoding.UTF8.GetBytes(string.Format(@"Content-Disposition: form-data; name=""file""; filename=""{0}""\n", filename));
            writer.Write(buffer, 0, buffer.Length);

            buffer = Encoding.UTF8.GetBytes("Content-Type: application/octet-stream\n");
            writer.Write(buffer, 0, buffer.Length);

            buffer = new byte[2048];
            int DataRead = 0;

            do
            {
                DataRead = f.Read(buffer, 0, 2048);
                if (DataRead > 0)
                {
                    writer.Write(buffer, 0, DataRead);
                }
            } while (DataRead > 0);

            buffer = Encoding.UTF8.GetBytes("\n--" + boundary + "\n--");
            writer.Write(buffer, 0, buffer.Length);
            writer.Flush();
        }

        double increasedMemory = ((double)Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage - initialMemory) / 1000000;
    }
}

The boundary var seems to be missing, so a coding error still remains here!

Pedro Lamas
  • 7,185
  • 4
  • 27
  • 35
  • The problem is in `AllowWriteStreamBuffering`, that is missing in WP7 in `HttpWebRequest` and does not make sense in `WebClient`. So internal `MemoryStream` is take the size of all data need to be written. – Ku6opr Mar 23 '12 at 17:43
  • Quite true, missed that part! – Pedro Lamas Mar 23 '12 at 18:42
  • Yes, the code runs ok on foreground, and also runs fine if DEBUGGER plugged (because that ignores memory cap). Anyone has been able to workaround this? – Jandieg Mar 23 '12 at 20:42
  • This is the line increasing memory each time in the loop: writer.Write(buffer, 0, DataRead); It accumulates bytes till the app crashes. – Jandieg Mar 23 '12 at 20:45