diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 2dfdcb0d79..7fd2260505 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -170,6 +170,7 @@ add_library(core STATIC
     hle/kernel/server_port.h
     hle/kernel/server_session.cpp
     hle/kernel/server_session.h
+    hle/kernel/session.cpp
     hle/kernel/session.h
     hle/kernel/shared_memory.cpp
     hle/kernel/shared_memory.h
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index b9f9ae1fa5..0dc6a4a437 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -19,6 +19,7 @@
 #include "core/hle/kernel/hle_ipc.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/kernel/server_session.h"
+#include "core/hle/kernel/session.h"
 #include "core/hle/result.h"
 
 namespace IPC {
@@ -139,10 +140,9 @@ public:
             context->AddDomainObject(std::move(iface));
         } else {
             auto& kernel = Core::System::GetInstance().Kernel();
-            auto [server, client] =
-                Kernel::ServerSession::CreateSessionPair(kernel, iface->GetServiceName());
-            iface->ClientConnected(server);
+            auto [client, server] = Kernel::Session::Create(kernel, iface->GetServiceName());
             context->AddMoveObject(std::move(client));
+            iface->ClientConnected(std::move(server));
         }
     }
 
diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp
index 4637b6017b..00bb939a0b 100644
--- a/src/core/hle/kernel/client_port.cpp
+++ b/src/core/hle/kernel/client_port.cpp
@@ -9,6 +9,7 @@
 #include "core/hle/kernel/object.h"
 #include "core/hle/kernel/server_port.h"
 #include "core/hle/kernel/server_session.h"
+#include "core/hle/kernel/session.h"
 
 namespace Kernel {
 
@@ -20,28 +21,23 @@ std::shared_ptr<ServerPort> ClientPort::GetServerPort() const {
 }
 
 ResultVal<std::shared_ptr<ClientSession>> ClientPort::Connect() {
-    // Note: Threads do not wait for the server endpoint to call
-    // AcceptSession before returning from this call.
-
     if (active_sessions >= max_sessions) {
         return ERR_MAX_CONNECTIONS_REACHED;
     }
     active_sessions++;
 
-    // Create a new session pair, let the created sessions inherit the parent port's HLE handler.
-    auto [server, client] =
-        ServerSession::CreateSessionPair(kernel, server_port->GetName(), SharedFrom(this));
+    auto [client, server] = Kernel::Session::Create(kernel, name);
 
     if (server_port->HasHLEHandler()) {
-        server_port->GetHLEHandler()->ClientConnected(server);
+        server_port->GetHLEHandler()->ClientConnected(std::move(server));
     } else {
-        server_port->AppendPendingSession(server);
+        server_port->AppendPendingSession(std::move(server));
     }
 
     // Wake the threads waiting on the ServerPort
     server_port->WakeupAllWaitingThreads();
 
-    return MakeResult(client);
+    return MakeResult(std::move(client));
 }
 
 void ClientPort::ConnectionClosed() {
diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp
index 9849dbe914..4669a14adc 100644
--- a/src/core/hle/kernel/client_session.cpp
+++ b/src/core/hle/kernel/client_session.cpp
@@ -1,4 +1,4 @@
-// Copyright 2016 Citra Emulator Project
+// Copyright 2019 yuzu emulator team
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
@@ -12,22 +12,44 @@
 
 namespace Kernel {
 
-ClientSession::ClientSession(KernelCore& kernel) : Object{kernel} {}
+ClientSession::ClientSession(KernelCore& kernel) : WaitObject{kernel} {}
+
 ClientSession::~ClientSession() {
     // This destructor will be called automatically when the last ClientSession handle is closed by
     // the emulated application.
-    if (auto server = parent->server.lock()) {
-        server->ClientDisconnected();
+    if (parent->Server()) {
+        parent->Server()->ClientDisconnected();
     }
 }
 
-ResultCode ClientSession::SendSyncRequest(Thread* thread, Memory::Memory& memory) {
-    // Signal the server session that new data is available
-    if (auto server = parent->server.lock()) {
-        return server->HandleSyncRequest(SharedFrom(thread), memory);
+bool ClientSession::ShouldWait(const Thread* thread) const {
+    UNIMPLEMENTED();
+    return {};
+}
+
+void ClientSession::Acquire(Thread* thread) {
+    UNIMPLEMENTED();
+}
+
+ResultVal<std::shared_ptr<ClientSession>> ClientSession::Create(KernelCore& kernel,
+                                                                std::shared_ptr<Session> parent,
+                                                                std::string name) {
+    std::shared_ptr<ClientSession> client_session{std::make_shared<ClientSession>(kernel)};
+
+    client_session->name = std::move(name);
+    client_session->parent = std::move(parent);
+
+    return MakeResult(std::move(client_session));
+}
+
+ResultCode ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory) {
+    // Keep ServerSession alive until we're done working with it.
+    if (!parent->Server()) {
+        return ERR_SESSION_CLOSED_BY_REMOTE;
     }
 
-    return ERR_SESSION_CLOSED_BY_REMOTE;
+    // Signal the server session that new data is available
+    return parent->Server()->HandleSyncRequest(std::move(thread), memory);
 }
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h
index 484dd7bc90..b4289a9a81 100644
--- a/src/core/hle/kernel/client_session.h
+++ b/src/core/hle/kernel/client_session.h
@@ -1,4 +1,4 @@
-// Copyright 2016 Citra Emulator Project
+// Copyright 2019 yuzu emulator team
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
@@ -6,7 +6,9 @@
 
 #include <memory>
 #include <string>
-#include "core/hle/kernel/object.h"
+
+#include "core/hle/kernel/wait_object.h"
+#include "core/hle/result.h"
 
 union ResultCode;
 
@@ -18,15 +20,14 @@ namespace Kernel {
 
 class KernelCore;
 class Session;
-class ServerSession;
 class Thread;
 
-class ClientSession final : public Object {
+class ClientSession final : public WaitObject {
 public:
     explicit ClientSession(KernelCore& kernel);
     ~ClientSession() override;
 
-    friend class ServerSession;
+    friend class Session;
 
     std::string GetTypeName() const override {
         return "ClientSession";
@@ -41,9 +42,17 @@ public:
         return HANDLE_TYPE;
     }
 
-    ResultCode SendSyncRequest(Thread* thread, Memory::Memory& memory);
+    ResultCode SendSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory);
+
+    bool ShouldWait(const Thread* thread) const override;
+
+    void Acquire(Thread* thread) override;
 
 private:
+    static ResultVal<std::shared_ptr<ClientSession>> Create(KernelCore& kernel,
+                                                            std::shared_ptr<Session> parent,
+                                                            std::string name = "Unknown");
+
     /// The parent session, which links to the server endpoint.
     std::shared_ptr<Session> parent;
 
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 8b01567a84..2db28dcf03 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -74,6 +74,8 @@ std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
         thread->WakeAfterDelay(timeout);
     }
 
+    is_thread_waiting = true;
+
     return writable_event;
 }
 
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index dab37ba0d4..050ad8fd74 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -264,6 +264,18 @@ public:
 
     std::string Description() const;
 
+    Thread& GetThread() {
+        return *thread;
+    }
+
+    const Thread& GetThread() const {
+        return *thread;
+    }
+
+    bool IsThreadWaiting() const {
+        return is_thread_waiting;
+    }
+
 private:
     void ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf, bool incoming);
 
@@ -290,6 +302,7 @@ private:
     u32_le command{};
 
     std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
+    bool is_thread_waiting{};
 };
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/object.cpp b/src/core/hle/kernel/object.cpp
index 10431e94c1..2c571792b2 100644
--- a/src/core/hle/kernel/object.cpp
+++ b/src/core/hle/kernel/object.cpp
@@ -27,6 +27,7 @@ bool Object::IsWaitable() const {
     case HandleType::ResourceLimit:
     case HandleType::ClientPort:
     case HandleType::ClientSession:
+    case HandleType::Session:
         return false;
     }
 
diff --git a/src/core/hle/kernel/object.h b/src/core/hle/kernel/object.h
index bbbb4e7cce..e3391e2af8 100644
--- a/src/core/hle/kernel/object.h
+++ b/src/core/hle/kernel/object.h
@@ -29,6 +29,7 @@ enum class HandleType : u32 {
     ServerPort,
     ClientSession,
     ServerSession,
+    Session,
 };
 
 class Object : NonCopyable, public std::enable_shared_from_this<Object> {
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 1198c7a970..7825e1ec40 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -1,4 +1,4 @@
-// Copyright 2016 Citra Emulator Project
+// Copyright 2019 yuzu emulator team
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
@@ -9,6 +9,7 @@
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "core/core.h"
+#include "core/core_timing.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/client_port.h"
 #include "core/hle/kernel/client_session.h"
@@ -24,34 +25,29 @@
 namespace Kernel {
 
 ServerSession::ServerSession(KernelCore& kernel) : WaitObject{kernel} {}
-ServerSession::~ServerSession() {
-    // This destructor will be called automatically when the last ServerSession handle is closed by
-    // the emulated application.
-
-    // Decrease the port's connection count.
-    if (parent->port) {
-        parent->port->ConnectionClosed();
-    }
-}
+ServerSession::~ServerSession() = default;
 
 ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelCore& kernel,
+                                                                std::shared_ptr<Session> parent,
                                                                 std::string name) {
-    std::shared_ptr<ServerSession> server_session = std::make_shared<ServerSession>(kernel);
+    std::shared_ptr<ServerSession> session{std::make_shared<ServerSession>(kernel)};
 
-    server_session->name = std::move(name);
-    server_session->parent = nullptr;
+    session->request_event = Core::Timing::CreateEvent(
+        name, [session](u64 userdata, s64 cycles_late) { session->CompleteSyncRequest(); });
+    session->name = std::move(name);
+    session->parent = std::move(parent);
 
-    return MakeResult(std::move(server_session));
+    return MakeResult(std::move(session));
 }
 
 bool ServerSession::ShouldWait(const Thread* thread) const {
-    // Wait if we have no pending requests, or if we're currently handling a request.
-    if (auto client = parent->client.lock()) {
-        return pending_requesting_threads.empty() || currently_handling != nullptr;
+    // Closed sessions should never wait, an error will be returned from svcReplyAndReceive.
+    if (!parent->Client()) {
+        return false;
     }
 
-    // Closed sessions should never wait, an error will be returned from svcReplyAndReceive.
-    return {};
+    // Wait if we have no pending requests, or if we're currently handling a request.
+    return pending_requesting_threads.empty() || currently_handling != nullptr;
 }
 
 void ServerSession::Acquire(Thread* thread) {
@@ -128,14 +124,21 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con
     return RESULT_SUCCESS;
 }
 
-ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
-                                            Memory::Memory& memory) {
-    // 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(SharedFrom(this), thread);
-    u32* cmd_buf = (u32*)memory.GetPointer(thread->GetTLSAddress());
-    context.PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
+ResultCode ServerSession::QueueSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory) {
+    u32* cmd_buf{reinterpret_cast<u32*>(memory.GetPointer(thread->GetTLSAddress()))};
+    std::shared_ptr<Kernel::HLERequestContext> context{
+        std::make_shared<Kernel::HLERequestContext>(SharedFrom(this), std::move(thread))};
+
+    context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
+    request_queue.Push(std::move(context));
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode ServerSession::CompleteSyncRequest() {
+    ASSERT(!request_queue.Empty());
+
+    auto& context = *request_queue.Front();
 
     ResultCode result = RESULT_SUCCESS;
     // If the session has been converted to a domain, handle the domain request
@@ -147,61 +150,27 @@ ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
         result = hle_handler->HandleSyncRequest(context);
     }
 
-    if (thread->GetStatus() == ThreadStatus::Running) {
-        // Put the thread to sleep until the server replies, it will be awoken in
-        // svcReplyAndReceive for LLE servers.
-        thread->SetStatus(ThreadStatus::WaitIPC);
-
-        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 {
-            // 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
-    // on it.
-    WakeupAllWaitingThreads();
-
-    // Handle scenario when ConvertToDomain command was issued, as we must do the conversion at the
-    // end of the command such that only commands following this one are handled as domains
     if (convert_to_domain) {
         ASSERT_MSG(IsSession(), "ServerSession is already a domain instance.");
         domain_request_handlers = {hle_handler};
         convert_to_domain = false;
     }
 
+    // Some service requests require the thread to block
+    if (!context.IsThreadWaiting()) {
+        context.GetThread().ResumeFromWait();
+        context.GetThread().SetWaitSynchronizationResult(result);
+    }
+
+    request_queue.Pop();
+
     return result;
 }
 
-ServerSession::SessionPair ServerSession::CreateSessionPair(KernelCore& kernel,
-                                                            const std::string& name,
-                                                            std::shared_ptr<ClientPort> port) {
-    auto server_session = ServerSession::Create(kernel, name + "_Server").Unwrap();
-    std::shared_ptr<ClientSession> client_session = std::make_shared<ClientSession>(kernel);
-    client_session->name = name + "_Client";
-
-    std::shared_ptr<Session> parent = std::make_shared<Session>();
-    parent->client = client_session;
-    parent->server = server_session;
-    parent->port = std::move(port);
-
-    client_session->parent = parent;
-    server_session->parent = parent;
-
-    return std::make_pair(std::move(server_session), std::move(client_session));
+ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
+                                            Memory::Memory& memory) {
+    Core::System::GetInstance().CoreTiming().ScheduleEvent(20000, request_event, {});
+    return QueueSyncRequest(std::move(thread), memory);
 }
+
 } // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index 641709a45a..d6e48109e1 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright 2019 yuzu emulator team
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
@@ -9,7 +9,7 @@
 #include <utility>
 #include <vector>
 
-#include "core/hle/kernel/object.h"
+#include "common/threadsafe_queue.h"
 #include "core/hle/kernel/wait_object.h"
 #include "core/hle/result.h"
 
@@ -17,13 +17,14 @@ namespace Memory {
 class Memory;
 }
 
+namespace Core::Timing {
+struct EventType;
+}
+
 namespace Kernel {
 
-class ClientPort;
-class ClientSession;
 class HLERequestContext;
 class KernelCore;
-class ServerSession;
 class Session;
 class SessionRequestHandler;
 class Thread;
@@ -45,6 +46,12 @@ public:
     explicit ServerSession(KernelCore& kernel);
     ~ServerSession() override;
 
+    friend class Session;
+
+    static ResultVal<std::shared_ptr<ServerSession>> Create(KernelCore& kernel,
+                                                            std::shared_ptr<Session> parent,
+                                                            std::string name = "Unknown");
+
     std::string GetTypeName() const override {
         return "ServerSession";
     }
@@ -66,18 +73,6 @@ public:
         return parent.get();
     }
 
-    using SessionPair = std::pair<std::shared_ptr<ServerSession>, std::shared_ptr<ClientSession>>;
-
-    /**
-     * Creates a pair of ServerSession and an associated ClientSession.
-     * @param kernel      The kernal instance to create the session pair under.
-     * @param name        Optional name of the ports.
-     * @param client_port Optional The ClientPort that spawned this session.
-     * @return The created session tuple
-     */
-    static SessionPair CreateSessionPair(KernelCore& kernel, const std::string& name = "Unknown",
-                                         std::shared_ptr<ClientPort> client_port = nullptr);
-
     /**
      * Sets the HLE handler for the session. This handler will be called to service IPC requests
      * instead of the regular IPC machinery. (The regular IPC machinery is currently not
@@ -128,15 +123,11 @@ public:
     }
 
 private:
-    /**
-     * Creates a server session. The server session can have an optional HLE handler,
-     * which will be invoked to handle the IPC requests that this session receives.
-     * @param kernel The kernel instance to create this server session under.
-     * @param name Optional name of the server session.
-     * @return The created server session
-     */
-    static ResultVal<std::shared_ptr<ServerSession>> Create(KernelCore& kernel,
-                                                            std::string name = "Unknown");
+    /// Queues a sync request from the emulated application.
+    ResultCode QueueSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory);
+
+    /// Completes a sync request from the emulated application.
+    ResultCode CompleteSyncRequest();
 
     /// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an
     /// object handle.
@@ -166,6 +157,12 @@ private:
 
     /// The name of this session (optional)
     std::string name;
+
+    /// Core timing event used to schedule the service request at some point in the future
+    std::shared_ptr<Core::Timing::EventType> request_event;
+
+    /// Queue of scheduled service requests
+    Common::MPSCQueue<std::shared_ptr<Kernel::HLERequestContext>> request_queue;
 };
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/session.cpp b/src/core/hle/kernel/session.cpp
index 6429147442..dee6e2b72e 100644
--- a/src/core/hle/kernel/session.cpp
+++ b/src/core/hle/kernel/session.cpp
@@ -1,12 +1,36 @@
-// Copyright 2015 Citra Emulator Project
+// Copyright 2019 yuzu emulator team
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/assert.h"
+#include "core/hle/kernel/client_session.h"
+#include "core/hle/kernel/server_session.h"
 #include "core/hle/kernel/session.h"
-#include "core/hle/kernel/thread.h"
 
 namespace Kernel {
 
-Session::Session() {}
-Session::~Session() {}
+Session::Session(KernelCore& kernel) : WaitObject{kernel} {}
+Session::~Session() = default;
+
+Session::SessionPair Session::Create(KernelCore& kernel, std::string name) {
+    auto session{std::make_shared<Session>(kernel)};
+    auto client_session{Kernel::ClientSession::Create(kernel, session, name + "_Client").Unwrap()};
+    auto server_session{Kernel::ServerSession::Create(kernel, session, name + "_Server").Unwrap()};
+
+    session->name = std::move(name);
+    session->client = client_session;
+    session->server = server_session;
+
+    return std::make_pair(std::move(client_session), std::move(server_session));
+}
+
+bool Session::ShouldWait(const Thread* thread) const {
+    UNIMPLEMENTED();
+    return {};
+}
+
+void Session::Acquire(Thread* thread) {
+    UNIMPLEMENTED();
+}
+
 } // namespace Kernel
diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h
index 94395f9f55..5a9d4e9ad6 100644
--- a/src/core/hle/kernel/session.h
+++ b/src/core/hle/kernel/session.h
@@ -1,27 +1,64 @@
-// Copyright 2018 yuzu emulator team
+// Copyright 2019 yuzu emulator team
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
 #pragma once
 
-#include "core/hle/kernel/object.h"
+#include <memory>
+#include <string>
+
+#include "core/hle/kernel/wait_object.h"
+#include "core/hle/result.h"
 
 namespace Kernel {
 
 class ClientSession;
-class ClientPort;
 class ServerSession;
 
 /**
  * Parent structure to link the client and server endpoints of a session with their associated
- * client port. The client port need not exist, as is the case for portless sessions like the
- * FS File and Directory sessions. When one of the endpoints of a session is destroyed, its
- * corresponding field in this structure will be set to nullptr.
+ * client port.
  */
-class Session final {
+class Session final : public WaitObject {
 public:
-    std::weak_ptr<ClientSession> client; ///< The client endpoint of the session.
-    std::weak_ptr<ServerSession> server; ///< The server endpoint of the session.
-    std::shared_ptr<ClientPort> port; ///< The port that this session is associated with (optional).
+    explicit Session(KernelCore& kernel);
+    ~Session() override;
+
+    using SessionPair = std::pair<std::shared_ptr<ClientSession>, std::shared_ptr<ServerSession>>;
+
+    static SessionPair Create(KernelCore& kernel, std::string name = "Unknown");
+
+    std::string GetName() const override {
+        return name;
+    }
+
+    static constexpr HandleType HANDLE_TYPE = HandleType::Session;
+    HandleType GetHandleType() const override {
+        return HANDLE_TYPE;
+    }
+
+    bool ShouldWait(const Thread* thread) const override;
+
+    void Acquire(Thread* thread) override;
+
+    std::shared_ptr<ClientSession> Client() {
+        if (auto result{client.lock()}) {
+            return result;
+        }
+        return {};
+    }
+
+    std::shared_ptr<ServerSession> Server() {
+        if (auto result{server.lock()}) {
+            return result;
+        }
+        return {};
+    }
+
+private:
+    std::string name;
+    std::weak_ptr<ClientSession> client;
+    std::weak_ptr<ServerSession> server;
 };
+
 } // namespace Kernel
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index db3ae3eb88..bd25de478e 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -381,11 +381,12 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
 
     LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
 
-    system.PrepareReschedule();
+    auto thread = system.CurrentScheduler().GetCurrentThread();
+    thread->InvalidateWakeupCallback();
+    thread->SetStatus(ThreadStatus::WaitIPC);
+    system.PrepareReschedule(thread->GetProcessorID());
 
-    // TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server
-    // responds and cause a reschedule.
-    return session->SendSyncRequest(system.CurrentScheduler().GetCurrentThread(), system.Memory());
+    return session->SendSyncRequest(SharedFrom(thread), system.Memory());
 }
 
 /// Get the ID for the specified thread.
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index ec0367978c..4b79eb81d6 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -189,7 +189,7 @@ private:
         LOG_DEBUG(Service_NFP, "called");
 
         auto nfc_event = nfp_interface.GetNFCEvent();
-        if (!nfc_event->ShouldWait(Kernel::GetCurrentThread()) && !has_attached_handle) {
+        if (!nfc_event->ShouldWait(&ctx.GetThread()) && !has_attached_handle) {
             device_state = DeviceState::TagFound;
             nfc_event->Clear();
         }
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 5698f429f6..fa5347af99 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -186,7 +186,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
         UNIMPLEMENTED_MSG("command_type={}", static_cast<int>(context.GetCommandType()));
     }
 
-    context.WriteToOutgoingCommandBuffer(*Kernel::GetCurrentThread());
+    context.WriteToOutgoingCommandBuffer(context.GetThread());
 
     return RESULT_SUCCESS;
 }
@@ -201,7 +201,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
     auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(system);
     system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false);
 
-    SM::ServiceManager::InstallInterfaces(sm);
+    SM::ServiceManager::InstallInterfaces(sm, system.Kernel());
 
     Account::InstallInterfaces(system);
     AM::InstallInterfaces(*sm, nv_flinger, system);
diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp
index af2fadcef4..c45b285f8f 100644
--- a/src/core/hle/service/sm/controller.cpp
+++ b/src/core/hle/service/sm/controller.cpp
@@ -30,10 +30,7 @@ void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) {
 
     IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
     rb.Push(RESULT_SUCCESS);
-    std::shared_ptr<Kernel::ClientSession> session{ctx.Session()->GetParent()->client};
-    rb.PushMoveObjects(session);
-
-    LOG_DEBUG(Service, "session={}", session->GetObjectId());
+    rb.PushMoveObjects(ctx.Session()->GetParent()->Client());
 }
 
 void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index a0a7206bb0..88909504da 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -36,10 +36,11 @@ static ResultCode ValidateServiceName(const std::string& name) {
     return RESULT_SUCCESS;
 }
 
-void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self) {
+void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self,
+                                       Kernel::KernelCore& kernel) {
     ASSERT(self->sm_interface.expired());
 
-    auto sm = std::make_shared<SM>(self);
+    auto sm = std::make_shared<SM>(self, kernel);
     sm->InstallAsNamedPort();
     self->sm_interface = sm;
     self->controller_interface = std::make_unique<Controller>();
@@ -114,8 +115,6 @@ void SM::GetService(Kernel::HLERequestContext& ctx) {
 
     std::string name(name_buf.begin(), end);
 
-    // TODO(yuriks): Permission checks go here
-
     auto client_port = service_manager->GetServicePort(name);
     if (client_port.Failed()) {
         IPC::ResponseBuilder rb{ctx, 2};
@@ -127,14 +126,22 @@ void SM::GetService(Kernel::HLERequestContext& ctx) {
         return;
     }
 
-    auto session = client_port.Unwrap()->Connect();
-    ASSERT(session.Succeeded());
-    if (session.Succeeded()) {
-        LOG_DEBUG(Service_SM, "called service={} -> session={}", name, (*session)->GetObjectId());
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
-        rb.Push(session.Code());
-        rb.PushMoveObjects(std::move(session).Unwrap());
+    auto [client, server] = Kernel::Session::Create(kernel, name);
+
+    const auto& server_port = client_port.Unwrap()->GetServerPort();
+    if (server_port->GetHLEHandler()) {
+        server_port->GetHLEHandler()->ClientConnected(server);
+    } else {
+        server_port->AppendPendingSession(server);
     }
+
+    // Wake the threads waiting on the ServerPort
+    server_port->WakeupAllWaitingThreads();
+
+    LOG_DEBUG(Service_SM, "called service={} -> session={}", name, client->GetObjectId());
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
+    rb.Push(RESULT_SUCCESS);
+    rb.PushMoveObjects(std::move(client));
 }
 
 void SM::RegisterService(Kernel::HLERequestContext& ctx) {
@@ -178,8 +185,8 @@ void SM::UnregisterService(Kernel::HLERequestContext& ctx) {
     rb.Push(service_manager->UnregisterService(name));
 }
 
-SM::SM(std::shared_ptr<ServiceManager> service_manager)
-    : ServiceFramework("sm:", 4), service_manager(std::move(service_manager)) {
+SM::SM(std::shared_ptr<ServiceManager> service_manager, Kernel::KernelCore& kernel)
+    : ServiceFramework{"sm:", 4}, service_manager{std::move(service_manager)}, kernel{kernel} {
     static const FunctionInfo functions[] = {
         {0x00000000, &SM::Initialize, "Initialize"},
         {0x00000001, &SM::GetService, "GetService"},
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index 3de22268bb..b06d2f1030 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -18,6 +18,7 @@
 namespace Kernel {
 class ClientPort;
 class ClientSession;
+class KernelCore;
 class ServerPort;
 class SessionRequestHandler;
 } // namespace Kernel
@@ -29,7 +30,7 @@ class Controller;
 /// Interface to "sm:" service
 class SM final : public ServiceFramework<SM> {
 public:
-    explicit SM(std::shared_ptr<ServiceManager> service_manager);
+    explicit SM(std::shared_ptr<ServiceManager> service_manager, Kernel::KernelCore& kernel);
     ~SM() override;
 
 private:
@@ -39,11 +40,12 @@ private:
     void UnregisterService(Kernel::HLERequestContext& ctx);
 
     std::shared_ptr<ServiceManager> service_manager;
+    Kernel::KernelCore& kernel;
 };
 
 class ServiceManager {
 public:
-    static void InstallInterfaces(std::shared_ptr<ServiceManager> self);
+    static void InstallInterfaces(std::shared_ptr<ServiceManager> self, Kernel::KernelCore& kernel);
 
     ServiceManager();
     ~ServiceManager();