aboutsummaryrefslogtreecommitdiff
path: root/src/cancel.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/cancel.cpp282
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
12Copyright (C) 2011-2024 Benoit Germain <bnt.germain@gmail.com>
13
14Permission is hereby granted, free of charge, to any person obtaining a copy
15of this software and associated documentation files (the "Software"), to deal
16in the Software without restriction, including without limitation the rights
17to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18copies of the Software, and to permit persons to whom the Software is
19furnished to do so, subject to the following conditions:
20
21The above copyright notice and this permission notice shall be included in
22all copies or substantial portions of the Software.
23
24THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30THE 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//
69LUAG_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
148CancelResult 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
175CancelOp 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])
226LUAG_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}