diff options
Diffstat (limited to '')
-rw-r--r-- | src/cancel.cpp | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/src/cancel.cpp b/src/cancel.cpp new file mode 100644 index 0000000..b3e52b6 --- /dev/null +++ b/src/cancel.cpp | |||
@@ -0,0 +1,282 @@ | |||
1 | /* | ||
2 | -- | ||
3 | -- CANCEL.CPP | ||
4 | -- | ||
5 | -- Lane cancellation support | ||
6 | -- | ||
7 | -- Author: Benoit Germain <bnt.germain@gmail.com> | ||
8 | -- | ||
9 | --[[ | ||
10 | =============================================================================== | ||
11 | |||
12 | Copyright (C) 2011-2024 Benoit Germain <bnt.germain@gmail.com> | ||
13 | |||
14 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
15 | of this software and associated documentation files (the "Software"), to deal | ||
16 | in the Software without restriction, including without limitation the rights | ||
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
18 | copies of the Software, and to permit persons to whom the Software is | ||
19 | furnished to do so, subject to the following conditions: | ||
20 | |||
21 | The above copyright notice and this permission notice shall be included in | ||
22 | all copies or substantial portions of the Software. | ||
23 | |||
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
30 | THE SOFTWARE. | ||
31 | |||
32 | =============================================================================== | ||
33 | ]]-- | ||
34 | */ | ||
35 | |||
36 | #include "cancel.h" | ||
37 | |||
38 | #include "lanes_private.h" | ||
39 | #include "threading.h" | ||
40 | #include "tools.h" | ||
41 | |||
42 | // ################################################################################################ | ||
43 | // ################################################################################################ | ||
44 | |||
45 | /* | ||
46 | * Check if the thread in question ('L') has been signalled for cancel. | ||
47 | * | ||
48 | * Called by cancellation hooks and/or pending Linda operations (because then | ||
49 | * the check won't affect performance). | ||
50 | * | ||
51 | * Returns CANCEL_SOFT/HARD if any locks are to be exited, and 'raise_cancel_error()' called, | ||
52 | * to make execution of the lane end. | ||
53 | */ | ||
54 | [[nodiscard]] static inline CancelRequest cancel_test(lua_State* L) | ||
55 | { | ||
56 | Lane* const lane{ LANE_POINTER_REGKEY.readLightUserDataValue<Lane>(L) }; | ||
57 | // 'lane' is nullptr for the original main state (and no-one can cancel that) | ||
58 | return lane ? lane->cancel_request : CancelRequest::None; | ||
59 | } | ||
60 | |||
61 | // ################################################################################################ | ||
62 | |||
63 | //--- | ||
64 | // bool = cancel_test() | ||
65 | // | ||
66 | // Available inside the global namespace of lanes | ||
67 | // returns a boolean saying if a cancel request is pending | ||
68 | // | ||
69 | LUAG_FUNC( cancel_test) | ||
70 | { | ||
71 | CancelRequest test{ cancel_test(L) }; | ||
72 | lua_pushboolean(L, test != CancelRequest::None); | ||
73 | return 1; | ||
74 | } | ||
75 | |||
76 | // ################################################################################################ | ||
77 | // ################################################################################################ | ||
78 | |||
79 | [[nodiscard]] static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar) | ||
80 | { | ||
81 | DEBUGSPEW_CODE(fprintf(stderr, "cancel_hook\n")); | ||
82 | if (cancel_test(L) != CancelRequest::None) | ||
83 | { | ||
84 | lua_sethook(L, nullptr, 0, 0); | ||
85 | raise_cancel_error(L); | ||
86 | } | ||
87 | } | ||
88 | |||
89 | // ################################################################################################ | ||
90 | // ################################################################################################ | ||
91 | |||
92 | //--- | ||
93 | // = thread_cancel( lane_ud [,timeout_secs=0.0] [,wake_lindas_bool=false] ) | ||
94 | // | ||
95 | // The originator thread asking us specifically to cancel the other thread. | ||
96 | // | ||
97 | // 'timeout': <0: wait forever, until the lane is finished | ||
98 | // 0.0: just signal it to cancel, no time waited | ||
99 | // >0: time to wait for the lane to detect cancellation | ||
100 | // | ||
101 | // 'wake_lindas_bool': if true, signal any linda the thread is waiting on | ||
102 | // instead of waiting for its timeout (if any) | ||
103 | // | ||
104 | // Returns: true if the lane was already finished (Done/Error/Cancelled) or if we | ||
105 | // managed to cancel it. | ||
106 | // false if the cancellation timed out, or a kill was needed. | ||
107 | // | ||
108 | |||
109 | // ################################################################################################ | ||
110 | |||
111 | [[nodiscard]] static CancelResult thread_cancel_soft(Lane* lane_, lua_Duration duration_, bool wake_lane_) | ||
112 | { | ||
113 | lane_->cancel_request = CancelRequest::Soft; // it's now signaled to stop | ||
114 | // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own | ||
115 | if (wake_lane_) // wake the thread so that execution returns from any pending linda operation if desired | ||
116 | { | ||
117 | std::condition_variable* const waiting_on{ lane_->m_waiting_on }; | ||
118 | if (lane_->m_status == Lane::Waiting && waiting_on != nullptr) | ||
119 | { | ||
120 | waiting_on->notify_all(); | ||
121 | } | ||
122 | } | ||
123 | |||
124 | return lane_->waitForCompletion(duration_) ? CancelResult::Cancelled : CancelResult::Timeout; | ||
125 | } | ||
126 | |||
127 | // ################################################################################################ | ||
128 | |||
129 | [[nodiscard]] static CancelResult thread_cancel_hard(Lane* lane_, lua_Duration duration_, bool wake_lane_) | ||
130 | { | ||
131 | lane_->cancel_request = CancelRequest::Hard; // it's now signaled to stop | ||
132 | //lane_->m_thread.get_stop_source().request_stop(); | ||
133 | if (wake_lane_) // wake the thread so that execution returns from any pending linda operation if desired | ||
134 | { | ||
135 | std::condition_variable* waiting_on = lane_->m_waiting_on; | ||
136 | if (lane_->m_status == Lane::Waiting && waiting_on != nullptr) | ||
137 | { | ||
138 | waiting_on->notify_all(); | ||
139 | } | ||
140 | } | ||
141 | |||
142 | CancelResult result{ lane_->waitForCompletion(duration_) ? CancelResult::Cancelled : CancelResult::Timeout }; | ||
143 | return result; | ||
144 | } | ||
145 | |||
146 | // ################################################################################################ | ||
147 | |||
148 | CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hook_count_, lua_Duration duration_, bool wake_lane_) | ||
149 | { | ||
150 | // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here | ||
151 | // We can read 'lane_->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) | ||
152 | if (lane_->m_status >= Lane::Done) | ||
153 | { | ||
154 | // say "ok" by default, including when lane is already done | ||
155 | return CancelResult::Cancelled; | ||
156 | } | ||
157 | |||
158 | // signal the linda the wake up the thread so that it can react to the cancel query | ||
159 | // let us hope we never land here with a pointer on a linda that has been destroyed... | ||
160 | if (op_ == CancelOp::Soft) | ||
161 | { | ||
162 | return thread_cancel_soft(lane_, duration_, wake_lane_); | ||
163 | } | ||
164 | else if (static_cast<int>(op_) > static_cast<int>(CancelOp::Soft)) | ||
165 | { | ||
166 | lua_sethook(lane_->L, cancel_hook, static_cast<int>(op_), hook_count_); | ||
167 | } | ||
168 | |||
169 | return thread_cancel_hard(lane_, duration_, wake_lane_); | ||
170 | } | ||
171 | |||
172 | // ################################################################################################ | ||
173 | // ################################################################################################ | ||
174 | |||
175 | CancelOp which_cancel_op(char const* op_string_) | ||
176 | { | ||
177 | CancelOp op{ CancelOp::Invalid }; | ||
178 | if (strcmp(op_string_, "hard") == 0) | ||
179 | { | ||
180 | op = CancelOp::Hard; | ||
181 | } | ||
182 | else if (strcmp(op_string_, "soft") == 0) | ||
183 | { | ||
184 | op = CancelOp::Soft; | ||
185 | } | ||
186 | else if (strcmp(op_string_, "call") == 0) | ||
187 | { | ||
188 | op = CancelOp::MaskCall; | ||
189 | } | ||
190 | else if (strcmp(op_string_, "ret") == 0) | ||
191 | { | ||
192 | op = CancelOp::MaskRet; | ||
193 | } | ||
194 | else if (strcmp(op_string_, "line") == 0) | ||
195 | { | ||
196 | op = CancelOp::MaskLine; | ||
197 | } | ||
198 | else if (strcmp(op_string_, "count") == 0) | ||
199 | { | ||
200 | op = CancelOp::MaskCount; | ||
201 | } | ||
202 | return op; | ||
203 | } | ||
204 | |||
205 | // ################################################################################################ | ||
206 | |||
207 | [[nodiscard]] static CancelOp which_cancel_op(lua_State* L, int idx_) | ||
208 | { | ||
209 | if (lua_type(L, idx_) == LUA_TSTRING) | ||
210 | { | ||
211 | char const* const str{ lua_tostring(L, idx_) }; | ||
212 | CancelOp op{ which_cancel_op(str) }; | ||
213 | lua_remove(L, idx_); // argument is processed, remove it | ||
214 | if (op == CancelOp::Invalid) | ||
215 | { | ||
216 | luaL_error(L, "invalid hook option %s", str); // doesn't return | ||
217 | } | ||
218 | return op; | ||
219 | } | ||
220 | return CancelOp::Hard; | ||
221 | } | ||
222 | |||
223 | // ################################################################################################ | ||
224 | |||
225 | // bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, wake_lindas]) | ||
226 | LUAG_FUNC(thread_cancel) | ||
227 | { | ||
228 | Lane* const lane{ lua_toLane(L, 1) }; | ||
229 | CancelOp const op{ which_cancel_op(L, 2) }; // this removes the op string from the stack | ||
230 | |||
231 | int hook_count{ 0 }; | ||
232 | if (static_cast<int>(op) > static_cast<int>(CancelOp::Soft)) // hook is requested | ||
233 | { | ||
234 | hook_count = static_cast<int>(luaL_checkinteger(L, 2)); | ||
235 | lua_remove(L, 2); // argument is processed, remove it | ||
236 | if (hook_count < 1) | ||
237 | { | ||
238 | return luaL_error(L, "hook count cannot be < 1"); | ||
239 | } | ||
240 | } | ||
241 | |||
242 | lua_Duration wait_timeout{ 0.0 }; | ||
243 | if (lua_type(L, 2) == LUA_TNUMBER) | ||
244 | { | ||
245 | wait_timeout = lua_Duration{ lua_tonumber(L, 2) }; | ||
246 | lua_remove(L, 2); // argument is processed, remove it | ||
247 | if (wait_timeout.count() < 0.0) | ||
248 | { | ||
249 | return luaL_error(L, "cancel timeout cannot be < 0"); | ||
250 | } | ||
251 | } | ||
252 | // we wake by default in "hard" mode (remember that hook is hard too), but this can be turned off if desired | ||
253 | bool wake_lane{ op != CancelOp::Soft }; | ||
254 | if (lua_gettop(L) >= 2) | ||
255 | { | ||
256 | if (!lua_isboolean(L, 2)) | ||
257 | { | ||
258 | return luaL_error(L, "wake_lindas parameter is not a boolean"); | ||
259 | } | ||
260 | wake_lane = lua_toboolean(L, 2); | ||
261 | lua_remove(L, 2); // argument is processed, remove it | ||
262 | } | ||
263 | STACK_CHECK_START_REL(L, 0); | ||
264 | switch (thread_cancel(lane, op, hook_count, wait_timeout, wake_lane)) | ||
265 | { | ||
266 | default: // should never happen unless we added a case and forgot to handle it | ||
267 | ASSERT_L(false); | ||
268 | break; | ||
269 | |||
270 | case CancelResult::Timeout: | ||
271 | lua_pushboolean(L, 0); // false | ||
272 | lua_pushstring(L, "timeout"); // false "timeout" | ||
273 | break; | ||
274 | |||
275 | case CancelResult::Cancelled: | ||
276 | lua_pushboolean(L, 1); // true | ||
277 | push_thread_status(L, lane); // true status | ||
278 | break; | ||
279 | } | ||
280 | STACK_CHECK(L, 2); | ||
281 | return 2; | ||
282 | } | ||