diff options
author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-10-28 18:09:51 +0100 |
---|---|---|
committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-10-28 18:09:51 +0100 |
commit | 3cf9c3c9d076d5822595834bdbf5153d4e923c67 (patch) | |
tree | 13ff16c05ff047a569b90e8981bdc349fd573c6e /src/lane.h | |
parent | df60f71fe943686deed8ca0f85c6d597570ab030 (diff) | |
download | lanes-3cf9c3c9d076d5822595834bdbf5153d4e923c67.tar.gz lanes-3cf9c3c9d076d5822595834bdbf5153d4e923c67.tar.bz2 lanes-3cf9c3c9d076d5822595834bdbf5153d4e923c67.zip |
Renamed lane.h → lane.hpp, linda.h → linda.hpp, threading.h → threading.hpp
Diffstat (limited to 'src/lane.h')
-rw-r--r-- | src/lane.h | 209 |
1 files changed, 0 insertions, 209 deletions
diff --git a/src/lane.h b/src/lane.h deleted file mode 100644 index 7821112..0000000 --- a/src/lane.h +++ /dev/null | |||
@@ -1,209 +0,0 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include "cancel.hpp" | ||
4 | #include "uniquekey.hpp" | ||
5 | #include "universe.hpp" | ||
6 | |||
7 | // ################################################################################################# | ||
8 | |||
9 | // xxh64 of string "kExtendedStackTraceRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
10 | static constexpr RegistryUniqueKey kExtendedStackTraceRegKey{ 0x38147AD48FB426E2ull }; // used as registry key | ||
11 | |||
12 | /* | ||
13 | * registry[FINALIZER_REG_KEY] is either nil (no finalizers) or a table | ||
14 | * of functions that Lanes will call after the executing 'pcall' has ended. | ||
15 | * | ||
16 | * We're NOT using the GC system for finalizer mainly because providing the | ||
17 | * error (and maybe stack trace) arguments to the finalizer functions would | ||
18 | * anyways complicate that approach. | ||
19 | */ | ||
20 | // xxh64 of string "kCoroutineRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
21 | static constexpr RegistryUniqueKey kCoroutineRegKey{ 0x72B049B0D130F009ull }; | ||
22 | |||
23 | // xxh64 of string "kFinalizerRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
24 | static constexpr RegistryUniqueKey kFinalizerRegKey{ 0xFE936BFAA718FEEAull }; | ||
25 | |||
26 | // xxh64 of string "kLaneGC" generated at https://www.pelock.com/products/hash-calculator | ||
27 | static constexpr UniqueKey kLaneGC{ 0x5D6122141727F960ull }; | ||
28 | |||
29 | // xxh64 of string "kLanePointerRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
30 | static constexpr RegistryUniqueKey kLanePointerRegKey{ 0x2D8CF03FE9F0A51Aull }; // used as registry key | ||
31 | |||
32 | // xxh64 of string "debugName" generated at https://www.pelock.com/products/hash-calculator | ||
33 | static constexpr RegistryUniqueKey kLaneNameRegKey{ 0xA194E2645C57F6DDull }; | ||
34 | |||
35 | // ################################################################################################# | ||
36 | |||
37 | // The chain is ended by '(Lane*)(-1)', not nullptr: 'selfdestructFirst -> ... -> ... -> (-1)' | ||
38 | #define SELFDESTRUCT_END ((Lane*) (-1)) | ||
39 | |||
40 | static constexpr std::string_view kLaneMetatableName{ "Lane" }; | ||
41 | |||
42 | // must be a #define instead of a constexpr to benefit from compile-time string concatenation | ||
43 | #define kLanesLibName "lanes" | ||
44 | #define kLanesCoreLibName kLanesLibName ".core" | ||
45 | |||
46 | // for cancel() argument | ||
47 | enum class WakeLane | ||
48 | { | ||
49 | No, | ||
50 | Yes | ||
51 | }; | ||
52 | |||
53 | // NOTE: values to be changed by either thread, during execution, without locking, are marked "volatile" | ||
54 | class Lane | ||
55 | { | ||
56 | public: | ||
57 | /* | ||
58 | Pending: The Lua VM hasn't done anything yet. | ||
59 | Resuming: The user requested the lane to resume execution from Suspended state. | ||
60 | Suspended: returned from lua_resume, waiting for the client to request a lua_resume. | ||
61 | Running, Suspended, Waiting: Thread is inside the Lua VM. | ||
62 | Done, Error, Cancelled: Thread execution is outside the Lua VM. It can be lua_close()d. | ||
63 | */ | ||
64 | enum class Status | ||
65 | { | ||
66 | Pending, | ||
67 | Running, | ||
68 | Suspended, | ||
69 | Resuming, | ||
70 | Waiting, | ||
71 | Done, | ||
72 | Error, | ||
73 | Cancelled | ||
74 | }; | ||
75 | using enum Status; | ||
76 | |||
77 | enum class ErrorTraceLevel | ||
78 | { | ||
79 | Minimal, // no error handler function when running the lane body | ||
80 | Basic, // lane body errors caught by a error handler | ||
81 | Extended // same as above, but with more data extracted from the debug infos | ||
82 | }; | ||
83 | using enum ErrorTraceLevel; | ||
84 | |||
85 | // the thread | ||
86 | std::thread thread; // use jthread if we ever need a stop_source | ||
87 | #ifndef __PROSPERO__ | ||
88 | // a latch to wait for the lua_State to be ready | ||
89 | std::latch ready{ 1 }; | ||
90 | #else // __PROSPERO__ | ||
91 | std::atomic_flag ready{}; | ||
92 | #endif // __PROSPERO__ | ||
93 | // to wait for stop requests through thread's stop_source | ||
94 | std::mutex doneMutex; | ||
95 | std::condition_variable doneCondVar; // use condition_variable_any if waiting for a stop_token | ||
96 | // | ||
97 | // M: sub-thread OS thread | ||
98 | // S: not used | ||
99 | |||
100 | private: | ||
101 | |||
102 | mutable std::mutex debugNameMutex; | ||
103 | std::string_view debugName{ "<unnamed>" }; | ||
104 | |||
105 | public: | ||
106 | |||
107 | Universe* const U{}; | ||
108 | lua_State* S{}; // the master state of the lane | ||
109 | lua_State* L{}; // the state we run things in (either S or a lua_newthread() state if we run in coroutine mode) | ||
110 | int nresults{}; // number of results to extract from the stack (can differ from the actual number of values when in coroutine mode) | ||
111 | // | ||
112 | // M: prepares the state, and reads results | ||
113 | // S: while S is running, M must keep out of modifying the state | ||
114 | |||
115 | Status volatile status{ Pending }; | ||
116 | // | ||
117 | // M: sets to Pending (before launching) | ||
118 | // S: updates -> Running/Waiting/Suspended -> Done/Error/Cancelled | ||
119 | |||
120 | std::condition_variable* volatile waiting_on{ nullptr }; | ||
121 | // | ||
122 | // When status is Waiting, points on the linda's signal the thread waits on, else nullptr | ||
123 | |||
124 | CancelRequest volatile cancelRequest{ CancelRequest::None }; | ||
125 | // | ||
126 | // M: sets to false, flags true for cancel request | ||
127 | // S: reads to see if cancel is requested | ||
128 | |||
129 | Lane* volatile selfdestruct_next{ nullptr }; | ||
130 | // | ||
131 | // M: sets to non-nullptr if facing lane handle '__gc' cycle but the lane | ||
132 | // is still running | ||
133 | // S: cleans up after itself if non-nullptr at lane exit | ||
134 | |||
135 | // For tracking only | ||
136 | Lane* volatile tracking_next{ nullptr }; | ||
137 | |||
138 | ErrorTraceLevel const errorTraceLevel{ Basic }; | ||
139 | |||
140 | [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internalAllocator.alloc(size_); } | ||
141 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception | ||
142 | static void operator delete(void* p_, Universe* U_) { U_->internalAllocator.free(p_, sizeof(Lane)); } | ||
143 | // this one is for us, to make sure memory is freed by the correct allocator | ||
144 | static void operator delete(void* p_) { static_cast<Lane*>(p_)->U->internalAllocator.free(p_, sizeof(Lane)); } | ||
145 | |||
146 | ~Lane(); | ||
147 | Lane(Universe* U_, lua_State* L_, ErrorTraceLevel errorTraceLevel_, bool asCoroutine_); | ||
148 | |||
149 | // rule of 5 | ||
150 | Lane(Lane const&) = delete; | ||
151 | Lane(Lane&&) = delete; | ||
152 | Lane& operator=(Lane const&) = delete; | ||
153 | Lane& operator=(Lane&&) = delete; | ||
154 | |||
155 | private: | ||
156 | |||
157 | [[nodiscard]] CancelResult internalCancel(CancelRequest rq_, std::chrono::time_point<std::chrono::steady_clock> until_, WakeLane wakeLane_); | ||
158 | |||
159 | public: | ||
160 | |||
161 | CancelResult cancel(CancelOp op_, std::chrono::time_point<std::chrono::steady_clock> until_, WakeLane wakeLane_, int hookCount_); | ||
162 | void changeDebugName(StackIndex nameIdx_); | ||
163 | void closeState() | ||
164 | { | ||
165 | lua_State* const _L{ S }; | ||
166 | S = nullptr; | ||
167 | L = nullptr; | ||
168 | nresults = 0; | ||
169 | lua_close(_L); // this collects our coroutine thread at the same time | ||
170 | } | ||
171 | [[nodiscard]] std::string_view errorTraceLevelString() const; | ||
172 | [[nodiscard]] int errorHandlerCount() const noexcept | ||
173 | { | ||
174 | // don't push a error handler when in coroutine mode, as the first lua_resume wants only the function and its arguments on the stack | ||
175 | return ((errorTraceLevel == Lane::Minimal) || isCoroutine()) ? 0 : 1; | ||
176 | } | ||
177 | [[nodiscard]] bool isCoroutine() const noexcept { return S != L; } | ||
178 | [[nodiscard]] std::string_view getDebugName() const | ||
179 | { | ||
180 | std::lock_guard<std::mutex> _guard{ debugNameMutex }; | ||
181 | return debugName; | ||
182 | } | ||
183 | static int LuaErrorHandler(lua_State* L_); | ||
184 | [[nodiscard]] int pushErrorHandler() const noexcept { return (errorHandlerCount() == 0) ? 0 : (lua_pushcfunction(L, LuaErrorHandler), 1); } | ||
185 | [[nodiscard]] std::string_view pushErrorTraceLevel(lua_State* L_) const; | ||
186 | static void PushMetatable(lua_State* L_); | ||
187 | void pushStatusString(lua_State* L_) const; | ||
188 | void pushIndexedResult(lua_State* L_, int key_) const; | ||
189 | void resetResultsStorage(lua_State* L_, StackIndex self_idx_); | ||
190 | void selfdestructAdd(); | ||
191 | [[nodiscard]] bool selfdestructRemove(); | ||
192 | void securizeDebugName(lua_State* L_); | ||
193 | void startThread(int priority_); | ||
194 | [[nodiscard]] int storeResults(lua_State* L_); | ||
195 | [[nodiscard]] std::string_view threadStatusString() const; | ||
196 | // wait until the lane stops working with its state (either Suspended or Done+) | ||
197 | [[nodiscard]] bool waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_); | ||
198 | }; | ||
199 | |||
200 | // ################################################################################################# | ||
201 | |||
202 | // To allow free-running threads (longer lifespan than the handle's) | ||
203 | // 'Lane' are malloc/free'd and the handle only carries a pointer. | ||
204 | // This is not deep userdata since the handle is not portable among lanes. | ||
205 | // | ||
206 | [[nodiscard]] inline Lane* ToLane(lua_State* const L_, StackIndex const i_) | ||
207 | { | ||
208 | return *(static_cast<Lane**>(luaL_checkudata(L_, i_, kLaneMetatableName.data()))); | ||
209 | } | ||