Skip to content

Blazor Server .NET 8/9 WebSocket closed with status code 1006 #62106

@dkorecko

Description

@dkorecko

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Hello,

For some time now, I've been experiencing an issue where on a specific page in a .NET 8/9 application hosted on a raspberry Pi (self-contained publish for linux-arm64), sometimes when a search happens, the WebSocket connection drops.

Image

This page used to work in the past, nothing has changed on it for it to stop working.

I found this issue:
#58932

  • tried disabling response compression on WebSockets and removed it since it was enabled for other stuff as well, did not help the issue

Program.cs

public class Program
{
    public static object RESTORE_LOCK = new object();

    public static void Main(string[] args)
    {
        bool afterUpdate = false;

        if (UpdateHelper.IsUpdateInProgress())
        {
            UpdateHelper.FinalizeUpdate();
            afterUpdate = true;
        }

        var builder = WebApplication.CreateBuilder(args);

        builder.WebHost.UseUrls("http://0.0.0.0:5000");
        builder.Services.AddRazorComponents().AddInteractiveServerComponents();

        builder.Services.Configure<RouteOptions>(options => options.LowercaseUrls = true);

        builder.Services.AddDbContextFactory<DataContext>(options =>
        {
            options.UseNpgsql(builder.Configuration.GetConnectionString("MainDB"));
        });

        builder.Services.AddAutoMapper(typeof(SettingsProfile));

        builder.Services.AddScoped<HubService>();
        builder.Services.AddScoped<FileService>();
        builder.Services.AddScoped<DeletionService>();
        builder.Services.AddScoped<BackupService>();
        builder.Services.AddScoped<PsalmService>();
        builder.Services.AddScoped<BreviarService>();
        builder.Services.AddScoped<SystemService>();
        builder.Services.AddHostedService<CleanupHostedService>();
        builder.Services.AddHostedService<PsalmHostedService>();
        builder.Services.AddHostedService<BreviarHostedService>();

        builder.Services.AddControllers();

        var configuration = builder.Configuration;
        builder.Services.AddLogging(loggingBuilder =>
        {
            var path = configuration
                .GetSection("Logging")
                .GetSection("File")
                .GetValue<string>("Path");
            var fileName =
                path + "/app_{0:yyyy}-{0:MM}-{0:dd}_" + Process.GetCurrentProcess().Id + ".log";

            loggingBuilder.AddFile(
                fileName,
                fileLoggerOpts =>
                {
                    fileLoggerOpts.FormatLogFileName = fName =>
                    {
                        return string.Format(fName, DateTime.Now);
                    };

                    fileLoggerOpts.MinLevel = LogLevel.Information;
                }
            );
        });

        var app = builder.Build();

        using (var scope = app.Services.CreateScope())
        {
            var dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();

            if (afterUpdate)
            {
                try
                {
                    dbContext.Database.Migrate();
                }
                catch
                {
                    var logging = scope.ServiceProvider.GetService<ILogger<Program>>()!;
                    logging.LogError(
                        "An error occurred while migrating after an update, resetting database..."
                    );
                    dbContext.Database.EnsureDeleted();
                    dbContext.Database.EnsureCreated();
                    dbContext.Database.Migrate();
                    logging.LogInformation("Database reset successful.");
                }
            }
            else
                dbContext.Database.Migrate();
        }

        // Configure the HTTP request pipeline.
        if (!app.Environment.IsDevelopment())
        {
            app.UseExceptionHandler("/Error", createScopeForErrors: true);
        }

        var staticFileProvider = new FileExtensionContentTypeProvider();
        staticFileProvider.Mappings[".apk"] = "application/vnd.android.package-archive";

        app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = staticFileProvider });
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAntiforgery();

        app.MapControllers();

        app.MapRazorComponents<App>()
            .AddInteractiveServerRenderMode(o => o.DisableWebSocketCompression = true);
        app.MapHub<DisplayHub>(Constants.HubRoutes.DISPLAY_HUB);

        var logger = app.Services.GetService<ILogger<Program>>()!;
        logger.LogInformation($"Launching with version {VersionService.CurrentVersion.Version}.");

        app.Run();
    }
}

Page with the issue in question: - removed irrelevant parts:

<div class="p-2 ">
        <h2><b>Search</b></h2>
        <div class="input-search flex flex-row w-[95%]">
            <input type="text" class="input-style" @oninput="SearchTextChanged" value="@_searchText" />
        </div>

        <div>
            @if (_results is null)
            {
                <Spinner />
            }
            else
            {
                if (_results.Any())
                {
                    if (_isLoading)
                    {
                        <ElementSpinner />
                    }
                    else
                    {
                        foreach (var song in _results)
                        {
                            <div class="result grid grid-cols-10 gap-2 cursor-pointer" @onclick="() => AddToQueue(song)">
                                <label class="p-2 col-span-9 cursor-pointer">
                                    @song.Name.Shorten() (@song.Number)
                                </label>
                                <div class="p-2 col-span-1">
                                    <h4 class="primary-color pointer material-symbols-rounded">
                                        add_circle
                                    </h4>
                                </div>
                            </div>
                        }
                    }
                }
                else
                {
                    <label>No results</label>
                }
            }
        </div>
    </div>

@code {
    private Queue? _currentQueue;
    private bool _automaticallyAddResult;
    private IEnumerable<Song>? _results;
    private string _searchText = string.Empty;
    private bool _isLoading;
    private Timer? _timer;
    private CancellationToken _cancellationToken = new CancellationToken();

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            _timer = new Timer(Constants.Times.DEBOUNCE_TIME);
            _timer.Stop();
            _timer.Elapsed += UpdateResults;
            _timer.AutoReset = false;

            using var db = _dbContextFactory.CreateDbContext();

            _currentQueue = await db.Queues
            .Include(x => x.Playable)
            .AsNoTracking()
            .FirstAsync();

            _automaticallyAddResult = await db.Settings
            .AsNoTracking()
            .Select(x => x.AutomaticallyAddResult)
            .FirstAsync();

            UpdateResults();
        }
    }

    private async void UpdateResults(Object? obj = null, ElapsedEventArgs? e = null)
    {
        _isLoading = true;

        if (!string.IsNullOrWhiteSpace(_searchText))
        {
            using var db = _dbContextFactory.CreateDbContext();
            var queryableResults = db.Songs.AsNoTracking()
            .Where(x => (x.Number != null && x.Number!.ToString()!.Contains(_searchText)) ||
            x.SearchName.Contains(_searchText.NormalizeForSearch()))
            .OrderBy(x => x.Number)
            .ThenBy(x => x.Name)
            .Take(10);

            _results = await queryableResults.ToListAsync(_cancellationToken);
        }
        else
        {
            _results = new List<Song>();
        }

        if (_automaticallyAddResult && _results.Count() == 1)
            AddToQueue(_results.First());

        _isLoading = false;

        await InvokeAsync(StateHasChanged);
    }

    private async void AddToQueue(Song song)
    {
        if (_currentQueue is null)
            return;

        if (!_currentQueue.Playable.Contains(song))
        {
            using var db = _dbContextFactory.CreateDbContext();

            var targetSong = await db.Songs.FirstOrDefaultAsync(x => x.Id.Equals(song.Id));

            if (targetSong is null)
                return;

            targetSong.AddedToQueue = DateTime.Now;
            targetSong.PlayableQueueId = _currentQueue.Id;

            await db.SaveChangesAsync();

            song.AddedToQueue = targetSong.AddedToQueue;
            _currentQueue.Playable.Add(song);

            StateHasChanged();
        }

        ClearSearchText();
    }

    private async void RemoveFromQueue(Song song)
    {
        if (_currentQueue is null)
            return;

        using var db = _dbContextFactory.CreateDbContext();

        var targetSong = await db.Songs.FirstOrDefaultAsync(x => x.Id.Equals(song.Id));

        if (targetSong is null)
            return;

        targetSong.AddedToQueue = null;
        targetSong.PlayableQueueId = null;

        await db.SaveChangesAsync();

        song.AddedToQueue = null;
        _currentQueue.Playable.Remove(song);
        StateHasChanged();
    }

    private void SearchTextChanged(ChangeEventArgs e)
    {
        if (e?.Value is null || e.Value is not string value)
            return;

        _searchText = value;
        _timer?.Stop();
        _timer?.Start();
    }

    private void AddToSearchText(int value)
    {
        _searchText += value.ToString();
        UpdateResults();
    }

    private void ClearSearchText()
    {
        _searchText = string.Empty;
        UpdateResults();
    }

    void IDisposable.Dispose()
    => _timer?.Dispose();
}

Importantly, this does not happen when debugging in Visual Studio, does not happen on any other page. Only happens when searching with my keyboard on the provided page.

Expected Behavior

No response

Steps To Reproduce

No response

Exceptions (if any)

Server-side no exceptions

.NET Version

9

Anything else?

Issue occurs on Raspbian, Raspberry Pi 4, published for linux-arm64 (self-contained)

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-area-labelUsed by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions