1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-04-30 11:50:28 +02:00
guile/libguile/coop.c
Mikael Djurfeldt b322f09ab8 * coop.c (coop_condition_variable_wait): Removed
(coop_condition_variable_wait_mutex): Folded logic of
coop_mutex_unlock into coop_condition_variable_wait_mutex to
prevent condvar signal lossage.  Previously, another thread could
start to run after unlocking the mutex but before putting the
current thread on the wait queue.  If that thread then would
signal the first, the signal would be lost.  (Thanks to Christian
Lynbech.)
1998-11-19 08:15:22 +00:00

708 lines
14 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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.15 1998-11-19 08:15:22 mdj Exp $ */
/* Cooperative thread library, based on QuickThreads */
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <qt.h>
#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. */
#ifdef __STDC__
static void
coop_qinit (coop_q_t *q)
#else
static void
coop_qinit (q)
coop_q_t *q;
#endif
{
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
}
#ifdef __STDC__
coop_t *
coop_qget (coop_q_t *q)
#else
coop_t *
coop_qget (q)
coop_q_t *q;
#endif
{
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);
}
#ifdef __STDC__
void
coop_qput (coop_q_t *q, coop_t *t)
#else
void
coop_qput (q, t)
coop_q_t *q;
coop_t *t;
#endif
{
q->tail->next = t;
t->next = &q->t;
q->tail = t;
}
#ifdef __STDC__
static void
coop_all_qput (coop_q_t *q, coop_t *t)
#else
static void
coop_all_qput (q, t)
coop_q_t *q;
coop_t *t;
#endif
{
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;
}
#ifdef __STDC__
static void
coop_all_qremove (coop_q_t *q, coop_t *t)
#else
static void
coop_all_qremove (q, t)
coop_q_t *q;
coop_t *t;
#endif
{
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);
#ifdef __STDC__
void
coop_init()
#else
void
coop_init()
#endif
{
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
#ifdef __STDC__
coop_t *
coop_next_runnable_thread()
#else
coop_t *
coop_next_runnable_thread()
#endif
{
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
#ifdef __STDC__
void
coop_start()
#else
void
coop_start()
#endif
{
coop_t *next;
while ((next = coop_qget (&coop_global_runq)) != NULL) {
coop_global_curr = next;
QT_BLOCK (coop_starthelp, 0, 0, next->sp);
}
}
#ifdef __STDC__
static void *
coop_starthelp (qt_t *old, void *ignore0, void *ignore1)
#else
static void *
coop_starthelp (old, ignore0, ignore1)
qt_t *old;
void *ignore0;
void *ignore1;
#endif
{
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 */
}
#ifdef __STDC__
int
coop_mutex_init (coop_m *m)
#else
int
coop_mutex_init (m)
coop_m *m;
#endif
{
m->owner = NULL;
coop_qinit(&(m->waiting));
return 0;
}
#ifdef __STDC__
int
coop_mutex_lock (coop_m *m)
#else
int
coop_mutex_lock ()
coop_m *m;
#endif
{
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;
}
#ifdef __STDC__
int
coop_mutex_unlock (coop_m *m)
#else
int
coop_mutex_unlock (m)
coop_m *m;
#endif
{
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;
}
#ifdef __STDC__
int
coop_mutex_destroy (coop_m *m)
#else
int
coop_mutex_destroy (m)
coop_m *m;
#endif
{
return 0;
}
#ifdef __STDC__
int
coop_condition_variable_init (coop_c *c)
#else
int
coop_condition_variable_init (c)
coop_c *c;
#endif
{
coop_qinit(&(c->waiting));
return 0;
}
#ifdef __STDC__
int
coop_condition_variable_wait_mutex (coop_c *c, coop_m *m)
#else
int
coop_condition_variable_wait_mutex (c, m)
coop_c *c;
coop_m *m;
#endif
{
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;
}
#ifdef __STDC__
int
coop_condition_variable_signal (coop_c *c)
#else
int
coop_condition_variable_signal (c)
coop_c *c;
#endif
{
coop_t *newthread;
while ((newthread = coop_qget (&(c->waiting))) != NULL)
{
coop_qput (&coop_global_runq, newthread);
}
return 0;
}
#ifdef __STDC__
int
coop_condition_variable_destroy (coop_c *c)
#else
int
coop_condition_variable_destroy (c)
coop_c *c;
#endif
{
return 0;
}
#ifdef __STDC__
coop_t *
coop_create (coop_userf_t *f, void *pu)
#else
coop_t *
coop_create (f, pu)
coop_userf_t *f;
void *pu;
#endif
{
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;
}
#ifdef __STDC__
static void
coop_only (void *pu, void *pt, qt_userf_t *f)
#else
static void
coop_only (pu. pt, f)
void *pu,
void *pt,
qt_userf_t *f;
#endif
{
coop_global_curr = (coop_t *)pt;
(*(coop_userf_t *)f)(pu);
coop_abort();
/* NOTREACHED */
}
#ifdef __STDC__
void
coop_abort ()
#else
void
coop_abort ()
#endif
{
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);
}
#ifdef __STDC__
static void *
coop_aborthelp (qt_t *sp, void *old, void *null)
#else
static void *
coop_aborthelp (sp, old, null)
qt_t *sp;
void *old;
void *null;
#endif
{
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;
}
#ifdef __STDC__
void
coop_join(coop_t *t)
#else
void
coop_join()
coop_t *t;
#endif
{
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);
}
#ifdef __STDC__
void
coop_yield()
#else
void
coop_yield()
#endif
{
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);
}
#ifdef __STDC__
static void *
coop_yieldhelp (qt_t *sp, void *old, void *blockq)
#else
static void *
coop_yieldhelp (sp, old, blockq)
qt_t *sp;
void *old;
void *blockq;
#endif
{
((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 */