fix deadlock when loading state with hotkeys and RAIntegration is active

hotkeys don't go through messages so unlike other methods of loading a state this caused a deadlock (load state implies memory peeking to restore state)
This commit is contained in:
CasualPokePlayer 2023-01-26 07:12:54 -08:00
parent e8dd2e94f2
commit e67e646ca5
3 changed files with 74 additions and 31 deletions

View File

@ -810,11 +810,16 @@ namespace BizHawk.Client.EmuHawk
Render(); Render();
// HACK: RAIntegration might peek at memory during messages
// we need this to allow memory access here, otherwise it will deadlock
var raMemHack = (RA as RAIntegration)?.ThisIsTheRAMemHack(); var raMemHack = (RA as RAIntegration)?.ThisIsTheRAMemHack();
raMemHack?.Enter();
CheckMessages(); CheckMessages();
if (RA is not null) raMemHack?.Dispose(); // RA == null possibly due MainForm Dispose disposing RA (which case Exit is not valid anymore)
// RA != null possibly due to RA object being created (which case raMemHack is null, as RA was null before)
if (RA is not null) raMemHack?.Exit();
if (_exitRequestPending) if (_exitRequestPending)
{ {

View File

@ -45,7 +45,11 @@ namespace BizHawk.Client.EmuHawk
// Memory may be accessed by another thread (mainly rich presence, some other things too) // Memory may be accessed by another thread (mainly rich presence, some other things too)
// and peeks for us are not thread safe, so we need to guard it // and peeks for us are not thread safe, so we need to guard it
private readonly RAMemGuard _memGuard = new(); private readonly ManualResetEventSlim _memLock = new(false);
private readonly SemaphoreSlim _memSema = new(1);
private readonly object _memSync = new();
private readonly RAMemGuard _memGuard;
private readonly RAMemAccess _memAccess;
private bool _firstRestart = true; private bool _firstRestart = true;
@ -110,6 +114,9 @@ namespace BizHawk.Client.EmuHawk
Func<Config> getConfig, ToolStripItemCollection raDropDownItems, Action shutdownRACallback) Func<Config> getConfig, ToolStripItemCollection raDropDownItems, Action shutdownRACallback)
: base(mainForm, inputManager, tools, getConfig, raDropDownItems, shutdownRACallback) : base(mainForm, inputManager, tools, getConfig, raDropDownItems, shutdownRACallback)
{ {
_memGuard = new(_memLock, _memSema, _memSync);
_memAccess = new(_memLock, _memSema, _memSync);
RA.InitClient(_mainForm.Handle, "BizHawk", VersionInfo.GetEmuVersion()); RA.InitClient(_mainForm.Handle, "BizHawk", VersionInfo.GetEmuVersion());
_isActive = () => !Emu.IsNull(); _isActive = () => !Emu.IsNull();
@ -146,6 +153,7 @@ namespace BizHawk.Client.EmuHawk
HandleHardcoreModeDisable("Loading savestates is not allowed in hardcore mode."); HandleHardcoreModeDisable("Loading savestates is not allowed in hardcore mode.");
} }
using var access = _memAccess.EnterExit();
RA.OnLoadState(path); RA.OnLoadState(path);
} }
@ -228,7 +236,7 @@ namespace BizHawk.Client.EmuHawk
public override void Update() public override void Update()
{ {
using var access = _memGuard.GetAccess(); using var access = _memAccess.EnterExit();
if (RA.HardcoreModeIsActive()) if (RA.HardcoreModeIsActive())
{ {
@ -260,7 +268,7 @@ namespace BizHawk.Client.EmuHawk
public override void OnFrameAdvance() public override void OnFrameAdvance()
{ {
using var access = _memGuard.GetAccess(); using var access = _memAccess.EnterExit();
var input = _inputManager.ControllerOutput; var input = _inputManager.ControllerOutput;
if (input.Definition.BoolButtons.Any(b => (b.Contains("Power") || b.Contains("Reset")) && input.IsPressed(b))) if (input.Definition.BoolButtons.Any(b => (b.Contains("Power") || b.Contains("Reset")) && input.IsPressed(b)))
@ -283,7 +291,7 @@ namespace BizHawk.Client.EmuHawk
} }
// FIXME: THIS IS GARBAGE // FIXME: THIS IS GARBAGE
public RAMemGuard.AccessWrapper? ThisIsTheRAMemHack() public IMonitor ThisIsTheRAMemHack()
=> _memGuard.GetAccess(); => _memAccess;
} }
} }

View File

@ -11,56 +11,86 @@ namespace BizHawk.Client.EmuHawk
{ {
public abstract partial class RetroAchievements public abstract partial class RetroAchievements
{ {
public class RAMemGuard : IMonitor, IDisposable protected class RAMemGuard : IMonitor, IDisposable
{ {
private readonly ManualResetEventSlim MemLock = new(false); private readonly ManualResetEventSlim _memLock;
private readonly SemaphoreSlim MemSema = new(1); private readonly SemaphoreSlim _memSema;
private readonly object MemSync = new(); private readonly object _memSync;
public RAMemGuard(ManualResetEventSlim memLock, SemaphoreSlim memSema, object memSync)
{
_memLock = memLock;
_memSema = memSema;
_memSync = memSync;
}
public void Enter() public void Enter()
{ {
lock (MemSync) lock (_memSync)
{ {
MemLock.Wait(); _memLock.Wait();
MemSema.Wait(); _memSema.Wait();
} }
} }
public void Exit() public void Exit()
{ {
MemSema.Release(); _memSema.Release();
} }
public void Dispose() public void Dispose()
{ {
MemLock.Dispose(); _memLock.Dispose();
MemSema.Dispose(); _memSema.Dispose();
}
}
protected class RAMemAccess : IMonitor
{
private readonly ManualResetEventSlim _memLock;
private readonly SemaphoreSlim _memSema;
private readonly object _memSync;
private int _refCount;
public RAMemAccess(ManualResetEventSlim memLock, SemaphoreSlim memSema, object memSync)
{
_memLock = memLock;
_memSema = memSema;
_memSync = memSync;
_refCount = 0;
} }
// can't be a ref struct due to ThisIsTheRAMemHack :( public void Enter()
public readonly struct AccessWrapper : IDisposable
{ {
private readonly RAMemGuard _guard; if (_refCount == 0)
internal AccessWrapper(RAMemGuard guard)
{ {
_guard = guard; _memLock.Set();
_guard.MemLock.Set();
} }
public void Dispose() _refCount++;
}
public void Exit()
{
switch (_refCount)
{ {
lock (_guard.MemSync) case <= 0:
throw new InvalidOperationException($"Invalid {nameof(_refCount)}");
case 1:
{ {
_guard.MemLock.Reset(); lock (_memSync)
_guard.MemSema.Wait(); {
_guard.MemSema.Release(); _memLock.Reset();
_memSema.Wait();
_memSema.Release();
}
break;
} }
} }
}
public AccessWrapper GetAccess() _refCount--;
=> new(this); }
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]