Search code examples
c#wpf.net-corereactiveui

ReactiveUI ViewModel memory leak


I am having really weird issues with memory leak and I cannot find source after long hours of testing, debugging and trial/error attempts.

The source of memory leak is reloading object base on push from subject. On which part I load current database values and add it to SourceCache.

This part is getting the push, to initiate reload

this.databaseSignalRClient
                .UpdatedObservable<JobLine>()
                .WhereNotNull()
                .Where(x => JobLinesCache.Lookup(x.Id).HasValue)
                .SelectMany(LoadJobLine)
                .ObserveOn(RxApp.MainThreadScheduler)
                .Subscribe(jl => JobLinesCache.AddOrUpdate(jl));

Here I have the LoadJobLine func

        private async Task<JobLine> LoadJobLine(DbNotification dbNotification)
        {
            using var repo = Locator.Current.GetService<IWmsRepository>();
            return await repo.Job.JobLineRepository.FindAsync(dbNotification.Id);
        }

And this is how I provide the ViewModels to UI:

        private readonly ReadOnlyObservableCollection<JobLineViewModel> jobLineViewModels;
        public ReadOnlyObservableCollection<JobLineViewModel> JobLineViewModels => jobLineViewModels;
            JobLinesCache.Connect()
            .SortBy(jl => jl.CaseNo)
            .Transform(jl => new JobLineViewModel(jl))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Bind(out jobLineViewModels)
            .Subscribe();

This is the created ViewModel:

    public class JobLineViewModel : ReactiveObject
    {

        public JobLineViewModel(
            JobLine jobLine
            )
        {
            JobLine = jobLine;
        }


        [Reactive]
        public JobLine JobLine { get; private set; }

        [Reactive]
        public string ContainingItemsString { get; private set; }
    }

At last binding collection to UI:

            this.WhenActivated(d =>
            {
                this.OneWayBind(ViewModel, vm => vm.JobLineViewModels, v => v.TestIC.ItemsSource)
                .DisposeWith(d);
            });

I did test out virtualizing stack panel and without.

            <ItemsControl x:Name="TestIC">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>

And for the issue. The JobLineViewModel is for some reason not getting garbage collected. I simulate it by pinging the signalR update every 10ms.

enter image description here enter image description here

I am seriously clueless what is making the JobLineView staying alive. I can post code of it as well, but I dont see any issue...:

<rx:ReactiveUserControl x:Class="Mtc.UserControls.JobLineView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Mtc.UserControls"
             mc:Ignorable="d" 
                              x:TypeArguments="vm:JobLineViewModel"
             xmlns:rx="http://reactiveui.net"
                        xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
             xmlns:vm="clr-namespace:Wms.Mtc.ViewModels;assembly=Wms.Mtc"
             d:DesignHeight="450" d:DesignWidth="800">
 
        <md:Card Margin="20,10">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto" />
                <RowDefinition Height="auto" />
            </Grid.RowDefinitions>

            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="auto" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                <Viewbox Margin="10">
                    <TextBlock x:Name="CaseNo_TextBlock" VerticalAlignment="Center" HorizontalAlignment="Center" />
                </Viewbox>
                <Grid Margin="10" Grid.Column="1">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="auto" />
                        <RowDefinition Height="auto" />
                        <RowDefinition Height="auto" />
                    </Grid.RowDefinitions>
                    <TextBlock Text="D01 D01-020-0B-21" Grid.Row="0" HorizontalAlignment="Center" />
                    <!--<md:PackIcon Kind="ArrowBottom" Grid.Row="1" HorizontalAlignment="Center" Width="45" Height="45" />-->
                    <TextBlock Text="D09" Grid.Row="2" HorizontalAlignment="Center" />
                </Grid>
            </Grid>
            <TextBlock x:Name="Items_TextBlock" FontSize="28" HorizontalAlignment="Center" Margin="5" Grid.Row="1" />
        </Grid>
        </md:Card>
</rx:ReactiveUserControl>

and code behind:

    public partial class JobLineView : ReactiveUserControl<JobLineViewModel>
    {
        public JobLineView()
        {
            InitializeComponent();
            this.WhenActivated(d =>
            {
                this.OneWayBind(ViewModel, vm => vm.JobLine, v => v.CaseNo_TextBlock.Text, jl=>jl.CaseNo)
                .DisposeWith(d);

         /*       this.OneWayBind(ViewModel, vm => vm.JobLine.Status, v => v.CaseNo_TextBlock.Foreground, status=> JobLineStatusToBrushConverter.Instance.Convert(status,null,null,null))
                .DisposeWith(d);

                this.OneWayBind(ViewModel, vm => vm.JobLine, v => v.Items_TextBlock.Text, jl=> JobLineToItemsStringConverter.Instance.Convert(jl, null,null,null))
                .DisposeWith(d);*/
            });
        }
    }

I will be gratefull for any idea. This one is really tricky and I dont know how to solve it.

I am also adding screenshot of the managed memory: enter image description here

Thanks

-----EDIT

I did strip whole View of everything, to dodge potential issue in MaterialDesign: enter image description here enter image description here

But the issue persist, gotta be something with ReactiveUI, but what...

Also this is JobLine class, which is getting passed to the VM:

    public class JobLine : ReactiveObject
    {
        [Reactive]
        public string CaseNo { get; set; }
        [Reactive]
        public decimal? CreatedBy { get; set; }
        [Reactive]
        public DateTime? CreatedDate { get; set; }
        [Reactive]
        public string FromInventory { get; set; }
        [Reactive]
        public string FromLocator { get; set; }
        [Reactive]
        public decimal Id { get; set; }
        [Reactive]
        public decimal? JobHeaderId { get; set; }
        [Reactive]
        public decimal? MachineId { get; set; }
        [Reactive]
        public decimal? PickingOrder { get; set; }
        [Reactive]
        public decimal? Printed { get; set; }
        [Reactive]
        public decimal? RoutingId { get; set; }
        [Reactive]
        public decimal? SourceLineId { get; set; }
        [Reactive]
        public decimal? Status { get; set; }
        [Reactive]
        public string ToInventory { get; set; }
        [Reactive]
        public string ToLocator { get; set; }
        [Reactive]
        public decimal? UpdatedBy { get; set; }
        [Reactive]
        public DateTime? UpdatedDate { get; set; }
        [Reactive]
        public decimal? Valid { get; set; }
        [Reactive]
        public string WindowsUser { get; set; }
    }

After eliminating every factor possible (stripped the view and view model empty of any binding, or material design stuff (icon) etc) the issue still persist. Something is keeping the View alive in memory, altogether with the ViewModel. While not being in the visual tree anymore. enter image description here

Any ideas are welcome.


Solution

  • The bug was reported and fixed here in library repository: https://github.com/reactiveui/ReactiveUI/issues/3091