diff options
Diffstat (limited to 'src/cancel.c')
-rw-r--r-- | src/cancel.c | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/src/cancel.c b/src/cancel.c new file mode 100644 index 0000000..11e100d --- /dev/null +++ b/src/cancel.c | |||
@@ -0,0 +1,252 @@ | |||
1 | /* | ||
2 | -- | ||
3 | -- CANCEL.C | ||
4 | -- | ||
5 | -- Lane cancellation support | ||
6 | -- | ||
7 | -- Author: Benoit Germain <bnt.germain@gmail.com> | ||
8 | -- | ||
9 | --[[ | ||
10 | =============================================================================== | ||
11 | |||
12 | Copyright (C) 2011-2019 Benoit Germain <bnt.germain@gmail.com> | ||
13 | |||
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 | ||
16 | in the Software without restriction, including without limitation the rights | ||
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
18 | copies of the Software, and to permit persons to whom the Software is | ||
19 | furnished to do so, subject to the following conditions: | ||
20 | |||
21 | The above copyright notice and this permission notice shall be included in | ||
22 | all copies or substantial portions of the Software. | ||
23 | |||
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
30 | THE SOFTWARE. | ||
31 | |||
32 | =============================================================================== | ||
33 | ]]-- | ||
34 | */ | ||
35 | |||
36 | #include <assert.h> | ||
37 | |||
38 | #include "threading.h" | ||
39 | #include "cancel.h" | ||
40 | #include "tools.h" | ||
41 | #include "lanes_private.h" | ||
42 | |||
43 | // ################################################################################################ | ||
44 | // ################################################################################################ | ||
45 | |||
46 | /* | ||
47 | * Check if the thread in question ('L') has been signalled for cancel. | ||
48 | * | ||
49 | * Called by cancellation hooks and/or pending Linda operations (because then | ||
50 | * the check won't affect performance). | ||
51 | * | ||
52 | * Returns TRUE if any locks are to be exited, and 'cancel_error()' called, | ||
53 | * to make execution of the lane end. | ||
54 | */ | ||
55 | static inline enum e_cancel_request cancel_test( lua_State* L) | ||
56 | { | ||
57 | Lane* const s = get_lane_from_registry( L); | ||
58 | // 's' is NULL for the original main state (and no-one can cancel that) | ||
59 | return s ? s->cancel_request : CANCEL_NONE; | ||
60 | } | ||
61 | |||
62 | // ################################################################################################ | ||
63 | |||
64 | //--- | ||
65 | // bool = cancel_test() | ||
66 | // | ||
67 | // Available inside the global namespace of lanes | ||
68 | // returns a boolean saying if a cancel request is pending | ||
69 | // | ||
70 | LUAG_FUNC( cancel_test) | ||
71 | { | ||
72 | enum e_cancel_request test = cancel_test( L); | ||
73 | lua_pushboolean( L, test != CANCEL_NONE); | ||
74 | return 1; | ||
75 | } | ||
76 | |||
77 | // ################################################################################################ | ||
78 | // ################################################################################################ | ||
79 | |||
80 | void cancel_hook( lua_State* L, lua_Debug* ar) | ||
81 | { | ||
82 | (void)ar; | ||
83 | DEBUGSPEW_CODE( fprintf( stderr, "cancel_hook\n")); | ||
84 | if( cancel_test( L) != CANCEL_NONE) | ||
85 | { | ||
86 | cancel_error( L); | ||
87 | } | ||
88 | } | ||
89 | |||
90 | // ################################################################################################ | ||
91 | // ################################################################################################ | ||
92 | |||
93 | //--- | ||
94 | // = thread_cancel( lane_ud [,timeout_secs=0.0] [,force_kill_bool=false] ) | ||
95 | // | ||
96 | // The originator thread asking us specifically to cancel the other thread. | ||
97 | // | ||
98 | // 'timeout': <0: wait forever, until the lane is finished | ||
99 | // 0.0: just signal it to cancel, no time waited | ||
100 | // >0: time to wait for the lane to detect cancellation | ||
101 | // | ||
102 | // 'force_kill': if true, and lane does not detect cancellation within timeout, | ||
103 | // it is forcefully killed. Using this with 0.0 timeout means just kill | ||
104 | // (unless the lane is already finished). | ||
105 | // | ||
106 | // Returns: true if the lane was already finished (DONE/ERROR_ST/CANCELLED) or if we | ||
107 | // managed to cancel it. | ||
108 | // false if the cancellation timed out, or a kill was needed. | ||
109 | // | ||
110 | |||
111 | // ################################################################################################ | ||
112 | |||
113 | static cancel_result thread_cancel_soft( Lane* s, bool_t wake_lindas_) | ||
114 | { | ||
115 | s->cancel_request = CANCEL_SOFT; // it's now signaled to stop | ||
116 | // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own | ||
117 | if( wake_lindas_) // wake the thread so that execution returns from any pending linda operation if desired | ||
118 | { | ||
119 | SIGNAL_T *waiting_on = s->waiting_on; | ||
120 | if( s->status == WAITING && waiting_on != NULL) | ||
121 | { | ||
122 | SIGNAL_ALL( waiting_on); | ||
123 | } | ||
124 | } | ||
125 | // say we succeeded though | ||
126 | return CR_Cancelled; | ||
127 | } | ||
128 | |||
129 | // ################################################################################################ | ||
130 | |||
131 | static cancel_result thread_cancel_hard( lua_State* L, Lane* s, double secs_, bool_t force_, double waitkill_timeout_) | ||
132 | { | ||
133 | cancel_result result; | ||
134 | |||
135 | s->cancel_request = CANCEL_HARD; // it's now signaled to stop | ||
136 | { | ||
137 | SIGNAL_T *waiting_on = s->waiting_on; | ||
138 | if( s->status == WAITING && waiting_on != NULL) | ||
139 | { | ||
140 | SIGNAL_ALL( waiting_on); | ||
141 | } | ||
142 | } | ||
143 | |||
144 | result = THREAD_WAIT( &s->thread, secs_, &s->done_signal, &s->done_lock, &s->status) ? CR_Cancelled : CR_Timeout; | ||
145 | |||
146 | if( (result == CR_Timeout) && force_) | ||
147 | { | ||
148 | // Killing is asynchronous; we _will_ wait for it to be done at | ||
149 | // GC, to make sure the data structure can be released (alternative | ||
150 | // would be use of "cancellation cleanup handlers" that at least | ||
151 | // PThread seems to have). | ||
152 | // | ||
153 | THREAD_KILL( &s->thread); | ||
154 | #if THREADAPI == THREADAPI_PTHREAD | ||
155 | // pthread: make sure the thread is really stopped! | ||
156 | // note that this may block forever if the lane doesn't call a cancellation point and pthread doesn't honor PTHREAD_CANCEL_ASYNCHRONOUS | ||
157 | result = THREAD_WAIT( &s->thread, waitkill_timeout_, &s->done_signal, &s->done_lock, &s->status); | ||
158 | if( result == CR_Timeout) | ||
159 | { | ||
160 | return luaL_error( L, "force-killed lane failed to terminate within %f second%s", waitkill_timeout_, waitkill_timeout_ > 1 ? "s" : ""); | ||
161 | } | ||
162 | #else | ||
163 | (void) waitkill_timeout_; // unused | ||
164 | (void) L; // unused | ||
165 | #endif // THREADAPI == THREADAPI_PTHREAD | ||
166 | s->mstatus = KILLED; // mark 'gc' to wait for it | ||
167 | // note that s->status value must remain to whatever it was at the time of the kill | ||
168 | // because we need to know if we can lua_close() the Lua State or not. | ||
169 | result = CR_Killed; | ||
170 | } | ||
171 | return result; | ||
172 | } | ||
173 | |||
174 | // ################################################################################################ | ||
175 | |||
176 | cancel_result thread_cancel( lua_State* L, Lane* s, double secs_, bool_t force_, double waitkill_timeout_) | ||
177 | { | ||
178 | // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here | ||
179 | // We can read 's->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) | ||
180 | if( s->mstatus == KILLED) | ||
181 | { | ||
182 | return CR_Killed; | ||
183 | } | ||
184 | |||
185 | if( s->status >= DONE) | ||
186 | { | ||
187 | // say "ok" by default, including when lane is already done | ||
188 | return CR_Cancelled; | ||
189 | } | ||
190 | |||
191 | // signal the linda the wake up the thread so that it can react to the cancel query | ||
192 | // let us hope we never land here with a pointer on a linda that has been destroyed... | ||
193 | if( secs_ < 0.0) | ||
194 | { | ||
195 | return thread_cancel_soft( s, force_); | ||
196 | } | ||
197 | |||
198 | return thread_cancel_hard( L, s, secs_, force_, waitkill_timeout_); | ||
199 | } | ||
200 | |||
201 | // ################################################################################################ | ||
202 | // ################################################################################################ | ||
203 | |||
204 | // lane_h:cancel( [timeout] [, force [, forcekill_timeout]]) | ||
205 | LUAG_FUNC( thread_cancel) | ||
206 | { | ||
207 | Lane* s = lua_toLane( L, 1); | ||
208 | double secs = 0.0; | ||
209 | int force_i = 2; | ||
210 | int forcekill_timeout_i = 3; | ||
211 | |||
212 | if( lua_isnumber( L, 2)) | ||
213 | { | ||
214 | secs = lua_tonumber( L, 2); | ||
215 | if( secs < 0.0 && lua_gettop( L) > 3) | ||
216 | { | ||
217 | return luaL_error( L, "can't force_kill a soft cancel"); | ||
218 | } | ||
219 | // negative timeout and force flag means we want to wake linda-waiting threads | ||
220 | ++ force_i; | ||
221 | ++ forcekill_timeout_i; | ||
222 | } | ||
223 | else if( lua_isnil( L, 2)) | ||
224 | { | ||
225 | ++ force_i; | ||
226 | ++ forcekill_timeout_i; | ||
227 | } | ||
228 | |||
229 | { | ||
230 | bool_t force = lua_toboolean( L, force_i); // FALSE if nothing there | ||
231 | double forcekill_timeout = luaL_optnumber( L, forcekill_timeout_i, 0.0); | ||
232 | |||
233 | switch( thread_cancel( L, s, secs, force, forcekill_timeout)) | ||
234 | { | ||
235 | case CR_Timeout: | ||
236 | lua_pushboolean( L, 0); | ||
237 | lua_pushstring( L, "timeout"); | ||
238 | return 2; | ||
239 | |||
240 | case CR_Cancelled: | ||
241 | lua_pushboolean( L, 1); | ||
242 | return 1; | ||
243 | |||
244 | case CR_Killed: | ||
245 | lua_pushboolean( L, 0); | ||
246 | lua_pushstring( L, "killed"); | ||
247 | return 2; | ||
248 | } | ||
249 | } | ||
250 | // should never happen, only here to prevent the compiler from complaining of "not all control paths returning a value" | ||
251 | return 0; | ||
252 | } | ||