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();
// 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();
raMemHack?.Enter();
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)
{

View File

@ -45,7 +45,11 @@ namespace BizHawk.Client.EmuHawk
// 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
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;
@ -110,6 +114,9 @@ namespace BizHawk.Client.EmuHawk
Func<Config> getConfig, ToolStripItemCollection raDropDownItems, Action 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());
_isActive = () => !Emu.IsNull();
@ -146,6 +153,7 @@ namespace BizHawk.Client.EmuHawk
HandleHardcoreModeDisable("Loading savestates is not allowed in hardcore mode.");
}
using var access = _memAccess.EnterExit();
RA.OnLoadState(path);
}
@ -228,7 +236,7 @@ namespace BizHawk.Client.EmuHawk
public override void Update()
{
using var access = _memGuard.GetAccess();
using var access = _memAccess.EnterExit();
if (RA.HardcoreModeIsActive())
{
@ -260,7 +268,7 @@ namespace BizHawk.Client.EmuHawk
public override void OnFrameAdvance()
{
using var access = _memGuard.GetAccess();
using var access = _memAccess.EnterExit();
var input = _inputManager.ControllerOutput;
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
public RAMemGuard.AccessWrapper? ThisIsTheRAMemHack()
=> _memGuard.GetAccess();
public IMonitor ThisIsTheRAMemHack()
=> _memAccess;
}
}

View File

@ -11,56 +11,86 @@ namespace BizHawk.Client.EmuHawk
{
public abstract partial class RetroAchievements
{
public class RAMemGuard : IMonitor, IDisposable
protected class RAMemGuard : IMonitor, IDisposable
{
private readonly ManualResetEventSlim MemLock = new(false);
private readonly SemaphoreSlim MemSema = new(1);
private readonly object MemSync = new();
private readonly ManualResetEventSlim _memLock;
private readonly SemaphoreSlim _memSema;
private readonly object _memSync;
public RAMemGuard(ManualResetEventSlim memLock, SemaphoreSlim memSema, object memSync)
{
_memLock = memLock;
_memSema = memSema;
_memSync = memSync;
}
public void Enter()
{
lock (MemSync)
lock (_memSync)
{
MemLock.Wait();
MemSema.Wait();
_memLock.Wait();
_memSema.Wait();
}
}
public void Exit()
{
MemSema.Release();
_memSema.Release();
}
public void Dispose()
{
MemLock.Dispose();
MemSema.Dispose();
_memLock.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 readonly struct AccessWrapper : IDisposable
public void Enter()
{
private readonly RAMemGuard _guard;
internal AccessWrapper(RAMemGuard guard)
if (_refCount == 0)
{
_guard = guard;
_guard.MemLock.Set();
_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();
_guard.MemSema.Wait();
_guard.MemSema.Release();
lock (_memSync)
{
_memLock.Reset();
_memSema.Wait();
_memSema.Release();
}
break;
}
}
}
public AccessWrapper GetAccess()
=> new(this);
_refCount--;
}
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]