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:
Subv 2018-02-18 13:22:19 -05:00
parent f6e548fbc0
commit 94ee8fc97b
4 changed files with 62 additions and 49 deletions

View file

@ -57,6 +57,33 @@ void ServerSession::Acquire(Thread* thread) {
pending_requesting_threads.pop_back(); pending_requesting_threads.pop_back();
} }
ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
auto& domain_message_header = context.GetDomainMessageHeader();
if (domain_message_header) {
// If there is a DomainMessageHeader, then this is CommandType "Request"
const u32 object_id{context.GetDomainMessageHeader()->object_id};
switch (domain_message_header->command) {
case IPC::DomainMessageHeader::CommandType::SendMessage:
return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x%08X", object_id);
domain_request_handlers[object_id - 1] = nullptr;
IPC::ResponseBuilder rb{context, 2};
rb.Push(RESULT_SUCCESS);
return RESULT_SUCCESS;
}
}
LOG_CRITICAL(IPC, "Unknown domain command=%d", domain_message_header->command.Value());
ASSERT(false);
}
return RESULT_SUCCESS;
}
ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) { ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
// The ServerSession received a sync request, this means that there's new data available // 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 // from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or
@ -67,46 +94,39 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process, context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process,
Kernel::g_handle_table); Kernel::g_handle_table);
// If the session has been converted to a domain, handle the doomain request ResultCode result = RESULT_SUCCESS;
// If the session has been converted to a domain, handle the domain request
if (IsDomain()) { if (IsDomain()) {
auto& domain_message_header = context.GetDomainMessageHeader(); result = HandleDomainSyncRequest(context);
if (domain_message_header) {
// If there is a DomainMessageHeader, then this is CommandType "Request"
const u32 object_id{context.GetDomainMessageHeader()->object_id};
switch (domain_message_header->command) {
case IPC::DomainMessageHeader::CommandType::SendMessage:
return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x%08X", object_id);
domain_request_handlers[object_id - 1] = nullptr;
IPC::ResponseBuilder rb{context, 2};
rb.Push(RESULT_SUCCESS);
return RESULT_SUCCESS;
}
}
LOG_CRITICAL(IPC, "Unknown domain command=%d", domain_message_header->command.Value());
ASSERT(false);
}
// If there is no domain header, the regular session handler is used // 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);
} }
// If this ServerSession has an associated HLE handler, forward the request to it. if (thread->status == THREADSTATUS_RUNNING) {
ResultCode result{RESULT_SUCCESS}; // Put the thread to sleep until the server replies, it will be awoken in
if (hle_handler != nullptr) { // svcReplyAndReceive for LLE servers.
// Attempt to translate the incoming request's command buffer. thread->status = THREADSTATUS_WAIT_IPC;
ResultCode translate_result = TranslateHLERequest(this);
if (translate_result.IsError())
return translate_result;
result = hle_handler->HandleSyncRequest(context); if (hle_handler != nullptr) {
} else { // For HLE services, we put the request threads to sleep for a short duration to
// Add the thread to the list of threads that have issued a sync request with this // simulate IPC overhead, but only if the HLE handler didn't put the thread to sleep for
// server. // other reasons like an async callback. The IPC overhead is needed to prevent
pending_requesting_threads.push_back(std::move(thread)); // 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 {
// Add the thread to the list of threads that have issued a sync request with this
// server.
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
@ -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

View file

@ -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

View file

@ -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:

View file

@ -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