aboutsummaryrefslogtreecommitdiff
path: root/src/cancel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/cancel.cpp')
-rw-r--r--src/cancel.cpp213
1 files changed, 99 insertions, 114 deletions
diff --git a/src/cancel.cpp b/src/cancel.cpp
index 4667f07..b3e52b6 100644
--- a/src/cancel.cpp
+++ b/src/cancel.cpp
@@ -1,6 +1,6 @@
1/* 1/*
2-- 2--
3-- CANCEL.C 3-- CANCEL.CPP
4-- 4--
5-- Lane cancellation support 5-- Lane cancellation support
6-- 6--
@@ -9,7 +9,7 @@
9--[[ 9--[[
10=============================================================================== 10===============================================================================
11 11
12Copyright (C) 2011-2019 Benoit Germain <bnt.germain@gmail.com> 12Copyright (C) 2011-2024 Benoit Germain <bnt.germain@gmail.com>
13 13
14Permission is hereby granted, free of charge, to any person obtaining a copy 14Permission is hereby granted, free of charge, to any person obtaining a copy
15of this software and associated documentation files (the "Software"), to deal 15of this software and associated documentation files (the "Software"), to deal
@@ -33,13 +33,11 @@ THE SOFTWARE.
33]]-- 33]]--
34*/ 34*/
35 35
36#include <assert.h> 36#include "cancel.h"
37#include <string.h>
38 37
38#include "lanes_private.h"
39#include "threading.h" 39#include "threading.h"
40#include "cancel.h"
41#include "tools.h" 40#include "tools.h"
42#include "lanes_private.h"
43 41
44// ################################################################################################ 42// ################################################################################################
45// ################################################################################################ 43// ################################################################################################
@@ -53,7 +51,7 @@ THE SOFTWARE.
53* Returns CANCEL_SOFT/HARD if any locks are to be exited, and 'raise_cancel_error()' called, 51* Returns CANCEL_SOFT/HARD if any locks are to be exited, and 'raise_cancel_error()' called,
54* to make execution of the lane end. 52* to make execution of the lane end.
55*/ 53*/
56static inline CancelRequest cancel_test(lua_State* L) 54[[nodiscard]] static inline CancelRequest cancel_test(lua_State* L)
57{ 55{
58 Lane* const lane{ LANE_POINTER_REGKEY.readLightUserDataValue<Lane>(L) }; 56 Lane* const lane{ LANE_POINTER_REGKEY.readLightUserDataValue<Lane>(L) };
59 // 'lane' is nullptr for the original main state (and no-one can cancel that) 57 // 'lane' is nullptr for the original main state (and no-one can cancel that)
@@ -78,7 +76,7 @@ LUAG_FUNC( cancel_test)
78// ################################################################################################ 76// ################################################################################################
79// ################################################################################################ 77// ################################################################################################
80 78
81static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar) 79[[nodiscard]] static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar)
82{ 80{
83 DEBUGSPEW_CODE(fprintf(stderr, "cancel_hook\n")); 81 DEBUGSPEW_CODE(fprintf(stderr, "cancel_hook\n"));
84 if (cancel_test(L) != CancelRequest::None) 82 if (cancel_test(L) != CancelRequest::None)
@@ -92,7 +90,7 @@ static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar)
92// ################################################################################################ 90// ################################################################################################
93 91
94//--- 92//---
95// = thread_cancel( lane_ud [,timeout_secs=0.0] [,force_kill_bool=false] ) 93// = thread_cancel( lane_ud [,timeout_secs=0.0] [,wake_lindas_bool=false] )
96// 94//
97// The originator thread asking us specifically to cancel the other thread. 95// The originator thread asking us specifically to cancel the other thread.
98// 96//
@@ -100,88 +98,58 @@ static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar)
100// 0.0: just signal it to cancel, no time waited 98// 0.0: just signal it to cancel, no time waited
101// >0: time to wait for the lane to detect cancellation 99// >0: time to wait for the lane to detect cancellation
102// 100//
103// 'force_kill': if true, and lane does not detect cancellation within timeout, 101// 'wake_lindas_bool': if true, signal any linda the thread is waiting on
104// it is forcefully killed. Using this with 0.0 timeout means just kill 102// instead of waiting for its timeout (if any)
105// (unless the lane is already finished).
106// 103//
107// Returns: true if the lane was already finished (DONE/ERROR_ST/CANCELLED) or if we 104// Returns: true if the lane was already finished (Done/Error/Cancelled) or if we
108// managed to cancel it. 105// managed to cancel it.
109// false if the cancellation timed out, or a kill was needed. 106// false if the cancellation timed out, or a kill was needed.
110// 107//
111 108
112// ################################################################################################ 109// ################################################################################################
113 110
114static CancelResult thread_cancel_soft(Lane* lane_, double secs_, bool wake_lindas_) 111[[nodiscard]] static CancelResult thread_cancel_soft(Lane* lane_, lua_Duration duration_, bool wake_lane_)
115{ 112{
116 lane_->cancel_request = CancelRequest::Soft; // it's now signaled to stop 113 lane_->cancel_request = CancelRequest::Soft; // it's now signaled to stop
117 // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own 114 // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own
118 if (wake_lindas_) // wake the thread so that execution returns from any pending linda operation if desired 115 if (wake_lane_) // wake the thread so that execution returns from any pending linda operation if desired
119 { 116 {
120 SIGNAL_T* const waiting_on{ lane_->waiting_on }; 117 std::condition_variable* const waiting_on{ lane_->m_waiting_on };
121 if (lane_->status == WAITING && waiting_on != nullptr) 118 if (lane_->m_status == Lane::Waiting && waiting_on != nullptr)
122 { 119 {
123 SIGNAL_ALL( waiting_on); 120 waiting_on->notify_all();
124 } 121 }
125 } 122 }
126 123
127 return THREAD_WAIT(&lane_->thread, secs_, &lane_->done_signal, &lane_->done_lock, &lane_->status) ? CancelResult::Cancelled : CancelResult::Timeout; 124 return lane_->waitForCompletion(duration_) ? CancelResult::Cancelled : CancelResult::Timeout;
128} 125}
129 126
130// ################################################################################################ 127// ################################################################################################
131 128
132static CancelResult thread_cancel_hard(lua_State* L, Lane* lane_, double secs_, bool force_, double waitkill_timeout_) 129[[nodiscard]] static CancelResult thread_cancel_hard(Lane* lane_, lua_Duration duration_, bool wake_lane_)
133{ 130{
134 lane_->cancel_request = CancelRequest::Hard; // it's now signaled to stop 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
135 { 134 {
136 SIGNAL_T* waiting_on = lane_->waiting_on; 135 std::condition_variable* waiting_on = lane_->m_waiting_on;
137 if (lane_->status == WAITING && waiting_on != nullptr) 136 if (lane_->m_status == Lane::Waiting && waiting_on != nullptr)
138 { 137 {
139 SIGNAL_ALL( waiting_on); 138 waiting_on->notify_all();
140 } 139 }
141 } 140 }
142 141
143 CancelResult result{ THREAD_WAIT(&lane_->thread, secs_, &lane_->done_signal, &lane_->done_lock, &lane_->status) ? CancelResult::Cancelled : CancelResult::Timeout }; 142 CancelResult result{ lane_->waitForCompletion(duration_) ? CancelResult::Cancelled : CancelResult::Timeout };
144
145 if ((result == CancelResult::Timeout) && force_)
146 {
147 // Killing is asynchronous; we _will_ wait for it to be done at
148 // GC, to make sure the data structure can be released (alternative
149 // would be use of "cancellation cleanup handlers" that at least
150 // PThread seems to have).
151 //
152 THREAD_KILL(&lane_->thread);
153#if THREADAPI == THREADAPI_PTHREAD
154 // pthread: make sure the thread is really stopped!
155 // note that this may block forever if the lane doesn't call a cancellation point and pthread doesn't honor PTHREAD_CANCEL_ASYNCHRONOUS
156 result = THREAD_WAIT(&lane_->thread, waitkill_timeout_, &lane_->done_signal, &lane_->done_lock, &lane_->status) ? CancelResult::Killed : CancelResult::Timeout;
157 if (result == CancelResult::Timeout)
158 {
159 std::ignore = luaL_error( L, "force-killed lane failed to terminate within %f second%s", waitkill_timeout_, waitkill_timeout_ > 1 ? "s" : "");
160 }
161#else
162 (void) waitkill_timeout_; // unused
163 (void) L; // unused
164#endif // THREADAPI == THREADAPI_PTHREAD
165 lane_->mstatus = Lane::Killed; // mark 'gc' to wait for it
166 // note that lane_->status value must remain to whatever it was at the time of the kill
167 // because we need to know if we can lua_close() the Lua State or not.
168 result = CancelResult::Killed;
169 }
170 return result; 143 return result;
171} 144}
172 145
173// ################################################################################################ 146// ################################################################################################
174 147
175CancelResult thread_cancel(lua_State* L, Lane* lane_, CancelOp op_, double secs_, bool force_, double waitkill_timeout_) 148CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hook_count_, lua_Duration duration_, bool wake_lane_)
176{ 149{
177 // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here 150 // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here
178 // We can read 'lane_->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) 151 // We can read 'lane_->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN)
179 if (lane_->mstatus == Lane::Killed) 152 if (lane_->m_status >= Lane::Done)
180 {
181 return CancelResult::Killed;
182 }
183
184 if (lane_->status >= DONE)
185 { 153 {
186 // say "ok" by default, including when lane is already done 154 // say "ok" by default, including when lane is already done
187 return CancelResult::Cancelled; 155 return CancelResult::Cancelled;
@@ -191,52 +159,61 @@ CancelResult thread_cancel(lua_State* L, Lane* lane_, CancelOp op_, double secs_
191 // let us hope we never land here with a pointer on a linda that has been destroyed... 159 // let us hope we never land here with a pointer on a linda that has been destroyed...
192 if (op_ == CancelOp::Soft) 160 if (op_ == CancelOp::Soft)
193 { 161 {
194 return thread_cancel_soft(lane_, secs_, force_); 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_);
195 } 167 }
196 168
197 return thread_cancel_hard(L, lane_, secs_, force_, waitkill_timeout_); 169 return thread_cancel_hard(lane_, duration_, wake_lane_);
198} 170}
199 171
200// ################################################################################################ 172// ################################################################################################
201// ################################################################################################ 173// ################################################################################################
202 174
203// > 0: the mask 175CancelOp which_cancel_op(char const* op_string_)
204// = 0: soft 176{
205// < 0: hard 177 CancelOp op{ CancelOp::Invalid };
206static CancelOp which_op(lua_State* L, int idx_) 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_)
207{ 208{
208 if (lua_type(L, idx_) == LUA_TSTRING) 209 if (lua_type(L, idx_) == LUA_TSTRING)
209 { 210 {
210 CancelOp op{ CancelOp::Invalid }; 211 char const* const str{ lua_tostring(L, idx_) };
211 char const* str = lua_tostring(L, idx_); 212 CancelOp op{ which_cancel_op(str) };
212 if (strcmp(str, "hard") == 0)
213 {
214 op = CancelOp::Hard;
215 }
216 else if (strcmp(str, "soft") == 0)
217 {
218 op = CancelOp::Soft;
219 }
220 else if (strcmp(str, "call") == 0)
221 {
222 op = CancelOp::MaskCall;
223 }
224 else if (strcmp(str, "ret") == 0)
225 {
226 op = CancelOp::MaskRet;
227 }
228 else if (strcmp(str, "line") == 0)
229 {
230 op = CancelOp::MaskLine;
231 }
232 else if (strcmp(str, "count") == 0)
233 {
234 op = CancelOp::MaskCount;
235 }
236 lua_remove(L, idx_); // argument is processed, remove it 213 lua_remove(L, idx_); // argument is processed, remove it
237 if (op == CancelOp::Invalid) 214 if (op == CancelOp::Invalid)
238 { 215 {
239 std::ignore = luaL_error(L, "invalid hook option %s", str); 216 luaL_error(L, "invalid hook option %s", str); // doesn't return
240 } 217 }
241 return op; 218 return op;
242 } 219 }
@@ -245,53 +222,61 @@ static CancelOp which_op(lua_State* L, int idx_)
245 222
246// ################################################################################################ 223// ################################################################################################
247 224
248// bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, force [, forcekill_timeout]]) 225// bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, wake_lindas])
249LUAG_FUNC(thread_cancel) 226LUAG_FUNC(thread_cancel)
250{ 227{
251 Lane* const lane{ lua_toLane(L, 1) }; 228 Lane* const lane{ lua_toLane(L, 1) };
252 CancelOp const op{ which_op(L, 2) }; // this removes the op string from the stack 229 CancelOp const op{ which_cancel_op(L, 2) }; // this removes the op string from the stack
253 230
231 int hook_count{ 0 };
254 if (static_cast<int>(op) > static_cast<int>(CancelOp::Soft)) // hook is requested 232 if (static_cast<int>(op) > static_cast<int>(CancelOp::Soft)) // hook is requested
255 { 233 {
256 int const hook_count{ static_cast<int>(lua_tointeger(L, 2)) }; 234 hook_count = static_cast<int>(luaL_checkinteger(L, 2));
257 lua_remove(L, 2); // argument is processed, remove it 235 lua_remove(L, 2); // argument is processed, remove it
258 if (hook_count < 1) 236 if (hook_count < 1)
259 { 237 {
260 return luaL_error(L, "hook count cannot be < 1"); 238 return luaL_error(L, "hook count cannot be < 1");
261 } 239 }
262 lua_sethook(lane->L, cancel_hook, static_cast<int>(op), hook_count);
263 } 240 }
264 241
265 double secs{ 0.0 }; 242 lua_Duration wait_timeout{ 0.0 };
266 if (lua_type(L, 2) == LUA_TNUMBER) 243 if (lua_type(L, 2) == LUA_TNUMBER)
267 { 244 {
268 secs = lua_tonumber(L, 2); 245 wait_timeout = lua_Duration{ lua_tonumber(L, 2) };
269 lua_remove(L, 2); // argument is processed, remove it 246 lua_remove(L, 2); // argument is processed, remove it
270 if (secs < 0.0) 247 if (wait_timeout.count() < 0.0)
271 { 248 {
272 return luaL_error(L, "cancel timeout cannot be < 0"); 249 return luaL_error(L, "cancel timeout cannot be < 0");
273 } 250 }
274 } 251 }
275 252 // we wake by default in "hard" mode (remember that hook is hard too), but this can be turned off if desired
276 bool const force{ lua_toboolean(L, 2) ? true : false }; // false if nothing there 253 bool wake_lane{ op != CancelOp::Soft };
277 double const forcekill_timeout{ luaL_optnumber(L, 3, 0.0) }; 254 if (lua_gettop(L) >= 2)
278 switch (thread_cancel(L, lane, op, secs, force, forcekill_timeout))
279 { 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
280 case CancelResult::Timeout: 270 case CancelResult::Timeout:
281 lua_pushboolean(L, 0); 271 lua_pushboolean(L, 0); // false
282 lua_pushstring(L, "timeout"); 272 lua_pushstring(L, "timeout"); // false "timeout"
283 return 2; 273 break;
284 274
285 case CancelResult::Cancelled: 275 case CancelResult::Cancelled:
286 lua_pushboolean(L, 1); 276 lua_pushboolean(L, 1); // true
287 push_thread_status(L, lane); 277 push_thread_status(L, lane); // true status
288 return 2; 278 break;
289
290 case CancelResult::Killed:
291 lua_pushboolean(L, 1);
292 push_thread_status(L, lane);
293 return 2;
294 } 279 }
295 // should never happen, only here to prevent the compiler from complaining of "not all control paths returning a value" 280 STACK_CHECK(L, 2);
296 return 0; 281 return 2;
297} 282}