[HLE] Remove ServerBase 1ms polling (#5855)

Added a KEvent for each ServerBase which signals whenever a session is added to the _sessions list, which allows it to rerun the ReplyAndReceive with the new session handle.

This greatly reduces the presence of ServerBase on profiles, especially of games that aren't particularly busy. It should also increase responsiveness when adding session objects, as it doesn't take at most 1ms for them to start working.

It also reduces the load on KTimeManager, which could allow it to spin less often. I have noticed that a bunch of games still do 1ms waits (they actually request a bit less than 1ms), so they still end up spinning to the next millisecond. Maybe for waits like this, it could attempt to nudge the timepoints to snap to each other when they're close enough, and also snap to whole millisecond waits when close enough.
This commit is contained in:
riperiperi 2023-10-30 22:26:31 +00:00 committed by GitHub
parent 9ef0be477b
commit a16d582a10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -39,6 +39,8 @@ namespace Ryujinx.HLE.HOS.Services
private readonly KernelContext _context; private readonly KernelContext _context;
private KProcess _selfProcess; private KProcess _selfProcess;
private KThread _selfThread; private KThread _selfThread;
private KEvent _wakeEvent;
private int _wakeHandle = 0;
private readonly ReaderWriterLockSlim _handleLock = new(); private readonly ReaderWriterLockSlim _handleLock = new();
private readonly Dictionary<int, IpcService> _sessions = new(); private readonly Dictionary<int, IpcService> _sessions = new();
@ -125,6 +127,8 @@ namespace Ryujinx.HLE.HOS.Services
_handleLock.ExitWriteLock(); _handleLock.ExitWriteLock();
} }
} }
_wakeEvent.WritableEvent.Signal();
} }
private IpcService GetSessionObj(int serverSessionHandle) private IpcService GetSessionObj(int serverSessionHandle)
@ -195,9 +199,11 @@ namespace Ryujinx.HLE.HOS.Services
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0); _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48)); _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
int replyTargetHandle = 0; int replyTargetHandle = 0;
_wakeEvent = new KEvent(_context);
Result result = _selfProcess.HandleTable.GenerateHandle(_wakeEvent.ReadableEvent, out _wakeHandle);
while (true) while (true)
{ {
int portHandleCount; int portHandleCount;
@ -211,13 +217,15 @@ namespace Ryujinx.HLE.HOS.Services
portHandleCount = _ports.Count; portHandleCount = _ports.Count;
handleCount = portHandleCount + _sessions.Count; handleCount = portHandleCount + _sessions.Count + 1;
handles = ArrayPool<int>.Shared.Rent(handleCount); handles = ArrayPool<int>.Shared.Rent(handleCount);
_ports.Keys.CopyTo(handles, 0); handles[0] = _wakeHandle;
_sessions.Keys.CopyTo(handles, portHandleCount); _ports.Keys.CopyTo(handles, 1);
_sessions.Keys.CopyTo(handles, portHandleCount + 1);
} }
finally finally
{ {
@ -227,8 +235,7 @@ namespace Ryujinx.HLE.HOS.Services
} }
} }
// We still need a timeout here to allow the service to pick up and listen new sessions... var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, -1);
var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, 1000000L);
_selfThread.HandlePostSyscall(); _selfThread.HandlePostSyscall();
@ -239,7 +246,7 @@ namespace Ryujinx.HLE.HOS.Services
replyTargetHandle = 0; replyTargetHandle = 0;
if (rc == Result.Success && signaledIndex >= portHandleCount) if (rc == Result.Success && signaledIndex >= portHandleCount + 1)
{ {
// We got a IPC request, process it, pass to the appropriate service if needed. // We got a IPC request, process it, pass to the appropriate service if needed.
int signaledHandle = handles[signaledIndex]; int signaledHandle = handles[signaledIndex];
@ -253,24 +260,32 @@ namespace Ryujinx.HLE.HOS.Services
{ {
if (rc == Result.Success) if (rc == Result.Success)
{ {
// We got a new connection, accept the session to allow servicing future requests. if (signaledIndex > 0)
if (_context.Syscall.AcceptSession(out int serverSessionHandle, handles[signaledIndex]) == Result.Success)
{ {
bool handleWriteLockTaken = false; // We got a new connection, accept the session to allow servicing future requests.
try if (_context.Syscall.AcceptSession(out int serverSessionHandle, handles[signaledIndex]) == Result.Success)
{ {
handleWriteLockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite); bool handleWriteLockTaken = false;
IpcService obj = _ports[handles[signaledIndex]].Invoke(); try
_sessions.Add(serverSessionHandle, obj);
}
finally
{
if (handleWriteLockTaken)
{ {
_handleLock.ExitWriteLock(); handleWriteLockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite);
IpcService obj = _ports[handles[signaledIndex]].Invoke();
_sessions.Add(serverSessionHandle, obj);
}
finally
{
if (handleWriteLockTaken)
{
_handleLock.ExitWriteLock();
}
} }
} }
} }
else
{
// The _wakeEvent signalled, which means we have a new session.
_wakeEvent.WritableEvent.Clear();
}
} }
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0); _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
@ -499,6 +514,8 @@ namespace Ryujinx.HLE.HOS.Services
if (Interlocked.Exchange(ref _isDisposed, 1) == 0) if (Interlocked.Exchange(ref _isDisposed, 1) == 0)
{ {
_selfProcess.HandleTable.CloseHandle(_wakeHandle);
foreach (IpcService service in _sessions.Values) foreach (IpcService service in _sessions.Values)
{ {
(service as IDisposable)?.Dispose(); (service as IDisposable)?.Dispose();