/* Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA * * As a special exception, the Free Software Foundation gives permission * for additional uses of the text contained in its release of GUILE. * * The exception is that, if you link the GUILE library with other files * to produce an executable, this does not by itself cause the * resulting executable to be covered by the GNU General Public License. * Your use of that executable is in no way restricted on account of * linking the GUILE library code into it. * * This exception does not however invalidate any other reasons why * the executable file might be covered by the GNU General Public License. * * This exception applies only to the code released by the * Free Software Foundation under the name GUILE. If you copy * code from other Free Software Foundation releases into a copy of * GUILE, as the General Public License permits, the exception does * not apply to the code that you add in this way. To avoid misleading * anyone as to the status of such modified files, you must delete * this exception notice from them. * * If you write modifications of your own for GUILE, it is your choice * whether to permit this exception to apply to your modifications. * If you do not wish that, delete this exception notice. */ /* $Id: coop.c,v 1.16 1999/12/19 18:24:12 gjb Exp $ */ /* Cooperative thread library, based on QuickThreads */ #ifdef HAVE_UNISTD_H #include #endif #include #include "eval.h" /* #define COOP_STKSIZE (0x10000) */ #define COOP_STKSIZE (scm_eval_stack) /* `alignment' must be a power of 2. */ #define COOP_STKALIGN(sp, alignment) \ ((void *)((((qt_word_t)(sp)) + (alignment) - 1) & ~((alignment)-1))) /* Queue access functions. */ static void coop_qinit (coop_q_t *q) { q->t.next = q->tail = &q->t; q->t.all_prev = NULL; q->t.all_next = NULL; #ifdef GUILE_ISELECT q->t.nfds = 0; q->t.readfds = NULL; q->t.writefds = NULL; q->t.exceptfds = NULL; q->t.timeoutp = 0; #endif } coop_t * coop_qget (coop_q_t *q) { coop_t *t; t = q->t.next; q->t.next = t->next; if (t->next == &q->t) { if (t == &q->t) { /* If it was already empty .. */ return (NULL); /* .. say so. */ } q->tail = &q->t; /* Else now it is empty. */ } return (t); } void coop_qput (coop_q_t *q, coop_t *t) { q->tail->next = t; t->next = &q->t; q->tail = t; } static void coop_all_qput (coop_q_t *q, coop_t *t) { if (q->t.all_next) q->t.all_next->all_prev = t; t->all_prev = NULL; t->all_next = q->t.all_next; q->t.all_next = t; } static void coop_all_qremove (coop_q_t *q, coop_t *t) { if (t->all_prev) t->all_prev->all_next = t->all_next; else q->t.all_next = t->all_next; if (t->all_next) t->all_next->all_prev = t->all_prev; } /* Thread routines. */ coop_q_t coop_global_runq; /* A queue of runable threads. */ coop_q_t coop_global_sleepq; /* A queue of sleeping threads. */ coop_q_t coop_tmp_queue; /* A temp working queue */ coop_q_t coop_global_allq; /* A queue of all threads. */ static coop_t coop_global_main; /* Thread for the process. */ coop_t *coop_global_curr; /* Currently-executing thread. */ static void *coop_starthelp (qt_t *old, void *ignore0, void *ignore1); static void coop_only (void *pu, void *pt, qt_userf_t *f); static void *coop_aborthelp (qt_t *sp, void *old, void *null); static void *coop_yieldhelp (qt_t *sp, void *old, void *blockq); void coop_init() { coop_qinit (&coop_global_runq); coop_qinit (&coop_global_sleepq); coop_qinit (&coop_tmp_queue); coop_qinit (&coop_global_allq); coop_global_curr = &coop_global_main; } /* Return the next runnable thread. If no threads are currently runnable, and there are sleeping threads - wait until one wakes up. Otherwise, return NULL. */ #ifndef GUILE_ISELECT coop_t * coop_next_runnable_thread() { int sleepers; coop_t *t; time_t now; do { sleepers = 0; now = time(NULL); /* Check the sleeping queue */ while ((t = coop_qget(&coop_global_sleepq)) != NULL) { sleepers++; if (t->wakeup_time <= now) coop_qput(&coop_global_runq, t); else coop_qput(&coop_tmp_queue, t); } while ((t = coop_qget(&coop_tmp_queue)) != NULL) coop_qput(&coop_global_sleepq, t); t = coop_qget (&coop_global_runq); } while ((t == NULL) && (sleepers > 0)); return t; } #endif void coop_start() { coop_t *next; while ((next = coop_qget (&coop_global_runq)) != NULL) { coop_global_curr = next; QT_BLOCK (coop_starthelp, 0, 0, next->sp); } } static void * coop_starthelp (qt_t *old, void *ignore0, void *ignore1) { coop_global_main.sp = old; coop_global_main.joining = NULL; coop_qput (&coop_global_runq, &coop_global_main); return NULL; /* not used, but keeps compiler happy */ } int coop_mutex_init (coop_m *m) { m->owner = NULL; coop_qinit(&(m->waiting)); return 0; } int coop_mutex_lock (coop_m *m) { if (m->owner == NULL) { m->owner = coop_global_curr; } else { coop_t *old, *newthread; /* Record the current top-of-stack before going to sleep */ coop_global_curr->top = &old; #ifdef GUILE_ISELECT newthread = coop_wait_for_runnable_thread(); if (newthread == coop_global_curr) coop_abort (); #else newthread = coop_next_runnable_thread(); #endif old = coop_global_curr; coop_global_curr = newthread; QT_BLOCK (coop_yieldhelp, old, &(m->waiting), newthread->sp); } return 0; } int coop_mutex_unlock (coop_m *m) { coop_t *old, *newthread; newthread = coop_qget (&(m->waiting)); if (newthread != NULL) { /* Record the current top-of-stack before going to sleep */ coop_global_curr->top = &old; old = coop_global_curr; coop_global_curr = newthread; /* The new thread came into m->waiting through a lock operation. It now owns this mutex. */ m->owner = coop_global_curr; QT_BLOCK (coop_yieldhelp, old, &coop_global_runq, newthread->sp); } else { m->owner = NULL; } return 0; } int coop_mutex_destroy (coop_m *m) { return 0; } int coop_condition_variable_init (coop_c *c) { coop_qinit(&(c->waiting)); return 0; } int coop_condition_variable_wait_mutex (coop_c *c, coop_m *m) { coop_t *old, *newthread; /* coop_mutex_unlock (m); */ newthread = coop_qget (&(m->waiting)); if (newthread != NULL) { m->owner = newthread; } else { m->owner = NULL; #ifdef GUILE_ISELECT newthread = coop_wait_for_runnable_thread(); if (newthread == coop_global_curr) coop_abort (); #else newthread = coop_next_runnable_thread(); #endif } coop_global_curr->top = &old; old = coop_global_curr; coop_global_curr = newthread; QT_BLOCK (coop_yieldhelp, old, &(c->waiting), newthread->sp); coop_mutex_lock (m); return 0; } int coop_condition_variable_signal (coop_c *c) { coop_t *newthread; while ((newthread = coop_qget (&(c->waiting))) != NULL) { coop_qput (&coop_global_runq, newthread); } return 0; } int coop_condition_variable_destroy (coop_c *c) { return 0; } coop_t * coop_create (coop_userf_t *f, void *pu) { coop_t *t; void *sto; t = malloc (sizeof(coop_t)); t->data = NULL; t->sto = malloc (COOP_STKSIZE); sto = COOP_STKALIGN (t->sto, QT_STKALIGN); t->sp = QT_SP (sto, COOP_STKSIZE - QT_STKALIGN); t->base = t->sp; t->sp = QT_ARGS (t->sp, pu, t, (qt_userf_t *)f, coop_only); t->joining = NULL; coop_qput (&coop_global_runq, t); coop_all_qput (&coop_global_allq, t); return t; } static void coop_only (void *pu, void *pt, qt_userf_t *f) { coop_global_curr = (coop_t *)pt; (*(coop_userf_t *)f)(pu); coop_abort(); /* NOTREACHED */ } void coop_abort () { coop_t *old, *newthread; /* Wake up any threads that are waiting to join this one */ if (coop_global_curr->joining) { while ((newthread = coop_qget ((coop_q_t *)(coop_global_curr->joining))) != NULL) { coop_qput (&coop_global_runq, newthread); } free(coop_global_curr->joining); } #ifdef GUILE_ISELECT scm_I_am_dead = 1; do { newthread = coop_wait_for_runnable_thread(); } while (newthread == coop_global_curr); scm_I_am_dead = 0; #else newthread = coop_next_runnable_thread(); #endif coop_all_qremove(&coop_global_allq, coop_global_curr); old = coop_global_curr; coop_global_curr = newthread; QT_ABORT (coop_aborthelp, old, (void *)NULL, newthread->sp); } static void * coop_aborthelp (qt_t *sp, void *old, void *null) { coop_t *oldthread = (coop_t *) old; free (oldthread->sto); /* "old" is freed in scm_threads_thread_die(). Marking old->base NULL indicates that this thread is dead */ oldthread->base = NULL; return NULL; } void coop_join(coop_t *t) { coop_t *old, *newthread; /* Check if t is already finished */ if (t->base == NULL) return; /* Create a join list if necessary */ if (t->joining == NULL) { t->joining = malloc(sizeof(coop_q_t)); coop_qinit((coop_q_t *) t->joining); } #ifdef GUILE_ISELECT newthread = coop_wait_for_runnable_thread(); if (newthread == coop_global_curr) return; #else newthread = coop_next_runnable_thread(); #endif old = coop_global_curr; coop_global_curr = newthread; QT_BLOCK (coop_yieldhelp, old, (coop_q_t *) t->joining, newthread->sp); } void coop_yield() { coop_t *old = NULL; coop_t *newthread; newthread = coop_next_runnable_thread(); /* There may be no other runnable threads. Return if this is the case. */ #if GUILE_ISELECT if (newthread == coop_global_curr) return; #else if (newthread == NULL) return; #endif old = coop_global_curr; coop_global_curr = newthread; QT_BLOCK (coop_yieldhelp, old, &coop_global_runq, newthread->sp); } static void * coop_yieldhelp (qt_t *sp, void *old, void *blockq) { ((coop_t *)old)->sp = sp; coop_qput ((coop_q_t *)blockq, (coop_t *)old); return NULL; } /* Replacement for the system's sleep() function. Does the right thing for the process - but not for the system (it busy-waits) */ void * coop_sleephelp (qt_t *sp, void *old, void *blockq) { ((coop_t *)old)->sp = sp; /* old is already on the sleep queue - so there's no need to do anything extra here */ return NULL; } #ifdef GUILE_ISELECT unsigned long scm_thread_usleep (unsigned long usec) { struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = usec; scm_internal_select (0, NULL, NULL, NULL, &timeout); return 0; /* Maybe we should calculate actual time slept, but this is faster... :) */ } unsigned long scm_thread_sleep (unsigned long sec) { time_t now = time (NULL); struct timeval timeout; unsigned long slept; timeout.tv_sec = sec; timeout.tv_usec = 0; scm_internal_select (0, NULL, NULL, NULL, &timeout); slept = time (NULL) - now; return slept > sec ? 0 : sec - slept; } #else /* GUILE_ISELECT */ unsigned long scm_thread_sleep (unsigned long s) { coop_t *newthread, *old; time_t now = time (NULL); coop_global_curr->wakeup_time = now + s; /* Put the current thread on the sleep queue */ coop_qput (&coop_global_sleepq, coop_global_curr); newthread = coop_next_runnable_thread(); /* If newthread is the same as the sleeping thread, do nothing */ if (newthread == coop_global_curr) return s; old = coop_global_curr; coop_global_curr = newthread; QT_BLOCK (coop_sleephelp, old, NULL, newthread->sp); return s; } unsigned long scm_thread_usleep (unsigned long usec) { /* We're so cheap. */ scm_thread_sleep (usec / 1000000); struct timeval timeout; return 0; /* Maybe we should calculate actual time slept, but this is faster... :) */ } #endif /* GUILE_ISELECT */