diff options
Diffstat (limited to 'LuaSL/LuaSL_threads.c')
-rw-r--r-- | LuaSL/LuaSL_threads.c | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/LuaSL/LuaSL_threads.c b/LuaSL/LuaSL_threads.c new file mode 100644 index 0000000..234a7c5 --- /dev/null +++ b/LuaSL/LuaSL_threads.c | |||
@@ -0,0 +1,496 @@ | |||
1 | /* This code is heavily based on luaproc. | ||
2 | * | ||
3 | * The luaproc copyright notice and license is - | ||
4 | |||
5 | *************************************************** | ||
6 | |||
7 | Copyright 2008 Alexandre Skyrme, Noemi Rodriguez, Roberto Ierusalimschy | ||
8 | |||
9 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
10 | of this software and associated documentation files (the "Software"), to deal | ||
11 | in the Software without restriction, including without limitation the rights | ||
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
13 | copies of the Software, and to permit persons to whom the Software is | ||
14 | furnished to do so, subject to the following conditions: | ||
15 | |||
16 | The above copyright notice and this permission notice shall be included in | ||
17 | all copies or substantial portions of the Software. | ||
18 | |||
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
25 | THE SOFTWARE. | ||
26 | |||
27 | **************************************************** | ||
28 | * | ||
29 | * Additions and changes Copyright 2012 by David Seikel, using the above license. | ||
30 | */ | ||
31 | |||
32 | |||
33 | /* This is a redesign of luaproc. The design goals and notes - | ||
34 | * | ||
35 | * In general use EFL where it is useful. | ||
36 | * Probably one fixed unique message channel per object, which each script in the object shares. | ||
37 | * But might be better to handle that C side anyway. | ||
38 | * Better integration with LuaSL. | ||
39 | * Use ecore threads instead of raw pthreads. | ||
40 | * Ecore threads pretty much wraps pthreads on posix, but has Windows support to. | ||
41 | * Merge in the edje Lua code, and keep an eye on that, coz we might want to actually add this to edje Lua in the future. | ||
42 | * Use my coding standards, or EFL ones. Pffft. | ||
43 | * | ||
44 | */ | ||
45 | |||
46 | #include "LuaSL.h" | ||
47 | |||
48 | |||
49 | /* ready process queue insertion status */ | ||
50 | #define LUAPROC_SCHED_QUEUE_PROC_OK 0 | ||
51 | //#define LUAPROC_SCHED_QUEUE_PROC_ERR -1 | ||
52 | |||
53 | /* process is idle */ | ||
54 | #define LUAPROC_STAT_IDLE 0 | ||
55 | /* process is ready to run */ | ||
56 | #define LUAPROC_STAT_READY 1 | ||
57 | /* process is blocked on send */ | ||
58 | #define LUAPROC_STAT_BLOCKED_SEND 2 | ||
59 | /* process is blocked on receive */ | ||
60 | #define LUAPROC_STAT_BLOCKED_RECV 3 | ||
61 | |||
62 | |||
63 | typedef struct | ||
64 | { | ||
65 | Eina_Clist node; | ||
66 | lua_State *L; | ||
67 | } recycled; | ||
68 | |||
69 | /********* | ||
70 | * globals | ||
71 | *********/ | ||
72 | |||
73 | /* ready process list */ | ||
74 | Eina_Clist lpready; | ||
75 | |||
76 | /* ready process queue access mutex */ | ||
77 | pthread_mutex_t mutex_queue_access = PTHREAD_MUTEX_INITIALIZER; | ||
78 | |||
79 | /* wake worker up conditional variable */ | ||
80 | pthread_cond_t cond_wakeup_worker = PTHREAD_COND_INITIALIZER; | ||
81 | |||
82 | /* active luaproc count access mutex */ | ||
83 | pthread_mutex_t mutex_lp_count = PTHREAD_MUTEX_INITIALIZER; | ||
84 | |||
85 | /* no active luaproc conditional variable */ | ||
86 | pthread_cond_t cond_no_active_lp = PTHREAD_COND_INITIALIZER; | ||
87 | |||
88 | /* number of active luaprocs */ | ||
89 | int lpcount = 0; | ||
90 | |||
91 | /* no more lua processes flag */ | ||
92 | int no_more_processes = FALSE; | ||
93 | |||
94 | /* channel operations mutex */ | ||
95 | pthread_mutex_t mutex_channel = PTHREAD_MUTEX_INITIALIZER; | ||
96 | |||
97 | /* recycle list mutex */ | ||
98 | pthread_mutex_t mutex_recycle_list = PTHREAD_MUTEX_INITIALIZER; | ||
99 | |||
100 | /* recycled lua process list */ | ||
101 | Eina_Clist recyclelp; | ||
102 | |||
103 | |||
104 | /****************************** | ||
105 | * library functions prototypes | ||
106 | ******************************/ | ||
107 | /* send a message to a lua process */ | ||
108 | static int luaproc_send( lua_State *L ); | ||
109 | /* receive a message from a lua process */ | ||
110 | static int luaproc_receive( lua_State *L ); | ||
111 | /* send a message back to the main loop */ | ||
112 | static int luaproc_send_back( lua_State *L ); | ||
113 | |||
114 | /* luaproc function registration array - main (parent) functions */ | ||
115 | static const struct luaL_reg luaproc_funcs_parent[] = { | ||
116 | { "sendback", luaproc_send_back }, | ||
117 | { NULL, NULL } | ||
118 | }; | ||
119 | |||
120 | /* luaproc function registration array - newproc (child) functions */ | ||
121 | static const struct luaL_reg luaproc_funcs_child[] = { | ||
122 | { "send", luaproc_send }, | ||
123 | { "receive", luaproc_receive }, | ||
124 | { "sendback", luaproc_send_back }, | ||
125 | { NULL, NULL } | ||
126 | }; | ||
127 | |||
128 | |||
129 | /* increase active lua process count */ | ||
130 | static void sched_lpcount_inc(void) | ||
131 | { | ||
132 | pthread_mutex_lock(&mutex_lp_count); | ||
133 | lpcount++; | ||
134 | pthread_mutex_unlock(&mutex_lp_count); | ||
135 | } | ||
136 | |||
137 | /* decrease active lua process count */ | ||
138 | static void sched_lpcount_dec(void) | ||
139 | { | ||
140 | pthread_mutex_lock(&mutex_lp_count); | ||
141 | lpcount--; | ||
142 | /* if count reaches zero, signal there are no more active processes */ | ||
143 | if (lpcount == 0) | ||
144 | pthread_cond_signal(&cond_no_active_lp); | ||
145 | pthread_mutex_unlock(&mutex_lp_count); | ||
146 | } | ||
147 | |||
148 | /* worker thread main function */ | ||
149 | static void *workermain( void *args ) { | ||
150 | |||
151 | script *lp; | ||
152 | int procstat; | ||
153 | |||
154 | /* detach thread so resources are freed as soon as thread exits (no further joining) */ | ||
155 | pthread_detach( pthread_self( )); | ||
156 | |||
157 | /* main worker loop */ | ||
158 | while ( 1 ) { | ||
159 | |||
160 | /* get exclusive access to the ready process queue */ | ||
161 | pthread_mutex_lock( &mutex_queue_access ); | ||
162 | |||
163 | /* wait until instructed to wake up (because there's work to do or because its time to finish) */ | ||
164 | while (( eina_clist_count( &lpready ) == 0 ) && ( no_more_processes == FALSE )) { | ||
165 | pthread_cond_wait( &cond_wakeup_worker, &mutex_queue_access ); | ||
166 | } | ||
167 | |||
168 | /* pop the first node from the ready process queue */ | ||
169 | if ((lp = (script *) eina_clist_head(&lpready))) | ||
170 | eina_clist_remove(&(lp->node)); | ||
171 | else { | ||
172 | /* free access to the process ready queue */ | ||
173 | pthread_mutex_unlock( &mutex_queue_access ); | ||
174 | /* finished thread */ | ||
175 | pthread_exit( NULL ); | ||
176 | } | ||
177 | |||
178 | /* free access to the process ready queue */ | ||
179 | pthread_mutex_unlock( &mutex_queue_access ); | ||
180 | |||
181 | /* execute the lua code specified in the lua process struct */ | ||
182 | procstat = lua_resume(lp->L, lp->args); | ||
183 | /* reset the process argument count */ | ||
184 | lp->args = 0; | ||
185 | |||
186 | /* check if process finished its whole execution, then recycle it */ | ||
187 | if (procstat == 0) | ||
188 | { | ||
189 | recycled *trash = malloc(sizeof(recycled)); | ||
190 | |||
191 | if (trash) | ||
192 | { | ||
193 | trash->L = lp->L; | ||
194 | pthread_mutex_lock(&mutex_recycle_list); | ||
195 | eina_clist_add_tail(&recyclelp, &(trash->node)); | ||
196 | pthread_mutex_unlock(&mutex_recycle_list); | ||
197 | sched_lpcount_dec(); | ||
198 | } | ||
199 | lua_close(lp->L); | ||
200 | if (lp->timer) | ||
201 | ecore_timer_del(lp->timer); | ||
202 | free(lp); | ||
203 | } | ||
204 | |||
205 | /* check if process yielded */ | ||
206 | else if ( procstat == LUA_YIELD ) { | ||
207 | |||
208 | /* if so, further check if yield originated from an unmatched send/recv operation */ | ||
209 | if (lp->status == LUAPROC_STAT_BLOCKED_SEND) | ||
210 | { | ||
211 | } | ||
212 | else if (lp->status == LUAPROC_STAT_BLOCKED_RECV) | ||
213 | { | ||
214 | } | ||
215 | /* or if yield resulted from an explicit call to coroutine.yield in the lua code being executed */ | ||
216 | else | ||
217 | { | ||
218 | /* get exclusive access to the ready process queue */ | ||
219 | pthread_mutex_lock( &mutex_queue_access ); | ||
220 | /* re-insert the job at the end of the ready process queue */ | ||
221 | eina_clist_add_tail(&lpready, &(lp->node)); | ||
222 | /* free access to the process ready queue */ | ||
223 | pthread_mutex_unlock( &mutex_queue_access ); | ||
224 | } | ||
225 | } | ||
226 | /* check if there was any execution error (LUA_ERRRUN, LUA_ERRSYNTAX, LUA_ERRMEM or LUA_ERRERR) */ | ||
227 | else | ||
228 | { | ||
229 | /* print error message */ | ||
230 | fprintf( stderr, "close lua_State (error: %s)\n", luaL_checkstring(lp->L, -1 )); | ||
231 | /* close lua state */ | ||
232 | lua_close(lp->L); | ||
233 | /* decrease active lua process count */ | ||
234 | sched_lpcount_dec(); | ||
235 | } | ||
236 | } | ||
237 | } | ||
238 | |||
239 | /* move process to ready queue (ie, schedule process) */ | ||
240 | static int sched_queue_proc( script *lp ) { | ||
241 | |||
242 | /* get exclusive access to the ready process queue */ | ||
243 | pthread_mutex_lock( &mutex_queue_access ); | ||
244 | |||
245 | /* add process to ready queue */ | ||
246 | eina_clist_add_tail(&lpready, &(lp->node)); | ||
247 | |||
248 | lp->status = LUAPROC_STAT_READY; | ||
249 | |||
250 | /* wake worker up */ | ||
251 | pthread_cond_signal( &cond_wakeup_worker ); | ||
252 | /* free access to the process ready queue */ | ||
253 | pthread_mutex_unlock( &mutex_queue_access ); | ||
254 | |||
255 | return LUAPROC_SCHED_QUEUE_PROC_OK; | ||
256 | } | ||
257 | |||
258 | /* synchronize worker threads */ | ||
259 | void sched_join_workerthreads( void ) { | ||
260 | |||
261 | pthread_mutex_lock( &mutex_lp_count ); | ||
262 | |||
263 | /* wait until there is no more active lua processes */ | ||
264 | while( lpcount != 0 ) { | ||
265 | pthread_cond_wait( &cond_no_active_lp, &mutex_lp_count ); | ||
266 | } | ||
267 | /* get exclusive access to the ready process queue */ | ||
268 | pthread_mutex_lock( &mutex_queue_access ); | ||
269 | /* set the no more active lua processes flag to true */ | ||
270 | no_more_processes = TRUE; | ||
271 | /* wake ALL workers up */ | ||
272 | pthread_cond_broadcast( &cond_wakeup_worker ); | ||
273 | /* free access to the process ready queue */ | ||
274 | pthread_mutex_unlock( &mutex_queue_access ); | ||
275 | |||
276 | // We don't need this, as we only get here during shutdown. Linking this to EFL results in a hang otherwise anyway. | ||
277 | /* wait for (join) worker threads */ | ||
278 | // pthread_exit( NULL ); | ||
279 | |||
280 | pthread_mutex_unlock( &mutex_lp_count ); | ||
281 | |||
282 | } | ||
283 | |||
284 | /* create a new worker pthread */ | ||
285 | int sched_create_worker(void) | ||
286 | { | ||
287 | pthread_t worker; | ||
288 | |||
289 | /* create a new pthread */ | ||
290 | if (pthread_create( &worker, NULL, workermain, NULL ) != 0) | ||
291 | return LUAPROC_SCHED_PTHREAD_ERROR; | ||
292 | return LUAPROC_SCHED_OK; | ||
293 | } | ||
294 | |||
295 | void newProc(const char *code, int file, script *lp) | ||
296 | { | ||
297 | int ret; | ||
298 | recycled *trash; | ||
299 | |||
300 | // Try to recycle a Lua state, otherwise create one from scratch. | ||
301 | pthread_mutex_lock(&mutex_recycle_list); | ||
302 | /* pop list head */ | ||
303 | if ((trash = (recycled *) eina_clist_head(&recyclelp))) | ||
304 | { | ||
305 | eina_clist_remove(&(trash->node)); | ||
306 | lp->L = trash->L; | ||
307 | free(trash); | ||
308 | } | ||
309 | pthread_mutex_unlock(&mutex_recycle_list); | ||
310 | |||
311 | if (NULL == lp->L) | ||
312 | { | ||
313 | lp->L = luaL_newstate(); | ||
314 | |||
315 | /* store the script struct in its own Lua state */ | ||
316 | lua_pushlightuserdata(lp->L, lp); | ||
317 | lua_setfield(lp->L, LUA_REGISTRYINDEX, "_SELF"); | ||
318 | luaL_openlibs(lp->L); | ||
319 | luaL_register(lp->L, "luaproc", luaproc_funcs_child); | ||
320 | } | ||
321 | |||
322 | lp->status = LUAPROC_STAT_IDLE; | ||
323 | lp->args = 0; | ||
324 | eina_clist_element_init(&(lp->node)); | ||
325 | eina_clist_init(&(lp->messages)); | ||
326 | |||
327 | /* load process' code */ | ||
328 | if (file) | ||
329 | ret = luaL_loadfile(lp->L, code); | ||
330 | else | ||
331 | ret = luaL_loadstring(lp->L, code); | ||
332 | |||
333 | /* in case of errors, destroy Lua process */ | ||
334 | if (ret != 0) | ||
335 | { | ||
336 | lua_close(lp->L); | ||
337 | lp->L = NULL; | ||
338 | } | ||
339 | |||
340 | if (lp->L) | ||
341 | { | ||
342 | sched_lpcount_inc(); | ||
343 | |||
344 | /* schedule luaproc */ | ||
345 | if (sched_queue_proc(lp) != LUAPROC_SCHED_QUEUE_PROC_OK) | ||
346 | { | ||
347 | printf( "[luaproc] error queueing Lua process\n" ); | ||
348 | sched_lpcount_dec(); | ||
349 | lua_close(lp->L); | ||
350 | } | ||
351 | } | ||
352 | } | ||
353 | |||
354 | /* return the lua process associated with a given lua state */ | ||
355 | static script *luaproc_getself(lua_State *L) | ||
356 | { | ||
357 | script *lp; | ||
358 | |||
359 | lua_getfield(L, LUA_REGISTRYINDEX, "_SELF"); | ||
360 | lp = (script *) lua_touserdata(L, -1); | ||
361 | lua_pop(L, 1); | ||
362 | return lp; | ||
363 | } | ||
364 | |||
365 | /* send a message to the client process */ | ||
366 | static int luaproc_send_back(lua_State *L) | ||
367 | { | ||
368 | script *self = luaproc_getself(L); | ||
369 | const char *message = luaL_checkstring(L, 1); | ||
370 | |||
371 | if (self) | ||
372 | { | ||
373 | scriptMessage *sm = calloc(1, sizeof(scriptMessage)); | ||
374 | |||
375 | if (sm) | ||
376 | { | ||
377 | eina_clist_element_init(&(sm->node)); | ||
378 | sm->script = self; | ||
379 | strcpy((char *) sm->message, message); | ||
380 | ecore_main_loop_thread_safe_call_async(scriptSendBack, sm); | ||
381 | } | ||
382 | } | ||
383 | |||
384 | return 0; | ||
385 | } | ||
386 | |||
387 | /* error messages for the sendToChannel function */ | ||
388 | const char *sendToChannelErrors[] = | ||
389 | { | ||
390 | "non-existent channel", | ||
391 | "error scheduling process" | ||
392 | }; | ||
393 | |||
394 | /* send a message to a lua process */ | ||
395 | const char *sendToChannel(gameGlobals *ourGlobals, const char *SID, const char *message) | ||
396 | { | ||
397 | const char *result = NULL; | ||
398 | script *dstlp; | ||
399 | |||
400 | /* get exclusive access to operate on channels */ | ||
401 | pthread_mutex_lock(&mutex_channel); | ||
402 | |||
403 | // Add the message to the queue. | ||
404 | if ((dstlp = eina_hash_find(ourGlobals->scripts, SID))) | ||
405 | { | ||
406 | scriptMessage *sm = NULL; | ||
407 | |||
408 | if ((sm = malloc(sizeof(scriptMessage)))) | ||
409 | { | ||
410 | sm->script = dstlp; | ||
411 | strcpy((char *) sm->message, message); | ||
412 | eina_clist_add_tail(&(dstlp->messages), &(sm->node)); | ||
413 | } | ||
414 | |||
415 | /* if it's already waiting, send the next message to it and (queue) wake it */ | ||
416 | if (dstlp->status == LUAPROC_STAT_BLOCKED_RECV) | ||
417 | { | ||
418 | scriptMessage *msg = (scriptMessage *) eina_clist_head(&(dstlp->messages)); | ||
419 | |||
420 | // See if there's a message on the queue. Note, this may not be the same as the incoming message, if there was already a queue. | ||
421 | if (msg) | ||
422 | { | ||
423 | eina_clist_remove(&(msg->node)); | ||
424 | message = msg->message; | ||
425 | } | ||
426 | /* push the message onto the receivers stack */ | ||
427 | lua_pushstring(dstlp->L, message); | ||
428 | dstlp->args = lua_gettop(dstlp->L) - 1; | ||
429 | if (msg) | ||
430 | free(msg); | ||
431 | |||
432 | if (sched_queue_proc(dstlp) != LUAPROC_SCHED_QUEUE_PROC_OK) | ||
433 | { | ||
434 | sched_lpcount_dec(); | ||
435 | lua_close(dstlp->L); | ||
436 | result = sendToChannelErrors[1]; | ||
437 | } | ||
438 | } | ||
439 | } | ||
440 | |||
441 | pthread_mutex_unlock(&mutex_channel); | ||
442 | |||
443 | return result; | ||
444 | } | ||
445 | |||
446 | /* send a message to a lua process */ | ||
447 | static int luaproc_send(lua_State *L) | ||
448 | { | ||
449 | script *self = luaproc_getself(L); | ||
450 | const char *result = sendToChannel(self->game, luaL_checkstring(L, 1), luaL_checkstring(L, 2)); | ||
451 | |||
452 | if (result) | ||
453 | { | ||
454 | lua_pushnil(L); | ||
455 | lua_pushstring(L, result); | ||
456 | return 2; | ||
457 | } | ||
458 | |||
459 | lua_pushboolean(L, TRUE); | ||
460 | return 1; | ||
461 | } | ||
462 | |||
463 | /* receive a message from a lua process */ | ||
464 | static int luaproc_receive(lua_State *L) | ||
465 | { | ||
466 | script *self; | ||
467 | const char *chname = luaL_checkstring(L, 1); | ||
468 | scriptMessage *msg; | ||
469 | |||
470 | // First check if there are queued messages, and grab one. | ||
471 | self = luaproc_getself(L); | ||
472 | if ((msg = (scriptMessage *) eina_clist_head(&(self->messages)))) | ||
473 | { | ||
474 | eina_clist_remove(&(msg->node)); | ||
475 | lua_pushstring(L, msg->message); | ||
476 | free(msg); | ||
477 | return lua_gettop(L) - 1; | ||
478 | } | ||
479 | |||
480 | /* if trying an asynchronous receive, return an error */ | ||
481 | if ( lua_toboolean( L, 2 )) | ||
482 | { | ||
483 | lua_pushnil(L); | ||
484 | lua_pushfstring(L, "no senders waiting on channel %s", chname); | ||
485 | return 2; | ||
486 | } | ||
487 | /* otherwise (synchronous receive) simply block process */ | ||
488 | self->status = LUAPROC_STAT_BLOCKED_RECV; | ||
489 | return lua_yield(L, lua_gettop(L)); | ||
490 | } | ||
491 | |||
492 | void luaprocInit(void) | ||
493 | { | ||
494 | eina_clist_init(&recyclelp); | ||
495 | eina_clist_init(&lpready); | ||
496 | } | ||