-
Notifications
You must be signed in to change notification settings - Fork 580
[CoreCLR/NativeAOT] Use a no-GC region during Android startup #12782
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
base: main
Are you sure you want to change the base?
Changes from all commits
3edd00a
be35259
a0ee861
149aa6e
5b09e54
5c34535
5d71d43
904163a
ab4b439
fe4bff9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| using System; | ||
| using System.Threading; | ||
|
|
||
| namespace Android.Runtime; | ||
|
|
||
| sealed class StartupNoGCRegion | ||
| { | ||
| const long Budget = 24 * 1024 * 1024; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this work well on Android Arm32? I would expect TryStartNoGCRegion is always going to fail with 24MB reservation on 32-bit platforms. |
||
| // Bound the process-wide region when an app never reports that startup is fully drawn. | ||
| static readonly TimeSpan DefaultFallbackTimeout = TimeSpan.FromSeconds (10); | ||
| static readonly StartupNoGCRegion instance = new (); | ||
|
|
||
| readonly Lock sync = new (); | ||
| Timer? fallbackTimer; | ||
| State state; | ||
|
|
||
| enum State | ||
| { | ||
| NotStarted, | ||
| Active, | ||
| Ended, | ||
| } | ||
|
|
||
| internal static void Start () => instance.StartRegion (); | ||
|
|
||
| internal static void End () => instance.Finish (); | ||
|
|
||
| void StartRegion () | ||
| { | ||
| lock (sync) { | ||
| if (state != State.NotStarted) { | ||
| return; | ||
| } | ||
|
|
||
| bool started; | ||
| try { | ||
| started = GC.TryStartNoGCRegion (Budget, disallowFullBlockingGC: true); | ||
| } catch (InvalidOperationException) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You may want to catch all exceptions.
|
||
| state = State.Ended; | ||
| return; | ||
| } | ||
|
|
||
| if (!started) { | ||
| state = State.Ended; | ||
| return; | ||
| } | ||
|
|
||
| fallbackTimer = new Timer ( | ||
| static value => { | ||
| if (value is StartupNoGCRegion noGCRegion) { | ||
| noGCRegion.Finish (); | ||
| } | ||
| }, | ||
| this, | ||
| DefaultFallbackTimeout, | ||
| Timeout.InfiniteTimeSpan | ||
| ); | ||
| state = State.Active; | ||
| } | ||
| } | ||
|
|
||
| void Finish () | ||
| { | ||
| Timer? timer; | ||
| lock (sync) { | ||
| if (state != State.Active) { | ||
| return; | ||
| } | ||
|
|
||
| state = State.Ended; | ||
| timer = fallbackTimer; | ||
| fallbackTimer = null; | ||
| } | ||
|
|
||
| timer?.Dispose (); | ||
|
|
||
| try { | ||
| GC.EndNoGCRegion (); | ||
|
simonrozsival marked this conversation as resolved.
|
||
| } catch (InvalidOperationException) { | ||
| // The runtime already left the region because its budget was exhausted | ||
| // or a collection was induced. | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -558,6 +558,35 @@ void CheckAssembly (string assemblyPath, string projectDir) | |
| } | ||
| } | ||
|
|
||
| [Test] | ||
| public void StartupNoGCRegionFeatureSwitch ([Values (true, false, null)] bool? enabled) | ||
| { | ||
| const AndroidRuntime runtime = AndroidRuntime.CoreCLR; | ||
| if (IgnoreUnsupportedConfiguration (runtime, release: true)) { | ||
| return; | ||
| } | ||
|
|
||
| var proj = new XamarinAndroidApplicationProject { IsRelease = true }; | ||
| proj.SetRuntime (runtime); | ||
| // Keep the completion path reachable so it cannot accidentally retain the disabled helper. | ||
| proj.MainActivity = proj.DefaultMainActivity.Replace ( | ||
| "base.OnCreate (bundle);", | ||
| "base.OnCreate (bundle);\nReportFullyDrawn ();"); | ||
| if (enabled.HasValue) { | ||
| proj.SetProperty ("_AndroidEnableStartupNoGCRegion", enabled.Value.ToString ()); | ||
| } | ||
|
|
||
| using var b = CreateApkBuilder (); | ||
| Assert.IsTrue (b.Build (proj), "Build should have succeeded."); | ||
| using var assembly = AssemblyDefinition.ReadAssembly (BuildTest.GetLinkedPath (b, true, "Mono.Android.dll")); | ||
| var type = assembly.MainModule.GetType ("Android.Runtime.StartupNoGCRegion"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤖 Rule: Test edge cases |
||
| if (enabled != false) { | ||
| Assert.IsNotNull (type, "StartupNoGCRegion should be retained when enabled or unspecified."); | ||
| } else { | ||
| Assert.IsNull (type, "StartupNoGCRegion should be trimmed away completely when disabled."); | ||
| } | ||
| } | ||
|
|
||
| [Test] | ||
| public void AndroidUseNegotiateAuthentication ([Values (true, false, null)] bool? useNegotiateAuthentication, [Values (AndroidRuntime.CoreCLR, AndroidRuntime.NativeAOT)] AndroidRuntime runtime) | ||
| { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think this seems odd, do we already bind this? We document it somehow?!?
If we don't bind it, can we figure out why it's not bound already? Android docs here:
Then we can decide if a manual binding is appropriate, or if we need to just fix some binding bug or metadata transform instead.