1+ using System . Runtime . CompilerServices ;
2+ using System . Windows . Input ;
3+
4+ namespace Maui . Controls . Sample . Issues ;
5+
6+ [ Issue ( IssueTracker . Github , 37635 , "BackButtonBehavior.Command retains discarded behaviors" , PlatformAffected . All ) ]
7+ public class Issue37635 : Shell
8+ {
9+ public Issue37635 ( )
10+ {
11+ Items . Add ( new ShellContent
12+ {
13+ Title = "Issue 37635" ,
14+ ContentTemplate = new DataTemplate ( ( ) => new Issue37635Page ( ) )
15+ } ) ;
16+ }
17+ }
18+
19+ file class Issue37635Page : ContentPage
20+ {
21+ const int CohortSize = 30 ;
22+ readonly Issue37635Command _sharedCommand = new ( ) ;
23+ readonly Label _resultLabel ;
24+
25+ public Issue37635Page ( )
26+ {
27+ Title = "BackButtonBehavior leak" ;
28+
29+ _resultLabel = new Label
30+ {
31+ AutomationId = "ResultLabel" ,
32+ Text = "Run the test to check retained payloads."
33+ } ;
34+
35+ var runButton = new Button
36+ {
37+ AutomationId = "RunTestButton" ,
38+ Text = "Run test"
39+ } ;
40+ runButton . Clicked += OnRunTestClicked ;
41+
42+ Content = new VerticalStackLayout
43+ {
44+ Padding = 24 ,
45+ Spacing = 16 ,
46+ Children =
47+ {
48+ new Label
49+ {
50+ FontAttributes = FontAttributes . Bold ,
51+ FontSize = 22 ,
52+ Text = "BackButtonBehavior.Command memory test"
53+ } ,
54+ new Label
55+ {
56+ Text = "Creates discarded behaviors with 1 MB BindingContext payloads. The shared command must not retain them."
57+ } ,
58+ runButton ,
59+ _resultLabel
60+ }
61+ } ;
62+ }
63+
64+ async void OnRunTestClicked ( object sender , EventArgs e )
65+ {
66+ _resultLabel . Text = "Collecting..." ;
67+
68+ WeakReference [ ] control = CreateCohort ( null ) ;
69+ WeakReference [ ] command = CreateCohort ( _sharedCommand ) ;
70+
71+ try
72+ {
73+ await GarbageCollectionHelper . WaitForGC ( 5000 , control . Concat ( command ) . ToArray ( ) ) ;
74+ }
75+ catch
76+ {
77+ // Report retained references so the UI test fails with the observed counts.
78+ }
79+
80+ int controlAlive = control . Count ( reference => reference . IsAlive ) ;
81+ int commandAlive = command . Count ( reference => reference . IsAlive ) ;
82+ _resultLabel . Text = $ "Control: { controlAlive } /{ CohortSize } ; Command: { commandAlive } /{ CohortSize } ";
83+ GC . KeepAlive ( _sharedCommand ) ;
84+ }
85+
86+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
87+ static WeakReference [ ] CreateCohort ( ICommand command )
88+ {
89+ var references = new WeakReference [ CohortSize ] ;
90+
91+ for ( int index = 0 ; index < CohortSize ; index ++ )
92+ {
93+ references [ index ] = CreatePayloadReference ( command ) ;
94+ }
95+
96+ return references ;
97+ }
98+
99+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
100+ static WeakReference CreatePayloadReference ( ICommand command )
101+ {
102+ var payload = new Issue37635Payload ( ) ;
103+ var behavior = new BackButtonBehavior
104+ {
105+ BindingContext = payload ,
106+ Command = command
107+ } ;
108+
109+ return new WeakReference ( payload ) ;
110+ }
111+ }
112+
113+ file sealed class Issue37635Payload
114+ {
115+ readonly byte [ ] _bytes = new byte [ 1024 * 1024 ] ;
116+ }
117+
118+ file sealed class Issue37635Command : ICommand
119+ {
120+ public event EventHandler CanExecuteChanged ;
121+
122+ public bool CanExecute ( object parameter ) => true ;
123+
124+ public void Execute ( object parameter )
125+ {
126+ }
127+
128+ public void RaiseCanExecuteChanged ( ) => CanExecuteChanged ? . Invoke ( this , EventArgs . Empty ) ;
129+ }
0 commit comments