summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar ReinUsesLisp2020-07-21 04:26:20 -0300
committerGravatar ReinUsesLisp2020-07-28 01:47:03 -0300
commit5692c48ab7103a2051f351e08fd012fc9022d951 (patch)
tree54ce2d5cd07c319635a73a18969cb09033288b51
parentservice/sockets: Add translate functions (diff)
downloadyuzu-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.txt1
-rw-r--r--src/core/hle/service/sockets/blocking_worker.h132
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
25namespace 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 */
33template <class Service, class... Types>
34class BlockingWorker {
35 using This = BlockingWorker<Service, Types...>;
36 using WorkVariant = std::variant<std::monostate, Types...>;
37
38public:
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
93private:
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