/*
 * Really Simple Threads package.  Copyright Robert S. Thau 1996.
 * This code may be used for any purpose.  It may be redistributed on
 * the following terms: 1) this paragraph and the next must appear
 * unaltered in any source distribution, and be prominently displayed
 * in the supporting documentation for any source or binary
 * distribution; 2) if you wish to distribute modified or derived
 * works, each such work must bear a notice of who last altered the
 * included version of Really Simple Threads, and when.  The notice of
 * alterations shall be attached to the derivative works in the same
 * manner as these two paragraphs, and shall be at least as prominent.
 * Redistribution for any purpose is otherwise unrestricted.
 *
 * THIS CODE IS IN AN UNFINISHED STATE, AND HAS NOT BEEN TESTED
 * TO THE POINT THAT THE AUTHOR BELIEVES IT CAN BE EXPECTED TO
 * WORK RELIABLY.  IT IS SUITABLE, AT THIS POINT, FOR EXPERIMENTATION
 * AND DEBUGGING ONLY.  TREAT ACCORDINGLY.
 */

/*
 * This code has passed smoke tests on the following platforms,
 * at one time or another (but has changed since the last tests on some):
 *
 * Sparc -- SunOS
 * Alpha -- Digital Unix
 * MIPS(SGI) -- IRIX
 * Sparc -- Solaris (req. -DSOLARIS)
 * MIPS(Dec) -- Ultrix (req. -DULTRIX)
 * HPPA -- HP-UX (req. -DHP)
 * IBM Power -- AIX 3.2 (req. being linked with three lines of assembly)
 * i386 -- Linux (req. -DLINUX)
 * i386 -- NetBSD (req -DBSD4_4)
 */

#ifndef _rsthreads_h
#define _rsthreads_h

#include <sys/types.h>
#include <sys/socket.h>		/* For prototyping of I/O functions */
#include <sys/time.h>		/* For prototyping of timeout stuff */
#ifdef AIX
#include <sys/select.h>
#endif

/*
 * Basic operations -- starting, pausing, and interrupting threads.
 */

/* Values for flags of parameter blocks, and incidentally
 * for flags in thread structures themselves...
 */

#define RST_INTERRUPTS_BLOCKED 1
#define RST_ALARMS_BLOCKED 2
#define RST_NO_JOIN 4	/* If set, this thread can not be
				 * rst_joined
				 */

#define RST_USERFLAGS 7

#define RST_INTERRUPTED (1<<16)
#define RST_TIMED_OUT (1<<17)
#define RST_ALARMED (1<<18)
#define RST_IO_ERROR (1<<19)

#define RST_SIG_FLAGS \
  (RST_INTERRUPTED|RST_TIMED_OUT|RST_ALARMED|RST_IO_ERROR)

typedef struct rst_thread_parms {
    int priority;
    int flags;
    int stacksize;		/* In multiples of a per-machine quantum */
    void (*interrupt_handler)();
    void *interrupt_arg;
    char *name;
} rst_thread_parms;

extern const rst_thread_parms rst_thread_defaults;

typedef struct thread *thread_t;

void rst_init();		/* Initialize the package */

/* rst_spawn() creates a new thread, in which the given
 *   start_func is initially applied to the given arg.
 *
 * rst_spawn_parms() is the same, but the creater can specify
 *   non-default parameters for the thread.
 *
 * rst_current_thread() returns the currently running thread.
 * rst_exit() runs all the current thread's cleanups and destroys it.
 * rst_join() waits for the given thread to exit, and then sets *statusp
 *   to the value its initial function returned, or whatever was supplied as
 *   argument to rst_exit().  If the thread was created with
 *   RST_NO_JOIN in the flags, it is an error to try this, and
 *   the consequences are unpredictable.
 */

thread_t rst_spawn (void *(*start_func)(void*), void *arg);
thread_t rst_spawn_parms (void *(*start_func)(void *), void *arg,
			  const rst_thread_parms *parms);
thread_t rst_current_thread ();
void rst_exit(void *);
int rst_join (int timeout, thread_t, void **statusp);

/* Thread priorities go from 0 to RST_MAX_PRIORITY (which can really
 * be just about any integer).  Any thread can alter the priority of any
 * other thread.  At each context-switch, the highest priority runnable
 * thread is run; among threads of the same priority, the one which has
 * run least recently is chosen.
 */

#define RST_MAX_PRIORITY 2	/* priority from 0 to this */
#define RST_DEFAULT_PRIORITY 1 /* standard priority */

void rst_set_priority (thread_t, int);

/*
 * Potentially blocking operations all take a timeout argument
 * There are a few special values --- RST_NO_WAIT causes them
 * to return with a failure indication if they could not succeed
 * immediately.  RST_WAIT specifies the thread is willing to
 * wait as long as necessary for the operation.  Any positive
 * integer declares that the thread is willing to wait that many
 * seconds for success, or until it is interrupted or its alarm
 * clock goes off (see below).  RST_WAIT_SIGNAL waits
 * indefinitely, until success, alarm, or interrupt.  (If you think
 * you want to use this for inter-thread communication, having the
 * threads interrupt each other, think again; you'd probably be
 * happier with condition variables).
 *
 * Calling a timeout-taking function, rst_yield, or rst_exit
 * are the *only* ways a thread gives up control in this package.  The
 * rst_yield() call just gives up the processor; you may get it
 * back immediately if nothing else is runnable, but it's polite to
 * call it in case you've done something which might have unblocked
 * a higher-priority thread.
 *
 * All of the timeout-taking functions return an integer, which will
 * be negative in case of early return due to interruption, timeout,
 * or (in the case of I/O) other errors.  Note that while I/O routines
 * may return an error under any circumstances, other routines, when
 * given an RST_WAIT timeout argument, will not return unless
 * successful; therefore, it isn't necessary to check their return
 * values when they are given RST_WAIT as a timeout argument.
 * 
 * The rst_interrupted, _alarmed, and _timed_out routines
 * below can be used to diagnose failures.
 */

#define RST_NO_WAIT 0	/* Do not block under any circumstances */
#define RST_WAIT -1		/* Block indefinitely, or till signaled */
#define RST_WAIT_SIGNAL -2

void rst_yield();
int rst_wait (int timeout, int (*wait_func)(void*), void *arg);
int rst_sleep (int timeout);

/*
 * Interrupts ----
 * An interrupt is a signal to a thread that it should cease computation.
 * By default, it does so immediately.  However, they can be blocked and
 * unblocked, and the user can specify a handler for interrupts in a
 * particular thread other than rst_exit.  As with most handlers in
 * this package, we also take a (void*) argument which is given to the
 * handler function when it is invoked.
 *
 * (In case it isn't obvious, rst_unblock_interrupts may cause the
 * interrupt handler to be invoked, which may in turn cause the thread to
 * exit).
 *
 * When a thread has interrupts blocked, it may call rst_interrupted()
 * to determine whether it has been interrupted.  The call returns 1 if so,
 * 0 if not --- if an interrupt has occured, it will reset the bit.
 *
 * Note that if a thread is waiting, and has specified a timeout on the
 * wait, then rst_interrupt()ing that thread will cause the wait to
 * end early, even if the target thread has interrupts blocked as well.
 * Attempts to use this for general inter-thread signalling are discouraged;
 * condition variables will almost always be a better tool.
 */

void rst_interrupt(thread_t);
void rst_block_interrupts();
int rst_interrupted();
int rst_timed_out();
void rst_set_interrupt_handler (void (*)(void *), void *);
void rst_unblock_interrupts();

/*
 * Alarms --- the moral equivalent of Unix SIGALRM.  Note that a
 * thread has the same set of options for dealing with these as for
 * dealing with interrupts; the default way of handling an alarm,
 * however, is to ignore it rather than to cause instant death.
 */

void rst_set_alarm (int delay); /* Returns immediately, arranges
				 * for an alarm that many seconds
				 * in the future.
				 */
void rst_block_alarms();
int rst_alarmed();
void rst_unblock_alarms();
void rst_set_alarm_handler (void (*handler)(void *), void *arg);

/*
 * Cleanups --- these routines arrange for particular functions
 * to be called on thread termination.  An rst_cleanup_block
 * is used to chain these --- rst_cleanup_push allocates one
 * for itself, using malloc, while rst_cleanup_block_push is
 * available if it's just as easy for the caller to do it.  Either
 * way, rst_cleanup_pop gets rid of the thing.
 *
 * If you use rst_cleanup_block_push, note that the iown and next
 * fields are private to the system; don't mess with them.
 */

typedef struct rst_cleanup_block {
    void (*func)(void *);
    void *arg;
    int iown;
    struct rst_cleanup_block *next;
} rst_cleanup_block;

void rst_cleanup_push(void (*func)(void *), void *);
void rst_cleanup_block_push (rst_cleanup_block *);
void rst_cleanup_pop();

/*
 * Mutual exclusion operations --- initializing, grabbing, and
 * releasing mutex locks.  The timeout obeys the usual conventions
 * described above.
 */

typedef struct rst_mutex {
    thread_t owner;
    struct rst_mutex *next;
    char *name;
    int count;
} rst_mutex;

#define RST_MUTEX_INIT(name) { NULL, NULL, name, 0 }

void rst_mutex_init(rst_mutex *, char *name);
int rst_lock_mutex(int timeout, rst_mutex *);
void rst_release_mutex(rst_mutex *);
thread_t rst_mutex_holder(rst_mutex *); /* Who holds the mutex;
						 * NULL if it's free
						 */

/*
 * Condition variables --- right now, the only interesting attribute
 * of the cvar data structure is its name, but that could change
 * (might want to make a queue of blocked threads, for example).
 */

typedef struct rst_cvar {
    char *name;
} rst_cvar;

#define RST_CVAR_INIT(name) { name }

void rst_cvar_init (rst_cvar *cvar, char *name);
int rst_await_notify (int timeout, rst_mutex *mutex, rst_cvar *condition);
void rst_notify_one (rst_cvar *);
void rst_notify_all (rst_cvar *);

/*
 * I/O operations --- reading and writing from file descriptors
 * in a manner which allows other threads to proceed if you would
 * block.  You must call rst_prepare_fd on file descriptors 
 * before using any of the other routines...
 */

int rst_prepare_fd (int fd); /* returns 0 for success; -1 and
				  * sets errno for failure.
				  */
int rst_read (int timeout, int fd, char *, int); /* returns as read(2) */
int rst_write (int timeout, int fd, const char *, int); /* as write(2) */
int rst_accept (int timeout, int fd, struct sockaddr *addr, int *addrlen);
int rst_connect (int timeout, int fd, struct sockaddr *addr, int addrlen);

/* The fd's for these routines don't have to be "prepared";
 * they block the thread until select() shows that the fd is
 * either ready for reading/writing, or has an error condition,
 * in which case the return value will be 0 or -1, respectively.
 */

int rst_read_wait (int timeout, int fd);
int rst_write_wait (int timeout, int fd);

/* Select() variant --- all fd's set in the masks must have been
 * rst_prepare_fd'd.
 */

int rst_select (int timeout,
		fd_set *readfds, fd_set *writefds, fd_set *exceptfds);

/* Second arg to I/O error handler is fd on which error was encountered.
 * Handler and arg are per-thread.
 */

void rst_set_io_err_handler (void (*handler)(void *, int), void *arg);

/* Dealing with other potentially blocking syscalls... */

int rst_waitpid (int timeout, pid_t pid, int *statusp, int flags);

/*
 * Per-thread data --- these functions allow the user to reserve a block
 * of memory whose contents will be unique for each thread.
 *
 * An rst_per_thread_key designates a particular piece of per-thread
 * data.  The rst_make_per_thread routine arranges for each thread to
 * have a region of memory of size 'size' given to it, identified by the
 * key it returns.  A pointer to that data for the currently running thread
 * may be obtained by calling rst_get_per_thread with the key as the
 * argument.
 */

typedef long rst_per_thread_key;
rst_per_thread_key rst_make_per_thread (void (*destructor)(void*));
void *rst_get_per_thread (rst_per_thread_key);
void rst_set_per_thread (rst_per_thread_key, void *);

/* Miscellaneous */

int rst_count_threads();	/* Total number of live threads */
float rst_load_average();	/* Recent mean number of *running* threads */
void rst_set_thread_name(char *); /* Set thread's name, for debug display */

#endif
