Attempt to disable rulesets that can be linked to an unhandled crash

This commit is contained in:
Bartłomiej Dach 2024-03-22 19:05:58 +01:00
parent 77660e57ea
commit c7c0330265
No known key found for this signature in database
3 changed files with 65 additions and 11 deletions

View File

@ -678,12 +678,14 @@ private void onRulesetChanged(ValueChangedEvent<RulesetInfo> r)
/// <summary>
/// Allows a maximum of one unhandled exception, per second of execution.
/// </summary>
private bool onExceptionThrown(Exception _)
private bool onExceptionThrown(Exception ex)
{
bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0;
Logger.Log($"Unhandled exception has been {(continueExecution ? $"allowed with {allowableExceptions} more allowable exceptions" : "denied")} .");
RulesetStore.TryDisableCustomRulesetsCausing(ex);
// restore the stock of allowable exceptions after a short delay.
Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions));

View File

@ -3,8 +3,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Database;
@ -13,17 +16,20 @@ namespace osu.Game.Rulesets
{
public class RealmRulesetStore : RulesetStore
{
private readonly RealmAccess realmAccess;
public override IEnumerable<RulesetInfo> AvailableRulesets => availableRulesets;
private readonly List<RulesetInfo> availableRulesets = new List<RulesetInfo>();
public RealmRulesetStore(RealmAccess realm, Storage? storage = null)
public RealmRulesetStore(RealmAccess realmAccess, Storage? storage = null)
: base(storage)
{
prepareDetachedRulesets(realm);
this.realmAccess = realmAccess;
prepareDetachedRulesets();
informUserAboutBrokenRulesets();
}
private void prepareDetachedRulesets(RealmAccess realmAccess)
private void prepareDetachedRulesets()
{
realmAccess.Write(realm =>
{
@ -143,5 +149,41 @@ private void testRulesetCompatibility(RulesetInfo rulesetInfo)
instance.CreateBeatmapProcessor(converter.Convert());
}
private void informUserAboutBrokenRulesets()
{
if (RulesetStorage == null)
return;
foreach (string brokenRulesetDll in RulesetStorage.GetFiles(@".", @"*.dll.broken"))
{
Logger.Log($"Ruleset '{Path.GetFileNameWithoutExtension(brokenRulesetDll)}' has been disabled due to causing a crash.\n\n"
+ "Please update the ruleset or report the issue to the developers of the ruleset if no updates are available.", level: LogLevel.Important);
}
}
internal void TryDisableCustomRulesetsCausing(Exception exception)
{
var stackTrace = new StackTrace(exception);
foreach (var frame in stackTrace.GetFrames())
{
var declaringAssembly = frame.GetMethod()?.DeclaringType?.Assembly;
if (declaringAssembly == null)
continue;
if (UserRulesetAssemblies.Contains(declaringAssembly))
{
string sourceLocation = declaringAssembly.Location;
string destinationLocation = Path.ChangeExtension(sourceLocation, @".dll.broken");
if (File.Exists(sourceLocation))
{
Logger.Log($"Unhandled exception traced back to custom ruleset {Path.GetFileNameWithoutExtension(sourceLocation)}. Marking as broken.");
File.Move(sourceLocation, destinationLocation);
}
}
}
}
}
}

View File

@ -18,6 +18,8 @@ public abstract class RulesetStore : IDisposable, IRulesetStore
private const string ruleset_library_prefix = @"osu.Game.Rulesets";
protected readonly Dictionary<Assembly, Type> LoadedAssemblies = new Dictionary<Assembly, Type>();
protected readonly HashSet<Assembly> UserRulesetAssemblies = new HashSet<Assembly>();
protected readonly Storage? RulesetStorage;
/// <summary>
/// All available rulesets.
@ -41,9 +43,9 @@ protected RulesetStore(Storage? storage = null)
// to load as unable to locate the game core assembly.
AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly;
var rulesetStorage = storage?.GetStorageForDirectory(@"rulesets");
if (rulesetStorage != null)
loadUserRulesets(rulesetStorage);
RulesetStorage = storage?.GetStorageForDirectory(@"rulesets");
if (RulesetStorage != null)
loadUserRulesets(RulesetStorage);
}
/// <summary>
@ -105,7 +107,11 @@ private void loadUserRulesets(Storage rulesetStorage)
var rulesets = rulesetStorage.GetFiles(@".", @$"{ruleset_library_prefix}.*.dll");
foreach (string? ruleset in rulesets.Where(f => !f.Contains(@"Tests")))
loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset));
{
var assembly = loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset));
if (assembly != null)
UserRulesetAssemblies.Add(assembly);
}
}
private void loadFromDisk()
@ -126,21 +132,25 @@ private void loadFromDisk()
}
}
private void loadRulesetFromFile(string file)
private Assembly? loadRulesetFromFile(string file)
{
string filename = Path.GetFileNameWithoutExtension(file);
if (LoadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename))
return;
return null;
try
{
addRuleset(Assembly.LoadFrom(file));
var assembly = Assembly.LoadFrom(file);
addRuleset(assembly);
return assembly;
}
catch (Exception e)
{
LogFailedLoad(filename, e);
}
return null;
}
private void addRuleset(Assembly assembly)