Milán Major

netscape-revival

1 branch
Code

nspr-/src/swthread.c

#ifdef SW_THREADS
#include "prmem.h"
#include "prlog.h"
#include "swkern.h"
#include "mdint.h"
#include <string.h>

PR_LOG_DEFINE(THREAD);

/* XXX fix up most of the ints-off code to use the per-thread monitor */
#define LOCK_THREAD(t) PR_CEnterMonitor(t)
#define UNLOCK_THREAD(t) PR_CExitMonitor(t)

/*
** Software threading package. Designed to run on top of a SINGLE native
** OS process.
*/
int32 _pr_unique_thread_id;

#ifdef DEBUG
int _pr_scanning_threads;
#endif

#define _CLAMP_PRIORITY(newPri)		      \
    if (newPri < 0) {			      \
	newPri = 0;			      \
    } else if (newPri >= PR_NUM_PRIORITIES) { \
	newPri = PR_NUM_PRIORITIES - 1;	      \
    }

PR_PUBLIC_API(PRThread *) PR_CreateThread(char *name, int priority, 
                                        size_t stackSize)
{
    PRThread *t;

    /* Before we make a new thread, free any old ones */
    _PR_FreeZombies();

    _CLAMP_PRIORITY(priority);

    t = (PRThread*) calloc(1, sizeof(PRThread));
    if (t) {
	t->id = ++_pr_unique_thread_id;
	PR_INIT_CLIST(&t->monitors);
	PR_INIT_CLIST(&t->runqLinks);
	PR_INIT_CLIST(&t->waitLinks);
	t->name = strdup(name);
	if (!t->name) {
	    free(t);
	    return 0;
	}
	t->priority = priority;

	/* Allocate stack if necessary */
	if (!_MD_NewStack(t, stackSize)) {
	    free(t->name);
	    free(t);
	    return 0;
	}
    }
    return t;
}

/*
** Attach a thread object to an existing thread (the current thread).
*/
PR_PUBLIC_API(PRThread *) PR_AttachThread(char *name, int priority, 
                                        PRThreadStack *stack)
{
    PRThread *t;

    /* Before we make a new thread, free any old ones */
    _PR_FreeZombies();

    _CLAMP_PRIORITY(priority);

    t = (PRThread*) calloc(1, sizeof(PRThread));
    if (t) {
	t->id = ++_pr_unique_thread_id;
	PR_INIT_CLIST(&t->monitors);
	PR_INIT_CLIST(&t->runqLinks);
	PR_INIT_CLIST(&t->waitLinks);
	t->name = strdup(name);
	if (!t->name) {
	    free(t);
	    return 0;
	}
	t->priority = priority;

#if defined(XP_PC) && !defined(_WIN32)
	/* Allocate stack if necessary */
	if (!_MD_NewStack(t, stack->stackSize)) {
	    free(t->name);
	    free(t);
	    return 0;
	}
#else
	t->stack = stack;
#endif
    }
    return t;
}

PR_PUBLIC_API(void) PR_DestroyThread(PRThread *thread)
{
    int is;

    is = _PR_IntsOff();

    PR_LOG(SCHED, out, ("About to destroy thread %s [%d]",
			thread->name, thread->state));
    if (thread->flags & _PR_SYSTEM) {
	_pr_system_active--;
	PR_ASSERT(_pr_system_active >= 0);
    } else {
	_pr_user_active--;
	PR_ASSERT(_pr_user_active >= 0);
    }
    if (_pr_user_active == 0) {
	/* Can't call libc with interrupts off! */
	_PR_IntsOn(is, 0);
	exit(0);
    }

    switch (thread->state) {
      case _PR_UNBORN:
      case _PR_ZOMBIE:
	break;

      case _PR_RUNNING:
	/*
	** Put current thread on zombie list if it's ready to go there.
	** This will get processed the next time we are called. This is
	** done to avoid freeing the stack we are executing on. Note also
	** that this is done with interrupts off and that idle, which
	** cleans up the zombie list also works on the zombieq with
	** interrupts off. Finally, this is done synchronously so we know
	** that we cannot be screwing up the list.
	*/
	thread->state = _PR_ZOMBIE;
	PR_APPEND_LINK(&thread->runqLinks, &_pr_zombieq);
	_PR_Schedule();
	PR_NOT_REACHED("Continuing after destroying running thread.");

      case _PR_RUNNABLE:
	PR_REMOVE_AND_INIT_LINK(&thread->runqLinks);
	if (PR_CLIST_IS_EMPTY(&_pr_runq[thread->priority])) {
	    _pr_runq_ready_mask &= ~(1L << thread->priority);
	}
	break;

      case _PR_SLEEPING:
	_PR_FixSleepQ(thread);
	PR_REMOVE_AND_INIT_LINK(&thread->runqLinks);
	break;

      case _PR_SUSPENDED:
	PR_REMOVE_AND_INIT_LINK(&thread->runqLinks);
	break;

      case _PR_COND_WAIT:
	PR_REMOVE_AND_INIT_LINK(&thread->runqLinks);
	PR_REMOVE_AND_INIT_LINK(&thread->waitLinks);
	break;

      case _PR_MON_WAIT:
	PR_REMOVE_AND_INIT_LINK(&thread->waitLinks);
	break;
    }
    _PR_IntsOn(is, 0);

    _PR_DestroyThread(thread);
}

/*
** Do the actual work of destroying a thread.
*/
void _PR_DestroyThread(PRThread *thread)
{
    PRMonitor *mon;
    PRPerThreadData **pptd, *ptd;

    /* Release all monitors that we have entered */
    while (thread->monitors.next != &thread->monitors) {
	mon = MONITOR_PTR(thread->monitors.next);
	PR_REMOVE_AND_INIT_LINK(&mon->links);
	mon->owner = 0;
	mon->count = 0;
	(void) _PR_MonNotify(mon, NULL);
    }
    PR_ASSERT(PR_CLIST_IS_EMPTY(&thread->monitors));
    PR_ASSERT(PR_CLIST_IS_EMPTY(&thread->runqLinks));
    PR_ASSERT(PR_CLIST_IS_EMPTY(&thread->waitLinks));

    /* Free up memory and we're gone... */
    _MD_FreeStack(thread);

    /* XXX: this assumes that ptd->priv is GC'able memory or static */
    pptd = &thread->ptd;
    while ((ptd = *pptd) != 0) {
	*pptd = ptd->next;
	free(ptd);
    }

    if (thread->name) {
	free(thread->name);
	thread->name = 0;
    }

    /* Now we can free up the thread object */
    free(thread);
}

/*
** Eliminate any zombies. This is done synchronously, but with interrupts
** off (basically we are using interrupts off to lock the zombieQ which
** can't really be locked). Before we disable interrupts we get the heap
** lock so that the free calls in _PR_DestroyThread won't lock
** up.
*/
void _PR_FreeZombies(void)
{
    int 		is;
    PRCList 	zombieq, *qp;
    PRThread 	*thread;

    if (_pr_heap_lock) {
	/*
	** Snatch a copy of the current zombieQ. This is necessary
	** because we can't call free with interrupts disabled. We have
	** to run down the list to copy it because of the way the list
	** linkage works.
	*/
	PR_INIT_CLIST(&zombieq);
	is = _PR_IntsOff();
	qp = _pr_zombieq.next;
	while (qp != &_pr_zombieq) {
	    thread = THREAD_PTR(qp);
	    qp = qp->next;
	    PR_REMOVE_LINK(&thread->runqLinks);
	    PR_APPEND_LINK(&thread->runqLinks, &zombieq);
	}
	_PR_IntsOn(is, 0);

	qp = zombieq.next;
	while (qp != &zombieq) {
	    thread = THREAD_PTR(qp);
	    qp = qp->next;
	    PR_REMOVE_AND_INIT_LINK(&thread->runqLinks);
	    PR_LOG(SCHED, warn, ("free-zombies: destroying thread %s",
				 thread->name));
	    _PR_DestroyThread(thread);
	}
    }
}

PR_PUBLIC_API(void) PR_Exit(void)
{
    PR_ASSERT(_pr_current_thread->state == _PR_RUNNING);
    PR_LOG_FLUSH();
    PR_DestroyThread(_pr_current_thread);
#if !defined(XP_PC) || defined(_WIN32)
    PR_NOT_REACHED("Continuing after exit.");
#endif
}

PR_PUBLIC_API(void) PR_Yield(void)
{
    PRThread *thread = _pr_current_thread;
    int is;
    int32 wantResched;

    is = _PR_IntsOff();
    wantResched = _pr_runq_ready_mask >> thread->priority;
#ifdef PR_NO_PREEMPT
    wantResched |= pr_want_resched;
#endif
    if (wantResched) {
	/* Give cpu to next thread in _pr_runq */
	if (thread->priority == 0)
		PR_LOG(THREAD, debug, ("Yield: on to runq at priority %d", thread->priority));
	else
		PR_LOG(THREAD, out, ("Yield: on to runq at priority %d", thread->priority));
	thread->state = _PR_RUNNABLE;
	PR_APPEND_LINK(&thread->runqLinks, &_pr_runq[thread->priority]);
	_pr_runq_ready_mask |= (1L << thread->priority);
	_PR_SwitchThread(thread);
    }
    _PR_IntsOn(is, 0);
}

/*
** Intermediate that invokes startup routine for thread. Knows enough to
** release scheduler lock and call exit if thread returns.
*/
void HopToad(void (*e)(void*,void*), void *o, void *a)
{
    PR_ASSERT(_pr_intsOff != 0);
    PR_LOG(SCHED, out, ("Starting [sp=%x]", &e));
    _PR_IntsOn(0, 0);
    (*e)(o, a);
    PR_Exit();
}

/*
** Used to avoid assembly language in certain setjmp implementations
*/
void HopToadNoArgs(void)
{
    PRThread *thread = _pr_current_thread;
    void (*e)(void*,void*) = thread->asyncCall;
    void *o = thread->asyncArg0;
    void *a = thread->asyncArg1;

    thread->asyncCall = 0;
    thread->asyncArg0 = 0;
    thread->asyncArg1 = 0;
    HopToad(e, o, a);
}

PR_PUBLIC_API(int) PR_Start(PRThread *thread, void (*e)(void*,void*), 
                           void *o, void *a)
{
    int rv, needYield;
    int is;

    needYield = rv = 0;
    is = _PR_IntsOff();
    if (thread->state != _PR_UNBORN) {
	rv = -1;
	goto done;
    }
    if (thread->flags & _PR_SYSTEM) {
	_pr_system_active++;
    } else {
	_pr_user_active++;
    }
    _MD_INIT_CONTEXT(thread, e, o, a);

    /* Put thread on _pr_runq */
    thread->state = _PR_RUNNABLE;
    PR_LOG(THREAD, out, ("Start: %s on to runq at priority %d",
			 thread->name, thread->priority));
    PR_APPEND_LINK(&thread->runqLinks, &_pr_runq[thread->priority]);
    _pr_runq_ready_mask |= (1L << thread->priority);
    if (thread->priority > _pr_current_thread->priority) {
	needYield = 1;
    }

  done:
    _PR_IntsOn(is, needYield);
    return rv;
}

#if 0
PR_PUBLIC_API(int) PR_AsyncCall(PRThread *thread, void (*f)(void*,void*), 
                               void *o, void *a)
{
    int rv = 0;

    LockSched();
    switch (thread->state) {
      case PR_UNBORN:
      case PR_ZOMBIE:
	rv = -1;
	break;

      case PR_RUNNING:
	UnlockSched();
	(*f)(o, a);
	goto done;

      default:
	thread->asyncCall = f;
	thread->asyncArg0 = o;
	thread->asyncArg1 = a;
	break;
    }
    UnlockSched();

  done:
    return rv;
}

PR_PUBLIC_API(int) PR_PendingException(void)
{
    PRThread *thread = _pr_current_thread;
    if (thread->asyncCall) {
	return 1;
    } else {
	return 0;
    }
}
#endif

PR_PUBLIC_API(int) PR_Suspend(PRThread *thread)
{
    int rv = 0;
    int is;

    is = _PR_IntsOff();
    switch (thread->state) {
      case _PR_UNBORN:
      case _PR_ZOMBIE:
      case _PR_SUSPENDED:
	rv = -1;
	break;

      case _PR_RUNNING:
	thread->state = _PR_SUSPENDED;
	PR_APPEND_LINK(&thread->runqLinks, &_pr_suspendq);
	_PR_SwitchThread(thread);
	break;

      case _PR_RUNNABLE:
	thread->state = _PR_SUSPENDED;
	PR_REMOVE_LINK(&thread->runqLinks);
	if (PR_CLIST_IS_EMPTY(&_pr_runq[thread->priority])) {
	    _pr_runq_ready_mask &= ~(1L << thread->priority);
	}
	PR_APPEND_LINK(&thread->runqLinks, &_pr_suspendq);
	break;

      case _PR_MON_WAIT:
      case _PR_COND_WAIT:
      case _PR_SLEEPING:
	thread->flags |= _PR_SUSPENDING;
	break;
    }
    _PR_IntsOn(is, 0);
    return rv;
}

PR_PUBLIC_API(int) PR_Resume(PRThread *thread)
{
    int rv = 0;
    int needYield = 0;
    int is;

    is = _PR_IntsOff();
    switch (thread->state) {
      case _PR_UNBORN:
      case _PR_ZOMBIE:
      case _PR_RUNNING:
      case _PR_RUNNABLE:
	rv = -1;
	break;

      case _PR_SUSPENDED:
	PR_LOG(THREAD, out, ("Resume: %s on to runq at priority %d",
			     thread->name, thread->priority));
	thread->state = _PR_RUNNABLE;
	PR_REMOVE_LINK(&thread->runqLinks);
	PR_APPEND_LINK(&thread->runqLinks, &_pr_runq[thread->priority]);
	_pr_runq_ready_mask |= (1L << thread->priority);
	if (thread->priority >= _pr_current_thread->priority) {
	    needYield = 1;
	}
	break;

      case _PR_MON_WAIT:
	if (thread->flags & _PR_SUSPENDING) {
	    PRMonitor *mon;

	    /* Clear suspending flag because we aren't trying to anymore */
	    thread->flags &= ~_PR_SUSPENDING;

	    /* If monitor is idle, give it to the thread */
	    PR_ASSERT(thread->monitor != 0);
	    PR_ASSERT(thread->monitorEntryCount > 0);
	    mon = thread->monitor;
	    if (mon->owner == 0) {
		PR_ASSERT(mon->count == 0);
		needYield = _PR_MonNotify(mon, NULL);
	    }
	} else {
	    rv = -1;
	}
	break;

      default:
	if (thread->flags & _PR_SUSPENDING) {
	    /* Pretend it didn't happen */
	    thread->flags &= ~_PR_SUSPENDING;
	} else {
	    rv = -1;
	}
	break;
    }
    _PR_IntsOn(is, needYield);
    return rv;
}

PR_PUBLIC_API(int) PR_GetThreadPriority(PRThread *thread)
{
    return thread->priority;
}

int
_PR_SetThreadPriority(PRThread* thread, int newPri)
{
    /* returns whether the thread needs to be rescheduled */
    int needYield = 0;
    PR_ASSERT(_pr_intsOff != 0);

    PR_LOG(THREAD, out, ("SetThreadPriority: %s set to %d",
			 thread->name, newPri));
    if (newPri != thread->priority) {
	int oldPri = thread->priority;
	thread->priority = newPri;
	switch (thread->state) {
	  case _PR_RUNNING:
	    /* Change my priority */
	    if (_pr_runq_ready_mask >> (newPri + 1)) {
		/* Somebody else now has a higher priority */
		needYield = 1;
	    }
	    break;

	  case _PR_RUNNABLE:
	    /* Move to different _pr_runq */
	    PR_LOG(THREAD, out,
		   ("SetThreadPriority: %s on to runq at priority %d",
		    thread->name, newPri));
	    PR_REMOVE_LINK(&thread->runqLinks);
	    if (PR_CLIST_IS_EMPTY(&_pr_runq[oldPri])) {
		_pr_runq_ready_mask &= ~(1L << oldPri);
	    }
	    PR_APPEND_LINK(&thread->runqLinks, &_pr_runq[newPri]);
	    _pr_runq_ready_mask |= (1L << newPri);
	    if (newPri > _pr_current_thread->priority) {
		needYield = 1;
	    }
	    break;
	}
    }
    return needYield;
}

PR_PUBLIC_API(void)
PR_SetThreadPriority(PRThread *thread, int newPri)
{
    int needYield;
    int is;

    _CLAMP_PRIORITY(newPri);

    is = _PR_IntsOff();
    needYield = _PR_SetThreadPriority(thread, newPri);
    _PR_IntsOn(is, needYield);
}

PR_PUBLIC_API(int) PR_ActiveCount(void)
{
    return _pr_user_active + _pr_system_active;
}

PR_PUBLIC_API(int) PR_EnumerateThreads(PREnumerator func, void *arg)
{
    int i, j, rv;
    PRCList *qp;
    int is;

    is = _PR_IntsOff();
    PR_BEGIN_SCANNING_THREADS();
    i = 0;
    rv = (*func)(_pr_current_thread, i, arg);
    if (rv == 0) goto done;
    i++;

    for (j = 0; j < 32; j++) {
	qp = _pr_runq[j].next;
	while (qp != &_pr_runq[j]) {
	    rv = (*func)(THREAD_PTR(qp), i, arg);
	    if (rv == 0) goto done;
	    i++;
	    qp = qp->next;
	}
    }
    qp = _pr_sleepq.next;
    while (qp != &_pr_sleepq) {
	rv = (*func)(THREAD_PTR(qp), i, arg);
	if (rv == 0) goto done;
	i++;
	qp = qp->next;
    }
    qp = _pr_suspendq.next;
    while (qp != &_pr_suspendq) {
	rv = (*func)(THREAD_PTR(qp), i, arg);
	if (rv == 0) goto done;
	i++;
	qp = qp->next;
    }
    qp = _pr_monitorq.next;
    while (qp != &_pr_monitorq) {
	rv = (*func)(THREAD_PTR(qp), i, arg);
	if (rv == 0) goto done;
	i++;
	qp = qp->next;
    }

  done:
    PR_END_SCANNING_THREADS();
    _PR_IntsOn(is, 0);
    return i;
}

/*
** Sleep until sleep expires. However, if an exception is pending don't
** sleep.
*/
PR_PUBLIC_API(void) PR_Sleep(int64 sleep)
{
    PRThread *thread = _pr_current_thread;
    int is;

    is = _PR_IntsOff();
    if (!thread->pendingException) {
	_PR_PutOnSleepQ(thread, sleep);
	thread->state = _PR_SLEEPING;
	_PR_SwitchThread(thread);
    }
    _PR_IntsOn(is, 0);
}

/* XXX if priv == 0 we should remove the ptd */
PR_PUBLIC_API(int) PR_SetThreadPrivate(PRThread *t, int32 id, void GCPTR *priv)
{
    PRPerThreadData **pptd = &t->ptd;
    PRPerThreadData *ptd, *newptd;
    int is, rv = 0;
    int needmalloc = 1;

    /* Assume that we will need to create a new per-thread-data structure.
       We do this because we can't invoke libc with interrupts locked */
    newptd = malloc(sizeof(PRPerThreadData));

    is = _PR_IntsOff();
    while ((ptd = *pptd) != 0) {
	if (ptd->id == id) {
	    ptd->priv = priv;
	    rv = 1;
	    needmalloc = 0;
	    goto done;
	}
	pptd = &ptd->next;
    }

    if (newptd) {
	newptd->id = id;
	newptd->priv = priv;
	newptd->next = 0;
	*pptd = newptd;
	rv = 1;
    }

  done:
    _PR_IntsOn(is, 0);
    if (!needmalloc && newptd) {
	free(newptd);
    }
    return rv;
}

PR_PUBLIC_API(void GCPTR *) PR_GetThreadPrivate(PRThread *t, int32 id)
{
    PRPerThreadData *ptd;
    void *priv = 0;
    int is;

    is = _PR_IntsOff();
    ptd = t->ptd;
    while (ptd) {
	if (ptd->id == id) {
	    priv = ptd->priv;
	    break;
	}
	ptd = ptd->next;
    }
    _PR_IntsOn(is, 0);
    return priv;
}


static PRMonitor *_pr_threadKeyMonitor;
static int32 _pr_threadKeyNumber;

void _PR_InitPrivateIDs(void)
{
    _pr_threadKeyMonitor = PR_NewMonitor(0);
    _pr_threadKeyNumber = 0;
}

PR_PUBLIC_API(int32) PR_NewThreadPrivateID(void)
{
    int32 ret;
    PR_EnterMonitor(_pr_threadKeyMonitor);
    ret = _pr_threadKeyNumber++;
    PR_ExitMonitor(_pr_threadKeyMonitor);
    return ret;
}

PR_PUBLIC_API(PRThread *) PR_CurrentThread(void)
{
    return _pr_current_thread;
}

PR_PUBLIC_API(void) PR_SetError(int oserror)
{
    PRThread *thread = _pr_current_thread;
    thread->errcode = oserror;
}

PR_PUBLIC_API(int) PR_GetError(void)
{
    PRThread *thread = _pr_current_thread;
    return thread->errcode;
}

PR_PUBLIC_API(char*) PR_GetErrorString(void)
{
    PRThread* thread;
    char* str;
    int is;

    is = _PR_IntsOff();
    thread = _pr_current_thread;
    str = thread->errstr;
    thread->errstr = NULL;
    _PR_IntsOn(is, 0);
    return (str ? str : "");
}

PR_PUBLIC_API(char *) PR_GetThreadName(PRThread *t)
{
    return t->name;
}

PR_PUBLIC_API(int32) PR_GetCurrentThreadID(void)
{
    PRThread *thread = _pr_current_thread;
    return thread->id;
}

PR_PUBLIC_API(int) PR_PendingException(PRThread *thread)
{
    return thread->pendingException;
}

PR_PUBLIC_API(void) PR_SetPendingException(PRThread *thread)
{
    int is;
    int needYield = 0;

    is = _PR_IntsOff();
    if (thread->pendingException++ == 0) {
	switch (thread->state) {
	  case _PR_COND_WAIT: {
	    /* Kick the thread out of COND_WAIT state */
	    PRMonitor* mon = thread->monitor;
	    PR_ASSERT(mon != 0);
	    _PR_NotifyOneThread(mon, thread);
	    if (mon->owner == 0) {
		if (_PR_MonNotify(mon, NULL)) {
		    needYield = 1;
		}
	    }
	    break;
	  }
	  case _PR_SLEEPING:
	    _PR_FixSleepQ(thread);
	    PR_REMOVE_LINK(&thread->runqLinks);

	    /* Put it back on the runQ */
	    PR_APPEND_LINK(&thread->runqLinks, &_pr_runq[thread->priority]);
	    _pr_runq_ready_mask |= (1L << thread->priority);
	    thread->state = _PR_RUNNABLE;
	    if (thread->priority >= _pr_current_thread->priority) {
		needYield = 1;
	    }
	    break;
	}
    }
    _PR_IntsOn(is, needYield);
}

PR_PUBLIC_API(void) PR_ClearPendingException(PRThread *thread)
{
    thread->pendingException--;
}

#endif /* SW_THREADS */