Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blazor Server UploadFile Throws "System.InvalidOperationException: Reading is not allowed after reader was completed." #52723

Open
J3ster1337 opened this issue Dec 11, 2023 · 4 comments
Labels
area-blazor Includes: Blazor, Razor Components investigate
Milestone

Comments

@J3ster1337
Copy link

J3ster1337 commented Dec 11, 2023

Describe the bug

I use Blazor Server .NET 7.0.
Some of the users have difficulties uploading files (when I myself don't: I've uploaded over 1000 files at this point and never had an issue). After uploading some amount of files, upload silently stops, they even can click on the InputFile again, pick new files, and the upload will start again (and then stop by the same reason). Sometimes bug happens after 20 uploads, sometimes after 8, sometimes never, the amount is random.
In the end, I managed to get their chrome console and there is System.InvalidOperationException: Reading is not allowed after reader was completed. error.
All that time, there was no logs on the server (probably because of my appsettings?).
For the most time, files is around 10MB.
Deployed on Ubuntu with Nginx.
Websocked connection is established, even when the error occurs.
I had an idea that it's somehow connected to the MacBook usage, but one Windows user told that he has the same behaviour.

Code I use:

<InputFile OnChange="UploadFiles" multiple accept=".jpg,.jpeg" />

@code {
  async Task UploadFiles(InputFileChangeEventArgs e)
  {
	  try
	  {
		  foreach (var file in e.GetMultipleFiles(1000))
		  {
			  string newNameAndExtension = UploadTools.CreateUniqueFileNameAndExtension(Path.GetExtension(file.Name), directoryForFiles.FullName);
			  await using MemoryStream stream = new();
			  await file.OpenReadStream(1024 * 1024 * 1024).CopyToAsync(stream);
			  stream.Position = 0;
			  UploadTools.SavePhotoFirstTime(stream, directoryForFiles, newNameAndExtension);
			  stream.Position = 0;
			  UploadTools.SavePhotoSecondTime(stream, directoryForPreviews, newNameAndExtension);
			  DatabaseContext.Add(new Photo(newNameAndExtension));
			  DatabaseContext.SaveChanges();
			  StateHasChanged();
		  }
	  }
	  catch (Exception ex)
          {
	  	  Console.WriteLine(ex.Message);
	  	  Console.WriteLine(ex.StackTrace);
	  }
  }
}

Exception

Reading is not allowed after reader was completed.                                                                                          
  at System.IO.Pipelines.ThrowHelper.ThrowInvalidOperationException_NoReadingAllowed()                                                     
  at System.IO.Pipelines.Pipe.ReadAsync(CancellationToken token)                                                                           
  at System.IO.Pipelines.PipeReaderStream.ReadAsyncInternal(Memory`1 buffer, CancellationToken cancellationToken)                          
  at Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSDataStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)    
  at Microsoft.AspNetCore.Components.Forms.BrowserFileStream.CopyFileDataIntoBuffer(Memory`1 destination, CancellationToken cancellationTo>
  at Microsoft.AspNetCore.Components.Forms.BrowserFileStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)               
  at System.IO.Stream.<CopyToAsync>g__Core|27_0(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken)  
  at PhotoSite.Pages.Work.Photographer.SessionUpload.UploadFiles(InputFileChangeEventArgs e)                                               
  at PhotoSite.Pages.Work.Photographer.SessionUpload.UploadFiles(InputFileChangeEventArgs e)  

If there is any more information that is needed please feel free to ask.

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Dec 11, 2023
@javiercn javiercn added this to the Backlog milestone Dec 11, 2023
@ghost
Copy link

ghost commented Dec 11, 2023

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@J3ster1337
Copy link
Author

J3ster1337 commented Dec 12, 2023

The only workaround I managed to find is simply restarting the upload for this individual file like this:

CancellationTokenSource cts;
async Task UploadFiles(InputFileChangeEventArgs e)
{
	if (cts != null) { cts.Cancel(); }
	cts = new CancellationTokenSource();
	
	//your other logic

	foreach (IBrowserFile file in e.GetMultipleFiles(1000))
	{
		await LoadAndSaveFile(file, cts.Token);

                //other things you need to do with the file
	}
}
async Task LoadAndSaveFile(IBrowserFile file, CancellationToken token)
{
	try
	{
	        //put here everything where MemoryStream is used:
		await using MemoryStream stream = new();
		await file.OpenReadStream(1024 * 1024 * 1024, token).CopyToAsync(stream, token);
		stream.Position = 0;
		UploadTools.SavePhotoFirstTime(stream);
		stream.Position = 0;
		UploadTools.SavePhotoSecondTime(stream);
	}
	catch (Exception ex)
	{
		if (ex is InvalidOperationException) //we don't want it to restart if it's cancelled
		{
		        //just start the same method again with the same parameters
			await LoadAndSaveFile(file, token);
		}
	}
}

The exception will still occur every 5-70 files, now you just don't care about it.

@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 13, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@A9G-Data-Droid
Copy link

I am seeing the same kind of error in a .NET8 Blazor Server App. The test file I am using is around 170MB and it fails every time.

CODE EXAMPLE:

const int kb = 1024;
const int mb = 1024 * kb;
const long gb = 1024 * mb;
string destinationFullPath = Path.Combine(savePath, inputFile.Name);

byte[] buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(mb);
try
{
    await using FileStream destination = new(destinationFullPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None,  mb, FileOptions.Asynchronous);
    await using Stream readStream = inputFile.OpenReadStream(2 * gb);

    long totalBytesRead = 0;
    long fileSize = inputFile.Size;
    while (readStream.CanRead)
    {
        int bytesRead = await readStream.ReadAsync(buffer.AsMemory(0, mb));
        if (bytesRead <= 0) break;

        totalBytesRead += bytesRead;
        int newProgress = (int)(100 * totalBytesRead / fileSize);

        await destination.WriteAsync(buffer.AsMemory(0, bytesRead));

        if (newProgress != ProgressValue)
        {
            ProgressValue = newProgress;

            // Fire the event of OnProgress to notify the client about progress so far
            OnProgress?.Invoke(newProgress);
        }
    }

    logger.Info($"Downloaded file from user to: {destinationFullPath}");
}
catch (Exception ex)
{
    logger.Error(ex, $"Failure saving file submitted by user to: {destinationFullPath}");
}            
finally
{
    System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
}

ERROR EXAMPLE:

Reading is not allowed after reader was completed.
at System.IO.Pipelines.ThrowHelper.ThrowInvalidOperationException_NoReadingAllowed()
at System.IO.Pipelines.Pipe.ReadAsync(CancellationToken token)
at System.IO.Pipelines.PipeReaderStream.d__30.MoveNext()
at System.Threading.Tasks.ValueTask1.get_Result() at System.Runtime.CompilerServices.ValueTaskAwaiter1.GetResult()
at Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSDataStream.d__36.MoveNext()
at System.Threading.Tasks.ValueTask1.get_Result() at System.Runtime.CompilerServices.ValueTaskAwaiter1.GetResult()
at Microsoft.AspNetCore.Components.Forms.BrowserFileStream.d__30.MoveNext()
at System.Threading.Tasks.ValueTask1.get_Result() at System.Runtime.CompilerServices.ValueTaskAwaiter1.GetResult()
at Microsoft.AspNetCore.Components.Forms.BrowserFileStream.d__28.MoveNext()
at System.Threading.Tasks.ValueTask1.get_Result() at System.Runtime.CompilerServices.ValueTaskAwaiter1.GetResult()

@A9G-Data-Droid
Copy link

I believe this issue is a duplicate of #53951

Which is related to #47301

The workaround is to find your SignalR hub options and change MaximumParallelInvocationsPerClient to 1. If you actually need support multiple SignalR invocations then you can't also use InputFile to upload large files.

These things should be decoupled. SignalR settings should not break file transfers and vice versa.

WORKAROUND:

.AddHubOptions(options =>
{
    options.MaximumParallelInvocationsPerClient = 1;
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components investigate
Projects
None yet
Development

No branches or pull requests

4 participants