Skip to content

Upload Progress with ProgressMessageHandler Completes Unexpectedly #21537

@tylersouthard

Description

@tylersouthard

Apple platform

iOS

Framework version

net8.0-*

Affected platform version

.NET 8.0.302, VS Code 1.94.2, .NET MAUI extension v1.4.36

Description

When attempting to upload a file, it appears that the entirety of the file is first copied into memory, then uploaded. This causes two problems.

  1. Large file uploads cause devices with limited memory to run out of memory.
  2. Attempting to track progress for the actual upload doesn't work, since it jumps to 100% quickly (the copy to memory), then reports no progress for the actual upload, which takes much longer than the copy to memory.

The issue here seems to point to similar problems, but the discussion centered around whether the content was coming in as a MemoryStream or FileStream. This is a FileStream, which I would not expect to enter memory all at once, since it should be streamed from disk to be uploaded in chunks.

Steps to Reproduce

  1. Follow the steps here to create a new MAUI application (MAUI was used for simplicity, but the code in question appears to be generic to xamarin-macios).
  2. Write the code below. In summary, it's simply taking a local file (large enough to observe a delay when uploading) and uploading it to an endpoint that will accept it. (Note that the specifics of our endpoint are removed for privacy.)
  3. Observe that the ProgressMessageHandler reports progress in chunks, as expected, but the progress is much too fast to be the actual upload. They all happen within the same second or two.
  4. Then observe that the upload doesn't actually finish until several seconds later. See Relevant Log Output section below.
  5. We would expect to see the ProgressMessageHandler track the actual upload, instead of presumably tracking the copy to memory.
using System.Net.Http.Handlers;
using System.Net.Http.Headers;

namespace MyMauiApp
{
	public partial class MainPage : ContentPage
	{
		public MainPage()
		{
			InitializeComponent();
		}

		private async void OnTestBtnClicked(object sender, EventArgs e)
		{
			await PerformUploadAsync();
		}

		private static async Task PerformUploadAsync()
		{
			ProgressMessageHandler progressMsgHander = new ProgressMessageHandler();
			progressMsgHander.HttpSendProgress += (object? sender, HttpProgressEventArgs e) =>
			{
				Console.WriteLine($"{DateTime.Now}: Sent {e.BytesTransferred}, Percent Complete: {e.ProgressPercentage}%");
			};
			progressMsgHander.InnerHandler = new HttpClientHandler();

			HttpClient httpClient = new HttpClient(progressMsgHander);
			using MultipartFormDataContent formContent = new MultipartFormDataContent("mycoolboundary");
			if (formContent.Headers.ContentType != null)
				formContent.Headers.ContentType.MediaType = "multipart/form-data";

			using FileStream fs = new FileStream("test.zip", FileMode.Open);
			using StreamContent streamContent = new StreamContent(fs);

			string token = "<redacted>";
			httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
			httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("multipart/form-data"));

			streamContent.Headers.ContentLength = fs.Length;
			formContent.Add(streamContent, "test", fs.Name);

			HttpResponseMessage response = await httpClient.PostAsync("<redacted>", formContent);
			response.EnsureSuccessStatusCode();

			Console.WriteLine($"{DateTime.Now}: Done Uploading");
		}
	}
}

Did you find any workaround?

Adding a breakpoint to the HttpSendProgress event handler allows us to see NSUrlSessionHandler.cs in the call stack. That file appears to check MaxInputInMemory (line 503 currently). If we create a platform-specific shim to set this value to something much smaller than long.MaxValue, the behavior is as expected.

It doesn't seem possible that this workaround would be expected/required for what seems to be a common use case. Is that expectation correct?

Relevant log output

2024-10-28 10:19:10.982692-0400 MyMauiApp[99543:3985957] 10/28/2024 10:19:10 AM: Sent 18, Percent Complete: 0%
2024-10-28 10:19:10.983369-0400 MyMauiApp[99543:3985957] 10/28/2024 10:19:10 AM: Sent 515, Percent Complete: 0%
2024-10-28 10:19:11.002468-0400 MyMauiApp[99543:3986541] 10/28/2024 10:19:11 AM: Sent 131587, Percent Complete: 0%
2024-10-28 10:19:11.004298-0400 MyMauiApp[99543:3986543] 10/28/2024 10:19:11 AM: Sent 262659, Percent Complete: 1%
2024-10-28 10:19:11.005538-0400 MyMauiApp[99543:3986544] 10/28/2024 10:19:11 AM: Sent 393731, Percent Complete: 2%
2024-10-28 10:19:11.005892-0400 MyMauiApp[99543:3986543] 10/28/2024 10:19:11 AM: Sent 524803, Percent Complete: 3%
...
2024-10-28 10:19:11.044001-0400 MyMauiApp[99543:3986541] 10/28/2024 10:19:11 AM: Sent 17302019, Percent Complete: 99%
2024-10-28 10:19:11.044295-0400 MyMauiApp[99543:3986541] 10/28/2024 10:19:11 AM: Sent 17363459, Percent Complete: 99%
2024-10-28 10:19:11.045475-0400 MyMauiApp[99543:3986541] 10/28/2024 10:19:11 AM: Sent 17363481, Percent Complete: 100%
2024-10-28 10:19:27.667022-0400 MyMauiApp[99543:3985957] 10/28/2024 10:19:27 AM: Done Uploading

Metadata

Metadata

Assignees

No one assigned

    Labels

    networkingIf an issue or pull request is related to networking

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions