/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.lcs.mit.edu/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */

/* 
 * callbacks.c --
 *
 *	Support for Callbacks
 *
 * 	These functions allow the registration of callbacks
 *	that are run at various points during the server's execution.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/callbacks.c,v 1.5 2000/11/03 00:18:01 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

/*
 * This structure is used as nodes in a linked list of callbacks.
 */

typedef struct Callback {
    struct Callback *nextPtr;
    Ns_Callback     *proc;
    void            *arg;
} Callback;

/*
 * Local functions defined in this file
 */

static Ns_ThreadProc RunThread;
static void     RunCallbacks(Callback *firstPtr);
static void 	RunStart(Callback **firstPtrPtr, Ns_Thread *threadPtr);
static void 	RunWait(Callback **firstPtrPtr, Ns_Thread *threadPtr, Ns_Time *toPtr);
static void    *RegisterAt(Callback **firstPtrPtr, Ns_Callback *proc, void *arg);

/*
 * Static variables defined in this file
 */

static Callback *firstPreStartup;
static Callback *firstStartup;
static Callback *firstSignal;
static Callback *firstServerShutdown;
static Callback *firstShutdown;
static Callback *firstExit;
static Callback *firstReady;
static Ns_Mutex  lock;
static Ns_Cond   cond;
static int shutdownPending;
static Ns_Thread serverShutdownThread;

void *
Ns_RegisterAtReady(Ns_Callback *proc, void *arg)
{
    return RegisterAt(&firstReady, proc, arg);
}

void
NsRunAtReadyProcs(void)
{
    RunCallbacks(firstReady);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_RegisterAtStartup --
 *
 *	Register a callback to run at server startup 
 *
 * Results:
 *	None 
 *
 * Side effects:
 *	The callback will be registered 
 *
 *----------------------------------------------------------------------
 */

void *
Ns_RegisterAtStartup(Ns_Callback *proc, void *arg)
{
    return RegisterAt(&firstStartup, proc, arg);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_RegisterAtPreStartup --
 *
 *	Register a callback to run at pre-server startup 
 *
 * Results:
 *	None 
 *
 * Side effects:
 *	The callback will be registered 
 *
 *----------------------------------------------------------------------
 */

void *
Ns_RegisterAtPreStartup(Ns_Callback *proc, void *arg)
{
    return RegisterAt(&firstPreStartup, proc, arg);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_RegisterAtSignal --
 *
 *	Register a callback to run when a signal arrives 
 *
 * Results:
 *	None 
 *
 * Side effects:
 *	The callback will be registered
 *
 *----------------------------------------------------------------------
 */

void *
Ns_RegisterAtSignal(Ns_Callback * proc, void *arg)
{
    return RegisterAt(&firstSignal, proc, arg);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_RegisterAtServerShutdown --
 *
 *	Register a callback to run at server shutdown. This is
 *	identical to Ns_RegisterShutdown and only exists for
 *	historical reasons.
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	The callback will be registered 
 *
 *----------------------------------------------------------------------
 */

void *
Ns_RegisterAtServerShutdown(Ns_Callback *proc, void *arg)
{
    return RegisterAt(&firstServerShutdown, proc, arg);
}

void *
Ns_RegisterServerShutdown(char *ignored, Ns_Callback *proc, void *arg)
{
    return Ns_RegisterAtServerShutdown(proc, arg);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_RegisterAtShutdown --
 *
 *	Register a callback to run at server shutdown. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	The callback will be registered. 
 *
 *----------------------------------------------------------------------
 */

void *
Ns_RegisterAtShutdown(Ns_Callback *proc, void *arg)
{
    return RegisterAt(&firstShutdown, proc, arg);
}

void *
Ns_RegisterShutdown(Ns_Callback *proc, void *arg)
{
    return Ns_RegisterAtShutdown(proc, arg);
}



/*
 *----------------------------------------------------------------------
 *
 * Ns_RegisterAtExit --
 *
 *	Register a callback to be run at server exit. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	The callback will be registerd. 
 *
 *----------------------------------------------------------------------
 */

void *
Ns_RegisterAtExit(Ns_Callback * proc, void *arg)
{
    return RegisterAt(&firstExit, proc, arg);
}


/*
 *----------------------------------------------------------------------
 *
 * NsRunStartupProcs --
 *
 *	Run any callbacks registered for server startup. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Callbacks called back. 
 *
 *----------------------------------------------------------------------
 */

void
NsRunStartupProcs(void)
{
    RunCallbacks(firstStartup);
}


/*
 *----------------------------------------------------------------------
 *
 * NsRunPreStartupProcs --
 *
 *	Run any callbacks registered for pre-server startup. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Callbacks called back. 
 *
 *----------------------------------------------------------------------
 */

void
NsRunPreStartupProcs(void)
{
    RunCallbacks(firstPreStartup);
}


/*
 *----------------------------------------------------------------------
 *
 * NsRunSignalProcs --
 *
 *	Run any callbacks registered for when a signal arrives 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Callbacks called back. 
 *
 *----------------------------------------------------------------------
 */

void
NsRunSignalProcs(void)
{
    RunCallbacks(firstSignal);
}


/*
 *----------------------------------------------------------------------
 *
 * NsRunExitProcs --
 *
 *	Run any callbacks registered for server startup, then 
 *	shutdown, then exit. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Callbacks called back. 
 *
 *----------------------------------------------------------------------
 */

void
NsStartShutdownProcs(void)
{
    Ns_MutexLock(&lock);
    shutdownPending = 1;
    Ns_MutexUnlock(&lock);
    RunStart(&firstServerShutdown, &serverShutdownThread);
}
    
void
NsWaitShutdownProcs(Ns_Time *toPtr)
{
    Ns_Thread thread;

    RunWait(&firstServerShutdown, &serverShutdownThread, toPtr);
    RunStart(&firstShutdown, &thread);
    RunWait(&firstShutdown, &thread, toPtr);
}

void
NsRunAtExitProcs(void)
{
    RunCallbacks(firstExit);
}


/*
 *----------------------------------------------------------------------
 *
 * RegisterAt --
 *
 *	A generic function that registers callbacks for any event 
 *
 * Results:
 *	A pointer to the newly-allocated Callback structure 
 *
 * Side effects:
 *	A Callback struct will be alloacated and put in the linked list. 
 *
 *----------------------------------------------------------------------
 */

static void *
RegisterAt(Callback **firstPtrPtr, Ns_Callback *proc, void *arg)
{
    Callback       *cbPtr;
    static int first = 1;

    if (first) {
	Ns_MutexSetName2(&lock, "ns", "callbacks");
	first = 0;
    }

    cbPtr = ns_malloc(sizeof(Callback));
    cbPtr->proc = proc;
    cbPtr->arg = arg;

    Ns_MutexLock(&lock);
    if (shutdownPending) {
    	ns_free(cbPtr);
	cbPtr = NULL;
    } else {
	cbPtr->nextPtr = *firstPtrPtr;
	*firstPtrPtr = cbPtr;
    }
    Ns_MutexUnlock(&lock);

    return (void *) cbPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * RunCallbacks --
 *
 *	Run all callbacks in the passed-in linked list 
 *
 * Results:
 *	None 
 *
 * Side effects:
 *	Callbacks called back. 
 *
 *----------------------------------------------------------------------
 */

static void
RunCallbacks(Callback *cbPtr)
{
    while (cbPtr != NULL) {
        (*cbPtr->proc) (cbPtr->arg);
        cbPtr = cbPtr->nextPtr;
    }
}

static void
RunStart(Callback **firstPtrPtr, Ns_Thread *threadPtr)
{
    Ns_MutexLock(&lock);
    if (*firstPtrPtr != NULL) {
	Ns_ThreadCreate(RunThread, firstPtrPtr, 0, threadPtr);
    } else {
    	*threadPtr = NULL;
    }
    Ns_MutexUnlock(&lock);
}


static void
RunWait(Callback **firstPtrPtr, Ns_Thread *threadPtr, Ns_Time *toPtr)
{
    int status;

    status = NS_OK;
    Ns_MutexLock(&lock);
    while (status == NS_OK && *firstPtrPtr != NULL) {
	status = Ns_CondTimedWait(&cond, &lock, toPtr);
    }
    Ns_MutexUnlock(&lock);
    if (status != NS_OK) {
	Ns_Log(Warning, "callbacks: timeout waiting for shutdown procs");
    } else if (*threadPtr != NULL) {
	Ns_ThreadJoin(threadPtr, NULL);
    }
}


static void
RunThread(void *arg)
{
    Callback **firstPtrPtr = arg;
    Callback *firstPtr;

    Ns_ThreadSetName("-shutdown-");
    Ns_MutexLock(&lock);
    firstPtr = *firstPtrPtr;
    Ns_MutexUnlock(&lock);
    
    RunCallbacks(firstPtr);

    Ns_MutexLock(&lock);
    while (*firstPtrPtr != NULL) {
	firstPtr = *firstPtrPtr;
	*firstPtrPtr = firstPtr->nextPtr;
	ns_free(firstPtr);
    }
    Ns_CondSignal(&cond);
    Ns_MutexUnlock(&lock);
}


static void
AppendList(Tcl_DString *dsPtr, char *list, Callback *firstPtr)
{
    Callback *cbPtr;

    cbPtr = firstPtr;
    while (cbPtr != NULL) {
	Tcl_DStringStartSublist(dsPtr);
	Tcl_DStringAppendElement(dsPtr, list);
	Ns_GetProcInfo(dsPtr, (void *) cbPtr->proc, cbPtr->arg);
	Tcl_DStringEndSublist(dsPtr);
	cbPtr = cbPtr->nextPtr;
    }
}


void
NsGetCallbacks(Tcl_DString *dsPtr)
{
    Ns_MutexLock(&lock);
    AppendList(dsPtr, "prestartup", firstPreStartup);
    AppendList(dsPtr, "startup", firstStartup);
    AppendList(dsPtr, "signal", firstSignal);
    AppendList(dsPtr, "servershutdown", firstServerShutdown);
    AppendList(dsPtr, "shutdown", firstShutdown);
    AppendList(dsPtr, "exit", firstExit);
    Ns_MutexUnlock(&lock);
}
