diff options
Diffstat (limited to 'src/cancel.cpp')
-rw-r--r-- | src/cancel.cpp | 213 |
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 | ||
12 | Copyright (C) 2011-2019 Benoit Germain <bnt.germain@gmail.com> | 12 | Copyright (C) 2011-2024 Benoit Germain <bnt.germain@gmail.com> |
13 | 13 | ||
14 | Permission is hereby granted, free of charge, to any person obtaining a copy | 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 | 15 | of 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 | */ |
56 | static 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 | ||
81 | static 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 | ||
114 | static 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 | ||
132 | static 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 | ||
175 | CancelResult thread_cancel(lua_State* L, Lane* lane_, CancelOp op_, double secs_, bool force_, double waitkill_timeout_) | 148 | CancelResult 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 | 175 | CancelOp which_cancel_op(char const* op_string_) |
204 | // = 0: soft | 176 | { |
205 | // < 0: hard | 177 | CancelOp op{ CancelOp::Invalid }; |
206 | static 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]) |
249 | LUAG_FUNC(thread_cancel) | 226 | LUAG_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 | } |