aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/LuaSL/LuaSL_threads.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/LuaSL/LuaSL_threads.c')
-rw-r--r--src/LuaSL/LuaSL_threads.c496
1 files changed, 496 insertions, 0 deletions
diff --git a/src/LuaSL/LuaSL_threads.c b/src/LuaSL/LuaSL_threads.c
new file mode 100644
index 0000000..234a7c5
--- /dev/null
+++ b/src/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
7Copyright 2008 Alexandre Skyrme, Noemi Rodriguez, Roberto Ierusalimschy
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE 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
63typedef struct
64{
65 Eina_Clist node;
66 lua_State *L;
67} recycled;
68
69/*********
70* globals
71*********/
72
73/* ready process list */
74Eina_Clist lpready;
75
76/* ready process queue access mutex */
77pthread_mutex_t mutex_queue_access = PTHREAD_MUTEX_INITIALIZER;
78
79/* wake worker up conditional variable */
80pthread_cond_t cond_wakeup_worker = PTHREAD_COND_INITIALIZER;
81
82/* active luaproc count access mutex */
83pthread_mutex_t mutex_lp_count = PTHREAD_MUTEX_INITIALIZER;
84
85/* no active luaproc conditional variable */
86pthread_cond_t cond_no_active_lp = PTHREAD_COND_INITIALIZER;
87
88/* number of active luaprocs */
89int lpcount = 0;
90
91/* no more lua processes flag */
92int no_more_processes = FALSE;
93
94/* channel operations mutex */
95pthread_mutex_t mutex_channel = PTHREAD_MUTEX_INITIALIZER;
96
97/* recycle list mutex */
98pthread_mutex_t mutex_recycle_list = PTHREAD_MUTEX_INITIALIZER;
99
100/* recycled lua process list */
101Eina_Clist recyclelp;
102
103
104/******************************
105* library functions prototypes
106******************************/
107/* send a message to a lua process */
108static int luaproc_send( lua_State *L );
109/* receive a message from a lua process */
110static int luaproc_receive( lua_State *L );
111/* send a message back to the main loop */
112static int luaproc_send_back( lua_State *L );
113
114/* luaproc function registration array - main (parent) functions */
115static 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 */
121static 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 */
130static 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 */
138static 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 */
149static 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) */
240static 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 */
259void 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 */
285int 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
295void 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 */
355static 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 */
366static 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 */
388const char *sendToChannelErrors[] =
389{
390 "non-existent channel",
391 "error scheduling process"
392};
393
394/* send a message to a lua process */
395const 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 */
447static 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 */
464static 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
492void luaprocInit(void)
493{
494 eina_clist_init(&recyclelp);
495 eina_clist_init(&lpready);
496}