Kernel/IPC: Add a small delay after each SyncRequest to prevent thread starvation.
Ported from citra PR #3091 The delay specified here is from a Nintendo 3DS, and should be measured in a Nintendo Switch. This change is enough to prevent Puyo Puyo Tetris's main thread starvation.
This commit is contained in:
parent
f6e548fbc0
commit
94ee8fc97b
|
@ -57,18 +57,7 @@ void ServerSession::Acquire(Thread* thread) {
|
||||||
pending_requesting_threads.pop_back();
|
pending_requesting_threads.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
|
ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
|
||||||
// The ServerSession received a sync request, this means that there's new data available
|
|
||||||
// from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or
|
|
||||||
// similar.
|
|
||||||
|
|
||||||
Kernel::HLERequestContext context(this);
|
|
||||||
u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress());
|
|
||||||
context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process,
|
|
||||||
Kernel::g_handle_table);
|
|
||||||
|
|
||||||
// If the session has been converted to a domain, handle the doomain request
|
|
||||||
if (IsDomain()) {
|
|
||||||
auto& domain_message_header = context.GetDomainMessageHeader();
|
auto& domain_message_header = context.GetDomainMessageHeader();
|
||||||
if (domain_message_header) {
|
if (domain_message_header) {
|
||||||
// If there is a DomainMessageHeader, then this is CommandType "Request"
|
// If there is a DomainMessageHeader, then this is CommandType "Request"
|
||||||
|
@ -91,23 +80,54 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
|
||||||
LOG_CRITICAL(IPC, "Unknown domain command=%d", domain_message_header->command.Value());
|
LOG_CRITICAL(IPC, "Unknown domain command=%d", domain_message_header->command.Value());
|
||||||
ASSERT(false);
|
ASSERT(false);
|
||||||
}
|
}
|
||||||
// If there is no domain header, the regular session handler is used
|
|
||||||
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this ServerSession has an associated HLE handler, forward the request to it.
|
ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
|
||||||
ResultCode result{RESULT_SUCCESS};
|
// The ServerSession received a sync request, this means that there's new data available
|
||||||
if (hle_handler != nullptr) {
|
// from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or
|
||||||
// Attempt to translate the incoming request's command buffer.
|
// similar.
|
||||||
ResultCode translate_result = TranslateHLERequest(this);
|
|
||||||
if (translate_result.IsError())
|
|
||||||
return translate_result;
|
|
||||||
|
|
||||||
|
Kernel::HLERequestContext context(this);
|
||||||
|
u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress());
|
||||||
|
context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process,
|
||||||
|
Kernel::g_handle_table);
|
||||||
|
|
||||||
|
ResultCode result = RESULT_SUCCESS;
|
||||||
|
// If the session has been converted to a domain, handle the domain request
|
||||||
|
if (IsDomain()) {
|
||||||
|
result = HandleDomainSyncRequest(context);
|
||||||
|
// If there is no domain header, the regular session handler is used
|
||||||
|
} else if (hle_handler != nullptr) {
|
||||||
|
// If this ServerSession has an associated HLE handler, forward the request to it.
|
||||||
result = hle_handler->HandleSyncRequest(context);
|
result = hle_handler->HandleSyncRequest(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thread->status == THREADSTATUS_RUNNING) {
|
||||||
|
// Put the thread to sleep until the server replies, it will be awoken in
|
||||||
|
// svcReplyAndReceive for LLE servers.
|
||||||
|
thread->status = THREADSTATUS_WAIT_IPC;
|
||||||
|
|
||||||
|
if (hle_handler != nullptr) {
|
||||||
|
// For HLE services, we put the request threads to sleep for a short duration to
|
||||||
|
// simulate IPC overhead, but only if the HLE handler didn't put the thread to sleep for
|
||||||
|
// other reasons like an async callback. The IPC overhead is needed to prevent
|
||||||
|
// starvation when a thread only does sync requests to HLE services while a
|
||||||
|
// lower-priority thread is waiting to run.
|
||||||
|
|
||||||
|
// This delay was approximated in a homebrew application by measuring the average time
|
||||||
|
// it takes for svcSendSyncRequest to return when performing the SetLcdForceBlack IPC
|
||||||
|
// request to the GSP:GPU service in a n3DS with firmware 11.6. The measured values have
|
||||||
|
// a high variance and vary between models.
|
||||||
|
static constexpr u64 IPCDelayNanoseconds = 39000;
|
||||||
|
thread->WakeAfterDelay(IPCDelayNanoseconds);
|
||||||
} else {
|
} else {
|
||||||
// Add the thread to the list of threads that have issued a sync request with this
|
// Add the thread to the list of threads that have issued a sync request with this
|
||||||
// server.
|
// server.
|
||||||
pending_requesting_threads.push_back(std::move(thread));
|
pending_requesting_threads.push_back(std::move(thread));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If this ServerSession does not have an HLE implementation, just wake up the threads waiting
|
// If this ServerSession does not have an HLE implementation, just wake up the threads waiting
|
||||||
// on it.
|
// on it.
|
||||||
|
@ -140,9 +160,4 @@ ServerSession::SessionPair ServerSession::CreateSessionPair(const std::string& n
|
||||||
|
|
||||||
return std::make_tuple(std::move(server_session), std::move(client_session));
|
return std::make_tuple(std::move(server_session), std::move(client_session));
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode TranslateHLERequest(ServerSession* server_session) {
|
|
||||||
// TODO(Subv): Implement this function once multiple concurrent processes are supported.
|
|
||||||
return RESULT_SUCCESS;
|
|
||||||
}
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -21,6 +21,7 @@ class ServerSession;
|
||||||
class Session;
|
class Session;
|
||||||
class SessionRequestHandler;
|
class SessionRequestHandler;
|
||||||
class Thread;
|
class Thread;
|
||||||
|
class HLERequestContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS
|
* Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS
|
||||||
|
@ -116,17 +117,12 @@ private:
|
||||||
*/
|
*/
|
||||||
static ResultVal<SharedPtr<ServerSession>> Create(std::string name = "Unknown");
|
static ResultVal<SharedPtr<ServerSession>> Create(std::string name = "Unknown");
|
||||||
|
|
||||||
|
/// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an
|
||||||
|
/// object handle.
|
||||||
|
ResultCode HandleDomainSyncRequest(Kernel::HLERequestContext& context);
|
||||||
|
|
||||||
/// When set to True, converts the session to a domain at the end of the command
|
/// When set to True, converts the session to a domain at the end of the command
|
||||||
bool convert_to_domain{};
|
bool convert_to_domain{};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs command buffer translation for an HLE IPC request.
|
|
||||||
* The command buffer from the ServerSession thread's TLS is copied into a
|
|
||||||
* buffer and all descriptors in the buffer are processed.
|
|
||||||
* TODO(Subv): Implement this function, currently we do not support multiple processes running at
|
|
||||||
* once, but once that is implemented we'll need to properly translate all descriptors
|
|
||||||
* in the command buffer.
|
|
||||||
*/
|
|
||||||
ResultCode TranslateHLERequest(ServerSession* server_session);
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -284,6 +284,7 @@ void Thread::ResumeFromWait() {
|
||||||
case THREADSTATUS_WAIT_SYNCH_ANY:
|
case THREADSTATUS_WAIT_SYNCH_ANY:
|
||||||
case THREADSTATUS_WAIT_ARB:
|
case THREADSTATUS_WAIT_ARB:
|
||||||
case THREADSTATUS_WAIT_SLEEP:
|
case THREADSTATUS_WAIT_SLEEP:
|
||||||
|
case THREADSTATUS_WAIT_IPC:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case THREADSTATUS_READY:
|
case THREADSTATUS_READY:
|
||||||
|
|
|
@ -40,6 +40,7 @@ enum ThreadStatus {
|
||||||
THREADSTATUS_READY, ///< Ready to run
|
THREADSTATUS_READY, ///< Ready to run
|
||||||
THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter
|
THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter
|
||||||
THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC
|
THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC
|
||||||
|
THREADSTATUS_WAIT_IPC, ///< Waiting for the reply from an IPC request
|
||||||
THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
|
THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
|
||||||
THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true
|
THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true
|
||||||
THREADSTATUS_DORMANT, ///< Created but not yet made ready
|
THREADSTATUS_DORMANT, ///< Created but not yet made ready
|
||||||
|
|
Reference in New Issue