Skip to content

Commit 4d73c09

Browse files
authored
Merge pull request #239 from winnerspiros/copilot/fix-apk-crash-issue-yet-again
Capture every GameThread exception in native_crash.log to identify the real DrawThread killer
2 parents c011a4f + d809cba commit 4d73c09

1 file changed

Lines changed: 34 additions & 4 deletions

File tree

osu.Android/CrashDiagnostics.cs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,13 +199,24 @@ public static void InstallManagedExceptionHooks()
199199
// AppDomain.UnhandledException before aborting — so without this hook the
200200
// exception that ultimately kills the process can vanish without trace. We
201201
// record it here on every throw so the *last* recorded exception before a
202-
// SIGSEGV/SIGABRT is the candidate culprit. To avoid drowning the log in noise
203-
// we filter by exception type — only fatal-ish kinds are recorded.
202+
// SIGSEGV/SIGABRT is the candidate culprit.
203+
//
204+
// Filtering policy:
205+
// * On osu.Framework GameThreads (Draw/Update/Audio/Input): log *every*
206+
// exception. An unhandled throw on any of these threads will tear down
207+
// the process via Mono's tgkill(SIGSEGV) path with no managed trace
208+
// reaching AppDomain.UnhandledException, so we cannot afford to filter.
209+
// * On all other threads: keep the legacy "fatal-ish kinds" type filter
210+
// so the log is not flooded by routine first-chance noise (e.g. the
211+
// HidSharp / CFStringCreateWithCharacters EntryPointNotFoundException
212+
// that fires every startup on .NET TP Worker).
204213
try
205214
{
206215
AppDomain.CurrentDomain.FirstChanceException += (_, e) =>
207216
{
208-
if (e.Exception is NullReferenceException
217+
bool isGameThread = isOsuGameThread(Thread.CurrentThread.Name);
218+
219+
bool isFatalKind = e.Exception is NullReferenceException
209220
or AccessViolationException
210221
or StackOverflowException
211222
or TypeInitializationException
@@ -215,7 +226,9 @@ or BadImageFormatException
215226
or TypeLoadException
216227
or MissingMethodException
217228
or MissingFieldException
218-
or InvalidProgramException)
229+
or InvalidProgramException;
230+
231+
if (isGameThread || isFatalKind)
219232
{
220233
writeManagedException($"FirstChanceException ({e.Exception.GetType().Name})", e.Exception);
221234
}
@@ -227,6 +240,23 @@ or MissingFieldException
227240
}
228241
}
229242

243+
// osu.Framework names its game threads with stable prefixes such as
244+
// "DrawThread", "UpdateThread", "AudioThread", "InputThread", and the
245+
// tombstone we are debugging shows the comm name "Draw (GameThread)".
246+
// Match any of these so an exception thrown deep inside the renderer or
247+
// audio pipeline gets captured before Mono aborts the process.
248+
private static bool isOsuGameThread(string? name)
249+
{
250+
if (string.IsNullOrEmpty(name))
251+
return false;
252+
253+
return name.StartsWith("Draw", StringComparison.Ordinal)
254+
|| name.StartsWith("Update", StringComparison.Ordinal)
255+
|| name.StartsWith("Audio", StringComparison.Ordinal)
256+
|| name.StartsWith("Input", StringComparison.Ordinal)
257+
|| name.Contains("GameThread", StringComparison.Ordinal);
258+
}
259+
230260
/// <summary>
231261
/// Records a one-line summary of the native handler install state (sentinel exists?
232262
/// log path?) so the very first thing we see in the log on the next inspection tells

0 commit comments

Comments
 (0)