summaryrefslogtreecommitdiff
path: root/ocaml-binary-annot/otherlibs/threads/scheduler.c
diff options
context:
space:
mode:
Diffstat (limited to 'ocaml-binary-annot/otherlibs/threads/scheduler.c')
-rw-r--r--ocaml-binary-annot/otherlibs/threads/scheduler.c876
1 files changed, 876 insertions, 0 deletions
diff --git a/ocaml-binary-annot/otherlibs/threads/scheduler.c b/ocaml-binary-annot/otherlibs/threads/scheduler.c
new file mode 100644
index 0000000000..c73ac67e81
--- /dev/null
+++ b/ocaml-binary-annot/otherlibs/threads/scheduler.c
@@ -0,0 +1,876 @@
+/***********************************************************************/
+/* */
+/* Objective Caml */
+/* */
+/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
+/* */
+/* Copyright 1996 Institut National de Recherche en Informatique et */
+/* en Automatique. All rights reserved. This file is distributed */
+/* under the terms of the GNU Library General Public License, with */
+/* the special exception on linking described in file ../../LICENSE. */
+/* */
+/***********************************************************************/
+
+/* $Id$ */
+
+/* The thread scheduler */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "alloc.h"
+#include "backtrace.h"
+#include "callback.h"
+#include "config.h"
+#include "fail.h"
+#include "io.h"
+#include "memory.h"
+#include "misc.h"
+#include "mlvalues.h"
+#include "printexc.h"
+#include "roots.h"
+#include "signals.h"
+#include "stacks.h"
+#include "sys.h"
+
+#if ! (defined(HAS_SELECT) && \
+ defined(HAS_SETITIMER) && \
+ defined(HAS_GETTIMEOFDAY) && \
+ (defined(HAS_WAITPID) || defined(HAS_WAIT4)))
+#include "Cannot compile libthreads, system calls missing"
+#endif
+
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#ifdef HAS_UNISTD
+#include <unistd.h>
+#endif
+#ifdef HAS_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#ifndef HAS_WAITPID
+#define waitpid(pid,status,opts) wait4(pid,status,opts,NULL)
+#endif
+
+#ifndef O_NONBLOCK
+#define O_NONBLOCK O_NDELAY
+#endif
+
+/* Configuration */
+
+/* Initial size of stack when a thread is created (4kB) */
+#define Thread_stack_size (Stack_size / 4)
+
+/* Max computation time before rescheduling, in microseconds (50ms) */
+#define Thread_timeout 50000
+
+/* The thread descriptors */
+
+struct caml_thread_struct {
+ value ident; /* Unique id (for equality comparisons) */
+ struct caml_thread_struct * next; /* Double linking of threads */
+ struct caml_thread_struct * prev;
+ value * stack_low; /* The execution stack for this thread */
+ value * stack_high;
+ value * stack_threshold;
+ value * sp;
+ value * trapsp;
+ value backtrace_pos; /* The backtrace info for this thread */
+ code_t * backtrace_buffer;
+ value backtrace_last_exn;
+ value status; /* RUNNABLE, KILLED. etc (see below) */
+ value fd; /* File descriptor on which we're doing read or write */
+ value readfds, writefds, exceptfds;
+ /* Lists of file descriptors on which we're doing select() */
+ value delay; /* Time until which this thread is blocked */
+ value joining; /* Thread we're trying to join */
+ value waitpid; /* PID of process we're waiting for */
+ value retval; /* Value to return when thread resumes */
+};
+
+typedef struct caml_thread_struct * caml_thread_t;
+
+#define RUNNABLE Val_int(0)
+#define KILLED Val_int(1)
+#define SUSPENDED Val_int(2)
+#define BLOCKED_READ Val_int(4)
+#define BLOCKED_WRITE Val_int(8)
+#define BLOCKED_SELECT Val_int(16)
+#define BLOCKED_DELAY Val_int(32)
+#define BLOCKED_JOIN Val_int(64)
+#define BLOCKED_WAIT Val_int(128)
+
+#define RESUMED_WAKEUP Val_int(0)
+#define RESUMED_DELAY Val_int(1)
+#define RESUMED_JOIN Val_int(2)
+#define RESUMED_IO Val_int(3)
+
+#define TAG_RESUMED_SELECT 0
+#define TAG_RESUMED_WAIT 1
+
+#define NO_FDS Val_unit
+#define NO_DELAY Val_unit
+#define NO_JOINING Val_unit
+#define NO_WAITPID Val_int(0)
+
+#define DELAY_INFTY 1E30 /* +infty, for this purpose */
+
+/* The thread currently active */
+static caml_thread_t curr_thread = NULL;
+/* Identifier for next thread creation */
+static value next_ident = Val_int(0);
+
+#define Assign(dst,src) modify((value *)&(dst), (value)(src))
+
+/* Scan the stacks of the other threads */
+
+static void (*prev_scan_roots_hook) (scanning_action);
+
+static void thread_scan_roots(scanning_action action)
+{
+ caml_thread_t th, start;
+
+ /* Scan all active descriptors */
+ start = curr_thread;
+ (*action)((value) curr_thread, (value *) &curr_thread);
+ /* Don't scan curr_thread->sp, this has already been done.
+ Don't scan local roots either, for the same reason. */
+ for (th = start->next; th != start; th = th->next) {
+ do_local_roots(action, th->sp, th->stack_high, NULL);
+ }
+ /* Hook */
+ if (prev_scan_roots_hook != NULL) (*prev_scan_roots_hook)(action);
+}
+
+/* Forward declarations for async I/O handling */
+
+static int stdin_initial_status, stdout_initial_status, stderr_initial_status;
+static void thread_restore_std_descr(void);
+
+/* Initialize the thread machinery */
+
+value thread_initialize(value unit) /* ML */
+{
+ /* Protect against repeated initialization (PR#1325) */
+ if (curr_thread != NULL) return Val_unit;
+ /* Create a descriptor for the current thread */
+ curr_thread =
+ (caml_thread_t) alloc_shr(sizeof(struct caml_thread_struct)
+ / sizeof(value), 0);
+ curr_thread->ident = next_ident;
+ next_ident = Val_int(Int_val(next_ident) + 1);
+ curr_thread->next = curr_thread;
+ curr_thread->prev = curr_thread;
+ curr_thread->stack_low = stack_low;
+ curr_thread->stack_high = stack_high;
+ curr_thread->stack_threshold = stack_threshold;
+ curr_thread->sp = extern_sp;
+ curr_thread->trapsp = trapsp;
+ curr_thread->backtrace_pos = Val_int(backtrace_pos);
+ curr_thread->backtrace_buffer = backtrace_buffer;
+ caml_initialize (&curr_thread->backtrace_last_exn, backtrace_last_exn);
+ curr_thread->status = RUNNABLE;
+ curr_thread->fd = Val_int(0);
+ curr_thread->readfds = NO_FDS;
+ curr_thread->writefds = NO_FDS;
+ curr_thread->exceptfds = NO_FDS;
+ curr_thread->delay = NO_DELAY;
+ curr_thread->joining = NO_JOINING;
+ curr_thread->waitpid = NO_WAITPID;
+ curr_thread->retval = Val_unit;
+ /* Initialize GC */
+ prev_scan_roots_hook = scan_roots_hook;
+ scan_roots_hook = thread_scan_roots;
+ /* Set standard file descriptors to non-blocking mode */
+ stdin_initial_status = fcntl(0, F_GETFL);
+ stdout_initial_status = fcntl(1, F_GETFL);
+ stderr_initial_status = fcntl(2, F_GETFL);
+ if (stdin_initial_status != -1)
+ fcntl(0, F_SETFL, stdin_initial_status | O_NONBLOCK);
+ if (stdout_initial_status != -1)
+ fcntl(1, F_SETFL, stdout_initial_status | O_NONBLOCK);
+ if (stderr_initial_status != -1)
+ fcntl(2, F_SETFL, stderr_initial_status | O_NONBLOCK);
+ /* Register an at-exit function to restore the standard file descriptors */
+ atexit(thread_restore_std_descr);
+ return Val_unit;
+}
+
+/* Initialize the interval timer used for preemption */
+
+value thread_initialize_preemption(value unit) /* ML */
+{
+ struct itimerval timer;
+
+ timer.it_interval.tv_sec = 0;
+ timer.it_interval.tv_usec = Thread_timeout;
+ timer.it_value = timer.it_interval;
+ setitimer(ITIMER_VIRTUAL, &timer, NULL);
+ return Val_unit;
+}
+
+/* Create a thread */
+
+value thread_new(value clos) /* ML */
+{
+ caml_thread_t th;
+ /* Allocate the thread and its stack */
+ Begin_root(clos);
+ th = (caml_thread_t) alloc_shr(sizeof(struct caml_thread_struct)
+ / sizeof(value), 0);
+ End_roots();
+ th->ident = next_ident;
+ next_ident = Val_int(Int_val(next_ident) + 1);
+ th->stack_low = (value *) stat_alloc(Thread_stack_size);
+ th->stack_high = th->stack_low + Thread_stack_size / sizeof(value);
+ th->stack_threshold = th->stack_low + Stack_threshold / sizeof(value);
+ th->sp = th->stack_high;
+ th->trapsp = th->stack_high;
+ /* Set up a return frame that pretends we're applying the function to ().
+ This way, the next RETURN instruction will run the function. */
+ th->sp -= 5;
+ th->sp[0] = Val_unit; /* dummy local to be popped by RETURN 1 */
+ th->sp[1] = (value) Code_val(clos);
+ th->sp[2] = clos;
+ th->sp[3] = Val_long(0); /* no extra args */
+ th->sp[4] = Val_unit; /* the () argument */
+ /* Fake a C call frame */
+ th->sp--;
+ th->sp[0] = Val_unit; /* a dummy environment */
+ /* Finish initialization of th */
+ th->backtrace_pos = Val_int(0);
+ th->backtrace_buffer = NULL;
+ th->backtrace_last_exn = Val_unit;
+ /* The thread is initially runnable */
+ th->status = RUNNABLE;
+ th->fd = Val_int(0);
+ th->readfds = NO_FDS;
+ th->writefds = NO_FDS;
+ th->exceptfds = NO_FDS;
+ th->delay = NO_DELAY;
+ th->joining = NO_JOINING;
+ th->waitpid = NO_WAITPID;
+ th->retval = Val_unit;
+ /* Insert thread in doubly linked list of threads */
+ th->prev = curr_thread->prev;
+ th->next = curr_thread;
+ Assign(curr_thread->prev->next, th);
+ Assign(curr_thread->prev, th);
+ /* Return thread */
+ return (value) th;
+}
+
+/* Return the thread identifier */
+
+value thread_id(value th) /* ML */
+{
+ return ((caml_thread_t)th)->ident;
+}
+
+/* Return the current time as a floating-point number */
+
+static double timeofday(void)
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return (double) tv.tv_sec + (double) tv.tv_usec * 1e-6;
+}
+
+/* Find a runnable thread and activate it */
+
+#define FOREACH_THREAD(x) x = curr_thread; do { x = x->next;
+#define END_FOREACH(x) } while (x != curr_thread)
+
+static value alloc_process_status(int pid, int status);
+static void add_fdlist_to_set(value fdl, fd_set *set);
+static value inter_fdlist_set(value fdl, fd_set *set, int *count);
+static void find_bad_fd(int fd, fd_set *set);
+static void find_bad_fds(value fdl, fd_set *set);
+
+static value schedule_thread(void)
+{
+ caml_thread_t run_thread, th;
+ fd_set readfds, writefds, exceptfds;
+ double delay, now;
+ int need_select, need_wait;
+
+ /* Don't allow preemption during a callback */
+ if (callback_depth > 1) return curr_thread->retval;
+
+ /* Save the status of the current thread */
+ curr_thread->stack_low = stack_low;
+ curr_thread->stack_high = stack_high;
+ curr_thread->stack_threshold = stack_threshold;
+ curr_thread->sp = extern_sp;
+ curr_thread->trapsp = trapsp;
+ curr_thread->backtrace_pos = Val_int(backtrace_pos);
+ curr_thread->backtrace_buffer = backtrace_buffer;
+ caml_modify (&curr_thread->backtrace_last_exn, backtrace_last_exn);
+
+try_again:
+ /* Find if a thread is runnable.
+ Build fdsets and delay for select.
+ See if some join or wait operations succeeded. */
+ run_thread = NULL;
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&exceptfds);
+ delay = DELAY_INFTY;
+ now = -1.0;
+ need_select = 0;
+ need_wait = 0;
+
+ FOREACH_THREAD(th)
+ if (th->status <= SUSPENDED) continue;
+
+ if (th->status & (BLOCKED_READ - 1)) {
+ FD_SET(Int_val(th->fd), &readfds);
+ need_select = 1;
+ }
+ if (th->status & (BLOCKED_WRITE - 1)) {
+ FD_SET(Int_val(th->fd), &writefds);
+ need_select = 1;
+ }
+ if (th->status & (BLOCKED_SELECT - 1)) {
+ add_fdlist_to_set(th->readfds, &readfds);
+ add_fdlist_to_set(th->writefds, &writefds);
+ add_fdlist_to_set(th->exceptfds, &exceptfds);
+ need_select = 1;
+ }
+ if (th->status & (BLOCKED_DELAY - 1)) {
+ double th_delay;
+ if (now < 0.0) now = timeofday();
+ th_delay = Double_val(th->delay) - now;
+ if (th_delay <= 0) {
+ th->status = RUNNABLE;
+ Assign(th->retval,RESUMED_DELAY);
+ } else {
+ if (th_delay < delay) delay = th_delay;
+ }
+ }
+ if (th->status & (BLOCKED_JOIN - 1)) {
+ if (((caml_thread_t)(th->joining))->status == KILLED) {
+ th->status = RUNNABLE;
+ Assign(th->retval, RESUMED_JOIN);
+ }
+ }
+ if (th->status & (BLOCKED_WAIT - 1)) {
+ int status, pid;
+ pid = waitpid(Int_val(th->waitpid), &status, WNOHANG);
+ if (pid > 0) {
+ th->status = RUNNABLE;
+ Assign(th->retval, alloc_process_status(pid, status));
+ } else {
+ need_wait = 1;
+ }
+ }
+ END_FOREACH(th);
+
+ /* Find if a thread is runnable. */
+ run_thread = NULL;
+ FOREACH_THREAD(th)
+ if (th->status == RUNNABLE) { run_thread = th; break; }
+ END_FOREACH(th);
+
+ /* Do the select if needed */
+ if (need_select || run_thread == NULL) {
+ struct timeval delay_tv, * delay_ptr;
+ int retcode;
+ /* If a thread is blocked on wait, don't block forever */
+ if (need_wait && delay > Thread_timeout * 1e-6) {
+ delay = Thread_timeout * 1e-6;
+ }
+ /* Convert delay to a timeval */
+ /* If a thread is runnable, just poll */
+ if (run_thread != NULL) {
+ delay_tv.tv_sec = 0;
+ delay_tv.tv_usec = 0;
+ delay_ptr = &delay_tv;
+ }
+ else if (delay != DELAY_INFTY) {
+ delay_tv.tv_sec = (unsigned int) delay;
+ delay_tv.tv_usec = (delay - (double) delay_tv.tv_sec) * 1E6;
+ delay_ptr = &delay_tv;
+ }
+ else {
+ delay_ptr = NULL;
+ }
+ enter_blocking_section();
+ retcode = select(FD_SETSIZE, &readfds, &writefds, &exceptfds, delay_ptr);
+ leave_blocking_section();
+ if (retcode == -1)
+ switch (errno) {
+ case EINTR:
+ break;
+ case EBADF:
+ /* One of the descriptors in the sets was closed or is bad.
+ Find it using fstat() and wake up the threads waiting on it
+ so that they'll get an error when operating on it. */
+ FOREACH_THREAD(th)
+ if (th->status & (BLOCKED_READ - 1)) {
+ find_bad_fd(Int_val(th->fd), &readfds);
+ }
+ if (th->status & (BLOCKED_WRITE - 1)) {
+ find_bad_fd(Int_val(th->fd), &writefds);
+ }
+ if (th->status & (BLOCKED_SELECT - 1)) {
+ find_bad_fds(th->readfds, &readfds);
+ find_bad_fds(th->writefds, &writefds);
+ find_bad_fds(th->exceptfds, &exceptfds);
+ }
+ END_FOREACH(th);
+ retcode = FD_SETSIZE;
+ break;
+ default:
+ sys_error(NO_ARG);
+ }
+ if (retcode > 0) {
+ /* Some descriptors are ready.
+ Mark the corresponding threads runnable. */
+ FOREACH_THREAD(th)
+ if (retcode <= 0) break;
+ if ((th->status & (BLOCKED_READ - 1)) &&
+ FD_ISSET(Int_val(th->fd), &readfds)) {
+ Assign(th->retval, RESUMED_IO);
+ th->status = RUNNABLE;
+ if (run_thread == NULL) run_thread = th; /* Found one. */
+ /* Wake up only one thread per fd */
+ FD_CLR(Int_val(th->fd), &readfds);
+ retcode--;
+ }
+ if ((th->status & (BLOCKED_WRITE - 1)) &&
+ FD_ISSET(Int_val(th->fd), &writefds)) {
+ Assign(th->retval, RESUMED_IO);
+ th->status = RUNNABLE;
+ if (run_thread == NULL) run_thread = th; /* Found one. */
+ /* Wake up only one thread per fd */
+ FD_CLR(Int_val(th->fd), &readfds);
+ retcode--;
+ }
+ if (th->status & (BLOCKED_SELECT - 1)) {
+ value r = Val_unit, w = Val_unit, e = Val_unit;
+ Begin_roots3(r,w,e)
+ r = inter_fdlist_set(th->readfds, &readfds, &retcode);
+ w = inter_fdlist_set(th->writefds, &writefds, &retcode);
+ e = inter_fdlist_set(th->exceptfds, &exceptfds, &retcode);
+ if (r != NO_FDS || w != NO_FDS || e != NO_FDS) {
+ value retval = alloc_small(3, TAG_RESUMED_SELECT);
+ Field(retval, 0) = r;
+ Field(retval, 1) = w;
+ Field(retval, 2) = e;
+ Assign(th->retval, retval);
+ th->status = RUNNABLE;
+ if (run_thread == NULL) run_thread = th; /* Found one. */
+ }
+ End_roots();
+ }
+ END_FOREACH(th);
+ }
+ /* If we get here with run_thread still NULL, one of the following
+ may have happened:
+ - a delay has expired
+ - a wait() needs to be polled again
+ - the select() failed (e.g. was interrupted)
+ In these cases, we go through the loop once more to make the
+ corresponding threads runnable. */
+ if (run_thread == NULL &&
+ (delay != DELAY_INFTY || need_wait || retcode == -1))
+ goto try_again;
+ }
+
+ /* If we haven't something to run at that point, we're in big trouble. */
+ if (run_thread == NULL) invalid_argument("Thread: deadlock");
+
+ /* Free everything the thread was waiting on */
+ Assign(run_thread->readfds, NO_FDS);
+ Assign(run_thread->writefds, NO_FDS);
+ Assign(run_thread->exceptfds, NO_FDS);
+ Assign(run_thread->delay, NO_DELAY);
+ Assign(run_thread->joining, NO_JOINING);
+ run_thread->waitpid = NO_WAITPID;
+
+ /* Activate the thread */
+ curr_thread = run_thread;
+ stack_low = curr_thread->stack_low;
+ stack_high = curr_thread->stack_high;
+ stack_threshold = curr_thread->stack_threshold;
+ extern_sp = curr_thread->sp;
+ trapsp = curr_thread->trapsp;
+ backtrace_pos = Int_val(curr_thread->backtrace_pos);
+ backtrace_buffer = curr_thread->backtrace_buffer;
+ backtrace_last_exn = curr_thread->backtrace_last_exn;
+ return curr_thread->retval;
+}
+
+/* Since context switching is not allowed in callbacks, a thread that
+ blocks during a callback is a deadlock. */
+
+static void check_callback(void)
+{
+ if (callback_depth > 1)
+ caml_fatal_error("Thread: deadlock during callback");
+}
+
+/* Reschedule without suspending the current thread */
+
+value thread_yield(value unit) /* ML */
+{
+ Assert(curr_thread != NULL);
+ Assign(curr_thread->retval, Val_unit);
+ return schedule_thread();
+}
+
+/* Honor an asynchronous request for re-scheduling */
+
+static void thread_reschedule(void)
+{
+ value accu;
+
+ Assert(curr_thread != NULL);
+ /* Pop accu from event frame, making it look like a C_CALL frame
+ followed by a RETURN frame */
+ accu = *extern_sp++;
+ /* Reschedule */
+ Assign(curr_thread->retval, accu);
+ accu = schedule_thread();
+ /* Push accu below C_CALL frame so that it looks like an event frame */
+ *--extern_sp = accu;
+}
+
+/* Request a re-scheduling as soon as possible */
+
+value thread_request_reschedule(value unit) /* ML */
+{
+ async_action_hook = thread_reschedule;
+ something_to_do = 1;
+ return Val_unit;
+}
+
+/* Suspend the current thread */
+
+value thread_sleep(value unit) /* ML */
+{
+ Assert(curr_thread != NULL);
+ check_callback();
+ curr_thread->status = SUSPENDED;
+ return schedule_thread();
+}
+
+/* Suspend the current thread on a read() or write() request */
+
+static value thread_wait_rw(int kind, value fd)
+{
+ /* Don't do an error if we're not initialized yet
+ (we can be called from thread-safe Pervasives before initialization),
+ just return immediately. */
+ if (curr_thread == NULL) return RESUMED_WAKEUP;
+ /* As a special case, if we're in a callback, don't fail but block
+ the whole process till I/O is possible */
+ if (callback_depth > 1) {
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(Int_val(fd), &fds);
+ switch(kind) {
+ case BLOCKED_READ: select(FD_SETSIZE, &fds, NULL, NULL, NULL); break;
+ case BLOCKED_WRITE: select(FD_SETSIZE, NULL, &fds, NULL, NULL); break;
+ }
+ return RESUMED_IO;
+ } else {
+ curr_thread->fd = fd;
+ curr_thread->status = kind;
+ return schedule_thread();
+ }
+}
+
+value thread_wait_read(value fd)
+{
+ return thread_wait_rw(BLOCKED_READ, fd);
+}
+
+value thread_wait_write(value fd)
+{
+ return thread_wait_rw(BLOCKED_WRITE, fd);
+}
+
+/* Suspend the current thread on a read() or write() request with timeout */
+
+static value thread_wait_timed_rw(int kind, value arg)
+{
+ double date;
+
+ check_callback();
+ curr_thread->fd = Field(arg, 0);
+ date = timeofday() + Double_val(Field(arg, 1));
+ Assign(curr_thread->delay, copy_double(date));
+ curr_thread->status = kind | BLOCKED_DELAY;
+ return schedule_thread();
+}
+
+value thread_wait_timed_read(value arg)
+{
+ return thread_wait_timed_rw(BLOCKED_READ, arg);
+}
+
+value thread_wait_timed_write(value arg)
+{
+ return thread_wait_timed_rw(BLOCKED_WRITE, arg);
+}
+
+/* Suspend the current thread on a select() request */
+
+value thread_select(value arg) /* ML */
+{
+ double date;
+ check_callback();
+ Assign(curr_thread->readfds, Field(arg, 0));
+ Assign(curr_thread->writefds, Field(arg, 1));
+ Assign(curr_thread->exceptfds, Field(arg, 2));
+ date = Double_val(Field(arg, 3));
+ if (date >= 0.0) {
+ date += timeofday();
+ Assign(curr_thread->delay, copy_double(date));
+ curr_thread->status = BLOCKED_SELECT | BLOCKED_DELAY;
+ } else {
+ curr_thread->status = BLOCKED_SELECT;
+ }
+ return schedule_thread();
+}
+
+/* Primitives to implement suspension on buffered channels */
+
+value thread_inchan_ready(value vchan) /* ML */
+{
+ struct channel * chan = Channel(vchan);
+ return Val_bool(chan->curr < chan->max);
+}
+
+value thread_outchan_ready(value vchan, value vsize) /* ML */
+{
+ struct channel * chan = Channel(vchan);
+ intnat size = Long_val(vsize);
+ /* Negative size means we want to flush the buffer entirely */
+ if (size < 0) {
+ return Val_bool(chan->curr == chan->buff);
+ } else {
+ int free = chan->end - chan->curr;
+ if (chan->curr == chan->buff)
+ return Val_bool(size < free);
+ else
+ return Val_bool(size <= free);
+ }
+}
+
+/* Suspend the current thread for some time */
+
+value thread_delay(value time) /* ML */
+{
+ double date = timeofday() + Double_val(time);
+ Assert(curr_thread != NULL);
+ check_callback();
+ curr_thread->status = BLOCKED_DELAY;
+ Assign(curr_thread->delay, copy_double(date));
+ return schedule_thread();
+}
+
+/* Suspend the current thread until another thread terminates */
+
+value thread_join(value th) /* ML */
+{
+ check_callback();
+ Assert(curr_thread != NULL);
+ if (((caml_thread_t)th)->status == KILLED) return Val_unit;
+ curr_thread->status = BLOCKED_JOIN;
+ Assign(curr_thread->joining, th);
+ return schedule_thread();
+}
+
+/* Suspend the current thread until a Unix process exits */
+
+value thread_wait_pid(value pid) /* ML */
+{
+ Assert(curr_thread != NULL);
+ check_callback();
+ curr_thread->status = BLOCKED_WAIT;
+ curr_thread->waitpid = pid;
+ return schedule_thread();
+}
+
+/* Reactivate another thread */
+
+value thread_wakeup(value thread) /* ML */
+{
+ caml_thread_t th = (caml_thread_t) thread;
+ switch (th->status) {
+ case SUSPENDED:
+ th->status = RUNNABLE;
+ Assign(th->retval, RESUMED_WAKEUP);
+ break;
+ case KILLED:
+ failwith("Thread.wakeup: killed thread");
+ default:
+ failwith("Thread.wakeup: thread not suspended");
+ }
+ return Val_unit;
+}
+
+/* Return the current thread */
+
+value thread_self(value unit) /* ML */
+{
+ Assert(curr_thread != NULL);
+ return (value) curr_thread;
+}
+
+/* Kill a thread */
+
+value thread_kill(value thread) /* ML */
+{
+ value retval = Val_unit;
+ caml_thread_t th = (caml_thread_t) thread;
+ if (th->status == KILLED) failwith("Thread.kill: killed thread");
+ /* Don't paint ourselves in a corner */
+ if (th == th->next) failwith("Thread.kill: cannot kill the last thread");
+ /* This thread is no longer waiting on anything */
+ th->status = KILLED;
+ /* If this is the current thread, activate another one */
+ if (th == curr_thread) {
+ Begin_root(thread);
+ retval = schedule_thread();
+ th = (caml_thread_t) thread;
+ End_roots();
+ }
+ /* Remove thread from the doubly-linked list */
+ Assign(th->prev->next, th->next);
+ Assign(th->next->prev, th->prev);
+ /* Free its resources */
+ stat_free((char *) th->stack_low);
+ th->stack_low = NULL;
+ th->stack_high = NULL;
+ th->stack_threshold = NULL;
+ th->sp = NULL;
+ th->trapsp = NULL;
+ if (th->backtrace_buffer != NULL) {
+ free(th->backtrace_buffer);
+ th->backtrace_buffer = NULL;
+ }
+ return retval;
+}
+
+/* Print uncaught exception and backtrace */
+
+value thread_uncaught_exception(value exn) /* ML */
+{
+ char * msg = format_caml_exception(exn);
+ fprintf(stderr, "Thread %d killed on uncaught exception %s\n",
+ Int_val(curr_thread->ident), msg);
+ free(msg);
+ if (backtrace_active) print_exception_backtrace();
+ fflush(stderr);
+ return Val_unit;
+}
+
+/* Set a list of file descriptors in a fdset */
+
+static void add_fdlist_to_set(value fdl, fd_set *set)
+{
+ for (/*nothing*/; fdl != NO_FDS; fdl = Field(fdl, 1)) {
+ int fd = Int_val(Field(fdl, 0));
+ /* Ignore funky file descriptors, which can cause crashes */
+ if (fd >= 0 && fd < FD_SETSIZE) FD_SET(fd, set);
+ }
+}
+
+/* Build the intersection of a list and a fdset (the list of file descriptors
+ which are both in the list and in the fdset). */
+
+static value inter_fdlist_set(value fdl, fd_set *set, int *count)
+{
+ value res = Val_unit;
+ value cons;
+
+ Begin_roots2(fdl, res);
+ for (res = NO_FDS; fdl != NO_FDS; fdl = Field(fdl, 1)) {
+ int fd = Int_val(Field(fdl, 0));
+ if (FD_ISSET(fd, set)) {
+ cons = alloc_small(2, 0);
+ Field(cons, 0) = Val_int(fd);
+ Field(cons, 1) = res;
+ res = cons;
+ FD_CLR(fd, set); /* wake up only one thread per fd ready */
+ (*count)--;
+ }
+ }
+ End_roots();
+ return res;
+}
+
+/* Find closed file descriptors in a waiting list and set them to 1 in
+ the given fdset */
+
+static void find_bad_fd(int fd, fd_set *set)
+{
+ struct stat s;
+ if (fd >= 0 && fd < FD_SETSIZE && fstat(fd, &s) == -1 && errno == EBADF)
+ FD_SET(fd, set);
+}
+
+static void find_bad_fds(value fdl, fd_set *set)
+{
+ for (/*nothing*/; fdl != NO_FDS; fdl = Field(fdl, 1))
+ find_bad_fd(Int_val(Field(fdl, 0)), set);
+}
+
+/* Auxiliary function for allocating the result of a waitpid() call */
+
+#if !(defined(WIFEXITED) && defined(WEXITSTATUS) && defined(WIFSTOPPED) && \
+ defined(WSTOPSIG) && defined(WTERMSIG))
+/* Assume old-style V7 status word */
+#define WIFEXITED(status) (((status) & 0xFF) == 0)
+#define WEXITSTATUS(status) (((status) >> 8) & 0xFF)
+#define WIFSTOPPED(status) (((status) & 0xFF) == 0xFF)
+#define WSTOPSIG(status) (((status) >> 8) & 0xFF)
+#define WTERMSIG(status) ((status) & 0x3F)
+#endif
+
+#define TAG_WEXITED 0
+#define TAG_WSIGNALED 1
+#define TAG_WSTOPPED 2
+
+static value alloc_process_status(int pid, int status)
+{
+ value st, res;
+
+ if (WIFEXITED(status)) {
+ st = alloc_small(1, TAG_WEXITED);
+ Field(st, 0) = Val_int(WEXITSTATUS(status));
+ }
+ else if (WIFSTOPPED(status)) {
+ st = alloc_small(1, TAG_WSTOPPED);
+ Field(st, 0) = Val_int(WSTOPSIG(status));
+ }
+ else {
+ st = alloc_small(1, TAG_WSIGNALED);
+ Field(st, 0) = Val_int(WTERMSIG(status));
+ }
+ Begin_root(st);
+ res = alloc_small(2, TAG_RESUMED_WAIT);
+ Field(res, 0) = Val_int(pid);
+ Field(res, 1) = st;
+ End_roots();
+ return res;
+}
+
+/* Restore the standard file descriptors to their initial state */
+
+static void thread_restore_std_descr(void)
+{
+ if (stdin_initial_status != -1) fcntl(0, F_SETFL, stdin_initial_status);
+ if (stdout_initial_status != -1) fcntl(1, F_SETFL, stdout_initial_status);
+ if (stderr_initial_status != -1) fcntl(2, F_SETFL, stderr_initial_status);
+}