mrpmorris / Fluxor

Fluxor is a zero boilerplate Flux/Redux library for Microsoft .NET and Blazor.
MIT License
1.22k stars 139 forks source link

Getting DisposableCallback after app has been running for a while #491

Closed sip1n closed 4 weeks ago

sip1n commented 1 month ago

Hey I'm getting DisposableCallback with Id ""StateSubscriber.Subscribe / Fluxor.Blazor.Web.StoreInitializer" on my project usually after ~20 mins. I did a little test and created a new Blazor project to reproduce that and got this same exception after ~130 mins (time difference probably is from difference in memory usage).

.NET 8 with Fluxor 6.0.0-Beta2

in Program.cs i have builder.Services.AddFluxor(options => { options.ScanAssemblies(typeof(Program).Assembly); options.UseReduxDevTools(); });

and added <Fluxor.Blazor.Web.StoreInitializer /> in top of Routes.razor

I tried to recreate how I use Fluxor in my project as close as possible in Counter.razor except in my project I use effects to make api calls

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @CounterState.Value.ClickCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [Inject]
    private IState<CounterState> CounterState { get; set; }

    [Inject]
    public IDispatcher Dispatcher { get; set; }

    protected override async Task OnInitializedAsync()
    {
        CounterState.StateChanged += OnStateChanged;

        await base.OnInitializedAsync();
    }

    protected override async ValueTask DisposeAsyncCore(bool disposing)
    {
        if (disposing)
        {
            CounterState.StateChanged -= OnStateChanged;
        }
        await base.DisposeAsyncCore(disposing);
    }

    private void OnStateChanged(object sender, EventArgs e)
    {
        StateHasChanged();
    }

    private void IncrementCount()
    {
        Dispatcher.Dispatch(new CounterIncrementAction());
    }
}

also the state and reducer are as follows

[FeatureState]
public record CounterState
{
    public int ClickCount { get; init; }
}

public static class CounterReducer
{
    [ReducerMethod]
    public static CounterState ReduceCounterIncrementAction(CounterState state, CounterIncrementAction action)
    {
        return state with 
            { ClickCount = state.ClickCount + 1 };
    }
}

I'm new to using Fluxor and Blazor so I don't know if I'm doing something wrong or does this has something to do with automatic GC maybe?

edit. I have also set rendermode to InteractiveServer

mrpmorris commented 1 month ago

Can you upload a full project to GitHub or somewhere, with instructions on how to reproduce?

sip1n commented 4 weeks ago

I added fluxor to one of my sideproject that can be found here

Best way to repro that DisposableCallback is reloading data ( I added a button to do that so there is no need to jump between home and weather page). It occurs after around 5-15 reloads.

Sometimes it also throws it while hot reloading from VS. Other way is just leave it running and it will come eventually (as this project isn't using so much memory it will take almost 2 hours, but on app that uses around 300-400MB of memory it comes around 20-30 minutes)

mrpmorris commented 4 weeks ago

Fixed, thank you. I will release shortly.

sip1n commented 4 weeks ago

Awesome! And thanks for resolving it so fast!

mrpmorris commented 4 weeks ago

Published, it should be available on NuGet soon.

Using your code I added a forced GC.Collect after ReloadData and that reproduced the problem 100% of the time.

As usual it was a case of a component descending from FluxorComponent not calling Base.DisposeAsync - but this time it was my fault :)