1 /* Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc.
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2, or (at your option)
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this software; see the file COPYING. If not, write to
15 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
16 * Boston, MA 02111-1307 USA
18 * As a special exception, the Free Software Foundation gives permission
19 * for additional uses of the text contained in its release of GUILE.
21 * The exception is that, if you link the GUILE library with other files
22 * to produce an executable, this does not by itself cause the
23 * resulting executable to be covered by the GNU General Public License.
24 * Your use of that executable is in no way restricted on account of
25 * linking the GUILE library code into it.
27 * This exception does not however invalidate any other reasons why
28 * the executable file might be covered by the GNU General Public License.
30 * This exception applies only to the code released by the
31 * Free Software Foundation under the name GUILE. If you copy
32 * code from other Free Software Foundation releases into a copy of
33 * GUILE, as the General Public License permits, the exception does
34 * not apply to the code that you add in this way. To avoid misleading
35 * anyone as to the status of such modified files, you must delete
36 * this exception notice from them.
38 * If you write modifications of your own for GUILE, it is your choice
39 * whether to permit this exception to apply to your modifications.
40 * If you do not wish that, delete this exception notice. */
43 /* $Id: coop.c,v 1.25 2000-04-21 14:16:30 mdj Exp $ */
45 /* Cooperative thread library, based on QuickThreads */
54 #include "libguile/eval.h"
56 \f/* #define COOP_STKSIZE (0x10000) */
57 #define COOP_STKSIZE (scm_eval_stack)
59 /* `alignment' must be a power of 2. */
60 #define COOP_STKALIGN(sp, alignment) \
61 ((void *)((((qt_word_t)(sp)) + (alignment) - 1) & ~((alignment)-1)))
65 /* Queue access functions. */
68 coop_qinit (coop_q_t
*q
)
70 q
->t
.next
= q
->tail
= &q
->t
;
78 q
->t
.exceptfds
= NULL
;
85 coop_qget (coop_q_t
*q
)
94 { /* If it was already empty .. */
95 return NULL
; /* .. say so. */
97 q
->tail
= &q
->t
; /* Else now it is empty. */
104 coop_qput (coop_q_t
*q
, coop_t
*t
)
112 coop_all_qput (coop_q_t
*q
, coop_t
*t
)
115 q
->t
.all_next
->all_prev
= t
;
117 t
->all_next
= q
->t
.all_next
;
122 coop_all_qremove (coop_q_t
*q
, coop_t
*t
)
125 t
->all_prev
->all_next
= t
->all_next
;
127 q
->t
.all_next
= t
->all_next
;
129 t
->all_next
->all_prev
= t
->all_prev
;
133 /* Insert thread t into the ordered queue q.
134 q is ordered after wakeup_time. Threads which aren't sleeping but
135 waiting for I/O go last into the queue. */
137 coop_timeout_qinsert (coop_q_t
*q
, coop_t
*t
)
139 coop_t
*pred
= &q
->t
;
140 int sec
= t
->wakeup_time
.tv_sec
;
141 int usec
= t
->wakeup_time
.tv_usec
;
142 while (pred
->next
!= &q
->t
143 && pred
->next
->timeoutp
144 && (pred
->next
->wakeup_time
.tv_sec
< sec
145 || (pred
->next
->wakeup_time
.tv_sec
== sec
146 && pred
->next
->wakeup_time
.tv_usec
< usec
)))
148 t
->next
= pred
->next
;
150 if (t
->next
== &q
->t
)
156 \f/* Thread routines. */
158 coop_q_t coop_global_runq
; /* A queue of runable threads. */
159 coop_q_t coop_global_sleepq
; /* A queue of sleeping threads. */
160 coop_q_t coop_tmp_queue
; /* A temp working queue */
161 coop_q_t coop_global_allq
; /* A queue of all threads. */
162 static coop_t coop_global_main
; /* Thread for the process. */
163 coop_t
*coop_global_curr
; /* Currently-executing thread. */
165 #ifdef GUILE_PTHREAD_COMPAT
166 static coop_q_t coop_deadq
;
167 static int coop_quitting_p
= -1;
168 static pthread_cond_t coop_cond_quit
;
169 static pthread_cond_t coop_cond_create
;
170 static pthread_mutex_t coop_mutex_create
;
171 static pthread_t coop_mother
;
172 static coop_t
*coop_child
;
175 static void *coop_starthelp (qt_t
*old
, void *ignore0
, void *ignore1
);
176 static void coop_only (void *pu
, void *pt
, qt_userf_t
*f
);
177 static void *coop_aborthelp (qt_t
*sp
, void *old
, void *null
);
178 static void *coop_yieldhelp (qt_t
*sp
, void *old
, void *blockq
);
181 /* called on process termination. */
187 extern int on_exit (void (*procp
) (), int arg
);
190 coop_finish (int status
, void *arg
)
192 #error Dont know how to setup a cleanup handler on your system.
196 #ifdef GUILE_PTHREAD_COMPAT
198 pthread_cond_signal (&coop_cond_create
);
199 pthread_cond_broadcast (&coop_cond_quit
);
206 coop_qinit (&coop_global_runq
);
207 coop_qinit (&coop_global_sleepq
);
208 coop_qinit (&coop_tmp_queue
);
209 coop_qinit (&coop_global_allq
);
210 coop_global_curr
= &coop_global_main
;
211 #ifdef GUILE_PTHREAD_COMPAT
212 coop_qinit (&coop_deadq
);
213 pthread_cond_init (&coop_cond_quit
, NULL
);
214 pthread_cond_init (&coop_cond_create
, NULL
);
215 pthread_mutex_init (&coop_mutex_create
, NULL
);
218 atexit (coop_finish
);
221 on_exit (coop_finish
, 0);
226 /* Return the next runnable thread. If no threads are currently runnable,
227 and there are sleeping threads - wait until one wakes up. Otherwise,
230 #ifndef GUILE_ISELECT
232 coop_next_runnable_thread()
242 /* Check the sleeping queue */
243 while ((t
= coop_qget(&coop_global_sleepq
)) != NULL
)
246 if (t
->wakeup_time
<= now
)
247 coop_qput(&coop_global_runq
, t
);
249 coop_qput(&coop_tmp_queue
, t
);
251 while ((t
= coop_qget(&coop_tmp_queue
)) != NULL
)
252 coop_qput(&coop_global_sleepq
, t
);
254 t
= coop_qget (&coop_global_runq
);
256 } while ((t
== NULL
) && (sleepers
> 0));
267 while ((next
= coop_qget (&coop_global_runq
)) != NULL
) {
268 coop_global_curr
= next
;
269 QT_BLOCK (coop_starthelp
, 0, 0, next
->sp
);
275 coop_starthelp (qt_t
*old
, void *ignore0
, void *ignore1
)
277 coop_global_main
.sp
= old
;
278 coop_global_main
.joining
= NULL
;
279 coop_qput (&coop_global_runq
, &coop_global_main
);
280 return NULL
; /* not used, but keeps compiler happy */
284 coop_mutex_init (coop_m
*m
)
286 return coop_new_mutex_init (m
, NULL
);
290 coop_new_mutex_init (coop_m
*m
, coop_mattr
*attr
)
293 coop_qinit(&(m
->waiting
));
298 coop_mutex_trylock (coop_m
*m
)
300 if (m
->owner
== NULL
)
302 m
->owner
= coop_global_curr
;
310 coop_mutex_lock (coop_m
*m
)
312 if (m
->owner
== NULL
)
314 m
->owner
= coop_global_curr
;
318 coop_t
*old
, *newthread
;
320 /* Record the current top-of-stack before going to sleep */
321 coop_global_curr
->top
= &old
;
324 newthread
= coop_wait_for_runnable_thread();
325 if (newthread
== coop_global_curr
)
328 newthread
= coop_next_runnable_thread();
330 old
= coop_global_curr
;
331 coop_global_curr
= newthread
;
332 QT_BLOCK (coop_yieldhelp
, old
, &(m
->waiting
), newthread
->sp
);
339 coop_mutex_unlock (coop_m
*m
)
341 coop_t
*old
, *newthread
;
343 newthread
= coop_qget (&(m
->waiting
));
344 if (newthread
!= NULL
)
346 /* Record the current top-of-stack before going to sleep */
347 coop_global_curr
->top
= &old
;
349 old
= coop_global_curr
;
350 coop_global_curr
= newthread
;
351 /* The new thread came into m->waiting through a lock operation.
352 It now owns this mutex. */
353 m
->owner
= coop_global_curr
;
354 QT_BLOCK (coop_yieldhelp
, old
, &coop_global_runq
, newthread
->sp
);
365 coop_mutex_destroy (coop_m
*m
)
372 coop_condition_variable_init (coop_c
*c
)
374 return coop_new_condition_variable_init (c
, NULL
);
378 coop_new_condition_variable_init (coop_c
*c
, coop_cattr
*a
)
380 coop_qinit(&(c
->waiting
));
385 coop_condition_variable_wait_mutex (coop_c
*c
, coop_m
*m
)
387 coop_t
*old
, *newthread
;
389 /* coop_mutex_unlock (m); */
390 newthread
= coop_qget (&(m
->waiting
));
391 if (newthread
!= NULL
)
393 m
->owner
= newthread
;
398 /*fixme* Should we really wait here? Isn't it OK just to proceed? */
400 newthread
= coop_wait_for_runnable_thread();
401 if (newthread
== coop_global_curr
)
404 newthread
= coop_next_runnable_thread();
407 coop_global_curr
->top
= &old
;
408 old
= coop_global_curr
;
409 coop_global_curr
= newthread
;
410 QT_BLOCK (coop_yieldhelp
, old
, &(c
->waiting
), newthread
->sp
);
417 coop_condition_variable_timed_wait_mutex (coop_c
*c
,
419 const struct timespec
*abstime
)
424 /* coop_mutex_unlock (m); */
425 t
= coop_qget (&(m
->waiting
));
434 coop_global_curr
->timeoutp
= 1;
435 coop_global_curr
->wakeup_time
.tv_sec
= abstime
->tv_sec
;
436 coop_global_curr
->wakeup_time
.tv_usec
= abstime
->tv_nsec
/ 1000;
437 coop_timeout_qinsert (&coop_global_sleepq
, coop_global_curr
);
438 t
= coop_wait_for_runnable_thread();
440 /*fixme* Implement!*/
441 t
= coop_next_runnable_thread();
444 if (t
!= coop_global_curr
)
446 coop_global_curr
->top
= &old
;
447 old
= coop_global_curr
;
448 coop_global_curr
= t
;
449 QT_BLOCK (coop_yieldhelp
, old
, &(c
->waiting
), t
->sp
);
451 /* Are we still in the sleep queue? */
452 old
= &coop_global_sleepq
.t
;
453 for (t
= old
->next
; t
!= &coop_global_sleepq
.t
; old
= t
, t
= t
->next
)
454 if (t
== coop_global_curr
)
456 old
->next
= t
->next
; /* unlink */
466 coop_condition_variable_signal (coop_c
*c
)
470 while ((newthread
= coop_qget (&(c
->waiting
))) != NULL
)
472 coop_qput (&coop_global_runq
, newthread
);
480 static int n_keys
= 0;
481 static int max_keys
= 0;
482 static void (**destructors
) (void *) = 0;
485 coop_key_create (coop_k
*keyp
, void (*destructor
) (void *value
))
487 if (n_keys
>= max_keys
)
490 max_keys
= max_keys
? max_keys
* 3 / 2 : 10;
491 destructors
= realloc (destructors
, sizeof (void *) * max_keys
);
492 if (destructors
== 0)
494 fprintf (stderr
, "Virtual memory exceeded in coop_key_create\n");
497 for (i
= n_keys
; i
< max_keys
; ++i
)
498 destructors
[i
] = NULL
;
500 destructors
[n_keys
] = destructor
;
506 coop_setspecific (coop_k key
, const void *value
)
508 int n_keys
= coop_global_curr
->n_keys
;
512 coop_global_curr
->n_keys
= max_keys
;
513 coop_global_curr
->specific
= realloc (n_keys
514 ? coop_global_curr
->specific
516 sizeof (void *) * max_keys
);
517 if (coop_global_curr
->specific
== 0)
519 fprintf (stderr
, "Virtual memory exceeded in coop_setspecific\n");
522 for (i
= n_keys
; i
< max_keys
; ++i
)
523 coop_global_curr
->specific
[i
] = NULL
;
525 coop_global_curr
->specific
[key
] = (void *) value
;
530 coop_getspecific (coop_k key
)
532 return (key
< coop_global_curr
->n_keys
533 ? coop_global_curr
->specific
[key
]
538 coop_key_delete (coop_k key
)
545 coop_condition_variable_destroy (coop_c
*c
)
550 #ifdef GUILE_PTHREAD_COMPAT
552 /* 1K room for the cond wait routine */
553 #ifdef SCM_STACK_GROWS_UP
554 #define COOP_STACK_ROOM (256)
556 #define COOP_STACK_ROOM (-256)
560 dummy_start (void *coop_thread
)
562 coop_t
*t
= (coop_t
*) coop_thread
;
564 t
->sp
= (qt_t
*) (&t
+ COOP_STACK_ROOM
);
565 pthread_mutex_init (&t
->dummy_mutex
, NULL
);
566 pthread_mutex_lock (&t
->dummy_mutex
);
569 res
= pthread_cond_wait (&coop_cond_quit
, &t
->dummy_mutex
);
570 while (res
== EINTR
);
577 pthread_mutex_lock (&coop_mutex_create
);
578 while (!coop_quitting_p
)
581 pthread_create (&coop_child
->dummy_thread
,
586 res
= pthread_cond_wait (&coop_cond_create
, &coop_mutex_create
);
587 while (res
== EINTR
);
595 coop_create (coop_userf_t
*f
, void *pu
)
598 #ifndef GUILE_PTHREAD_COMPAT
602 #ifdef GUILE_PTHREAD_COMPAT
603 t
= coop_qget (&coop_deadq
);
613 t
= malloc (sizeof (coop_t
));
617 #ifdef GUILE_PTHREAD_COMPAT
619 if (coop_quitting_p
< 0)
622 /* We can't create threads ourselves since the pthread
623 * corresponding to this stack might be sleeping.
625 pthread_create (&coop_mother
, NULL
, mother
, NULL
);
629 pthread_cond_signal (&coop_cond_create
);
631 /* We can't use a pthreads condition variable since "this"
632 * pthread could already be asleep. We can't use a COOP
633 * condition variable because they are not safe against
634 * pre-emptive switching.
639 t
->sto
= malloc (COOP_STKSIZE
);
640 sto
= COOP_STKALIGN (t
->sto
, QT_STKALIGN
);
641 t
->sp
= QT_SP (sto
, COOP_STKSIZE
- QT_STKALIGN
);
645 t
->sp
= QT_ARGS (t
->sp
, pu
, t
, (qt_userf_t
*)f
, coop_only
);
647 coop_qput (&coop_global_runq
, t
);
648 coop_all_qput (&coop_global_allq
, t
);
655 coop_only (void *pu
, void *pt
, qt_userf_t
*f
)
657 coop_global_curr
= (coop_t
*)pt
;
658 (*(coop_userf_t
*)f
)(pu
);
667 coop_t
*old
, *newthread
;
669 /* Wake up any threads that are waiting to join this one */
670 if (coop_global_curr
->joining
)
672 while ((newthread
= coop_qget ((coop_q_t
*)(coop_global_curr
->joining
)))
675 coop_qput (&coop_global_runq
, newthread
);
677 free (coop_global_curr
->joining
);
683 newthread
= coop_wait_for_runnable_thread();
684 } while (newthread
== coop_global_curr
);
687 newthread
= coop_next_runnable_thread();
689 coop_all_qremove (&coop_global_allq
, coop_global_curr
);
690 old
= coop_global_curr
;
691 coop_global_curr
= newthread
;
692 QT_ABORT (coop_aborthelp
, old
, (void *) NULL
, newthread
->sp
);
697 coop_aborthelp (qt_t
*sp
, void *old
, void *null
)
699 coop_t
*oldthread
= (coop_t
*) old
;
702 /* Marking old->base NULL indicates that this thread is dead */
703 oldthread
->base
= NULL
;
706 if (oldthread
->specific
)
707 free (oldthread
->specific
);
708 #ifndef GUILE_PTHREAD_COMPAT
709 free (oldthread
->sto
);
712 coop_qput (&coop_deadq
, oldthread
);
722 coop_t
*old
, *newthread
;
724 /* Check if t is already finished */
728 /* Create a join list if necessary */
729 if (t
->joining
== NULL
)
731 t
->joining
= malloc(sizeof(coop_q_t
));
732 coop_qinit((coop_q_t
*) t
->joining
);
736 newthread
= coop_wait_for_runnable_thread();
737 if (newthread
== coop_global_curr
)
740 newthread
= coop_next_runnable_thread();
742 old
= coop_global_curr
;
743 coop_global_curr
= newthread
;
744 QT_BLOCK (coop_yieldhelp
, old
, (coop_q_t
*) t
->joining
, newthread
->sp
);
753 newthread
= coop_next_runnable_thread();
755 /* There may be no other runnable threads. Return if this is the
758 if (newthread
== coop_global_curr
)
761 if (newthread
== NULL
)
765 old
= coop_global_curr
;
767 coop_global_curr
= newthread
;
768 QT_BLOCK (coop_yieldhelp
, old
, &coop_global_runq
, newthread
->sp
);
773 coop_yieldhelp (qt_t
*sp
, void *old
, void *blockq
)
775 ((coop_t
*)old
)->sp
= sp
;
776 coop_qput ((coop_q_t
*)blockq
, (coop_t
*)old
);
780 /* Replacement for the system's sleep() function. Does the right thing
781 for the process - but not for the system (it busy-waits) */
784 coop_sleephelp (qt_t
*sp
, void *old
, void *blockq
)
786 ((coop_t
*)old
)->sp
= sp
;
787 /* old is already on the sleep queue - so there's no need to
788 do anything extra here */
795 scm_thread_usleep (unsigned long usec
)
797 struct timeval timeout
;
799 timeout
.tv_usec
= usec
;
800 scm_internal_select (0, NULL
, NULL
, NULL
, &timeout
);
801 return 0; /* Maybe we should calculate actual time slept,
802 but this is faster... :) */
806 scm_thread_sleep (unsigned long sec
)
808 time_t now
= time (NULL
);
809 struct timeval timeout
;
811 timeout
.tv_sec
= sec
;
813 scm_internal_select (0, NULL
, NULL
, NULL
, &timeout
);
814 slept
= time (NULL
) - now
;
815 return slept
> sec
? 0 : sec
- slept
;
818 #else /* GUILE_ISELECT */
821 scm_thread_sleep (unsigned long s
)
823 coop_t
*newthread
, *old
;
824 time_t now
= time (NULL
);
825 coop_global_curr
->wakeup_time
= now
+ s
;
827 /* Put the current thread on the sleep queue */
828 coop_qput (&coop_global_sleepq
, coop_global_curr
);
830 newthread
= coop_next_runnable_thread();
832 /* If newthread is the same as the sleeping thread, do nothing */
833 if (newthread
== coop_global_curr
)
836 old
= coop_global_curr
;
838 coop_global_curr
= newthread
;
839 QT_BLOCK (coop_sleephelp
, old
, NULL
, newthread
->sp
);
845 scm_thread_usleep (unsigned long usec
)
847 /* We're so cheap. */
848 scm_thread_sleep (usec
/ 1000000);
849 struct timeval timeout
;
850 return 0; /* Maybe we should calculate actual time slept,
851 but this is faster... :) */
854 #endif /* GUILE_ISELECT */