mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-11 03:58:44 +08:00
[Kernel] Phase 4: rwlocks block in place; park-state diagnostics
Fourth phase of the 1:1 threading rework. pthread rwlocks were the last primitive on the cooperative path; with them converted, nothing calls RequestCurrentThreadBlock any more, so the continuation/wake-key machinery (including the nested-callback Thread.Sleep(1) spin the primary thread used) is unreachable by construction — the main thread now parks in place inside HLE calls like every other thread. - PthreadRwlockLockCore: drop the RequestCurrentThreadBlock branches bolted in front of the existing Monitor.Wait loops; writers and readers park in place on SyncRoot (sliced for teardown). Removed RwlockWaiter, TryAcquireBlockedRwlock, the per-lock WakeKey, and the unlock's WakeBlockedThreads dispatch. - Diagnostics: in-place-parked threads previously reported state=Running block=none, blinding the stall watchdog. GuestThreadBlocking now tracks what each parked thread waits on (recorded only on the contended slow path); the stall dump's per-thread block= field falls back to it, and a new "Stall in-place blocks" line lists every parked thread — covering the primary thread, which has no _guestThreads entry. The new diagnostics immediately paid for themselves on Silent Hill f: all 38 parked threads are workers (cond_wait/WaitEqueue); the main thread is NOT parked in any primitive — it spins in guest code calling the scePthreadGetspecific leaf (leaf fast-path calls are neither counted nor traced), i.e. it polls a TLS slot that is never filled. That relocates the game's wall from "scheduler wedge" to "missing produced value," to be chased in the bootstrap-cohort investigation. Verified (Metal, headless): void 5.6M imports, Hades 34.4M, Dead Cells 10.1M, Dream Sara, Astro 6.8M (rwlock-heavy: zero writer-conflict reports), Silent Hill / Lunar stable — zero unrecovered crashes across all eight. All 351 Libs tests pass.
This commit is contained in:
@@ -6033,6 +6033,21 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
Console.Error.WriteLine($"[LOADER][ERROR] Stall stack: [rsp]=0x{value:X16} [rsp+8]=0x{value2:X16}");
|
Console.Error.WriteLine($"[LOADER][ERROR] Stall stack: [rsp]=0x{value:X16} [rsp+8]=0x{value2:X16}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Threads blocked in place on host primitives (incl. the primary
|
||||||
|
// thread, which has no _guestThreads entry) and what they wait on.
|
||||||
|
var parked = GuestThreadBlocking.SnapshotBlockDescriptions();
|
||||||
|
if (parked.Length != 0)
|
||||||
|
{
|
||||||
|
var builder = new System.Text.StringBuilder(64 + parked.Length * 40);
|
||||||
|
builder.Append("[LOADER][ERROR] Stall in-place blocks:");
|
||||||
|
foreach (var entry in parked)
|
||||||
|
{
|
||||||
|
builder.Append($" 0x{entry.Key:X16}={entry.Value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.Error.WriteLine(builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
var threads = SnapshotGuestThreads();
|
var threads = SnapshotGuestThreads();
|
||||||
if (threads.Length != 0)
|
if (threads.Length != 0)
|
||||||
{
|
{
|
||||||
@@ -6053,12 +6068,17 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
hostContextText = $" host_tid={hostThreadId} host_ctx=unavailable";
|
hostContextText = $" host_tid={hostThreadId} host_ctx=unavailable";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Threads parked in place on a host primitive stay state=Running;
|
||||||
|
// GuestThreadBlocking records what they are parked on.
|
||||||
|
var blockText = thread.BlockReason
|
||||||
|
?? GuestThreadBlocking.DescribeBlock(thread.ThreadHandle)
|
||||||
|
?? "none";
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][ERROR] Stall guest-thread: handle=0x{thread.ThreadHandle:X16} name='{thread.Name}' " +
|
$"[LOADER][ERROR] Stall guest-thread: handle=0x{thread.ThreadHandle:X16} name='{thread.Name}' " +
|
||||||
$"state={thread.State} imports={Interlocked.Read(ref thread.ImportCount)} " +
|
$"state={thread.State} imports={Interlocked.Read(ref thread.ImportCount)} " +
|
||||||
$"nid={Volatile.Read(ref thread.LastImportNid) ?? "none"} ret=0x{Volatile.Read(ref thread.LastReturnRip):X16} " +
|
$"nid={Volatile.Read(ref thread.LastImportNid) ?? "none"} ret=0x{Volatile.Read(ref thread.LastReturnRip):X16} " +
|
||||||
$"rdi=0x{Volatile.Read(ref thread.LastImportRdi):X16} rsi=0x{Volatile.Read(ref thread.LastImportRsi):X16} " +
|
$"rdi=0x{Volatile.Read(ref thread.LastImportRdi):X16} rsi=0x{Volatile.Read(ref thread.LastImportRsi):X16} " +
|
||||||
$"rdx=0x{Volatile.Read(ref thread.LastImportRdx):X16} block={thread.BlockReason ?? "none"}{hostContextText}");
|
$"rdx=0x{Volatile.Read(ref thread.LastImportRdx):X16} block={blockText}{hostContextText}");
|
||||||
logged++;
|
logged++;
|
||||||
if (logged >= 48 && threads.Length > logged)
|
if (logged >= 48 && threads.Length > logged)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,9 +22,39 @@ public static class GuestThreadBlocking
|
|||||||
|
|
||||||
private static volatile bool _shutdownRequested;
|
private static volatile bool _shutdownRequested;
|
||||||
|
|
||||||
|
// Guest thread handle -> what it is parked on. Populated only while a
|
||||||
|
// thread is blocked (the slow path), read by the stall watchdog so
|
||||||
|
// in-place-blocked threads are not reported as opaque "Running" threads.
|
||||||
|
private static readonly System.Collections.Concurrent.ConcurrentDictionary<ulong, string> _blockDescriptions = new();
|
||||||
|
|
||||||
/// <summary>True once emulator teardown has begun; parked guest threads unwind.</summary>
|
/// <summary>True once emulator teardown has begun; parked guest threads unwind.</summary>
|
||||||
public static bool ShutdownRequested => _shutdownRequested;
|
public static bool ShutdownRequested => _shutdownRequested;
|
||||||
|
|
||||||
/// <summary>Called by the execution backend when guest execution is being torn down.</summary>
|
/// <summary>Called by the execution backend when guest execution is being torn down.</summary>
|
||||||
public static void RequestShutdown() => _shutdownRequested = true;
|
public static void RequestShutdown() => _shutdownRequested = true;
|
||||||
|
|
||||||
|
/// <summary>Records what the given guest thread is about to park on (diagnostics only).</summary>
|
||||||
|
public static void NoteBlocked(ulong guestThreadHandle, string description)
|
||||||
|
{
|
||||||
|
if (guestThreadHandle != 0)
|
||||||
|
{
|
||||||
|
_blockDescriptions[guestThreadHandle] = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Clears the parked-state note recorded by <see cref="NoteBlocked"/>.</summary>
|
||||||
|
public static void NoteUnblocked(ulong guestThreadHandle)
|
||||||
|
{
|
||||||
|
if (guestThreadHandle != 0)
|
||||||
|
{
|
||||||
|
_blockDescriptions.TryRemove(guestThreadHandle, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>What the thread is parked on, or null if it is not parked in place.</summary>
|
||||||
|
public static string? DescribeBlock(ulong guestThreadHandle) =>
|
||||||
|
_blockDescriptions.TryGetValue(guestThreadHandle, out var description) ? description : null;
|
||||||
|
|
||||||
|
/// <summary>All currently parked threads (diagnostics; covers the primary thread too).</summary>
|
||||||
|
public static KeyValuePair<ulong, string>[] SnapshotBlockDescriptions() => _blockDescriptions.ToArray();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
// event flags: OR/AND over the bit pattern, optional clear).
|
// event flags: OR/AND over the bit pattern, optional clear).
|
||||||
state.WaitingThreads++;
|
state.WaitingThreads++;
|
||||||
if (_traceEventFlag) TraceEventFlag($"wait-block handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads} guest_thread=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} ret=0x{returnRip:X16}");
|
if (_traceEventFlag) TraceEventFlag($"wait-block handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads} guest_thread=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} ret=0x{returnRip:X16}");
|
||||||
|
GuestThreadBlocking.NoteBlocked(GuestThreadExecution.CurrentGuestThreadHandle, "sceKernelWaitEventFlag");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
@@ -282,6 +283,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
|
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
|
||||||
|
GuestThreadBlocking.NoteUnblocked(GuestThreadExecution.CurrentGuestThreadHandle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -421,29 +421,37 @@ public static class KernelEventQueueCompatExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
TraceEventQueue(ctx, "wait-block", handle);
|
TraceEventQueue(ctx, "wait-block", handle);
|
||||||
lock (_eventQueueGate)
|
GuestThreadBlocking.NoteBlocked(GuestThreadExecution.CurrentGuestThreadHandle, "sceKernelWaitEqueue");
|
||||||
|
try
|
||||||
{
|
{
|
||||||
while (true)
|
lock (_eventQueueGate)
|
||||||
{
|
{
|
||||||
if ((_pendingEvents.TryGetValue(handle, out var queue) && queue.Count != 0) ||
|
while (true)
|
||||||
!_eventQueues.Contains(handle) ||
|
|
||||||
GuestThreadBlocking.ShutdownRequested)
|
|
||||||
{
|
{
|
||||||
break;
|
if ((_pendingEvents.TryGetValue(handle, out var queue) && queue.Count != 0) ||
|
||||||
}
|
!_eventQueues.Contains(handle) ||
|
||||||
|
GuestThreadBlocking.ShutdownRequested)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
var remaining = deadline - Environment.TickCount64;
|
var remaining = deadline - Environment.TickCount64;
|
||||||
if (timeoutAddress != 0 && remaining <= 0)
|
if (timeoutAddress != 0 && remaining <= 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var slice = timeoutAddress == 0
|
var slice = timeoutAddress == 0
|
||||||
? GuestThreadBlocking.WaitSliceMilliseconds
|
? GuestThreadBlocking.WaitSliceMilliseconds
|
||||||
: (int)Math.Min(remaining, GuestThreadBlocking.WaitSliceMilliseconds);
|
: (int)Math.Min(remaining, GuestThreadBlocking.WaitSliceMilliseconds);
|
||||||
_ = Monitor.Wait(_eventQueueGate, slice);
|
_ = Monitor.Wait(_eventQueueGate, slice);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
GuestThreadBlocking.NoteUnblocked(GuestThreadExecution.CurrentGuestThreadHandle);
|
||||||
|
}
|
||||||
|
|
||||||
deliveredCount = DequeueEvents(ctx, handle, eventsAddress, eventCapacity);
|
deliveredCount = DequeueEvents(ctx, handle, eventsAddress, eventCapacity);
|
||||||
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, (uint)deliveredCount))
|
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, (uint)deliveredCount))
|
||||||
|
|||||||
@@ -691,6 +691,7 @@ public static class KernelPthreadCompatExports
|
|||||||
// parks, so an unlock's PulseAll cannot be missed. Waits are
|
// parks, so an unlock's PulseAll cannot be missed. Waits are
|
||||||
// sliced only so teardown can unwind parked threads.
|
// sliced only so teardown can unwind parked threads.
|
||||||
TracePthreadMutex(ctx, "lock-block", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
|
TracePthreadMutex(ctx, "lock-block", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
|
GuestThreadBlocking.NoteBlocked(currentThreadId, "pthread_mutex_lock");
|
||||||
state.WaiterCount++;
|
state.WaiterCount++;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -711,6 +712,7 @@ public static class KernelPthreadCompatExports
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
state.WaiterCount--;
|
state.WaiterCount--;
|
||||||
|
GuestThreadBlocking.NoteUnblocked(currentThreadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
TracePthreadMutex(ctx, "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
|
TracePthreadMutex(ctx, "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
@@ -1260,22 +1262,30 @@ public static class KernelPthreadCompatExports
|
|||||||
var deadline = timed
|
var deadline = timed
|
||||||
? GuestThreadExecution.ComputeDeadlineTimestamp(GetCondWaitTimeout(timeoutUsec))
|
? GuestThreadExecution.ComputeDeadlineTimestamp(GetCondWaitTimeout(timeoutUsec))
|
||||||
: long.MaxValue;
|
: long.MaxValue;
|
||||||
while (state.SignalsPending == 0 && !GuestThreadBlocking.ShutdownRequested)
|
GuestThreadBlocking.NoteBlocked(currentThreadId, timed ? "pthread_cond_timedwait" : "pthread_cond_wait");
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var remaining = timed
|
while (state.SignalsPending == 0 && !GuestThreadBlocking.ShutdownRequested)
|
||||||
? GetRemainingTimeout(deadline)
|
|
||||||
: TimeSpan.FromMilliseconds(GuestThreadBlocking.WaitSliceMilliseconds);
|
|
||||||
if (timed && remaining <= TimeSpan.Zero)
|
|
||||||
{
|
{
|
||||||
break;
|
var remaining = timed
|
||||||
}
|
? GetRemainingTimeout(deadline)
|
||||||
|
: TimeSpan.FromMilliseconds(GuestThreadBlocking.WaitSliceMilliseconds);
|
||||||
|
if (timed && remaining <= TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (remaining > TimeSpan.FromMilliseconds(GuestThreadBlocking.WaitSliceMilliseconds))
|
if (remaining > TimeSpan.FromMilliseconds(GuestThreadBlocking.WaitSliceMilliseconds))
|
||||||
{
|
{
|
||||||
remaining = TimeSpan.FromMilliseconds(GuestThreadBlocking.WaitSliceMilliseconds);
|
remaining = TimeSpan.FromMilliseconds(GuestThreadBlocking.WaitSliceMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = Monitor.Wait(state.SyncRoot, remaining);
|
_ = Monitor.Wait(state.SyncRoot, remaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
GuestThreadBlocking.NoteUnblocked(currentThreadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.SignalsPending > 0)
|
if (state.SignalsPending > 0)
|
||||||
|
|||||||
@@ -91,10 +91,6 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
public PthreadAttrState Attributes { get; set; } = PthreadAttrState.Default;
|
public PthreadAttrState Attributes { get; set; } = PthreadAttrState.Default;
|
||||||
}
|
}
|
||||||
|
|
||||||
// On the outer class deliberately: a static on the nested state class gives it a type
|
|
||||||
// initializer that first runs on a guest thread and fail-fasts the CLR.
|
|
||||||
private static long _nextRwlockWakeId;
|
|
||||||
|
|
||||||
private sealed class PthreadRwlockState
|
private sealed class PthreadRwlockState
|
||||||
{
|
{
|
||||||
public object SyncRoot { get; } = new();
|
public object SyncRoot { get; } = new();
|
||||||
@@ -105,8 +101,6 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
public ulong WriterThreadId { get; set; }
|
public ulong WriterThreadId { get; set; }
|
||||||
public int WaitingWriters { get; set; }
|
public int WaitingWriters { get; set; }
|
||||||
|
|
||||||
// See PthreadMutexState.WakeKey.
|
|
||||||
public string WakeKey { get; } = "pthread_rwlock#" + Interlocked.Increment(ref _nextRwlockWakeId).ToString("X");
|
|
||||||
|
|
||||||
public int GetReaderCount(ulong threadId)
|
public int GetReaderCount(ulong threadId)
|
||||||
{
|
{
|
||||||
@@ -168,17 +162,6 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class RwlockWaiter : IGuestThreadBlockWaiter
|
|
||||||
{
|
|
||||||
public required PthreadRwlockState Rwlock { get; init; }
|
|
||||||
public required ulong ThreadId { get; init; }
|
|
||||||
public required bool Write { get; init; }
|
|
||||||
|
|
||||||
public int Resume() => (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
||||||
|
|
||||||
public bool TryWake() => TryAcquireBlockedRwlock(Rwlock, ThreadId, Write);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly record struct TlsKeyState(ulong Destructor);
|
private readonly record struct TlsKeyState(ulong Destructor);
|
||||||
|
|
||||||
private readonly record struct PthreadAttrState(
|
private readonly record struct PthreadAttrState(
|
||||||
@@ -1184,7 +1167,6 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(rwlock.WakeKey);
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1479,35 +1461,29 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In-place block: Monitor.Wait releases SyncRoot and parks
|
||||||
|
// atomically, so an unlock's PulseAll cannot be lost. Sliced
|
||||||
|
// only so teardown can unwind parked threads.
|
||||||
rwlock.WaitingWriters++;
|
rwlock.WaitingWriters++;
|
||||||
var transferredToScheduler = false;
|
GuestThreadBlocking.NoteBlocked(currentThreadId, "pthread_rwlock_wrlock");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (GuestThreadExecution.IsGuestThread &&
|
|
||||||
GuestThreadExecution.TryGetCurrentImportCallFrame(out _) &&
|
|
||||||
GuestThreadExecution.RequestCurrentThreadBlock(
|
|
||||||
ctx,
|
|
||||||
"pthread_rwlock_wrlock",
|
|
||||||
rwlock.WakeKey,
|
|
||||||
new RwlockWaiter { Rwlock = rwlock, ThreadId = currentThreadId, Write = true }))
|
|
||||||
{
|
|
||||||
transferredToScheduler = true;
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (rwlock.WriterThreadId != 0 || rwlock.ReaderTotalCount != 0 || rwlock.CompatWriterTotalCount != 0)
|
while (rwlock.WriterThreadId != 0 || rwlock.ReaderTotalCount != 0 || rwlock.CompatWriterTotalCount != 0)
|
||||||
{
|
{
|
||||||
Monitor.Wait(rwlock.SyncRoot);
|
if (GuestThreadBlocking.ShutdownRequested)
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Monitor.Wait(rwlock.SyncRoot, GuestThreadBlocking.WaitSliceMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
rwlock.WriterThreadId = currentThreadId;
|
rwlock.WriterThreadId = currentThreadId;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (!transferredToScheduler)
|
rwlock.WaitingWriters = Math.Max(0, rwlock.WaitingWriters - 1);
|
||||||
{
|
GuestThreadBlocking.NoteUnblocked(currentThreadId);
|
||||||
rwlock.WaitingWriters = Math.Max(0, rwlock.WaitingWriters - 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1517,20 +1493,25 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_DEADLOCK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_DEADLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (ReaderMustWaitForRwlock(rwlock, currentThreadId))
|
if (ReaderMustWaitForRwlock(rwlock, currentThreadId))
|
||||||
{
|
{
|
||||||
if (GuestThreadExecution.IsGuestThread &&
|
GuestThreadBlocking.NoteBlocked(currentThreadId, "pthread_rwlock_rdlock");
|
||||||
GuestThreadExecution.TryGetCurrentImportCallFrame(out _) &&
|
try
|
||||||
GuestThreadExecution.RequestCurrentThreadBlock(
|
|
||||||
ctx,
|
|
||||||
"pthread_rwlock_rdlock",
|
|
||||||
rwlock.WakeKey,
|
|
||||||
new RwlockWaiter { Rwlock = rwlock, ThreadId = currentThreadId, Write = false }))
|
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
while (ReaderMustWaitForRwlock(rwlock, currentThreadId))
|
||||||
}
|
{
|
||||||
|
if (GuestThreadBlocking.ShutdownRequested)
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
Monitor.Wait(rwlock.SyncRoot);
|
_ = Monitor.Wait(rwlock.SyncRoot, GuestThreadBlocking.WaitSliceMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
GuestThreadBlocking.NoteUnblocked(currentThreadId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rwlock.WriterThreadId != 0 ||
|
if (rwlock.WriterThreadId != 0 ||
|
||||||
@@ -1547,33 +1528,6 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryAcquireBlockedRwlock(PthreadRwlockState rwlock, ulong currentThreadId, bool write)
|
|
||||||
{
|
|
||||||
lock (rwlock.SyncRoot)
|
|
||||||
{
|
|
||||||
if (write)
|
|
||||||
{
|
|
||||||
if (rwlock.WriterThreadId != 0 || rwlock.ReaderTotalCount != 0 || rwlock.CompatWriterTotalCount != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DetectRwlockWriterConflict(0, rwlock, currentThreadId, "wrlock-resume");
|
|
||||||
rwlock.WriterThreadId = currentThreadId;
|
|
||||||
rwlock.WaitingWriters = Math.Max(0, rwlock.WaitingWriters - 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ReaderMustWaitForRwlock(rwlock, currentThreadId))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
rwlock.AddReader(currentThreadId);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call while holding lock(rwlock.SyncRoot): an existing reader/writer here means a
|
// Call while holding lock(rwlock.SyncRoot): an existing reader/writer here means a
|
||||||
// writer would share the rwlock with another holder — a data race.
|
// writer would share the rwlock with another holder — a data race.
|
||||||
private static void DetectRwlockWriterConflict(ulong resolvedAddress, PthreadRwlockState rwlock, ulong currentThreadId, string site)
|
private static void DetectRwlockWriterConflict(ulong resolvedAddress, PthreadRwlockState rwlock, ulong currentThreadId, string site)
|
||||||
@@ -1604,8 +1558,6 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
rwlock.GetReaderCount(currentThreadId) == 0;
|
rwlock.GetReaderCount(currentThreadId) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetRwlockWakeKey(ulong rwlockAddress) => $"pthread_rwlock:0x{rwlockAddress:X16}";
|
|
||||||
|
|
||||||
public static string? DumpRwlockStateForStall(ulong rwlockAddress)
|
public static string? DumpRwlockStateForStall(ulong rwlockAddress)
|
||||||
{
|
{
|
||||||
PthreadRwlockState? rwlock;
|
PthreadRwlockState? rwlock;
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ public static class KernelSemaphoreCompatExports
|
|||||||
TraceSemaphore($"wait-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} timeout={(timeoutAddress == 0 ? "infinite" : timeoutUsec)} waiters={semaphore.WaitingThreads} {FormatCallSite(ctx)}");
|
TraceSemaphore($"wait-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} timeout={(timeoutAddress == 0 ? "infinite" : timeoutUsec)} waiters={semaphore.WaitingThreads} {FormatCallSite(ctx)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GuestThreadBlocking.NoteBlocked(GuestThreadExecution.CurrentGuestThreadHandle, "sceKernelWaitSema");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (semaphore.Count < needCount)
|
while (semaphore.Count < needCount)
|
||||||
@@ -159,6 +160,7 @@ public static class KernelSemaphoreCompatExports
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1);
|
semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1);
|
||||||
|
GuestThreadBlocking.NoteUnblocked(GuestThreadExecution.CurrentGuestThreadHandle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user