diff options
| author | 2020-07-21 04:26:20 -0300 | |
|---|---|---|
| committer | 2020-07-28 01:47:03 -0300 | |
| commit | 5692c48ab7103a2051f351e08fd012fc9022d951 (patch) | |
| tree | 54ce2d5cd07c319635a73a18969cb09033288b51 | |
| parent | service/sockets: Add translate functions (diff) | |
| download | yuzu-5692c48ab7103a2051f351e08fd012fc9022d951.tar.gz yuzu-5692c48ab7103a2051f351e08fd012fc9022d951.tar.xz yuzu-5692c48ab7103a2051f351e08fd012fc9022d951.zip | |
service/sockets: Add worker abstraction to execute blocking calls asynchronously
This abstraction allows executing blocking functions (like recvfrom on a
socket configured for blocking) without blocking the service thread.
It is intended to be used with SleepClientThread.
| -rw-r--r-- | src/core/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/core/hle/service/sockets/blocking_worker.h | 132 |
2 files changed, 133 insertions, 0 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 48578ad48..b96ca9374 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -491,6 +491,7 @@ add_library(core STATIC | |||
| 491 | hle/service/sm/controller.h | 491 | hle/service/sm/controller.h |
| 492 | hle/service/sm/sm.cpp | 492 | hle/service/sm/sm.cpp |
| 493 | hle/service/sm/sm.h | 493 | hle/service/sm/sm.h |
| 494 | hle/service/sockets/blocking_worker.h | ||
| 494 | hle/service/sockets/bsd.cpp | 495 | hle/service/sockets/bsd.cpp |
| 495 | hle/service/sockets/bsd.h | 496 | hle/service/sockets/bsd.h |
| 496 | hle/service/sockets/ethc.cpp | 497 | hle/service/sockets/ethc.cpp |
diff --git a/src/core/hle/service/sockets/blocking_worker.h b/src/core/hle/service/sockets/blocking_worker.h new file mode 100644 index 000000000..7bd486530 --- /dev/null +++ b/src/core/hle/service/sockets/blocking_worker.h | |||
| @@ -0,0 +1,132 @@ | |||
| 1 | // Copyright 2020 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <atomic> | ||
| 8 | #include <memory> | ||
| 9 | #include <string> | ||
| 10 | #include <string_view> | ||
| 11 | #include <thread> | ||
| 12 | #include <variant> | ||
| 13 | |||
| 14 | #include <fmt/format.h> | ||
| 15 | |||
| 16 | #include "common/assert.h" | ||
| 17 | #include "common/microprofile.h" | ||
| 18 | #include "common/thread.h" | ||
| 19 | #include "core/core.h" | ||
| 20 | #include "core/hle/kernel/hle_ipc.h" | ||
| 21 | #include "core/hle/kernel/kernel.h" | ||
| 22 | #include "core/hle/kernel/thread.h" | ||
| 23 | #include "core/hle/kernel/writable_event.h" | ||
| 24 | |||
| 25 | namespace Service::Sockets { | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Worker abstraction to execute blocking calls on host without blocking the guest thread | ||
| 29 | * | ||
| 30 | * @tparam Service Service where the work is executed | ||
| 31 | * @tparam ...Types Types of work to execute | ||
| 32 | */ | ||
| 33 | template <class Service, class... Types> | ||
| 34 | class BlockingWorker { | ||
| 35 | using This = BlockingWorker<Service, Types...>; | ||
| 36 | using WorkVariant = std::variant<std::monostate, Types...>; | ||
| 37 | |||
| 38 | public: | ||
| 39 | /// Create a new worker | ||
| 40 | static std::unique_ptr<This> Create(Core::System& system, Service* service, | ||
| 41 | std::string_view name) { | ||
| 42 | return std::unique_ptr<This>(new This(system, service, name)); | ||
| 43 | } | ||
| 44 | |||
| 45 | ~BlockingWorker() { | ||
| 46 | while (!is_available.load(std::memory_order_relaxed)) { | ||
| 47 | // Busy wait until work is finished | ||
| 48 | std::this_thread::yield(); | ||
| 49 | } | ||
| 50 | // Monostate means to exit the thread | ||
| 51 | work = std::monostate{}; | ||
| 52 | work_event.Set(); | ||
| 53 | thread.join(); | ||
| 54 | } | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Try to capture the worker to send work after a success | ||
| 58 | * @returns True when the worker has been successfully captured | ||
| 59 | */ | ||
| 60 | bool TryCapture() { | ||
| 61 | bool expected = true; | ||
| 62 | return is_available.compare_exchange_weak(expected, false, std::memory_order_relaxed, | ||
| 63 | std::memory_order_relaxed); | ||
| 64 | } | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Send work to this worker abstraction | ||
| 68 | * @see TryCapture must be called before attempting to call this function | ||
| 69 | */ | ||
| 70 | template <class Work> | ||
| 71 | void SendWork(Work new_work) { | ||
| 72 | ASSERT_MSG(!is_available, "Trying to send work on a worker that's not captured"); | ||
| 73 | work = std::move(new_work); | ||
| 74 | work_event.Set(); | ||
| 75 | } | ||
| 76 | |||
| 77 | /// Generate a callback for @see SleepClientThread | ||
| 78 | template <class Work> | ||
| 79 | auto Callback() { | ||
| 80 | return [this](std::shared_ptr<Kernel::Thread>, Kernel::HLERequestContext& ctx, | ||
| 81 | Kernel::ThreadWakeupReason reason) { | ||
| 82 | ASSERT(reason == Kernel::ThreadWakeupReason::Signal); | ||
| 83 | std::get<Work>(work).Response(ctx); | ||
| 84 | is_available.store(true); | ||
| 85 | }; | ||
| 86 | } | ||
| 87 | |||
| 88 | /// Get kernel event that will be signalled by the worker when the host operation finishes | ||
| 89 | std::shared_ptr<Kernel::WritableEvent> KernelEvent() const { | ||
| 90 | return kernel_event; | ||
| 91 | } | ||
| 92 | |||
| 93 | private: | ||
| 94 | explicit BlockingWorker(Core::System& system, Service* service, std::string_view name) { | ||
| 95 | auto pair = Kernel::WritableEvent::CreateEventPair(system.Kernel(), std::string(name)); | ||
| 96 | kernel_event = std::move(pair.writable); | ||
| 97 | thread = std::thread([this, &system, service, name] { Run(system, service, name); }); | ||
| 98 | } | ||
| 99 | |||
| 100 | void Run(Core::System& system, Service* service, std::string_view name) { | ||
| 101 | system.RegisterHostThread(); | ||
| 102 | |||
| 103 | const std::string thread_name = fmt::format("yuzu:{}", name); | ||
| 104 | MicroProfileOnThreadCreate(thread_name.c_str()); | ||
| 105 | Common::SetCurrentThreadName(thread_name.c_str()); | ||
| 106 | |||
| 107 | bool keep_running = true; | ||
| 108 | while (keep_running) { | ||
| 109 | work_event.Wait(); | ||
| 110 | |||
| 111 | const auto visit_fn = [service, &keep_running](auto&& w) { | ||
| 112 | using T = std::decay_t<decltype(w)>; | ||
| 113 | if constexpr (std::is_same_v<T, std::monostate>) { | ||
| 114 | keep_running = false; | ||
| 115 | } else { | ||
| 116 | w.Execute(service); | ||
| 117 | } | ||
| 118 | }; | ||
| 119 | std::visit(visit_fn, work); | ||
| 120 | |||
| 121 | kernel_event->Signal(); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | std::thread thread; | ||
| 126 | WorkVariant work; | ||
| 127 | Common::Event work_event; | ||
| 128 | std::shared_ptr<Kernel::WritableEvent> kernel_event; | ||
| 129 | std::atomic_bool is_available{true}; | ||
| 130 | }; | ||
| 131 | |||
| 132 | } // namespace Service::Sockets | ||