* coop-defs.h (scm_mutex_trylock): New macro: alias for
[bpt/guile.git] / libguile / coop.c
CommitLineData
d90ca38d 1/* Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc.
7bfd3b9e
JB
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
82892bed
JB
15 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
16 * Boston, MA 02111-1307 USA
7bfd3b9e
JB
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.
82892bed 40 * If you do not wish that, delete this exception notice. */
7bfd3b9e
JB
41\f
42
bccab498 43/* $Id: coop.c,v 1.16 1999-12-19 18:24:12 gjb Exp $ */
7bfd3b9e
JB
44
45/* Cooperative thread library, based on QuickThreads */
46
6d71500e
JB
47#ifdef HAVE_UNISTD_H
48#include <unistd.h>
49#endif
50
7bfd3b9e 51#include <qt.h>
51d394a1 52#include "eval.h"
7bfd3b9e 53
6d71500e 54\f/* #define COOP_STKSIZE (0x10000) */
a74145b8 55#define COOP_STKSIZE (scm_eval_stack)
7bfd3b9e
JB
56
57/* `alignment' must be a power of 2. */
58#define COOP_STKALIGN(sp, alignment) \
59((void *)((((qt_word_t)(sp)) + (alignment) - 1) & ~((alignment)-1)))
60
61\f
62
63/* Queue access functions. */
64
7bfd3b9e
JB
65static void
66coop_qinit (coop_q_t *q)
7bfd3b9e
JB
67{
68 q->t.next = q->tail = &q->t;
69
70 q->t.all_prev = NULL;
71 q->t.all_next = NULL;
44e8413c
MD
72#ifdef GUILE_ISELECT
73 q->t.nfds = 0;
74 q->t.readfds = NULL;
75 q->t.writefds = NULL;
76 q->t.exceptfds = NULL;
77 q->t.timeoutp = 0;
78#endif
7bfd3b9e
JB
79}
80
81
44e8413c 82coop_t *
7bfd3b9e 83coop_qget (coop_q_t *q)
7bfd3b9e
JB
84{
85 coop_t *t;
86
87 t = q->t.next;
88 q->t.next = t->next;
89 if (t->next == &q->t) {
90 if (t == &q->t) { /* If it was already empty .. */
91 return (NULL); /* .. say so. */
92 }
93 q->tail = &q->t; /* Else now it is empty. */
94 }
95 return (t);
96}
97
98
44e8413c 99void
7bfd3b9e 100coop_qput (coop_q_t *q, coop_t *t)
7bfd3b9e
JB
101{
102 q->tail->next = t;
103 t->next = &q->t;
104 q->tail = t;
105}
106
7bfd3b9e
JB
107static void
108coop_all_qput (coop_q_t *q, coop_t *t)
7bfd3b9e
JB
109{
110 if (q->t.all_next)
111 q->t.all_next->all_prev = t;
112 t->all_prev = NULL;
113 t->all_next = q->t.all_next;
114 q->t.all_next = t;
115}
116
7bfd3b9e
JB
117static void
118coop_all_qremove (coop_q_t *q, coop_t *t)
7bfd3b9e
JB
119{
120 if (t->all_prev)
121 t->all_prev->all_next = t->all_next;
122 else
123 q->t.all_next = t->all_next;
124 if (t->all_next)
125 t->all_next->all_prev = t->all_prev;
126}
127
128
129\f/* Thread routines. */
130
44e8413c
MD
131coop_q_t coop_global_runq; /* A queue of runable threads. */
132coop_q_t coop_global_sleepq; /* A queue of sleeping threads. */
133coop_q_t coop_tmp_queue; /* A temp working queue */
134coop_q_t coop_global_allq; /* A queue of all threads. */
135static coop_t coop_global_main; /* Thread for the process. */
136coop_t *coop_global_curr; /* Currently-executing thread. */
7bfd3b9e
JB
137
138static void *coop_starthelp (qt_t *old, void *ignore0, void *ignore1);
139static void coop_only (void *pu, void *pt, qt_userf_t *f);
140static void *coop_aborthelp (qt_t *sp, void *old, void *null);
141static void *coop_yieldhelp (qt_t *sp, void *old, void *blockq);
142
143
7bfd3b9e
JB
144void
145coop_init()
7bfd3b9e
JB
146{
147 coop_qinit (&coop_global_runq);
148 coop_qinit (&coop_global_sleepq);
44e8413c 149 coop_qinit (&coop_tmp_queue);
7bfd3b9e
JB
150 coop_qinit (&coop_global_allq);
151 coop_global_curr = &coop_global_main;
152}
153
154
155/* Return the next runnable thread. If no threads are currently runnable,
156 and there are sleeping threads - wait until one wakes up. Otherwise,
157 return NULL. */
158
6d71500e 159#ifndef GUILE_ISELECT
7bfd3b9e
JB
160coop_t *
161coop_next_runnable_thread()
7bfd3b9e
JB
162{
163 int sleepers;
164 coop_t *t;
165 time_t now;
166
167 do {
168 sleepers = 0;
169 now = time(NULL);
170
171 /* Check the sleeping queue */
172 while ((t = coop_qget(&coop_global_sleepq)) != NULL)
173 {
174 sleepers++;
175 if (t->wakeup_time <= now)
176 coop_qput(&coop_global_runq, t);
177 else
44e8413c 178 coop_qput(&coop_tmp_queue, t);
7bfd3b9e 179 }
44e8413c 180 while ((t = coop_qget(&coop_tmp_queue)) != NULL)
7bfd3b9e
JB
181 coop_qput(&coop_global_sleepq, t);
182
183 t = coop_qget (&coop_global_runq);
184
185 } while ((t == NULL) && (sleepers > 0));
186
187 return t;
188}
44e8413c 189#endif
7bfd3b9e 190
7bfd3b9e
JB
191void
192coop_start()
7bfd3b9e
JB
193{
194 coop_t *next;
195
196 while ((next = coop_qget (&coop_global_runq)) != NULL) {
197 coop_global_curr = next;
198 QT_BLOCK (coop_starthelp, 0, 0, next->sp);
199 }
200}
201
202
7bfd3b9e
JB
203static void *
204coop_starthelp (qt_t *old, void *ignore0, void *ignore1)
7bfd3b9e
JB
205{
206 coop_global_main.sp = old;
207 coop_global_main.joining = NULL;
208 coop_qput (&coop_global_runq, &coop_global_main);
209 return NULL; /* not used, but keeps compiler happy */
210}
211
c8bf4ecd 212int
7bfd3b9e 213coop_mutex_init (coop_m *m)
7bfd3b9e
JB
214{
215 m->owner = NULL;
216 coop_qinit(&(m->waiting));
c8bf4ecd 217 return 0;
7bfd3b9e
JB
218}
219
c8bf4ecd 220int
7bfd3b9e 221coop_mutex_lock (coop_m *m)
7bfd3b9e
JB
222{
223 if (m->owner == NULL)
224 {
225 m->owner = coop_global_curr;
226 }
227 else
228 {
229 coop_t *old, *newthread;
230
231 /* Record the current top-of-stack before going to sleep */
232 coop_global_curr->top = &old;
233
44e8413c
MD
234#ifdef GUILE_ISELECT
235 newthread = coop_wait_for_runnable_thread();
d186aac6
MD
236 if (newthread == coop_global_curr)
237 coop_abort ();
44e8413c 238#else
7bfd3b9e 239 newthread = coop_next_runnable_thread();
44e8413c 240#endif
7bfd3b9e
JB
241 old = coop_global_curr;
242 coop_global_curr = newthread;
243 QT_BLOCK (coop_yieldhelp, old, &(m->waiting), newthread->sp);
244 }
c8bf4ecd 245 return 0;
7bfd3b9e
JB
246}
247
248
c8bf4ecd 249int
7bfd3b9e 250coop_mutex_unlock (coop_m *m)
7bfd3b9e
JB
251{
252 coop_t *old, *newthread;
253
254 newthread = coop_qget (&(m->waiting));
255 if (newthread != NULL)
256 {
257 /* Record the current top-of-stack before going to sleep */
258 coop_global_curr->top = &old;
259
260 old = coop_global_curr;
261 coop_global_curr = newthread;
b322f09a
MD
262 /* The new thread came into m->waiting through a lock operation.
263 It now owns this mutex. */
7bfd3b9e
JB
264 m->owner = coop_global_curr;
265 QT_BLOCK (coop_yieldhelp, old, &coop_global_runq, newthread->sp);
266 }
267 else
268 {
269 m->owner = NULL;
270 }
c8bf4ecd 271 return 0;
7bfd3b9e
JB
272}
273
274
c8bf4ecd
MD
275int
276coop_mutex_destroy (coop_m *m)
c8bf4ecd
MD
277{
278 return 0;
279}
280
281
c8bf4ecd 282int
7bfd3b9e 283coop_condition_variable_init (coop_c *c)
7bfd3b9e
JB
284{
285 coop_qinit(&(c->waiting));
c8bf4ecd 286 return 0;
7bfd3b9e
JB
287}
288
b322f09a
MD
289int
290coop_condition_variable_wait_mutex (coop_c *c, coop_m *m)
7bfd3b9e
JB
291{
292 coop_t *old, *newthread;
293
b322f09a
MD
294 /* coop_mutex_unlock (m); */
295 newthread = coop_qget (&(m->waiting));
296 if (newthread != NULL)
297 {
298 m->owner = newthread;
299 }
300 else
301 {
302 m->owner = NULL;
44e8413c 303#ifdef GUILE_ISELECT
b322f09a
MD
304 newthread = coop_wait_for_runnable_thread();
305 if (newthread == coop_global_curr)
306 coop_abort ();
44e8413c 307#else
b322f09a 308 newthread = coop_next_runnable_thread();
44e8413c 309#endif
b322f09a
MD
310 }
311 coop_global_curr->top = &old;
7bfd3b9e
JB
312 old = coop_global_curr;
313 coop_global_curr = newthread;
314 QT_BLOCK (coop_yieldhelp, old, &(c->waiting), newthread->sp);
c8bf4ecd 315
c8bf4ecd
MD
316 coop_mutex_lock (m);
317 return 0;
7bfd3b9e
JB
318}
319
c8bf4ecd 320
c8bf4ecd 321int
7bfd3b9e 322coop_condition_variable_signal (coop_c *c)
7bfd3b9e
JB
323{
324 coop_t *newthread;
325
326 while ((newthread = coop_qget (&(c->waiting))) != NULL)
327 {
328 coop_qput (&coop_global_runq, newthread);
329 }
c8bf4ecd
MD
330 return 0;
331}
332
333
c8bf4ecd
MD
334int
335coop_condition_variable_destroy (coop_c *c)
c8bf4ecd
MD
336{
337 return 0;
7bfd3b9e
JB
338}
339
340
7bfd3b9e
JB
341coop_t *
342coop_create (coop_userf_t *f, void *pu)
7bfd3b9e
JB
343{
344 coop_t *t;
345 void *sto;
346
347 t = malloc (sizeof(coop_t));
348
349 t->data = NULL;
350 t->sto = malloc (COOP_STKSIZE);
351 sto = COOP_STKALIGN (t->sto, QT_STKALIGN);
352 t->sp = QT_SP (sto, COOP_STKSIZE - QT_STKALIGN);
353 t->base = t->sp;
354 t->sp = QT_ARGS (t->sp, pu, t, (qt_userf_t *)f, coop_only);
355 t->joining = NULL;
356 coop_qput (&coop_global_runq, t);
357 coop_all_qput (&coop_global_allq, t);
358
359 return t;
360}
361
362
7bfd3b9e
JB
363static void
364coop_only (void *pu, void *pt, qt_userf_t *f)
7bfd3b9e
JB
365{
366 coop_global_curr = (coop_t *)pt;
367 (*(coop_userf_t *)f)(pu);
368 coop_abort();
369 /* NOTREACHED */
370}
371
372
7bfd3b9e
JB
373void
374coop_abort ()
7bfd3b9e
JB
375{
376 coop_t *old, *newthread;
377
378 /* Wake up any threads that are waiting to join this one */
379 if (coop_global_curr->joining)
380 {
381 while ((newthread = coop_qget ((coop_q_t *)(coop_global_curr->joining)))
382 != NULL)
383 {
384 coop_qput (&coop_global_runq, newthread);
385 }
386 free(coop_global_curr->joining);
387 }
388
44e8413c 389#ifdef GUILE_ISELECT
d186aac6
MD
390 scm_I_am_dead = 1;
391 do {
392 newthread = coop_wait_for_runnable_thread();
393 } while (newthread == coop_global_curr);
394 scm_I_am_dead = 0;
44e8413c 395#else
7bfd3b9e 396 newthread = coop_next_runnable_thread();
44e8413c 397#endif
7bfd3b9e
JB
398 coop_all_qremove(&coop_global_allq, coop_global_curr);
399 old = coop_global_curr;
400 coop_global_curr = newthread;
401 QT_ABORT (coop_aborthelp, old, (void *)NULL, newthread->sp);
402}
403
404
7bfd3b9e
JB
405static void *
406coop_aborthelp (qt_t *sp, void *old, void *null)
7bfd3b9e
JB
407{
408 coop_t *oldthread = (coop_t *) old;
409
410 free (oldthread->sto);
411
412 /* "old" is freed in scm_threads_thread_die().
413 Marking old->base NULL indicates that this thread is dead */
414
415 oldthread->base = NULL;
416
417 return NULL;
418}
419
420
7bfd3b9e
JB
421void
422coop_join(coop_t *t)
7bfd3b9e
JB
423{
424 coop_t *old, *newthread;
425
426 /* Check if t is already finished */
427 if (t->base == NULL)
428 return;
429
430 /* Create a join list if necessary */
431 if (t->joining == NULL)
432 {
433 t->joining = malloc(sizeof(coop_q_t));
434 coop_qinit((coop_q_t *) t->joining);
435 }
436
44e8413c
MD
437#ifdef GUILE_ISELECT
438 newthread = coop_wait_for_runnable_thread();
d186aac6
MD
439 if (newthread == coop_global_curr)
440 return;
44e8413c 441#else
7bfd3b9e 442 newthread = coop_next_runnable_thread();
44e8413c 443#endif
7bfd3b9e
JB
444 old = coop_global_curr;
445 coop_global_curr = newthread;
446 QT_BLOCK (coop_yieldhelp, old, (coop_q_t *) t->joining, newthread->sp);
447}
448
7bfd3b9e
JB
449void
450coop_yield()
7bfd3b9e
JB
451{
452 coop_t *old = NULL;
453 coop_t *newthread;
454
455 newthread = coop_next_runnable_thread();
456
457 /* There may be no other runnable threads. Return if this is the
458 case. */
d186aac6
MD
459#if GUILE_ISELECT
460 if (newthread == coop_global_curr)
461 return;
462#else
7bfd3b9e
JB
463 if (newthread == NULL)
464 return;
d186aac6 465#endif
7bfd3b9e
JB
466
467 old = coop_global_curr;
468
469 coop_global_curr = newthread;
470 QT_BLOCK (coop_yieldhelp, old, &coop_global_runq, newthread->sp);
471}
472
473
7bfd3b9e
JB
474static void *
475coop_yieldhelp (qt_t *sp, void *old, void *blockq)
7bfd3b9e
JB
476{
477 ((coop_t *)old)->sp = sp;
478 coop_qput ((coop_q_t *)blockq, (coop_t *)old);
479 return NULL;
480}
481
482/* Replacement for the system's sleep() function. Does the right thing
483 for the process - but not for the system (it busy-waits) */
484
44e8413c 485void *
7bfd3b9e 486coop_sleephelp (qt_t *sp, void *old, void *blockq)
7bfd3b9e
JB
487{
488 ((coop_t *)old)->sp = sp;
489 /* old is already on the sleep queue - so there's no need to
490 do anything extra here */
491 return NULL;
492}
493
44e8413c
MD
494#ifdef GUILE_ISELECT
495
6aa9316d
JB
496unsigned long
497scm_thread_usleep (unsigned long usec)
44e8413c
MD
498{
499 struct timeval timeout;
500 timeout.tv_sec = 0;
501 timeout.tv_usec = usec;
502 scm_internal_select (0, NULL, NULL, NULL, &timeout);
2c4e1a34
MD
503 return 0; /* Maybe we should calculate actual time slept,
504 but this is faster... :) */
44e8413c
MD
505}
506
6aa9316d
JB
507unsigned long
508scm_thread_sleep (unsigned long sec)
44e8413c
MD
509{
510 time_t now = time (NULL);
511 struct timeval timeout;
6aa9316d 512 unsigned long slept;
44e8413c
MD
513 timeout.tv_sec = sec;
514 timeout.tv_usec = 0;
515 scm_internal_select (0, NULL, NULL, NULL, &timeout);
516 slept = time (NULL) - now;
517 return slept > sec ? 0 : sec - slept;
518}
519
520#else /* GUILE_ISELECT */
521
6aa9316d
JB
522unsigned long
523scm_thread_sleep (unsigned long s)
7bfd3b9e
JB
524{
525 coop_t *newthread, *old;
44e8413c 526 time_t now = time (NULL);
7bfd3b9e
JB
527 coop_global_curr->wakeup_time = now + s;
528
529 /* Put the current thread on the sleep queue */
530 coop_qput (&coop_global_sleepq, coop_global_curr);
531
532 newthread = coop_next_runnable_thread();
533
534 /* If newthread is the same as the sleeping thread, do nothing */
535 if (newthread == coop_global_curr)
536 return s;
537
538 old = coop_global_curr;
539
540 coop_global_curr = newthread;
541 QT_BLOCK (coop_sleephelp, old, NULL, newthread->sp);
542
543 return s;
544}
44e8413c 545
6aa9316d
JB
546unsigned long
547scm_thread_usleep (unsigned long usec)
548{
549 /* We're so cheap. */
550 scm_thread_sleep (usec / 1000000);
551 struct timeval timeout;
552 return 0; /* Maybe we should calculate actual time slept,
553 but this is faster... :) */
554}
555
44e8413c 556#endif /* GUILE_ISELECT */