| 1 | /* Copyright (C) 1997, 1998, 2000 Free Software Foundation, Inc. |
| 2 | * |
| 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) |
| 6 | * any later version. |
| 7 | * |
| 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. |
| 12 | * |
| 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 |
| 17 | * |
| 18 | * As a special exception, the Free Software Foundation gives permission |
| 19 | * for additional uses of the text contained in its release of GUILE. |
| 20 | * |
| 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. |
| 26 | * |
| 27 | * This exception does not however invalidate any other reasons why |
| 28 | * the executable file might be covered by the GNU General Public License. |
| 29 | * |
| 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. |
| 37 | * |
| 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. */ |
| 41 | \f |
| 42 | #include <stdio.h> |
| 43 | #include <limits.h> |
| 44 | #include <string.h> |
| 45 | |
| 46 | #include "_scm.h" |
| 47 | |
| 48 | #include "iselect.h" |
| 49 | |
| 50 | #ifdef GUILE_ISELECT |
| 51 | |
| 52 | #include "coop-threads.h" |
| 53 | |
| 54 | #ifdef MISSING_BZERO_DECL |
| 55 | extern void bzero (void *, size_t); |
| 56 | #endif |
| 57 | |
| 58 | \f |
| 59 | |
| 60 | /* COOP queue macros */ |
| 61 | #define QEMPTYP(q) (q.t.next == &q.t) |
| 62 | #define QFIRST(q) (q.t.next) |
| 63 | |
| 64 | /* These macros count the number of bits in a word. */ |
| 65 | #define SCM_BITS_PER_LONG (8 * sizeof (unsigned long)) |
| 66 | /* Use LONG_MAX instead of ULONG_MAX here since not all systems define |
| 67 | ULONG_MAX */ |
| 68 | #if LONG_MAX >> 16 == 0 |
| 69 | #define SCM_NLONGBITS(p) (bc[((unsigned char *)(p))[0]]\ |
| 70 | + bc[((unsigned char *)(p))[1]]) |
| 71 | #elif LONG_MAX >> 32 == 0 |
| 72 | #define SCM_NLONGBITS(p) (bc[((unsigned char *)(p))[0]]\ |
| 73 | + bc[((unsigned char *)(p))[1]]\ |
| 74 | + bc[((unsigned char *)(p))[2]]\ |
| 75 | + bc[((unsigned char *)(p))[3]]) |
| 76 | #elif LONG_MAX >> 64 == 0 |
| 77 | #define SCM_NLONGBITS(p) (bc[((unsigned char *)(p))[0]]\ |
| 78 | + bc[((unsigned char *)(p))[1]]\ |
| 79 | + bc[((unsigned char *)(p))[2]]\ |
| 80 | + bc[((unsigned char *)(p))[3]]\ |
| 81 | + bc[((unsigned char *)(p))[4]]\ |
| 82 | + bc[((unsigned char *)(p))[5]]\ |
| 83 | + bc[((unsigned char *)(p))[6]]\ |
| 84 | + bc[((unsigned char *)(p))[7]]) |
| 85 | #endif |
| 86 | |
| 87 | #ifdef HAVE_BZERO |
| 88 | #define FD_ZERO_N(pos, n) bzero ((pos), (n)) |
| 89 | #else |
| 90 | #define FD_ZERO_N(pos, n) memset ((void *) (pos), 0, (n)) |
| 91 | #endif |
| 92 | |
| 93 | typedef unsigned long *ulongptr; |
| 94 | |
| 95 | static char bc[256]; /* Bit counting array. bc[x] is the number of |
| 96 | bits in x. */ |
| 97 | |
| 98 | int scm_I_am_dead; |
| 99 | |
| 100 | /* This flag indicates that several threads are waiting on the same |
| 101 | file descriptor. When this is the case, the common fd sets are |
| 102 | updated in a more inefficient way. */ |
| 103 | int collisionp; |
| 104 | |
| 105 | /* These are the common fd sets. When new select calls are made, |
| 106 | those sets are merged into these. */ |
| 107 | int gnfds; |
| 108 | SELECT_TYPE greadfds; |
| 109 | SELECT_TYPE gwritefds; |
| 110 | SELECT_TYPE gexceptfds; |
| 111 | |
| 112 | /* These are the result sets. They are used when we call OS select. |
| 113 | We couldn't use the common fd sets above, since that would destroy |
| 114 | them. */ |
| 115 | SELECT_TYPE rreadfds; |
| 116 | SELECT_TYPE rwritefds; |
| 117 | SELECT_TYPE rexceptfds; |
| 118 | |
| 119 | /* Constant timeval struct representing a zero timeout which we use |
| 120 | when polling. */ |
| 121 | static struct timeval timeout0; |
| 122 | |
| 123 | /* As select, but doesn't destroy the file descriptor sets passed as |
| 124 | arguments. The results are stored into the result sets. */ |
| 125 | static int |
| 126 | safe_select (int nfds, |
| 127 | SELECT_TYPE *readfds, |
| 128 | SELECT_TYPE *writefds, |
| 129 | SELECT_TYPE *exceptfds, |
| 130 | struct timeval *timeout) |
| 131 | { |
| 132 | int n = (nfds + 7) / 8; |
| 133 | /* Copy file descriptor sets to result area */ |
| 134 | if (readfds == NULL) |
| 135 | FD_ZERO (&rreadfds); |
| 136 | else |
| 137 | { |
| 138 | memcpy (&rreadfds, readfds, n); |
| 139 | FD_ZERO_N ((char *) &rreadfds + n, SELECT_SET_SIZE / 8 - n); |
| 140 | } |
| 141 | if (writefds == NULL) |
| 142 | FD_ZERO (&rwritefds); |
| 143 | else |
| 144 | { |
| 145 | memcpy (&rwritefds, writefds, n); |
| 146 | FD_ZERO_N ((char *) &rwritefds + n, SELECT_SET_SIZE / 8 - n); |
| 147 | } |
| 148 | if (exceptfds == NULL) |
| 149 | FD_ZERO (&rexceptfds); |
| 150 | else |
| 151 | { |
| 152 | memcpy (&rexceptfds, exceptfds, n); |
| 153 | FD_ZERO_N ((char *) &rexceptfds + n, SELECT_SET_SIZE / 8 - n); |
| 154 | } |
| 155 | return select (nfds, &rreadfds, &rwritefds, &rexceptfds, timeout); |
| 156 | } |
| 157 | |
| 158 | /* Merge new file descriptor sets into the common sets. */ |
| 159 | static void |
| 160 | add_fd_sets (coop_t *t) |
| 161 | { |
| 162 | int n = (t->nfds + SCM_BITS_PER_LONG - 1) / SCM_BITS_PER_LONG; |
| 163 | int i; |
| 164 | |
| 165 | /* Detect if the fd sets of the thread have any bits in common with |
| 166 | the rest of the waiting threads. If that is so, set the |
| 167 | collision flag. This causes a more time consuming handling of |
| 168 | the common fd sets---they need to recalculated every time a |
| 169 | thread wakes up. */ |
| 170 | if (!collisionp) |
| 171 | for (i = 0; i < n; ++i) |
| 172 | if ((t->readfds != NULL |
| 173 | && (((ulongptr) t->readfds)[i] & ((ulongptr) &greadfds)[i]) != 0) |
| 174 | || (t->writefds != NULL |
| 175 | && ((((ulongptr) t->writefds)[i] & ((ulongptr) &gwritefds)[i]) |
| 176 | != 0)) |
| 177 | || (t->exceptfds != NULL |
| 178 | && ((((ulongptr) t->exceptfds)[i] & ((ulongptr) &gexceptfds)[i]) |
| 179 | != 0))) |
| 180 | { |
| 181 | collisionp = 1; |
| 182 | break; |
| 183 | } |
| 184 | |
| 185 | /* We recalculate nfds below. The cost for this can be paid back |
| 186 | with a great bonus since many programs are lazy with the nfds |
| 187 | arg. Many even pass 1024 when using one of the lowest fd:s! |
| 188 | |
| 189 | We approach from above, checking for non-zero bits. As soon as |
| 190 | we have determined the value of nfds, we jump down to code below |
| 191 | which concludes the updating of the common sets. */ |
| 192 | t->nfds = 0; |
| 193 | i = n; |
| 194 | while (i > 0) |
| 195 | { |
| 196 | --i; |
| 197 | if (t->readfds != NULL && ((ulongptr) t->readfds)[i] != 0) |
| 198 | { |
| 199 | ((ulongptr) &greadfds)[i] |= ((ulongptr) t->readfds)[i]; |
| 200 | n = (i + 1) * SCM_BITS_PER_LONG; |
| 201 | t->nfds = n; |
| 202 | if (n > gnfds) |
| 203 | gnfds = n; |
| 204 | goto cont_read; |
| 205 | } |
| 206 | if (t->writefds != NULL && ((ulongptr) t->writefds)[i] != 0) |
| 207 | { |
| 208 | ((ulongptr) &gwritefds)[i] |= ((ulongptr) t->writefds)[i]; |
| 209 | n = (i + 1) * SCM_BITS_PER_LONG; |
| 210 | t->nfds = n; |
| 211 | if (n > gnfds) |
| 212 | gnfds = n; |
| 213 | goto cont_write; |
| 214 | } |
| 215 | if (t->exceptfds != NULL && ((ulongptr) t->exceptfds)[i] != 0) |
| 216 | { |
| 217 | ((ulongptr) &gexceptfds)[i] |= ((ulongptr) t->exceptfds)[i]; |
| 218 | n = (i + 1) * SCM_BITS_PER_LONG; |
| 219 | t->nfds = n; |
| 220 | if (n > gnfds) |
| 221 | gnfds = n; |
| 222 | goto cont_except; |
| 223 | } |
| 224 | } |
| 225 | return; |
| 226 | |
| 227 | /* nfds is now determined. Just finish updating the common sets. */ |
| 228 | while (i > 0) |
| 229 | { |
| 230 | --i; |
| 231 | if (t->readfds != NULL && ((ulongptr) t->readfds)[i] != 0) |
| 232 | ((ulongptr) &greadfds)[i] |= ((ulongptr) t->readfds)[i]; |
| 233 | cont_read: |
| 234 | if (t->writefds != NULL && ((ulongptr) t->writefds)[i] != 0) |
| 235 | ((ulongptr) &gwritefds)[i] |= ((ulongptr) t->writefds)[i]; |
| 236 | cont_write: |
| 237 | if (t->exceptfds != NULL && ((ulongptr) t->exceptfds)[i] != 0) |
| 238 | ((ulongptr) &gexceptfds)[i] |= ((ulongptr) t->exceptfds)[i]; |
| 239 | cont_except: |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | /* Update the fd sets pointed to by the thread so that they reflect |
| 244 | the status of the file descriptors which the thread was interested |
| 245 | in. Also clear those bits in the common sets. This function is |
| 246 | only called when there are no bit collisions. */ |
| 247 | static void |
| 248 | finalize_fd_sets (coop_t *t) |
| 249 | { |
| 250 | int i = (t->nfds + SCM_BITS_PER_LONG - 1) / SCM_BITS_PER_LONG; |
| 251 | int n_ones = 0; |
| 252 | register unsigned long s; |
| 253 | |
| 254 | if (t->nfds == gnfds) |
| 255 | { |
| 256 | /* This thread is the one responsible for the current high value |
| 257 | of gnfds. First do our other jobs while at the same time |
| 258 | trying to decrease gnfds. */ |
| 259 | while (i > 0) |
| 260 | { |
| 261 | --i; |
| 262 | if (t->readfds != NULL && (s = ((ulongptr) t->readfds)[i]) != 0) |
| 263 | { |
| 264 | ((ulongptr) t->readfds)[i] &= ((ulongptr) &rreadfds)[i]; |
| 265 | ((ulongptr) &greadfds)[i] &= ~s; |
| 266 | n_ones += SCM_NLONGBITS (&((ulongptr) t->readfds)[i]); |
| 267 | } |
| 268 | if (((ulongptr) &greadfds)[i] != 0) |
| 269 | { |
| 270 | gnfds = (i + 1) * SCM_BITS_PER_LONG; |
| 271 | goto cont_read; |
| 272 | } |
| 273 | if (t->writefds != NULL && (s = ((ulongptr) t->writefds)[i]) != 0) |
| 274 | { |
| 275 | ((ulongptr) t->writefds)[i] &= ((ulongptr) &rwritefds)[i]; |
| 276 | ((ulongptr) &gwritefds)[i] &= ~s; |
| 277 | n_ones += SCM_NLONGBITS (&((ulongptr) t->writefds)[i]); |
| 278 | } |
| 279 | if (((ulongptr) &gwritefds)[i] != 0) |
| 280 | { |
| 281 | gnfds = (i + 1) * SCM_BITS_PER_LONG; |
| 282 | goto cont_write; |
| 283 | } |
| 284 | if (t->exceptfds != NULL && (s = ((ulongptr) t->exceptfds)[i]) != 0) |
| 285 | { |
| 286 | ((ulongptr) t->exceptfds)[i] &= ((ulongptr) &rexceptfds)[i]; |
| 287 | ((ulongptr) &gexceptfds)[i] &= ~s; |
| 288 | n_ones += SCM_NLONGBITS (&((ulongptr) t->exceptfds)[i]); |
| 289 | } |
| 290 | if (((ulongptr) &gexceptfds)[i] != 0) |
| 291 | { |
| 292 | gnfds = (i + 1) * SCM_BITS_PER_LONG; |
| 293 | goto cont_except; |
| 294 | } |
| 295 | } |
| 296 | gnfds = 0; |
| 297 | t->retval = n_ones; |
| 298 | return; |
| 299 | } |
| 300 | |
| 301 | /* Either this thread wasn't responsible for gnfds or gnfds has been |
| 302 | determined. */ |
| 303 | while (i > 0) |
| 304 | { |
| 305 | --i; |
| 306 | if (t->readfds != NULL && (s = ((ulongptr) t->readfds)[i]) != 0) |
| 307 | { |
| 308 | ((ulongptr) t->readfds)[i] &= ((ulongptr) &rreadfds)[i]; |
| 309 | ((ulongptr) &greadfds)[i] &= ~s; |
| 310 | n_ones += SCM_NLONGBITS (&((ulongptr) t->readfds)[i]); |
| 311 | } |
| 312 | cont_read: |
| 313 | if (t->writefds != NULL && (s = ((ulongptr) t->writefds)[i]) != 0) |
| 314 | { |
| 315 | ((ulongptr) t->writefds)[i] &= ((ulongptr) &rwritefds)[i]; |
| 316 | ((ulongptr) &gwritefds)[i] &= ~s; |
| 317 | n_ones += SCM_NLONGBITS (&((ulongptr) t->writefds)[i]); |
| 318 | } |
| 319 | cont_write: |
| 320 | if (t->exceptfds != NULL && (s = ((ulongptr) t->exceptfds)[i]) != 0) |
| 321 | { |
| 322 | ((ulongptr) t->exceptfds)[i] &= ((ulongptr) &rexceptfds)[i]; |
| 323 | ((ulongptr) &gexceptfds)[i] &= ~s; |
| 324 | n_ones += SCM_NLONGBITS (&((ulongptr) t->exceptfds)[i]); |
| 325 | } |
| 326 | cont_except: |
| 327 | ; |
| 328 | } |
| 329 | t->retval = n_ones; |
| 330 | } |
| 331 | |
| 332 | /* Just like finalize_fd_sets except that we don't have to update the |
| 333 | global fd sets. Those will be recalulated elsewhere. */ |
| 334 | static void |
| 335 | finalize_fd_sets_lazily (coop_t *t) |
| 336 | { |
| 337 | int i = (t->nfds + SCM_BITS_PER_LONG - 1) / SCM_BITS_PER_LONG; |
| 338 | int n_ones = 0; |
| 339 | while (i > 0) |
| 340 | { |
| 341 | --i; |
| 342 | if (t->readfds != NULL && ((ulongptr) t->readfds)[i] != 0) |
| 343 | { |
| 344 | ((ulongptr) t->readfds)[i] &= ((ulongptr) &rreadfds)[i]; |
| 345 | n_ones += SCM_NLONGBITS (&((ulongptr) t->readfds)[i]); |
| 346 | } |
| 347 | if (t->writefds != NULL && ((ulongptr) t->writefds)[i] != 0) |
| 348 | { |
| 349 | ((ulongptr) t->writefds)[i] &= ((ulongptr) &rwritefds)[i]; |
| 350 | n_ones += SCM_NLONGBITS (&((ulongptr) t->writefds)[i]); |
| 351 | } |
| 352 | if (t->exceptfds != NULL && ((ulongptr) t->exceptfds)[i] != 0) |
| 353 | { |
| 354 | ((ulongptr) t->exceptfds)[i] &= ((ulongptr) &rexceptfds)[i]; |
| 355 | n_ones += SCM_NLONGBITS (&((ulongptr) t->exceptfds)[i]); |
| 356 | } |
| 357 | } |
| 358 | t->retval = n_ones; |
| 359 | } |
| 360 | |
| 361 | /* Return first fd with a non-zero bit in any of the result sets. */ |
| 362 | static int |
| 363 | first_interesting_fd (void) |
| 364 | { |
| 365 | int i = 0; |
| 366 | SELECT_TYPE *s; |
| 367 | while (1) |
| 368 | { |
| 369 | if (((ulongptr) &rreadfds)[i] != 0) |
| 370 | { |
| 371 | s = &rreadfds; |
| 372 | break; |
| 373 | } |
| 374 | if (((ulongptr) &rwritefds)[i] != 0) |
| 375 | { |
| 376 | s = &rwritefds; |
| 377 | break; |
| 378 | } |
| 379 | if (((ulongptr) &rexceptfds)[i] != 0) |
| 380 | { |
| 381 | s = &rexceptfds; |
| 382 | break; |
| 383 | } |
| 384 | ++i; |
| 385 | } |
| 386 | i *= SCM_BITS_PER_LONG; |
| 387 | while (i < gnfds) |
| 388 | { |
| 389 | if (FD_ISSET (i, s)) |
| 390 | return i; |
| 391 | ++i; |
| 392 | } |
| 393 | fprintf (stderr, "first_interesting_fd: internal error\n"); |
| 394 | exit (1); |
| 395 | } |
| 396 | |
| 397 | /* Revive all threads with an error status. */ |
| 398 | void |
| 399 | scm_error_revive_threads (void) |
| 400 | { |
| 401 | coop_t *t; |
| 402 | |
| 403 | while ((t = coop_qget (&coop_global_sleepq)) != NULL) |
| 404 | { |
| 405 | t->_errno = errno; |
| 406 | t->retval = -1; |
| 407 | if (t != coop_global_curr) |
| 408 | coop_qput (&coop_global_runq, t); |
| 409 | } |
| 410 | collisionp = 0; |
| 411 | gnfds = 0; |
| 412 | FD_ZERO (&greadfds); |
| 413 | FD_ZERO (&gwritefds); |
| 414 | FD_ZERO (&gexceptfds); |
| 415 | } |
| 416 | |
| 417 | /* Given the result of a call to safe_select and the current time, |
| 418 | try to wake up some threads and return the first one. Return NULL |
| 419 | if we couldn't find any. */ |
| 420 | static coop_t * |
| 421 | find_thread (int n, struct timeval *now, int sleepingp) |
| 422 | { |
| 423 | coop_t *t; |
| 424 | int fd; |
| 425 | |
| 426 | if (n < 0) |
| 427 | /* An error or a signal has occured. Wake all threads. Since we |
| 428 | don't care to calculate if there is a sinner we report the |
| 429 | error to all of them. */ |
| 430 | { |
| 431 | scm_error_revive_threads (); |
| 432 | if (!scm_I_am_dead) |
| 433 | return coop_global_curr; |
| 434 | } |
| 435 | else if (n == 0) |
| 436 | { |
| 437 | while (!QEMPTYP (coop_global_sleepq) |
| 438 | && (t = QFIRST (coop_global_sleepq))->timeoutp |
| 439 | && (t->wakeup_time.tv_sec < now->tv_sec |
| 440 | || (t->wakeup_time.tv_sec == now->tv_sec |
| 441 | && t->wakeup_time.tv_usec <= now->tv_usec))) |
| 442 | { |
| 443 | coop_qget (&coop_global_sleepq); |
| 444 | if (collisionp) |
| 445 | finalize_fd_sets_lazily (t); |
| 446 | else |
| 447 | finalize_fd_sets (t); |
| 448 | coop_qput (&coop_global_runq, t); |
| 449 | } |
| 450 | if (collisionp) |
| 451 | { |
| 452 | while ((t = coop_qget (&coop_global_sleepq)) != NULL) |
| 453 | coop_qput (&coop_tmp_queue, t); |
| 454 | goto rebuild_global_fd_sets; |
| 455 | } |
| 456 | } |
| 457 | else if (n > 0) |
| 458 | { |
| 459 | /* Find the first interesting file descriptor */ |
| 460 | fd = first_interesting_fd (); |
| 461 | /* Check the sleeping queue for this file descriptor. |
| 462 | Other file descriptors will be handled next time |
| 463 | coop_next_runnable_thread is called. */ |
| 464 | /* This code is inefficient. We'll improve it later. */ |
| 465 | while ((t = coop_qget (&coop_global_sleepq)) != NULL) |
| 466 | { |
| 467 | if ((t->readfds && FD_ISSET (fd, t->readfds)) |
| 468 | || (t->writefds && FD_ISSET (fd, t->writefds)) |
| 469 | || (t->exceptfds && FD_ISSET (fd, t->exceptfds)) |
| 470 | || (t->timeoutp |
| 471 | && (t->wakeup_time.tv_sec < now->tv_sec |
| 472 | || (t->wakeup_time.tv_sec == now->tv_sec |
| 473 | && t->wakeup_time.tv_usec <= now->tv_usec)))) |
| 474 | { |
| 475 | if (collisionp) |
| 476 | finalize_fd_sets_lazily (t); |
| 477 | else |
| 478 | finalize_fd_sets (t); |
| 479 | coop_qput (&coop_global_runq, t); |
| 480 | } |
| 481 | else |
| 482 | coop_qput(&coop_tmp_queue, t); |
| 483 | } |
| 484 | if (collisionp) |
| 485 | { |
| 486 | rebuild_global_fd_sets: |
| 487 | collisionp = 0; |
| 488 | gnfds = 0; |
| 489 | FD_ZERO (&greadfds); |
| 490 | FD_ZERO (&gwritefds); |
| 491 | FD_ZERO (&gexceptfds); |
| 492 | while ((t = coop_qget (&coop_tmp_queue)) != NULL) |
| 493 | { |
| 494 | add_fd_sets (t); |
| 495 | coop_qput (&coop_global_sleepq, t); |
| 496 | } |
| 497 | } |
| 498 | else |
| 499 | { |
| 500 | while ((t = coop_qget (&coop_tmp_queue)) != NULL) |
| 501 | coop_qput (&coop_global_sleepq, t); |
| 502 | } |
| 503 | } |
| 504 | |
| 505 | return coop_qget (&coop_global_runq); |
| 506 | } |
| 507 | |
| 508 | /* Return next runnable thread on the run queue. |
| 509 | * First update the queue with possible I/O or timeouts. |
| 510 | * If no thread is found, return NULL. |
| 511 | */ |
| 512 | coop_t * |
| 513 | coop_next_runnable_thread () |
| 514 | { |
| 515 | coop_t *t; |
| 516 | struct timeval now; |
| 517 | int n; |
| 518 | |
| 519 | /* Just return next thread on the runq if the sleepq is empty. */ |
| 520 | if (QEMPTYP (coop_global_sleepq)) |
| 521 | { |
| 522 | if (QEMPTYP (coop_global_runq)) |
| 523 | return coop_global_curr; |
| 524 | else |
| 525 | return coop_qget (&coop_global_runq); |
| 526 | } |
| 527 | |
| 528 | if (gnfds > 0) |
| 529 | n = safe_select (gnfds, &greadfds, &gwritefds, &gexceptfds, &timeout0); |
| 530 | else |
| 531 | n = 0; |
| 532 | if (QFIRST (coop_global_sleepq)->timeoutp) |
| 533 | { |
| 534 | gettimeofday (&now, NULL); |
| 535 | t = find_thread (n, &now, 0); |
| 536 | } |
| 537 | else |
| 538 | t = find_thread (n, 0, 0); |
| 539 | return t == NULL ? coop_global_curr : t; |
| 540 | } |
| 541 | |
| 542 | coop_t * |
| 543 | coop_wait_for_runnable_thread_now (struct timeval *now) |
| 544 | { |
| 545 | int n; |
| 546 | coop_t *t; |
| 547 | |
| 548 | if (gnfds > 0) |
| 549 | n = safe_select (gnfds, &greadfds, &gwritefds, &gexceptfds, &timeout0); |
| 550 | else |
| 551 | n = 0; |
| 552 | /* Is there any other runnable thread? */ |
| 553 | t = find_thread (n, now, 1); |
| 554 | while (t == NULL) |
| 555 | { |
| 556 | /* No. Let the process go to sleep. */ |
| 557 | if ((t = QFIRST (coop_global_sleepq))->timeoutp) |
| 558 | { |
| 559 | now->tv_sec = t->wakeup_time.tv_sec - now->tv_sec; |
| 560 | if (now->tv_usec > t->wakeup_time.tv_usec) |
| 561 | { |
| 562 | --now->tv_sec; |
| 563 | now->tv_usec = 1000000 + t->wakeup_time.tv_usec - now->tv_usec; |
| 564 | } |
| 565 | else |
| 566 | now->tv_usec = t->wakeup_time.tv_usec - now->tv_usec; |
| 567 | n = safe_select (gnfds, &greadfds, &gwritefds, &gexceptfds, now); |
| 568 | } |
| 569 | else |
| 570 | n = safe_select (gnfds, &greadfds, &gwritefds, &gexceptfds, NULL); |
| 571 | gettimeofday (now, NULL); |
| 572 | t = find_thread (n, now, 1); |
| 573 | } |
| 574 | |
| 575 | return t; |
| 576 | } |
| 577 | |
| 578 | coop_t * |
| 579 | coop_wait_for_runnable_thread () |
| 580 | { |
| 581 | struct timeval now; |
| 582 | |
| 583 | if (QEMPTYP (coop_global_sleepq)) |
| 584 | { |
| 585 | if (QEMPTYP (coop_global_runq)) |
| 586 | return coop_global_curr; |
| 587 | else |
| 588 | return coop_qget (&coop_global_runq); |
| 589 | } |
| 590 | |
| 591 | if (QFIRST (coop_global_sleepq)->timeoutp) |
| 592 | gettimeofday (&now, NULL); |
| 593 | |
| 594 | return coop_wait_for_runnable_thread_now (&now); |
| 595 | } |
| 596 | |
| 597 | /* Initialize bit counting array */ |
| 598 | static void init_bc (int bit, int i, int n) |
| 599 | { |
| 600 | if (bit == 0) |
| 601 | bc[i] = n; |
| 602 | else |
| 603 | { |
| 604 | init_bc (bit >> 1, i, n); |
| 605 | init_bc (bit >> 1, i | bit, n + 1); |
| 606 | } |
| 607 | } |
| 608 | |
| 609 | void |
| 610 | scm_init_iselect () |
| 611 | { |
| 612 | #if 0 /* This is just symbolic */ |
| 613 | collisionp = 0; |
| 614 | gnfds = 0; |
| 615 | FD_ZERO (&greadfds); |
| 616 | FD_ZERO (&gwritefds); |
| 617 | FD_ZERO (&gexceptfds); |
| 618 | timeout0.tv_sec = 0; |
| 619 | timeout0.tv_usec = 0; |
| 620 | #endif |
| 621 | init_bc (0x80, 0, 0); |
| 622 | #include "iselect.x" |
| 623 | } |
| 624 | |
| 625 | #endif /* GUILE_ISELECT */ |
| 626 | |
| 627 | int |
| 628 | scm_internal_select (int nfds, |
| 629 | SELECT_TYPE *readfds, |
| 630 | SELECT_TYPE *writefds, |
| 631 | SELECT_TYPE *exceptfds, |
| 632 | struct timeval *timeout) |
| 633 | { |
| 634 | #ifndef GUILE_ISELECT |
| 635 | int res = select (nfds, readfds, writefds, exceptfds, timeout); |
| 636 | SCM_ASYNC_TICK; |
| 637 | return res; |
| 638 | #else /* GUILE_ISELECT */ |
| 639 | struct timeval now; |
| 640 | coop_t *t, *curr = coop_global_curr; |
| 641 | |
| 642 | /* If the timeout is 0, we're polling and can handle it quickly. */ |
| 643 | if (timeout != NULL |
| 644 | && timeout->tv_sec == 0 |
| 645 | && timeout->tv_usec == 0) |
| 646 | return select (nfds, readfds, writefds, exceptfds, timeout); |
| 647 | |
| 648 | SCM_DEFER_INTS; |
| 649 | |
| 650 | /* Add our file descriptor flags to the common set. */ |
| 651 | curr->nfds = nfds; |
| 652 | curr->readfds = readfds; |
| 653 | curr->writefds = writefds; |
| 654 | curr->exceptfds = exceptfds; |
| 655 | add_fd_sets (curr); |
| 656 | |
| 657 | /* Place ourselves on the sleep queue and get a new thread to run. */ |
| 658 | if (timeout == NULL) |
| 659 | { |
| 660 | curr->timeoutp = 0; |
| 661 | coop_qput (&coop_global_sleepq, curr); |
| 662 | t = coop_wait_for_runnable_thread (); |
| 663 | } |
| 664 | else |
| 665 | { |
| 666 | gettimeofday (&now, NULL); |
| 667 | curr->timeoutp = 1; |
| 668 | curr->wakeup_time.tv_sec = now.tv_sec + timeout->tv_sec; |
| 669 | curr->wakeup_time.tv_usec = now.tv_usec + timeout->tv_usec; |
| 670 | if (curr->wakeup_time.tv_usec >= 1000000) |
| 671 | { |
| 672 | ++curr->wakeup_time.tv_sec; |
| 673 | curr->wakeup_time.tv_usec -= 1000000; |
| 674 | } |
| 675 | /* Insert the current thread at the right place in the sleep queue */ |
| 676 | coop_timeout_qinsert (&coop_global_sleepq, curr); |
| 677 | t = coop_wait_for_runnable_thread_now (&now); |
| 678 | } |
| 679 | |
| 680 | /* If the new thread is the same as the sleeping thread, do nothing */ |
| 681 | if (t != coop_global_curr) |
| 682 | { |
| 683 | /* Do a context switch. */ |
| 684 | coop_global_curr = t; |
| 685 | QT_BLOCK (coop_sleephelp, curr, NULL, t->sp); |
| 686 | } |
| 687 | |
| 688 | if (coop_global_curr->retval == -1) |
| 689 | errno = coop_global_curr->_errno; |
| 690 | SCM_ALLOW_INTS; |
| 691 | SCM_ASYNC_TICK; |
| 692 | return coop_global_curr->retval; |
| 693 | #endif /* GUILE_ISELECT */ |
| 694 | } |