exceptionptr update for native threads
[cacao.git] / threads / thread.c
1 /* -*- mode: c; tab-width: 4; c-basic-offset: 4 -*- */
2 /*
3  * thread.c
4  * Thread support.
5  *
6  * Copyright (c) 1996 T. J. Wilkinson & Associates, London, UK.
7  *
8  * See the file "license.terms" for information on usage and redistribution
9  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10  *
11  * Written by Tim Wilkinson <tim@tjwassoc.demon.co.uk>, 1996.
12  */
13
14
15 #include "global.h"
16 #include <stdlib.h>
17 #include <string.h>
18 #include <assert.h>
19 #include <sys/types.h>
20 #include <sys/mman.h>                   /* for mprotect */
21 #include <unistd.h>
22 #include <signal.h>
23 #include <sys/time.h>
24
25 #include "config.h"
26 #include "thread.h"
27 #include "locks.h"
28 #include "tables.h"
29 #include "native.h"
30 #include "loader.h"
31 #include "builtin.h"
32 #include "asmpart.h"
33 #include "toolbox/loging.h"
34 #include "toolbox/memory.h"
35
36 #if defined(NATIVE_THREADS)
37 pthread_mutex_t cast_mutex = PTHREAD_MUTEX_INITIALIZER;
38 pthread_mutex_t compiler_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
39
40 int cast_counter;
41
42 #ifndef HAVE___THREAD
43 pthread_key_t tkey_threadinfo;
44 #endif
45 #endif
46
47 static classinfo *class_java_lang_ThreadDeath;
48
49 thread* currentThread = NULL;
50 thread* mainThread;
51 thread* threadQhead[MAX_THREAD_PRIO + 1];
52 thread* threadQtail[MAX_THREAD_PRIO + 1];
53
54 thread* liveThreads = NULL;
55 thread* sleepThreads = NULL;
56
57 int blockInts;
58 bool needReschedule;
59
60 ctx contexts[MAXTHREADS];
61
62 /* Number of threads alive, also counting daemons */
63 static int talive;
64
65 /* Number of daemon threads alive */
66 static int tdaemon;
67
68 static void firstStartThread(void);
69
70 void reschedule(void);
71
72 /* Setup default thread stack size - this can be overwritten if required */
73 int threadStackSize = THREADSTACKSIZE;
74
75 /* Pointer to the stack of the last killed thread. The free is delayed. */
76 void *stack_to_be_freed = 0;
77
78 static thread* startDaemon(void* func, char* nm, int stackSize);
79
80 /*
81  * Allocate the stack for a thread
82  */
83 void
84 allocThreadStack (thread *tid, int size)
85 {
86     int pageSize = getpagesize();
87     unsigned long pageBegin;
88
89         assert(stack_to_be_freed == 0);
90
91     CONTEXT(tid).stackMem = GCNEW(u1, size + 4 * pageSize);
92     assert(CONTEXT(tid).stackMem != 0);
93     CONTEXT(tid).stackEnd = CONTEXT(tid).stackMem + size + 2 * pageSize;
94     
95     pageBegin = (unsigned long)(CONTEXT(tid).stackMem) + pageSize - 1;
96     pageBegin = pageBegin - pageBegin % pageSize;
97
98     CONTEXT(tid).stackBase = (u1*)pageBegin + pageSize;
99 }
100
101 /*
102  * Mark the stack for a thread to be freed. We cannot free the stack
103  * immediately because it is still in use!
104  */
105 void
106 freeThreadStack (thread *tid)
107 {
108     if (!(CONTEXT(tid).flags & THREAD_FLAGS_NOSTACKALLOC))
109     {
110                 int pageSize = getpagesize();
111                 unsigned long pageBegin;
112
113                 pageBegin = (unsigned long)(CONTEXT(tid).stackMem) + pageSize - 1;
114                 pageBegin = pageBegin - pageBegin % pageSize;
115
116                 assert(stack_to_be_freed == 0);
117
118                 stack_to_be_freed = CONTEXT(tid).stackMem;
119     }
120     CONTEXT(tid).stackMem = 0;
121     CONTEXT(tid).stackBase = 0;
122     CONTEXT(tid).stackEnd = 0;
123 }
124
125 /*
126  * Initialize threads.
127  */
128 void
129 initThreads(u1 *stackbottom)
130 {
131 #if defined(NATIVE_THREADS) && !defined(HAVE___THREAD)
132         pthread_key_create(&tkey_threadinfo, NULL);
133 #endif
134
135         thread *the_main_thread;
136     int i;
137         char mainname[] = "main";
138         /*int len = strlen(mainname);*/
139
140         signal(SIGPIPE, SIG_IGN);
141
142     initLocks();
143
144     for (i = 0; i < MAXTHREADS; ++i) {
145                 contexts[i].free = true;
146                 contexts[i].thread = NULL;
147         }
148
149     /* Allocate a thread to be the main thread */
150     liveThreads = the_main_thread = (thread*)builtin_new(loader_load_sysclass(NULL,utf_new_char("java/lang/Thread")));
151     assert(the_main_thread != 0);
152     
153     the_main_thread->PrivateInfo = 1;
154     CONTEXT(the_main_thread).free = false;
155
156 #if 0
157     {
158         /* stefan */
159         methodinfo *m;
160         m = class_fetchmethod(
161                         class_java_lang_String,
162                         utf_new_char ("toCharArray"),
163                         utf_new_char ("()[C")
164                         );
165 printf("DEADCODE LIVES ?????????\n");fflush(stdout);
166         the_main_thread->name = asm_calljavafunction (m, javastring_new(utf_new_char("main")), 0, 0, 0);
167     }
168 #endif
169         the_main_thread->name=javastring_new(utf_new_char(mainname));
170 /*      the_main_thread->name = builtin_newarray_char(len);
171         {   u2 *d = the_main_thread->name->data;
172                 for (i=0; i<len; i++)
173                         d[i] = mainname[i];
174         }*/
175     the_main_thread->priority = NORM_THREAD_PRIO;
176     CONTEXT(the_main_thread).priority = (u1)the_main_thread->priority;
177     CONTEXT(the_main_thread).texceptionptr = 0;
178     the_main_thread->next = 0;
179     CONTEXT(the_main_thread).status = THREAD_SUSPENDED;
180     CONTEXT(the_main_thread).stackBase = CONTEXT(the_main_thread).stackEnd = stackbottom;
181     THREADINFO(&CONTEXT(the_main_thread));
182
183     DBG( printf("main thread %p base %p end %p\n", 
184                                 the_main_thread,
185                                 CONTEXT(the_main_thread).stackBase,
186                                 CONTEXT(the_main_thread).stackEnd); );
187
188         CONTEXT(the_main_thread).flags = THREAD_FLAGS_NOSTACKALLOC;
189         CONTEXT(the_main_thread).nextlive = 0;
190         CONTEXT(the_main_thread).thread = the_main_thread;
191         /*the_main_thread->single_step = 0;*/
192         the_main_thread->daemon = 0;
193         /*the_main_thread->stillborn = 0;*/
194         /*the_main_thread->target = 0;*/
195
196         the_main_thread->contextClassLoader = 0;
197         /*the_main_thread->inheritedAccessControlContext = 0;*/
198         /*the_main_thread->values = 0;*/
199
200         /* Allocate and init ThreadGroup */
201         the_main_thread->group = (threadGroup*)native_new_and_init(loader_load(utf_new_char("java/lang/ThreadGroup")));
202         assert(the_main_thread->group != 0);
203
204         talive++;
205
206         /* Load exception classes */
207     loader_load_sysclass(&class_java_lang_ThreadDeath,
208                          utf_new_char("java/lang/ThreadDeath"));
209
210         DBG( fprintf(stderr, "finishing initThreads\n"); );
211
212     mainThread = currentThread = the_main_thread;
213
214         /* Add thread into runQ */
215         iresumeThread(mainThread);
216
217         assert(blockInts == 0);
218 }
219
220 /*
221  * Start a new thread running.
222  */
223 void
224 startThread (thread* tid)
225 {
226     int i;
227
228     /* Allocate a stack context */
229     for (i = 0; i < MAXTHREADS; ++i)
230                 if (contexts[i].free)
231                         break;
232
233     if (i == MAXTHREADS)
234                 panic("Too many threads");
235
236         assert(tid->priority >= MIN_THREAD_PRIO && tid->priority <= MAX_THREAD_PRIO);
237
238     tid->PrivateInfo = i + 1;
239     CONTEXT(tid).free = false;
240         CONTEXT(tid).thread = tid;
241     CONTEXT(tid).nextlive = liveThreads;
242     liveThreads = tid;
243     allocThreadStack(tid, threadStackSize);
244     CONTEXT(tid).usedStackTop = CONTEXT(tid).stackBase;
245     CONTEXT(tid).flags = THREAD_FLAGS_GENERAL;
246     CONTEXT(tid).status = THREAD_SUSPENDED;
247     CONTEXT(tid).priority = (u1)tid->priority;
248     CONTEXT(tid).texceptionptr = 0;
249
250     /* Construct the initial restore point. */
251     THREADINIT((&CONTEXT(tid)), firstStartThread);
252
253     DBG( printf("new thread %p base %p end %p\n",
254                                 tid, CONTEXT(tid).stackBase,
255                                 CONTEXT(tid).stackEnd); );
256
257         talive++;
258         if (tid->daemon)
259                 tdaemon++;
260
261         /* Add thread into runQ */
262         iresumeThread(tid);
263 }
264
265 /*
266  * Start a daemon thread.
267  */
268 static thread*
269 startDaemon(void* func, char* nm, int stackSize)
270 {
271     thread* tid;
272     int i;
273
274     DBG( printf("startDaemon %s\n", nm); );
275
276         tid = (thread*)builtin_new(loader_load_sysclass(NULL,utf_new_char("java/lang/Thread")));
277         assert(tid != 0);
278
279         for (i = 0; i < MAXTHREADS; ++i)
280                 if (contexts[i].free)
281                         break;
282         if (i == MAXTHREADS)
283                 panic("Too many threads");
284
285         tid->PrivateInfo = i + 1;
286         CONTEXT(tid).free = false;
287         tid->name = 0;          /* for the moment */
288         tid->priority = MAX_THREAD_PRIO;
289         CONTEXT(tid).priority = (u1)tid->priority;
290         tid->next = 0;
291         CONTEXT(tid).status = THREAD_SUSPENDED;
292
293         allocThreadStack(tid, stackSize);
294         /*tid->single_step = 0;*/
295         tid->daemon = 1;
296         /*tid->stillborn = 0;*/
297         /*tid->target = 0;*/
298         tid->group = 0;
299
300         /* Construct the initial restore point. */
301         THREADINIT((&CONTEXT(tid)), func);
302
303         talive++;
304         tdaemon++;
305
306         return tid;
307 }
308
309 /*
310  * All threads start here.
311  */
312 static void
313 firstStartThread(void)
314 {
315     methodinfo *method;
316
317     DBG( printf("firstStartThread %p\n", currentThread); );
318
319         if (stack_to_be_freed != 0)     {
320                 stack_to_be_freed = 0;
321         }
322
323         /* Every thread starts with the interrupts off */
324         intsRestore();
325         assert(blockInts == 0);
326
327         /* Find the run()V method and call it */
328         method = class_findmethod(currentThread->header.vftbl->class,
329                                                           utf_new_char("run"), utf_new_char("()V"));
330         if (method == 0)
331                 panic("Cannot find method \'void run ()\'");
332
333         asm_calljavafunction(method, currentThread, NULL, NULL, NULL);
334
335     if (*exceptionptr) {
336         utf_display((*exceptionptr)->vftbl->class->name);
337         printf("\n");
338     }
339
340         killThread(0);
341         assert("Thread returned from killThread" == 0);
342 }
343
344 /*
345  * Resume a thread running.
346  * This routine has to be called only from locations which ensure
347  * run / block queue consistency. There is no check for illegal resume
348  * conditions (like explicitly resuming an IO blocked thread). There also
349  * is no update of any blocking queue. Both has to be done by the caller
350  */
351 void
352 iresumeThread(thread* tid)
353 {
354     DBG( printf("resumeThread %p\n", tid); );
355
356         intsDisable();
357
358         if (CONTEXT(tid).status != THREAD_RUNNING)
359         {
360                 CONTEXT(tid).status = THREAD_RUNNING;
361
362                 DBG( fprintf(stderr, "prio is %d\n", CONTEXT(tid).priority); );
363
364                 /* Place thread on the end of its queue */
365                 if (threadQhead[CONTEXT(tid).priority] == 0) {
366                         threadQhead[CONTEXT(tid).priority] = tid;
367                         threadQtail[CONTEXT(tid).priority] = tid;
368                         if (CONTEXT(tid).priority
369                                 > CONTEXT(currentThread).priority)
370                                 needReschedule = true;
371                 }
372                 else
373                 {
374                         threadQtail[CONTEXT(tid).priority]->next = tid;
375                         threadQtail[CONTEXT(tid).priority] = tid;
376                 }
377                 tid->next = 0;
378         }
379         SDBG( else { printf("Re-resuming %p\n", tid); } );
380
381         intsRestore();
382 }
383
384 /*
385  * Yield process to another thread of equal priority.
386  */
387 void
388 yieldThread()
389 {
390     intsDisable();
391
392     if (threadQhead[CONTEXT(currentThread).priority]
393                 != threadQtail[CONTEXT(currentThread).priority])
394     {
395                 /* Get the next thread and move me to the end */
396                 threadQhead[CONTEXT(currentThread).priority] = currentThread->next;
397                 threadQtail[CONTEXT(currentThread).priority]->next = currentThread;
398                 threadQtail[CONTEXT(currentThread).priority] = currentThread;
399                 currentThread->next = 0;
400                 needReschedule = true;
401     }
402
403     intsRestore();
404 }
405
406 /*
407  * Explicit request by user to resume a thread
408  * The definition says that it is just legal to call this after a preceeding
409  * suspend (which got through). If the thread was blocked for some other
410  * reason (either sleep or IO or a muxSem), we simply can't do it
411  * We use a new thread flag THREAD_FLAGS_USER_SUSPEND for this purpose
412  * (which is set by suspendThread(.))
413  */
414 void
415 resumeThread (thread* tid)
416 {
417     if ((CONTEXT(tid).flags & THREAD_FLAGS_USER_SUSPEND) != 0)
418     {
419                 intsDisable();
420                 CONTEXT(tid).flags &= ~THREAD_FLAGS_USER_SUSPEND;
421                 iresumeThread(tid);
422                 intsRestore();
423     }
424 }
425
426 /*
427  * Suspend a thread.
428  * This is an explicit user request to suspend the thread - the counterpart
429  * for resumeThreadRequest(.). It is JUST called by the java method
430  * Thread.suspend()
431  * What makes it distinct is the fact that the suspended thread is not contained
432  * in any block queue. Without a special flag (indicating the user suspend), we
433  * can't check s suspended thread for this condition afterwards (which is
434  * required by resumeThreadRequest()). The new thread flag
435  * THREAD_FLAGS_USER_SUSPEND is used for this purpose.
436  */
437 void
438 suspendThread(thread* tid)
439 {
440     thread** ntid;
441
442     intsDisable();
443
444     if (CONTEXT(tid).status != THREAD_SUSPENDED)
445     {
446                 CONTEXT(tid).status = THREAD_SUSPENDED;
447                 
448                 /*
449                  * This is used to indicate the explicit suspend condition
450                  * required by resumeThreadRequest()
451                  */
452                 CONTEXT(tid).flags |= THREAD_FLAGS_USER_SUSPEND;
453
454                 for (ntid = &threadQhead[CONTEXT(tid).priority];
455                          *ntid != 0;
456                          ntid = &(*ntid)->next)
457                 {
458                         if (*ntid == tid)
459                         {
460                                 *ntid = tid->next;
461                                 tid->next = 0;
462                                 if (tid == currentThread)
463                                 {
464                                         reschedule();
465                                 }
466                                 break;
467                         }
468                 }
469     }
470         SDBG( else { printf("Re-suspending %p\n", tid); } );
471
472         intsRestore();
473 }
474
475 /*
476  * Suspend a thread on a queue.
477  */
478 void
479 suspendOnQThread(thread* tid, thread** queue)
480 {
481     thread** ntid;
482
483         DBG( printf("suspendOnQThread %p %p\n", tid, queue); );
484
485         assert(blockInts > 0);
486
487         if (CONTEXT(tid).status != THREAD_SUSPENDED)
488         {
489                 CONTEXT(tid).status = THREAD_SUSPENDED;
490
491                 for (ntid = &threadQhead[CONTEXT(tid).priority];
492                          *ntid != 0;
493                          ntid = &(*ntid)->next)
494                 {
495                         if (*ntid == tid)
496                         {
497                                 *ntid = tid->next;
498                                 /* Insert onto head of lock wait Q */
499                                 tid->next = *queue;
500                                 *queue = tid;
501                                 if (tid == currentThread)
502                                 {
503                                         DBG( fprintf(stderr, "suspending %p (cur=%p) with prio %d\n",
504                                                                  tid, currentThread, CONTEXT(tid).priority); );
505                                         reschedule();
506                                 }
507                                 break;
508                         }
509                 }
510         }
511         SDBG( else { printf("Re-suspending %p on %p\n", tid, *queue); } );
512 }
513
514 /*
515  * Kill thread.
516  */
517 void
518 killThread(thread* tid)
519 {
520     thread** ntid;
521
522     intsDisable();
523
524     /* A null tid means the current thread */
525     if (tid == 0)
526     {
527                 tid = currentThread;
528     }
529
530         DBG( printf("killThread %p\n", tid); );
531
532         if (CONTEXT(tid).status != THREAD_DEAD)
533         {
534                 /* Get thread off runq (if it needs it) */
535                 if (CONTEXT(tid).status == THREAD_RUNNING)
536                 {
537                         for (ntid = &threadQhead[CONTEXT(tid).priority];
538                                  *ntid != 0;
539                                  ntid = &(*ntid)->next)
540                         {
541                                 if (*ntid == tid)
542                                 {
543                                         *ntid = tid->next;
544                                         break;
545                                 }
546                         }
547                 }
548
549                 CONTEXT(tid).status = THREAD_DEAD;
550                 talive--;
551                 if (tid->daemon) {
552                         tdaemon--;
553                 }
554
555                 /* If we only have daemons left, then everyone is dead. */
556                 if (talive == tdaemon) {
557                         /* atexit functions get called to clean things up */
558                         intsRestore();
559                         exit(0);
560                 }
561
562                 /* Notify on the object just in case anyone is waiting */
563                 internal_lock_mutex_for_object(&tid->header);
564                 internal_broadcast_cond_for_object(&tid->header);
565                 internal_unlock_mutex_for_object(&tid->header);
566
567                 /* Remove thread from live list to it can be garbaged */
568                 for (ntid = &liveThreads;
569                          *ntid != 0;
570                          ntid = &(CONTEXT((*ntid)).nextlive))
571                 {
572                         if (tid == (*ntid))
573                         {
574                                 (*ntid) = CONTEXT(tid).nextlive;
575                                 break;
576                         }
577                 }
578
579                 /* Free stack */
580                 freeThreadStack(tid);
581
582                 /* free context */
583                 if (tid != mainThread) {
584                         CONTEXT(tid).free = true;
585                         CONTEXT(tid).thread = NULL;
586                 }
587
588                 /* Run something else */
589                 needReschedule = true;
590         }
591         intsRestore();
592 }
593
594 /*
595  * Change thread priority.
596  */
597 void
598 setPriorityThread(thread* tid, int prio)
599 {
600     thread** ntid;
601
602         assert(prio >= MIN_THREAD_PRIO && prio <= MAX_THREAD_PRIO);
603
604     if (tid->PrivateInfo == 0) {
605                 tid->priority = prio;
606                 return;
607     }
608
609     if (CONTEXT(tid).status == THREAD_SUSPENDED) {
610                 CONTEXT(tid).priority = (u8)prio;
611                 return;
612     }
613
614     intsDisable();
615
616     /* Remove from current thread list */
617     for (ntid = &threadQhead[CONTEXT(tid).priority]; *ntid != 0; ntid = &(*ntid)->next) {
618                 if (*ntid == tid) {
619                         *ntid = tid->next;
620                         break;
621                 }
622     }
623
624     /* Insert onto a new one */
625     tid->priority = prio;
626     CONTEXT(tid).priority = (u8)tid->priority;
627     if (threadQhead[prio] == 0) {
628                 threadQhead[prio] = tid;
629                 threadQtail[prio] = tid;
630                 if (prio > CONTEXT(currentThread).priority) {
631                         needReschedule = true;
632                 }
633     }
634     else {
635                 threadQtail[prio]->next = tid;
636                 threadQtail[prio] = tid;
637     }
638     tid->next = 0;
639
640     intsRestore();
641 }
642
643 /*
644  * Get the current time in milliseconds since 1970-01-01.
645  */
646 s8
647 currentTime (void)
648 {
649         struct timeval tv;
650         s8 time;
651
652         gettimeofday(&tv, 0);
653
654         time = tv.tv_sec;
655         time *= 1000;
656         time += tv.tv_usec / 1000;
657
658         return time;
659 }
660
661 /*
662  * Put a thread to sleep.
663  */
664 void
665 sleepThread (s8 time)
666 {
667     thread** tidp;
668
669     /* Sleep for no time */
670     if (time <= 0) {
671                 return;
672     }
673     
674     intsDisable();
675
676     /* Get absolute time */
677     CONTEXT(currentThread).time = time + currentTime();
678
679     /* Find place in alarm list */
680     for (tidp = &sleepThreads; (*tidp) != 0; tidp = &(*tidp)->next)
681         {
682                 if (CONTEXT(*tidp).time > CONTEXT(currentThread).time)
683                         break;
684     }
685
686     /* Suspend thread on it */
687     suspendOnQThread(currentThread, tidp);
688     
689     intsRestore();
690 }
691
692 /*
693  * Is this thread alive?
694  */
695 bool
696 aliveThread(thread* tid)
697 {
698     if (tid->PrivateInfo != 0 && CONTEXT(tid).status != THREAD_DEAD)
699                 return (true);
700     else
701                 return (false);
702 }
703
704 /*
705  * Reschedule the thread.
706  * Called whenever a change in the running thread is required.
707  */
708 void
709 reschedule(void)
710 {
711     int i;
712     thread* lastThread;
713     int b;
714     /*    sigset_t nsig; */
715
716     /* A reschedule in a non-blocked context is half way to hell */
717     assert(blockInts > 0);
718     b = blockInts;
719     
720     /* Check events - we may release a high priority thread */
721     /* Just check IO, no need for a reschedule call by checkEvents() */
722     needReschedule = false;
723     checkEvents(false);
724
725     for (;;)
726     {
727                 for (i = MAX_THREAD_PRIO; i >= MIN_THREAD_PRIO; i--)
728                 {
729                         if (threadQhead[i] != 0)
730                         {
731                                 DBG( fprintf(stderr, "found thread %p in head %d\n", threadQhead[i], i); );
732
733                                 if (threadQhead[i] != currentThread)
734                                 {
735                                         /* USEDSTACKTOP((CONTEXT(currentThread).usedStackTop)); */
736
737                                         lastThread = currentThread;
738                                         currentThread = threadQhead[i];
739
740                                         CONTEXT(currentThread).texceptionptr = *exceptionptr;
741
742                     DBG( fprintf(stderr, "thread switch from: %p to: %p\n", lastThread, currentThread); );
743                                         THREADSWITCH((&CONTEXT(currentThread)),
744                                                                  (&CONTEXT(lastThread)));
745                                         blockInts = b;
746
747                                         *exceptionptr = CONTEXT(currentThread).texceptionptr;
748
749                                         if (stack_to_be_freed != 0) {
750                                                 stack_to_be_freed = 0;
751                                         }
752
753                                         /* Alarm signal may be blocked - if so
754                                          * unblock it.
755                                          */
756                                         /*
757                                           if (alarmBlocked == true) {
758                                           alarmBlocked = false;
759                                           sigemptyset(&nsig);
760                                           sigaddset(&nsig, SIGALRM);
761                                           sigprocmask(SIG_UNBLOCK, &nsig, 0);
762                                           }
763                                         */
764
765                                         /* I might be dying */
766                                         if ((CONTEXT(lastThread).flags & THREAD_FLAGS_KILLED)
767                                                 != 0)
768                                         {
769                                                 CONTEXT(lastThread).flags &= ~THREAD_FLAGS_KILLED;
770                                                 *exceptionptr = native_new_and_init(class_java_lang_ThreadDeath);
771                                         }
772                                 }
773                                 /* Now we kill the schedule and turn ints
774                                    back on */
775                                 needReschedule = false;
776                                 return;
777                         }
778                 }
779                 /* Nothing to run - wait for external event */
780                 DBG( fprintf(stderr, "nothing more to do\n"); );
781                 checkEvents(true);
782     }
783 }
784
785 void cast_lock()
786 {
787 }
788
789 void cast_unlock()
790 {
791 }