aboutsummaryrefslogtreecommitdiff
path: root/src/cancel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/cancel.cpp')
-rw-r--r--src/cancel.cpp178
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
114static CancelResult thread_cancel_soft(Lane* lane_, double secs_, bool wake_lindas_) 113static 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
132static CancelResult thread_cancel_hard(lua_State* L, Lane* lane_, double secs_, bool force_, double waitkill_timeout_) 131static 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
175CancelResult thread_cancel(lua_State* L, Lane* lane_, CancelOp op_, double secs_, bool force_, double waitkill_timeout_) 150CancelResult 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 177CancelOp which_cancel_op(char const* op_string_)
204// = 0: soft 178{
205// < 0: hard 179 CancelOp op{ CancelOp::Invalid };
206static 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
209static 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])
249LUAG_FUNC(thread_cancel) 228LUAG_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}