hle: service: bsd: Update to work with service threads, removing SleepClientThread.

This commit is contained in:
bunnei 2020-12-11 16:44:27 -08:00
parent 0c81b83ca9
commit 87d6588cb5
4 changed files with 45 additions and 250 deletions

View File

@ -502,7 +502,6 @@ add_library(core STATIC
hle/service/sm/controller.h
hle/service/sm/sm.cpp
hle/service/sm/sm.h
hle/service/sockets/blocking_worker.h
hle/service/sockets/bsd.cpp
hle/service/sockets/bsd.h
hle/service/sockets/ethc.cpp

View File

@ -1,161 +0,0 @@
// Copyright 2020 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <memory>
#include <string>
#include <string_view>
#include <thread>
#include <variant>
#include <vector>
#include <fmt/format.h>
#include "common/assert.h"
#include "common/microprofile.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/writable_event.h"
namespace Service::Sockets {
/**
* Worker abstraction to execute blocking calls on host without blocking the guest thread
*
* @tparam Service Service where the work is executed
* @tparam Types Types of work to execute
*/
template <class Service, class... Types>
class BlockingWorker {
using This = BlockingWorker<Service, Types...>;
using WorkVariant = std::variant<std::monostate, Types...>;
public:
/// Create a new worker
static std::unique_ptr<This> Create(Core::System& system, Service* service,
std::string_view name) {
return std::unique_ptr<This>(new This(system, service, name));
}
~BlockingWorker() {
while (!is_available.load(std::memory_order_relaxed)) {
// Busy wait until work is finished
std::this_thread::yield();
}
// Monostate means to exit the thread
work = std::monostate{};
work_event.Set();
thread.join();
}
/**
* Try to capture the worker to send work after a success
* @returns True when the worker has been successfully captured
*/
bool TryCapture() {
bool expected = true;
return is_available.compare_exchange_weak(expected, false, std::memory_order_relaxed,
std::memory_order_relaxed);
}
/**
* Send work to this worker abstraction
* @see TryCapture must be called before attempting to call this function
*/
template <class Work>
void SendWork(Work new_work) {
ASSERT_MSG(!is_available, "Trying to send work on a worker that's not captured");
work = std::move(new_work);
work_event.Set();
}
/// Generate a callback for @see SleepClientThread
template <class Work>
auto Callback() {
return [this](std::shared_ptr<Kernel::Thread>, Kernel::HLERequestContext& ctx,
Kernel::ThreadWakeupReason reason) {
ASSERT(reason == Kernel::ThreadWakeupReason::Signal);
std::get<Work>(work).Response(ctx);
is_available.store(true);
};
}
/// Get kernel event that will be signalled by the worker when the host operation finishes
std::shared_ptr<Kernel::WritableEvent> KernelEvent() const {
return kernel_event;
}
private:
explicit BlockingWorker(Core::System& system, Service* service, std::string_view name) {
auto pair = Kernel::WritableEvent::CreateEventPair(system.Kernel(), std::string(name));
kernel_event = std::move(pair.writable);
thread = std::thread([this, &system, service, name] { Run(system, service, name); });
}
void Run(Core::System& system, Service* service, std::string_view name) {
system.RegisterHostThread();
const std::string thread_name = fmt::format("yuzu:{}", name);
MicroProfileOnThreadCreate(thread_name.c_str());
Common::SetCurrentThreadName(thread_name.c_str());
bool keep_running = true;
while (keep_running) {
work_event.Wait();
const auto visit_fn = [service, &keep_running]<typename T>(T&& w) {
if constexpr (std::is_same_v<std::decay_t<T>, std::monostate>) {
keep_running = false;
} else {
w.Execute(service);
}
};
std::visit(visit_fn, work);
kernel_event->Signal();
}
}
std::thread thread;
WorkVariant work;
Common::Event work_event;
std::shared_ptr<Kernel::WritableEvent> kernel_event;
std::atomic_bool is_available{true};
};
template <class Service, class... Types>
class BlockingWorkerPool {
using Worker = BlockingWorker<Service, Types...>;
public:
explicit BlockingWorkerPool(Core::System& system_, Service* service_)
: system{system_}, service{service_} {}
/// Returns a captured worker thread, creating new ones if necessary
Worker* CaptureWorker() {
for (auto& worker : workers) {
if (worker->TryCapture()) {
return worker.get();
}
}
auto new_worker = Worker::Create(system, service, fmt::format("BSD:{}", workers.size()));
[[maybe_unused]] const bool success = new_worker->TryCapture();
ASSERT(success);
return workers.emplace_back(std::move(new_worker)).get();
}
private:
Core::System& system;
Service* const service;
std::vector<std::unique_ptr<Worker>> workers;
};
} // namespace Service::Sockets

View File

@ -178,8 +178,7 @@ void BSD::Poll(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. nfds={} timeout={}", nfds, timeout);
ExecuteWork(ctx, "BSD:Poll", timeout != 0,
PollWork{
ExecuteWork(ctx, PollWork{
.nfds = nfds,
.timeout = timeout,
.read_buffer = ctx.ReadBuffer(),
@ -193,8 +192,7 @@ void BSD::Accept(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={}", fd);
ExecuteWork(ctx, "BSD:Accept", IsBlockingSocket(fd),
AcceptWork{
ExecuteWork(ctx, AcceptWork{
.fd = fd,
.write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
});
@ -215,8 +213,7 @@ void BSD::Connect(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize());
ExecuteWork(ctx, "BSD:Connect", IsBlockingSocket(fd),
ConnectWork{
ExecuteWork(ctx, ConnectWork{
.fd = fd,
.addr = ctx.ReadBuffer(),
});
@ -327,8 +324,7 @@ void BSD::Recv(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize());
ExecuteWork(ctx, "BSD:Recv", IsBlockingSocket(fd),
RecvWork{
ExecuteWork(ctx, RecvWork{
.fd = fd,
.flags = flags,
.message = std::vector<u8>(ctx.GetWriteBufferSize()),
@ -344,8 +340,7 @@ void BSD::RecvFrom(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={} addrlen={}", fd, flags,
ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1));
ExecuteWork(ctx, "BSD:RecvFrom", IsBlockingSocket(fd),
RecvFromWork{
ExecuteWork(ctx, RecvFromWork{
.fd = fd,
.flags = flags,
.message = std::vector<u8>(ctx.GetWriteBufferSize(0)),
@ -361,8 +356,7 @@ void BSD::Send(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetReadBufferSize());
ExecuteWork(ctx, "BSD:Send", IsBlockingSocket(fd),
SendWork{
ExecuteWork(ctx, SendWork{
.fd = fd,
.flags = flags,
.message = ctx.ReadBuffer(),
@ -377,8 +371,7 @@ void BSD::SendTo(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} flags=0x{} len={} addrlen={}", fd, flags,
ctx.GetReadBufferSize(0), ctx.GetReadBufferSize(1));
ExecuteWork(ctx, "BSD:SendTo", IsBlockingSocket(fd),
SendToWork{
ExecuteWork(ctx, SendToWork{
.fd = fd,
.flags = flags,
.message = ctx.ReadBuffer(0),
@ -392,8 +385,7 @@ void BSD::Write(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} len={}", fd, ctx.GetReadBufferSize());
ExecuteWork(ctx, "BSD:Write", IsBlockingSocket(fd),
SendWork{
ExecuteWork(ctx, SendWork{
.fd = fd,
.flags = 0,
.message = ctx.ReadBuffer(),
@ -410,24 +402,9 @@ void BSD::Close(Kernel::HLERequestContext& ctx) {
}
template <typename Work>
void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason,
bool is_blocking, Work work) {
if (!is_blocking) {
void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, Work work) {
work.Execute(this);
work.Response(ctx);
return;
}
// Signal a dummy response to make IPC validation happy
// This will be overwritten by the SleepClientThread callback
work.Response(ctx);
auto worker = worker_pool.CaptureWorker();
ctx.SleepClientThread(std::string(sleep_reason), std::numeric_limits<u64>::max(),
worker->Callback<Work>(), worker->KernelEvent());
worker->SendWork(std::move(work));
}
std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protocol) {
@ -807,18 +784,6 @@ bool BSD::IsFileDescriptorValid(s32 fd) const noexcept {
return true;
}
bool BSD::IsBlockingSocket(s32 fd) const noexcept {
// Inform invalid sockets as non-blocking
// This way we avoid using a worker thread as it will fail without blocking host
if (fd > static_cast<s32>(MAX_FD) || fd < 0) {
return false;
}
if (!file_descriptors[fd]) {
return false;
}
return (file_descriptors[fd]->flags & FLAG_O_NONBLOCK) != 0;
}
void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept {
IPC::ResponseBuilder rb{ctx, 4};
@ -827,8 +792,7 @@ void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) co
rb.PushEnum(bsd_errno);
}
BSD::BSD(Core::System& system_, const char* name)
: ServiceFramework{system_, name}, worker_pool{system_, this} {
BSD::BSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BSD::RegisterClient, "RegisterClient"},

View File

@ -11,7 +11,6 @@
#include "common/common_types.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sockets/blocking_worker.h"
#include "core/hle/service/sockets/sockets.h"
namespace Core {
@ -138,8 +137,7 @@ private:
void Close(Kernel::HLERequestContext& ctx);
template <typename Work>
void ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason,
bool is_blocking, Work work);
void ExecuteWork(Kernel::HLERequestContext& ctx, Work work);
std::pair<s32, Errno> SocketImpl(Domain domain, Type type, Protocol protocol);
std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer,
@ -163,15 +161,10 @@ private:
s32 FindFreeFileDescriptorHandle() noexcept;
bool IsFileDescriptorValid(s32 fd) const noexcept;
bool IsBlockingSocket(s32 fd) const noexcept;
void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept;
std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
BlockingWorkerPool<BSD, PollWork, AcceptWork, ConnectWork, RecvWork, RecvFromWork, SendWork,
SendToWork>
worker_pool;
};
class BSDCFG final : public ServiceFramework<BSDCFG> {