diff options
Diffstat (limited to 'src/cancel.cpp')
-rw-r--r-- | src/cancel.cpp | 178 |
1 files changed, 82 insertions, 96 deletions
diff --git a/src/cancel.cpp b/src/cancel.cpp index 4667f07..6a94343 100644 --- a/src/cancel.cpp +++ b/src/cancel.cpp | |||
@@ -92,7 +92,7 @@ static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar) | |||
92 | // ################################################################################################ | 92 | // ################################################################################################ |
93 | 93 | ||
94 | //--- | 94 | //--- |
95 | // = thread_cancel( lane_ud [,timeout_secs=0.0] [,force_kill_bool=false] ) | 95 | // = thread_cancel( lane_ud [,timeout_secs=0.0] [,wake_lindas_bool=false] ) |
96 | // | 96 | // |
97 | // The originator thread asking us specifically to cancel the other thread. | 97 | // The originator thread asking us specifically to cancel the other thread. |
98 | // | 98 | // |
@@ -100,9 +100,8 @@ static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar) | |||
100 | // 0.0: just signal it to cancel, no time waited | 100 | // 0.0: just signal it to cancel, no time waited |
101 | // >0: time to wait for the lane to detect cancellation | 101 | // >0: time to wait for the lane to detect cancellation |
102 | // | 102 | // |
103 | // 'force_kill': if true, and lane does not detect cancellation within timeout, | 103 | // '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 | 104 | // instead of waiting for its timeout (if any) |
105 | // (unless the lane is already finished). | ||
106 | // | 105 | // |
107 | // Returns: true if the lane was already finished (DONE/ERROR_ST/CANCELLED) or if we | 106 | // Returns: true if the lane was already finished (DONE/ERROR_ST/CANCELLED) or if we |
108 | // managed to cancel it. | 107 | // managed to cancel it. |
@@ -111,76 +110,47 @@ static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar) | |||
111 | 110 | ||
112 | // ################################################################################################ | 111 | // ################################################################################################ |
113 | 112 | ||
114 | static CancelResult thread_cancel_soft(Lane* lane_, double secs_, bool wake_lindas_) | 113 | static CancelResult thread_cancel_soft(Lane* lane_, lua_Duration duration_, bool wake_lane_) |
115 | { | 114 | { |
116 | lane_->cancel_request = CancelRequest::Soft; // it's now signaled to stop | 115 | 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 | 116 | // 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 | 117 | if (wake_lane_) // wake the thread so that execution returns from any pending linda operation if desired |
119 | { | 118 | { |
120 | SIGNAL_T* const waiting_on{ lane_->waiting_on }; | 119 | std::condition_variable* const waiting_on{ lane_->m_waiting_on }; |
121 | if (lane_->status == WAITING && waiting_on != nullptr) | 120 | if (lane_->status == WAITING && waiting_on != nullptr) |
122 | { | 121 | { |
123 | SIGNAL_ALL( waiting_on); | 122 | waiting_on->notify_all(); |
124 | } | 123 | } |
125 | } | 124 | } |
126 | 125 | ||
127 | return THREAD_WAIT(&lane_->thread, secs_, &lane_->done_signal, &lane_->done_lock, &lane_->status) ? CancelResult::Cancelled : CancelResult::Timeout; | 126 | return lane_->waitForCompletion(duration_) ? CancelResult::Cancelled : CancelResult::Timeout; |
128 | } | 127 | } |
129 | 128 | ||
130 | // ################################################################################################ | 129 | // ################################################################################################ |
131 | 130 | ||
132 | static CancelResult thread_cancel_hard(lua_State* L, Lane* lane_, double secs_, bool force_, double waitkill_timeout_) | 131 | static CancelResult thread_cancel_hard(Lane* lane_, lua_Duration duration_, bool wake_lane_) |
133 | { | 132 | { |
134 | lane_->cancel_request = CancelRequest::Hard; // it's now signaled to stop | 133 | lane_->cancel_request = CancelRequest::Hard; // it's now signaled to stop |
134 | //lane_->m_thread.get_stop_source().request_stop(); | ||
135 | if (wake_lane_) // wake the thread so that execution returns from any pending linda operation if desired | ||
135 | { | 136 | { |
136 | SIGNAL_T* waiting_on = lane_->waiting_on; | 137 | std::condition_variable* waiting_on = lane_->m_waiting_on; |
137 | if (lane_->status == WAITING && waiting_on != nullptr) | 138 | if (lane_->status == WAITING && waiting_on != nullptr) |
138 | { | 139 | { |
139 | SIGNAL_ALL( waiting_on); | 140 | waiting_on->notify_all(); |
140 | } | 141 | } |
141 | } | 142 | } |
142 | 143 | ||
143 | CancelResult result{ THREAD_WAIT(&lane_->thread, secs_, &lane_->done_signal, &lane_->done_lock, &lane_->status) ? CancelResult::Cancelled : CancelResult::Timeout }; | 144 | 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; | 145 | return result; |
171 | } | 146 | } |
172 | 147 | ||
173 | // ################################################################################################ | 148 | // ################################################################################################ |
174 | 149 | ||
175 | CancelResult thread_cancel(lua_State* L, Lane* lane_, CancelOp op_, double secs_, bool force_, double waitkill_timeout_) | 150 | CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hook_count_, lua_Duration duration_, bool wake_lane_) |
176 | { | 151 | { |
177 | // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here | 152 | // 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) | 153 | // We can read 'lane_->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) |
179 | if (lane_->mstatus == Lane::Killed) | ||
180 | { | ||
181 | return CancelResult::Killed; | ||
182 | } | ||
183 | |||
184 | if (lane_->status >= DONE) | 154 | if (lane_->status >= DONE) |
185 | { | 155 | { |
186 | // say "ok" by default, including when lane is already done | 156 | // say "ok" by default, including when lane is already done |
@@ -191,48 +161,57 @@ 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... | 161 | // let us hope we never land here with a pointer on a linda that has been destroyed... |
192 | if (op_ == CancelOp::Soft) | 162 | if (op_ == CancelOp::Soft) |
193 | { | 163 | { |
194 | return thread_cancel_soft(lane_, secs_, force_); | 164 | return thread_cancel_soft(lane_, duration_, wake_lane_); |
165 | } | ||
166 | else if (static_cast<int>(op_) > static_cast<int>(CancelOp::Soft)) | ||
167 | { | ||
168 | lua_sethook(lane_->L, cancel_hook, static_cast<int>(op_), hook_count_); | ||
195 | } | 169 | } |
196 | 170 | ||
197 | return thread_cancel_hard(L, lane_, secs_, force_, waitkill_timeout_); | 171 | return thread_cancel_hard(lane_, duration_, wake_lane_); |
198 | } | 172 | } |
199 | 173 | ||
200 | // ################################################################################################ | 174 | // ################################################################################################ |
201 | // ################################################################################################ | 175 | // ################################################################################################ |
202 | 176 | ||
203 | // > 0: the mask | 177 | CancelOp which_cancel_op(char const* op_string_) |
204 | // = 0: soft | 178 | { |
205 | // < 0: hard | 179 | CancelOp op{ CancelOp::Invalid }; |
206 | static CancelOp which_op(lua_State* L, int idx_) | 180 | if (strcmp(op_string_, "hard") == 0) |
181 | { | ||
182 | op = CancelOp::Hard; | ||
183 | } | ||
184 | else if (strcmp(op_string_, "soft") == 0) | ||
185 | { | ||
186 | op = CancelOp::Soft; | ||
187 | } | ||
188 | else if (strcmp(op_string_, "call") == 0) | ||
189 | { | ||
190 | op = CancelOp::MaskCall; | ||
191 | } | ||
192 | else if (strcmp(op_string_, "ret") == 0) | ||
193 | { | ||
194 | op = CancelOp::MaskRet; | ||
195 | } | ||
196 | else if (strcmp(op_string_, "line") == 0) | ||
197 | { | ||
198 | op = CancelOp::MaskLine; | ||
199 | } | ||
200 | else if (strcmp(op_string_, "count") == 0) | ||
201 | { | ||
202 | op = CancelOp::MaskCount; | ||
203 | } | ||
204 | return op; | ||
205 | } | ||
206 | |||
207 | // ################################################################################################ | ||
208 | |||
209 | static CancelOp which_cancel_op(lua_State* L, int idx_) | ||
207 | { | 210 | { |
208 | if (lua_type(L, idx_) == LUA_TSTRING) | 211 | if (lua_type(L, idx_) == LUA_TSTRING) |
209 | { | 212 | { |
210 | CancelOp op{ CancelOp::Invalid }; | 213 | char const* const str{ lua_tostring(L, idx_) }; |
211 | char const* str = lua_tostring(L, idx_); | 214 | 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 | 215 | lua_remove(L, idx_); // argument is processed, remove it |
237 | if (op == CancelOp::Invalid) | 216 | if (op == CancelOp::Invalid) |
238 | { | 217 | { |
@@ -245,53 +224,60 @@ static CancelOp which_op(lua_State* L, int idx_) | |||
245 | 224 | ||
246 | // ################################################################################################ | 225 | // ################################################################################################ |
247 | 226 | ||
248 | // bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, force [, forcekill_timeout]]) | 227 | // bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, wake_lindas]) |
249 | LUAG_FUNC(thread_cancel) | 228 | LUAG_FUNC(thread_cancel) |
250 | { | 229 | { |
251 | Lane* const lane{ lua_toLane(L, 1) }; | 230 | Lane* const lane{ lua_toLane(L, 1) }; |
252 | CancelOp const op{ which_op(L, 2) }; // this removes the op string from the stack | 231 | CancelOp const op{ which_cancel_op(L, 2) }; // this removes the op string from the stack |
253 | 232 | ||
233 | int hook_count{ 0 }; | ||
254 | if (static_cast<int>(op) > static_cast<int>(CancelOp::Soft)) // hook is requested | 234 | if (static_cast<int>(op) > static_cast<int>(CancelOp::Soft)) // hook is requested |
255 | { | 235 | { |
256 | int const hook_count{ static_cast<int>(lua_tointeger(L, 2)) }; | 236 | hook_count = static_cast<int>(luaL_checkinteger(L, 2)); |
257 | lua_remove(L, 2); // argument is processed, remove it | 237 | lua_remove(L, 2); // argument is processed, remove it |
258 | if (hook_count < 1) | 238 | if (hook_count < 1) |
259 | { | 239 | { |
260 | return luaL_error(L, "hook count cannot be < 1"); | 240 | return luaL_error(L, "hook count cannot be < 1"); |
261 | } | 241 | } |
262 | lua_sethook(lane->L, cancel_hook, static_cast<int>(op), hook_count); | ||
263 | } | 242 | } |
264 | 243 | ||
265 | double secs{ 0.0 }; | 244 | lua_Duration wait_timeout{ 0.0 }; |
266 | if (lua_type(L, 2) == LUA_TNUMBER) | 245 | if (lua_type(L, 2) == LUA_TNUMBER) |
267 | { | 246 | { |
268 | secs = lua_tonumber(L, 2); | 247 | wait_timeout = lua_Duration{ lua_tonumber(L, 2) }; |
269 | lua_remove(L, 2); // argument is processed, remove it | 248 | lua_remove(L, 2); // argument is processed, remove it |
270 | if (secs < 0.0) | 249 | if (wait_timeout.count() < 0.0) |
271 | { | 250 | { |
272 | return luaL_error(L, "cancel timeout cannot be < 0"); | 251 | return luaL_error(L, "cancel timeout cannot be < 0"); |
273 | } | 252 | } |
274 | } | 253 | } |
275 | 254 | // 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 | 255 | bool wake_lane{ op != CancelOp::Soft }; |
277 | double const forcekill_timeout{ luaL_optnumber(L, 3, 0.0) }; | 256 | if (lua_gettop(L) >= 2) |
278 | switch (thread_cancel(L, lane, op, secs, force, forcekill_timeout)) | 257 | { |
258 | if (!lua_isboolean(L, 2)) | ||
259 | { | ||
260 | return luaL_error(L, "wake_lindas parameter is not a boolean"); | ||
261 | } | ||
262 | wake_lane = lua_toboolean(L, 2); | ||
263 | lua_remove(L, 2); // argument is processed, remove it | ||
264 | } | ||
265 | switch (thread_cancel(lane, op, hook_count, wait_timeout, wake_lane)) | ||
279 | { | 266 | { |
267 | default: // should never happen unless we added a case and forgot to handle it | ||
268 | ASSERT_L(false); | ||
269 | break; | ||
270 | |||
280 | case CancelResult::Timeout: | 271 | case CancelResult::Timeout: |
281 | lua_pushboolean(L, 0); | 272 | lua_pushboolean(L, 0); |
282 | lua_pushstring(L, "timeout"); | 273 | lua_pushstring(L, "timeout"); |
283 | return 2; | 274 | break; |
284 | 275 | ||
285 | case CancelResult::Cancelled: | 276 | case CancelResult::Cancelled: |
286 | lua_pushboolean(L, 1); | 277 | lua_pushboolean(L, 1); |
287 | push_thread_status(L, lane); | 278 | push_thread_status(L, lane); |
288 | return 2; | 279 | break; |
289 | |||
290 | case CancelResult::Killed: | ||
291 | lua_pushboolean(L, 1); | ||
292 | push_thread_status(L, lane); | ||
293 | return 2; | ||
294 | } | 280 | } |
295 | // should never happen, only here to prevent the compiler from complaining of "not all control paths returning a value" | 281 | // should never happen, only here to prevent the compiler from complaining of "not all control paths returning a value" |
296 | return 0; | 282 | return 2; |
297 | } | 283 | } |