1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-05-01 12:20:26 +02:00
guile/libguile/iselect.c
Mikael Djurfeldt cafc12ffdd * filesys.c (set_element): Return file descriptor.
(fill_select_type): Return the highest file descriptor.
(scm_select): Tell select about the highest file descriptor.  On
some systems the SELECT_SET_SIZE can be as much as 128 bytes.
Therefore the extra overhead for calculating the maximum fd seems
to be more than compensated.  Is this correct?  In any case,
scm_internal_select will be much faster with this info.
(scm_select, fill_select_type, set_element): Don't accept any kind
of object in the file descriptor list or vector.
1997-12-08 18:15:44 +00:00

611 lines
16 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) 1997 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. */
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include "_scm.h"
/*
* iselect.c is linked with Guile only when threads are in use. However,
* when threads are *not* in use, the `make depend' mechanism will try
* to process this file anyway and get tangled up in iselect.h and
* coop-threads.h. Therefore, we use the USE_THREADS macro (which is
* otherwise redundant for this file) to prevent `make depend' from
* failing.
*/
#ifdef USE_THREADS
#include "iselect.h"
#include "coop-threads.h"
#endif
#define QEMPTYP(q) (q.t.next == &q.t)
#define QFIRST(q) (q.t.next)
#define SCM_BITS_PER_LONG (8 * sizeof (unsigned long))
#if ULONG_MAX >> 16 == 0
#define SCM_NLONGBITS(p) (bc[((unsigned char *)(p))[0]]\
+ bc[((unsigned char *)(p))[1]])
#elif ULONG_MAX >> 32 == 0
#define SCM_NLONGBITS(p) (bc[((unsigned char *)(p))[0]]\
+ bc[((unsigned char *)(p))[1]]\
+ bc[((unsigned char *)(p))[2]]\
+ bc[((unsigned char *)(p))[3]])
#elif ULONG_MAX >> 64 == 0
#define SCM_NLONGBITS(p) (bc[((unsigned char *)(p))[0]]\
+ bc[((unsigned char *)(p))[1]]\
+ bc[((unsigned char *)(p))[2]]\
+ bc[((unsigned char *)(p))[3]]\
+ bc[((unsigned char *)(p))[4]]\
+ bc[((unsigned char *)(p))[5]]\
+ bc[((unsigned char *)(p))[6]]\
+ bc[((unsigned char *)(p))[7]])
#endif
#ifdef HAVE_BZERO
#define FD_ZERO_N(pos, n) bzero ((pos), (n))
#else
#define FD_ZERO_N(pos, n) memset ((void *) (pos), 0, (n))
#endif
typedef unsigned long *ulongptr;
static char bc[256]; /* Bit counting array. bc[x] is the number of
bits in x. */
int gnfds;
SELECT_TYPE greadfds;
SELECT_TYPE gwritefds;
SELECT_TYPE gexceptfds;
SELECT_TYPE rreadfds;
SELECT_TYPE rwritefds;
SELECT_TYPE rexceptfds;
static struct timeval timeout0;
/* Insert thread t into the ordered queue q.
q is ordered after wakeup_time. Threads which aren't sleeping but
waiting for I/O go last into the queue. */
static void
coop_timeout_qinsert (coop_q_t *q, coop_t *t)
{
coop_t *pred = &q->t;
int sec = t->wakeup_time.tv_sec;
int usec = t->wakeup_time.tv_usec;
while (pred->next != &q->t
&& pred->next->timeoutp
&& (pred->next->wakeup_time.tv_sec < sec
|| (pred->next->wakeup_time.tv_sec == sec
&& pred->next->wakeup_time.tv_usec < usec)))
pred = pred->next;
t->next = pred->next;
pred->next = t;
if (t->next == &q->t)
q->tail = t;
}
#ifdef DEBUG_ISELECT
static void
qp (char* qn, coop_q_t *q)
{
coop_t *t = q->t.next;
fprintf (stderr, "%s:", qn);
while (t != &q->t)
{
if (t->timeoutp)
fprintf (stderr, " %04x (%d,%d)", (unsigned) t,
t->wakeup_time.tv_sec, t->wakeup_time.tv_usec);
else
fprintf (stderr, " %04x", (unsigned) t);
t = t->next;
}
fprintf (stderr, "\n");
}
#endif
/* As select, but doesn't destroy the file descriptor sets passed as
arguments. */
static int
safe_select (int nfds,
SELECT_TYPE *readfds,
SELECT_TYPE *writefds,
SELECT_TYPE *exceptfds,
struct timeval *timeout)
{
int n = (nfds + 7) / 8;
/* Copy file descriptor sets to result area */
if (readfds == NULL)
FD_ZERO (&rreadfds);
else
{
memcpy (&rreadfds, readfds, n);
FD_ZERO_N ((char *) &rreadfds + n, SELECT_SET_SIZE / 8 - n);
}
if (writefds == NULL)
FD_ZERO (&rwritefds);
else
{
memcpy (&rwritefds, writefds, n);
FD_ZERO_N ((char *) &rwritefds + n, SELECT_SET_SIZE / 8 - n);
}
if (exceptfds == NULL)
FD_ZERO (&rexceptfds);
else
{
memcpy (&rexceptfds, exceptfds, n);
FD_ZERO_N ((char *) &rexceptfds + n, SELECT_SET_SIZE / 8 - n);
}
return select (nfds, &rreadfds, &rwritefds, &rexceptfds, timeout);
}
/* Merge new file descriptor sets into the global sets.
Return 0 on success. Return -1 if some other thread is already
waiting for one or more of the requested descriptors. */
static int
add_fd_sets (int nfds,
SELECT_TYPE *readfds,
SELECT_TYPE *writefds,
SELECT_TYPE *exceptfds)
{
int i, n = (nfds + SCM_BITS_PER_LONG - 1) / SCM_BITS_PER_LONG;
for (i = 0; i < n; ++i)
if ((readfds != NULL
&& (((ulongptr) readfds)[i] & ((ulongptr) &greadfds)[i]) != 0)
|| (writefds != NULL
&& (((ulongptr) writefds)[i] & ((ulongptr) &gwritefds)[i]) != 0)
|| (exceptfds != NULL
&& (((ulongptr) exceptfds)[i] & ((ulongptr) &gexceptfds)[i]) != 0))
return -1;
coop_global_curr->nfds = 0;
coop_global_curr->readfds = readfds;
coop_global_curr->writefds = writefds;
coop_global_curr->exceptfds = exceptfds;
while (i > 0)
{
--i;
if (readfds != NULL && ((ulongptr) readfds)[i] != 0)
{
((ulongptr) &greadfds)[i] |= ((ulongptr) readfds)[i];
n = (i + 1) * SCM_BITS_PER_LONG;
coop_global_curr->nfds = n;
if (n > gnfds)
gnfds = n;
goto cont_read;
}
if (writefds != NULL && ((ulongptr) writefds)[i] != 0)
{
((ulongptr) &gwritefds)[i] |= ((ulongptr) writefds)[i];
n = (i + 1) * SCM_BITS_PER_LONG;
coop_global_curr->nfds = n;
if (n > gnfds)
gnfds = n;
goto cont_write;
}
if (exceptfds != NULL && ((ulongptr) exceptfds)[i] != 0)
{
((ulongptr) &gexceptfds)[i] |= ((ulongptr) exceptfds)[i];
n = (i + 1) * SCM_BITS_PER_LONG;
coop_global_curr->nfds = n;
if (n > gnfds)
gnfds = n;
goto cont_except;
}
}
while (i > 0)
{
--i;
if (readfds != NULL && ((ulongptr) readfds)[i] != 0)
((ulongptr) &greadfds)[i] |= ((ulongptr) readfds)[i];
cont_read:
if (writefds != NULL && ((ulongptr) writefds)[i] != 0)
((ulongptr) &gwritefds)[i] |= ((ulongptr) writefds)[i];
cont_write:
if (exceptfds != NULL && ((ulongptr) exceptfds)[i] != 0)
((ulongptr) &gexceptfds)[i] |= ((ulongptr) exceptfds)[i];
cont_except:
}
return 0;
}
static void
finalize_fd_sets (coop_t *t)
{
int i = (t->nfds + SCM_BITS_PER_LONG - 1) / SCM_BITS_PER_LONG;
int n_ones = 0;
register unsigned long s;
if (t->nfds == gnfds)
{
while (i > 0)
{
--i;
if (t->readfds != NULL && (s = ((ulongptr) t->readfds)[i]) != 0)
{
((ulongptr) t->readfds)[i] &= ((ulongptr) &rreadfds)[i];
((ulongptr) &greadfds)[i] &= ~s;
n_ones += SCM_NLONGBITS (&((ulongptr) t->readfds)[i]);
}
if (((ulongptr) &greadfds)[i] != 0)
{
gnfds = (i + 1) * SCM_BITS_PER_LONG;
goto cont_read;
}
if (t->writefds != NULL && (s = ((ulongptr) t->writefds)[i]) != 0)
{
((ulongptr) t->writefds)[i] &= ((ulongptr) &rwritefds)[i];
((ulongptr) &gwritefds)[i] &= ~s;
n_ones += SCM_NLONGBITS (&((ulongptr) t->writefds)[i]);
}
if (((ulongptr) &gwritefds)[i] != 0)
{
gnfds = (i + 1) * SCM_BITS_PER_LONG;
goto cont_write;
}
if (t->exceptfds != NULL && (s = ((ulongptr) t->exceptfds)[i]) != 0)
{
((ulongptr) t->exceptfds)[i] &= ((ulongptr) &rexceptfds)[i];
((ulongptr) &gexceptfds)[i] &= ~s;
n_ones += SCM_NLONGBITS (&((ulongptr) t->exceptfds)[i]);
}
if (((ulongptr) &gexceptfds)[i] != 0)
{
gnfds = (i + 1) * SCM_BITS_PER_LONG;
goto cont_except;
}
}
gnfds = 0;
t->retval = n_ones;
return;
}
while (i > 0)
{
--i;
if (t->readfds != NULL && (s = ((ulongptr) t->readfds)[i]) != 0)
{
((ulongptr) t->readfds)[i] &= ((ulongptr) &rreadfds)[i];
((ulongptr) &greadfds)[i] &= ~s;
n_ones += SCM_NLONGBITS (&((ulongptr) t->readfds)[i]);
}
cont_read:
if (t->writefds != NULL && (s = ((ulongptr) t->writefds)[i]) != 0)
{
((ulongptr) t->writefds)[i] &= ((ulongptr) &rwritefds)[i];
((ulongptr) &gwritefds)[i] &= ~s;
n_ones += SCM_NLONGBITS (&((ulongptr) t->writefds)[i]);
}
cont_write:
if (t->exceptfds != NULL && (s = ((ulongptr) t->exceptfds)[i]) != 0)
{
((ulongptr) t->exceptfds)[i] &= ((ulongptr) &rexceptfds)[i];
((ulongptr) &gexceptfds)[i] &= ~s;
n_ones += SCM_NLONGBITS (&((ulongptr) t->exceptfds)[i]);
}
cont_except:
}
t->retval = n_ones;
}
static int
first_interesting_fd (void)
{
int i = 0;
SELECT_TYPE *s;
while (1)
{
if (((ulongptr) &rreadfds)[i] != 0)
{
s = &rreadfds;
break;
}
if (((ulongptr) &rwritefds)[i] != 0)
{
s = &rwritefds;
break;
}
if (((ulongptr) &rexceptfds)[i] != 0)
{
s = &rexceptfds;
break;
}
++i;
}
i *= SCM_BITS_PER_LONG;
while (i < gnfds)
{
if (FD_ISSET (i, s))
return i;
++i;
}
fprintf (stderr, "first_interesting_fd: internal error\n");
exit (1);
}
static void
error_revive (void)
{
coop_t *t;
while ((t = coop_qget (&coop_global_sleepq)) != NULL)
{
t->_errno = errno;
t->retval = -1;
coop_qput (&coop_global_runq, t);
}
gnfds = 0;
FD_ZERO (&greadfds);
FD_ZERO (&gwritefds);
FD_ZERO (&gexceptfds);
}
static coop_t *
find_thread (int n, struct timeval *now)
{
coop_t *t;
int fd;
if (n == 0)
{
while (!QEMPTYP (coop_global_sleepq)
&& (t = QFIRST (coop_global_sleepq))->timeoutp
&& (t->wakeup_time.tv_sec < now->tv_sec
|| (t->wakeup_time.tv_sec == now->tv_sec
&& t->wakeup_time.tv_usec <= now->tv_usec)))
{
coop_qget (&coop_global_sleepq);
finalize_fd_sets (t);
coop_qput (&coop_global_runq, t);
}
}
else if (n > 0)
{
/* Find the first interesting file descriptor */
fd = first_interesting_fd ();
/* Check the sleeping queue for this file descriptor.
Other file descriptors will be handled next time
coop_next_runnable_thread is called. */
/* This code is inefficient. We'll improve it later. */
while ((t = coop_qget (&coop_global_sleepq)) != NULL)
{
if ((t->readfds && FD_ISSET (fd, t->readfds))
|| (t->writefds && FD_ISSET (fd, t->writefds))
|| (t->exceptfds && FD_ISSET (fd, t->exceptfds))
|| (t->timeoutp
&& (t->wakeup_time.tv_sec < now->tv_sec
|| (t->wakeup_time.tv_sec == now->tv_sec
&& t->wakeup_time.tv_usec <= now->tv_usec))))
{
finalize_fd_sets (t);
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);
}
else /* n < 0 */
/* An error has occured. It is not EBADF since
scm_internal_select called select before putting the
threads on the sleep queue. The most robust and select
compatible behaviour is probably to let all sleeping
threads return with an error. */
error_revive ();
return coop_qget (&coop_global_runq);
}
/* Return next runnable thread on the run queue.
* First update the queue with possible I/O or timeouts.
* If no thread is found, return NULL.
*/
coop_t *
coop_next_runnable_thread ()
{
coop_t *t;
struct timeval now;
int n;
/* Just return next thread on the runq if the sleepq is empty. */
if (QEMPTYP (coop_global_sleepq))
return coop_qget (&coop_global_runq);
++scm_ints_disabled;
if (gnfds > 0)
n = safe_select (gnfds, &greadfds, &gwritefds, &gexceptfds, &timeout0);
else
n = 0;
if (QFIRST (coop_global_sleepq)->timeoutp)
{
gettimeofday (&now, NULL);
t = find_thread (n, &now);
}
else
t = find_thread (n, 0);
if (!--scm_ints_disabled)
SCM_ASYNC_TICK;
return t;
}
coop_t *
coop_wait_for_runnable_thread_now (struct timeval *now)
{
int n;
coop_t *t;
++scm_ints_disabled;
if (gnfds > 0)
n = safe_select (gnfds, &greadfds, &gwritefds, &gexceptfds, &timeout0);
else
n = 0;
/* Is there any other runnable thread? */
t = find_thread (n, now);
while (t == NULL)
{
/* No. Let the process go to sleep. */
if ((t = QFIRST (coop_global_sleepq))->timeoutp)
{
now->tv_sec = t->wakeup_time.tv_sec - now->tv_sec;
if (now->tv_usec > t->wakeup_time.tv_usec)
{
--now->tv_sec;
now->tv_usec = 1000000 + t->wakeup_time.tv_usec - now->tv_usec;
}
else
now->tv_usec = t->wakeup_time.tv_usec - now->tv_usec;
n = safe_select (gnfds, &greadfds, &gwritefds, &gexceptfds, now);
}
else
n = safe_select (gnfds, &greadfds, &gwritefds, &gexceptfds, NULL);
gettimeofday (now, NULL);
t = find_thread (n, now);
}
if (!--scm_ints_disabled)
SCM_ASYNC_TICK;
return t;
}
coop_t *
coop_wait_for_runnable_thread ()
{
struct timeval now;
if (QEMPTYP (coop_global_sleepq))
return coop_qget (&coop_global_runq);
if (QFIRST (coop_global_sleepq)->timeoutp)
gettimeofday (&now, NULL);
return coop_wait_for_runnable_thread_now (&now);
}
int
scm_internal_select (int nfds,
SELECT_TYPE *readfds,
SELECT_TYPE *writefds,
SELECT_TYPE *exceptfds,
struct timeval *timeout)
{
struct timeval now;
coop_t *t, *curr = coop_global_curr;
/* If the timeout is 0, we're polling and can handle it quickly. */
if (timeout != NULL
&& timeout->tv_sec == 0
&& timeout->tv_usec == 0)
return select (nfds, readfds, writefds, exceptfds, timeout);
++scm_ints_disabled;
/* Add our file descriptor flags to the common set. */
if (add_fd_sets (nfds, readfds, writefds, exceptfds))
{
errno = EBADF; /* Several threads can't select on same fds. */
if (!--scm_ints_disabled)
SCM_ASYNC_TICK;
return -1;
}
/* Place ourselves on the sleep queue and get a new thread to run. */
if (timeout == NULL)
{
curr->timeoutp = 0;
coop_qput (&coop_global_sleepq, curr);
t = coop_wait_for_runnable_thread ();
}
else
{
gettimeofday (&now, NULL);
curr->timeoutp = 1;
curr->wakeup_time.tv_sec = now.tv_sec + timeout->tv_sec;
curr->wakeup_time.tv_usec = now.tv_usec + timeout->tv_usec;
if (curr->wakeup_time.tv_usec >= 1000000)
{
++curr->wakeup_time.tv_sec;
curr->wakeup_time.tv_usec -= 1000000;
}
/* Insert the current thread at the right place in the sleep queue */
coop_timeout_qinsert (&coop_global_sleepq, curr);
t = coop_wait_for_runnable_thread_now (&now);
}
if (!--scm_ints_disabled)
SCM_ASYNC_TICK;
/* If the new thread is the same as the sleeping thread, do nothing */
if (t != curr)
{
/* Do a context switch. */
coop_global_curr = t;
QT_BLOCK (coop_sleephelp, curr, NULL, t->sp);
}
if (curr->retval == -1)
errno = curr->_errno;
return curr->retval;
}
/* Initialize bit counting array */
static void init_bc (int bit, int i, int n)
{
if (bit == 0)
bc[i] = n;
else
{
init_bc (bit >> 1, i, n);
init_bc (bit >> 1, i | bit, n + 1);
}
}
void
scm_init_iselect ()
{
gnfds = 0;
FD_ZERO (&greadfds);
FD_ZERO (&gwritefds);
FD_ZERO (&gexceptfds);
timeout0.tv_sec = 0;
timeout0.tv_usec = 0;
init_bc (0x80, 0, 0);
#include "iselect.x"
}