/*
 * Copyright (c) 1996 Regents of the University of Michigan.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and that due credit is given
 * to the University of Michigan at Ann Arbor. The name of the University
 * may not be used to endorse or promote products derived from this
 * software without specific prior written permission. This software
 * is provided ``as is'' without express or implied warranty.
 */

/*
 * tsleep.c - allow a thread to sleep without putting the whole process
 * (e.g. pod under lwp) to sleep.  Contains platform-specific code to
 * allow this:
 *
 * Under non-preemptive threads packages like SunOS lwp, tsleep() adds
 * the thread to a list of sleepers.  The lwp_scheduler process takes
 * care of resuming suspended threads.
 *
 * Under a fully-preemptive threads package, like Solaris threads,
 * tsleep just calls sleep(), and there is no scheduler thread.  Life
 * is so much simpler...
 */

#include "portable.h"

#include <stdio.h>
#include <stdlib.h>
#include <ac/unistd.h>		/* get sleep() */

#include "slurp.h"
#include "globals.h"


#if defined( HAVE_LWP )

int
tsleep(
    time_t	interval
)
{
    thread_t	mylwp;
    tl_t	*t, *nt;
    time_t	now;


    if ( lwp_self( &mylwp ) < 0 ) {
	return -1;
    }
    time( &now );

    mon_enter( &sglob->tsl_mon );
    if ( sglob->tsl_list != NULL ) {
	for ( t = sglob->tsl_list; t != NULL; t = t->tl_next ) {
	    if ( SAMETHREAD( t->tl_tid, mylwp )) {
		/* We're already sleeping? */
		t->tl_wake = now + interval;
		mon_exit( &sglob->tsl_mon );
		lwp_suspend( mylwp );
		return 0;
	    }
	}
    }
    nt = (tl_t *) malloc( sizeof( tl_t ));

    nt->tl_next = sglob->tsl_list;
    nt->tl_wake = now + interval;
    nt->tl_tid = mylwp;
    sglob->tsl_list = nt;
    mon_exit( &sglob->tsl_mon );
    lwp_suspend( mylwp );
    return 0;
}

/*
 * The lwp_scheduler thread periodically checks to see if any threads
 * are due to be resumed.  If there are, it resumes them.  Otherwise,
 * it computes the lesser of ( 1 second ) or ( the minimum time until
 * a thread need to be resumed ) and puts itself to sleep for that amount
 * of time.
 */
void
lwp_scheduler(
    int	stackno
)
{
    time_t		now, min;
    struct timeval	interval;
    tl_t		*t;

    while ( !sglob->slurpd_shutdown ) {
	mon_enter( &sglob->tsl_mon );
	time( &now );
	min = 0L;
	if ( sglob->tsl_list != NULL ) {
	    for ( t = sglob->tsl_list; t != NULL; t = t->tl_next ) {
		if (( t->tl_wake  > 0L ) && ( t->tl_wake < now )) {
		    lwp_resume( t->tl_tid );
		    t->tl_wake = 0L;
		}
		if (( t->tl_wake > now ) && ( t->tl_wake < min )) {
		    min =  t->tl_wake;
		}
	    }
	}
	mon_exit( &sglob->tsl_mon );
	interval.tv_usec = 0L;
	if ( min == 0L ) {
	    interval.tv_sec = 1L;
	} else {
	    interval.tv_sec = min;
	}
	lwp_sleep( &interval );
    }
    mon_enter( &sglob->tsl_mon );
    for ( t = sglob->tsl_list; t != NULL; t = t->tl_next ) {
	lwp_resume( t->tl_tid );
    }
    mon_exit( &sglob->tsl_mon );
    free_stack( stackno );
}


/*
 * Create the lwp_scheduler thread.
 */
void
start_lwp_scheduler( void )
{
    thread_t	tid;
    stkalign_t	*stack;
    int		stackno;

    if (( stack = get_stack( &stackno )) == NULL ) {
	return;
    }
    lwp_create( &tid, lwp_scheduler, MINPRIO, 0, stack, 1, stackno );
    return;
}


#else /* !HAVE_LWP */

/*
 * Here we assume we have fully preemptive threads, and that sleep()
 * does the right thing.
 */
int
tsleep(
    time_t	interval
)
{
    sleep( interval );
    return 0;
}
#endif /* !HAVE_LWP */
