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:
parent
e8dd2e94f2
commit
e67e646ca5
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
Loading…
Reference in New Issue