* ports.h: #include <sys/types.h>, to get a definition for `off_t'.
[bpt/guile.git] / libguile / iselect.c
index 0f05bb4..4614cce 100644 (file)
@@ -1,4 +1,4 @@
-/*     Copyright (C) 1997 Free Software Foundation, Inc.
+/*     Copyright (C) 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
 
 #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
+
+#ifdef MISSING_BZERO_DECL
+extern void bzero (void *, size_t);
+#endif
 
 \f
 
+/* COOP queue macros */
 #define QEMPTYP(q) (q.t.next == &q.t)
 #define QFIRST(q) (q.t.next)
 
+/* These macros count the number of bits in a word.  */
 #define SCM_BITS_PER_LONG (8 * sizeof (unsigned long))
-#if ULONG_MAX >> 16 == 0
+/* Use LONG_MAX instead of ULONG_MAX here since not all systems define
+   ULONG_MAX */
+#if LONG_MAX >> 16 == 0
 #define SCM_NLONGBITS(p) (bc[((unsigned char *)(p))[0]]\
                          + bc[((unsigned char *)(p))[1]])
-#elif ULONG_MAX >> 32 == 0
+#elif LONG_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
+#elif LONG_MAX >> 64 == 0
 #define SCM_NLONGBITS(p) (bc[((unsigned char *)(p))[0]]\
                          + bc[((unsigned char *)(p))[1]]\
                          + bc[((unsigned char *)(p))[2]]\
@@ -84,13 +103,29 @@ typedef unsigned long *ulongptr;
 static char bc[256]; /* Bit counting array.  bc[x] is the number of
                        bits in x. */
 
+int scm_I_am_dead;
+
+/* This flag indicates that several threads are waiting on the same
+   file descriptor.  When this is the case, the common fd sets are
+   updated in a more inefficient way.  */
+int collisionp;
+
+/* These are the common fd sets.  When new select calls are made,
+   those sets are merged into these.  */
 int gnfds;
 SELECT_TYPE greadfds;
 SELECT_TYPE gwritefds;
 SELECT_TYPE gexceptfds;
+
+/* These are the result sets.  They are used when we call OS select.
+   We couldn't use the common fd sets above, since that would destroy
+   them.  */
 SELECT_TYPE rreadfds;
 SELECT_TYPE rwritefds;
 SELECT_TYPE rexceptfds;
+
+/* Constant timeval struct representing a zero timeout which we use
+   when polling.  */
 static struct timeval timeout0;
 
 /* Insert thread t into the ordered queue q.
@@ -114,27 +149,8 @@ coop_timeout_qinsert (coop_q_t *q, coop_t *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. */
+   arguments.  The results are stored into the result sets.  */
 static int
 safe_select (int nfds,
             SELECT_TYPE *readfds,
@@ -168,83 +184,107 @@ safe_select (int nfds,
   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)
+/* Merge new file descriptor sets into the common sets.  */
+static void
+add_fd_sets (coop_t *t)
 {
-  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;
+  int n = (t->nfds + SCM_BITS_PER_LONG - 1) / SCM_BITS_PER_LONG;
+  int i;
+
+  /* Detect if the fd sets of the thread have any bits in common with
+     the rest of the waiting threads.  If that is so, set the
+     collision flag.  This causes a more time consuming handling of
+     the common fd sets---they need to recalculated every time a
+     thread wakes up.  */
+  if (!collisionp)
+    for (i = 0; i < n; ++i)
+      if ((t->readfds != NULL
+          && (((ulongptr) t->readfds)[i] & ((ulongptr) &greadfds)[i]) != 0)
+         || (t->writefds != NULL
+             && ((((ulongptr) t->writefds)[i] & ((ulongptr) &gwritefds)[i])
+                 != 0))
+         || (t->exceptfds != NULL
+             && ((((ulongptr) t->exceptfds)[i] & ((ulongptr) &gexceptfds)[i])
+                 != 0)))
+       {
+         collisionp = 1;
+         break;
+       }
+  
+  /* We recalculate nfds below.  The cost for this can be paid back
+     with a great bonus since many programs are lazy with the nfds
+     arg.  Many even pass 1024 when using one of the lowest fd:s!
+
+     We approach from above, checking for non-zero bits.  As soon as
+     we have determined the value of nfds, we jump down to code below
+     which concludes the updating of the common sets.  */
+  t->nfds = 0;
+  i = n;
   while (i > 0)
     {
       --i;
-      if (readfds != NULL && ((ulongptr) readfds)[i] != 0)
+      if (t->readfds != NULL && ((ulongptr) t->readfds)[i] != 0)
        {
-         ((ulongptr) &greadfds)[i] |= ((ulongptr) readfds)[i];
+         ((ulongptr) &greadfds)[i] |= ((ulongptr) t->readfds)[i];
          n = (i + 1) * SCM_BITS_PER_LONG;
-         coop_global_curr->nfds = n;
+         t->nfds = n;
          if (n > gnfds)
            gnfds = n;
          goto cont_read;
        }
-      if (writefds != NULL && ((ulongptr) writefds)[i] != 0)
+      if (t->writefds != NULL && ((ulongptr) t->writefds)[i] != 0)
        {
-         ((ulongptr) &gwritefds)[i] |= ((ulongptr) writefds)[i];
+         ((ulongptr) &gwritefds)[i] |= ((ulongptr) t->writefds)[i];
          n = (i + 1) * SCM_BITS_PER_LONG;
-         coop_global_curr->nfds = n;
+         t->nfds = n;
          if (n > gnfds)
            gnfds = n;
          goto cont_write;
        }
-      if (exceptfds != NULL && ((ulongptr) exceptfds)[i] != 0)
+      if (t->exceptfds != NULL && ((ulongptr) t->exceptfds)[i] != 0)
        {
-         ((ulongptr) &gexceptfds)[i] |= ((ulongptr) exceptfds)[i];
+         ((ulongptr) &gexceptfds)[i] |= ((ulongptr) t->exceptfds)[i];
          n = (i + 1) * SCM_BITS_PER_LONG;
-         coop_global_curr->nfds = n;
+         t->nfds = n;
          if (n > gnfds)
            gnfds = n;
          goto cont_except;
        }
     }
+  return;
+
+  /* nfds is now determined.  Just finish updating the common sets.  */
   while (i > 0)
     {
       --i;
-      if (readfds != NULL && ((ulongptr) readfds)[i] != 0)
-       ((ulongptr) &greadfds)[i] |= ((ulongptr) readfds)[i];
+      if (t->readfds != NULL && ((ulongptr) t->readfds)[i] != 0)
+       ((ulongptr) &greadfds)[i] |= ((ulongptr) t->readfds)[i];
     cont_read:
-      if (writefds != NULL && ((ulongptr) writefds)[i] != 0)
-       ((ulongptr) &gwritefds)[i] |= ((ulongptr) writefds)[i];
+      if (t->writefds != NULL && ((ulongptr) t->writefds)[i] != 0)
+       ((ulongptr) &gwritefds)[i] |= ((ulongptr) t->writefds)[i];
     cont_write:
-      if (exceptfds != NULL && ((ulongptr) exceptfds)[i] != 0)
-       ((ulongptr) &gexceptfds)[i] |= ((ulongptr) exceptfds)[i];
+      if (t->exceptfds != NULL && ((ulongptr) t->exceptfds)[i] != 0)
+       ((ulongptr) &gexceptfds)[i] |= ((ulongptr) t->exceptfds)[i];
     cont_except:
     }
-  return 0;
 }
 
+/* Update the fd sets pointed to by the thread so that they reflect
+   the status of the file descriptors which the thread was interested
+   in.  Also clear those bits in the common sets.  This function is
+   only called when there are no bit collisions.  */
 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)
     {
+      /* This thread is the one responsible for the current high value
+        of gnfds.  First do our other jobs while at the same time
+        trying to decrease gnfds.  */
       while (i > 0)
        {
          --i;
@@ -286,6 +326,9 @@ finalize_fd_sets (coop_t *t)
       t->retval = n_ones;
       return;
     }
+
+  /* Either this thread wasn't responsible for gnfds or gnfds has been
+     determined.  */
   while (i > 0)
     {
       --i;
@@ -300,20 +343,51 @@ finalize_fd_sets (coop_t *t)
        {
          ((ulongptr) t->writefds)[i] &= ((ulongptr) &rwritefds)[i];
          ((ulongptr) &gwritefds)[i] &= ~s;
-         n_ones += SCM_NLONGBITS (&((ulongptr) t->readfds)[i]);
+         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->readfds)[i]);
+         n_ones += SCM_NLONGBITS (&((ulongptr) t->exceptfds)[i]);
        }
     cont_except:
+      ;
     }
   t->retval = n_ones;
 }
 
+/* Just like finalize_fd_sets except that we don't have to update the
+   global fd sets.  Those will be recalulated elsewhere.  */
+static void
+finalize_fd_sets_lazily (coop_t *t)
+{
+  int i = (t->nfds + SCM_BITS_PER_LONG - 1) / SCM_BITS_PER_LONG;
+  int n_ones = 0;
+  while (i > 0)
+    {
+      --i;
+      if (t->readfds != NULL && ((ulongptr) t->readfds)[i] != 0)
+       {
+         ((ulongptr) t->readfds)[i] &= ((ulongptr) &rreadfds)[i];
+         n_ones += SCM_NLONGBITS (&((ulongptr) t->readfds)[i]);
+       }
+      if (t->writefds != NULL && ((ulongptr) t->writefds)[i] != 0)
+       {
+         ((ulongptr) t->writefds)[i] &= ((ulongptr) &rwritefds)[i];
+         n_ones += SCM_NLONGBITS (&((ulongptr) t->writefds)[i]);
+       }
+      if (t->exceptfds != NULL && ((ulongptr) t->exceptfds)[i] != 0)
+       {
+         ((ulongptr) t->exceptfds)[i] &= ((ulongptr) &rexceptfds)[i];
+         n_ones += SCM_NLONGBITS (&((ulongptr) t->exceptfds)[i]);
+       }
+    }
+  t->retval = n_ones;
+}
+
+/* Return first fd with a non-zero bit in any of the result sets.  */
 static int
 first_interesting_fd (void)
 {
@@ -349,30 +423,45 @@ first_interesting_fd (void)
   exit (1);
 }
 
-static void
-error_revive (void)
+/* Revive all threads with an error status.  */
+void
+scm_error_revive_threads (void)
 {
   coop_t *t;
   
   while ((t = coop_qget (&coop_global_sleepq)) != NULL)
     {
-      t->errno = errno;
+      t->_errno = errno;
       t->retval = -1;
-      coop_qput (&coop_global_runq, t);
+      if (t != coop_global_curr)
+       coop_qput (&coop_global_runq, t);
     }
+  collisionp = 0;
   gnfds = 0;
   FD_ZERO (&greadfds);
   FD_ZERO (&gwritefds);
   FD_ZERO (&gexceptfds);
 }
 
+/* Given the result of a call to safe_select and the current time,
+   try to wake up some threads and return the first one.  Return NULL
+   if we couldn't find any.  */
 static coop_t *
-find_thread (int n, struct timeval *now)
+find_thread (int n, struct timeval *now, int sleepingp)
 {
   coop_t *t;
   int fd;
 
-  if (n == 0)
+  if (n < 0)
+    /* An error or a signal has occured.  Wake all threads.  Since we
+       don't care to calculate if there is a sinner we report the
+       error to all of them.  */
+    {
+      scm_error_revive_threads ();
+      if (!scm_I_am_dead)
+       return coop_global_curr;
+    }
+  else if (n == 0)
     {
       while (!QEMPTYP (coop_global_sleepq)
             && (t = QFIRST (coop_global_sleepq))->timeoutp
@@ -381,9 +470,18 @@ find_thread (int n, struct timeval *now)
                     && t->wakeup_time.tv_usec <= now->tv_usec)))
        {
          coop_qget (&coop_global_sleepq);
-         finalize_fd_sets (t);
+         if (collisionp)
+           finalize_fd_sets_lazily (t);
+         else
+           finalize_fd_sets (t);
          coop_qput (&coop_global_runq, t);
        }
+      if (collisionp)
+       {
+         while ((t = coop_qget (&coop_global_sleepq)) != NULL)
+           coop_qput (&coop_tmp_queue, t);
+         goto rebuild_global_fd_sets;
+       }
     }
   else if (n > 0)
     {
@@ -403,22 +501,35 @@ find_thread (int n, struct timeval *now)
                      || (t->wakeup_time.tv_sec == now->tv_sec
                          && t->wakeup_time.tv_usec <= now->tv_usec))))
            {
-             finalize_fd_sets (t);
+             if (collisionp)
+               finalize_fd_sets_lazily (t);
+             else
+               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);
+      if (collisionp)
+       {
+       rebuild_global_fd_sets:
+         collisionp = 0;
+         gnfds = 0;
+         FD_ZERO (&greadfds);
+         FD_ZERO (&gwritefds);
+         FD_ZERO (&gexceptfds);
+         while ((t = coop_qget (&coop_tmp_queue)) != NULL)
+           {
+             add_fd_sets (t);
+             coop_qput (&coop_global_sleepq, t);
+           }
+       }
+      else
+       {
+         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);
 }
@@ -430,12 +541,18 @@ find_thread (int n, struct timeval *now)
 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);
+    {
+      if (QEMPTYP (coop_global_runq))
+       return coop_global_curr;
+      else
+       return coop_qget (&coop_global_runq);
+    }
 
   if (gnfds > 0)
     n = safe_select (gnfds, &greadfds, &gwritefds, &gexceptfds, &timeout0);
@@ -444,9 +561,11 @@ coop_next_runnable_thread ()
   if (QFIRST (coop_global_sleepq)->timeoutp)
     {
       gettimeofday (&now, NULL);
-      return find_thread (n, &now);
+      t = find_thread (n, &now, 0);
     }
-  return find_thread (n, 0);
+  else
+    t = find_thread (n, 0, 0);
+  return t == NULL ? coop_global_curr : t;
 }
 
 coop_t *
@@ -460,7 +579,7 @@ coop_wait_for_runnable_thread_now (struct timeval *now)
   else
     n = 0;
   /* Is there any other runnable thread? */
-  t = find_thread (n, now);
+  t = find_thread (n, now, 1);
   while (t == NULL)
     {
       /* No.  Let the process go to sleep. */
@@ -479,7 +598,7 @@ coop_wait_for_runnable_thread_now (struct timeval *now)
       else
        n = safe_select (gnfds, &greadfds, &gwritefds, &gexceptfds, NULL);
       gettimeofday (now, NULL);
-      t = find_thread (n, now);
+      t = find_thread (n, now, 1);
     }
 
   return t;
@@ -491,7 +610,12 @@ coop_wait_for_runnable_thread ()
   struct timeval now;
 
   if (QEMPTYP (coop_global_sleepq))
-    return coop_qget (&coop_global_runq);
+    {
+      if (QEMPTYP (coop_global_runq))
+       return coop_global_curr;
+      else
+       return coop_qget (&coop_global_runq);
+    }
 
   if (QFIRST (coop_global_sleepq)->timeoutp)
     gettimeofday (&now, NULL);
@@ -508,19 +632,21 @@ scm_internal_select (int nfds,
 {
   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_DEFER_INTS;
+
   /* 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. */
-      return -1;
-    }
+  curr->nfds = nfds;
+  curr->readfds = readfds;
+  curr->writefds = writefds;
+  curr->exceptfds = exceptfds;
+  add_fd_sets (curr);
 
   /* Place ourselves on the sleep queue and get a new thread to run. */
   if (timeout == NULL)
@@ -546,16 +672,17 @@ scm_internal_select (int nfds,
     }
 
   /* If the new thread is the same as the sleeping thread, do nothing */
-  if (t != curr)
+  if (t != coop_global_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;
+  if (coop_global_curr->retval == -1)
+    errno = coop_global_curr->_errno;
+  SCM_ALLOW_INTS;
+  return coop_global_curr->retval;
 }
 
 /* Initialize bit counting array */
@@ -573,12 +700,15 @@ static void init_bc (int bit, int i, int n)
 void
 scm_init_iselect ()
 {
+#if 0 /* This is just symbolic */
+  collisionp = 0;
   gnfds = 0;
   FD_ZERO (&greadfds);
   FD_ZERO (&gwritefds);
   FD_ZERO (&gexceptfds);
   timeout0.tv_sec = 0;
   timeout0.tv_usec = 0;
+#endif
   init_bc (0x80, 0, 0);
 #include "iselect.x"
 }