text: /*
   * tclAsync.c --
   *
   *	This file provides low-level support needed to invoke signal handlers
   *	in a safe way. The code here doesn't actually handle signals, though.
   *	This code is based on proposals made by Mark Diekhans and Don Libes.
   *
   * Copyright (c) 1993 The Regents of the University of California.
   * Copyright (c) 1994 Sun Microsystems, Inc.
   *
   * See the file "license.terms" for information on usage and redistribution of
   * this file, and for a DISCLAIMER OF ALL WARRANTIES.
   */
  
  #include "tclInt.h"
  
  /* Forward declaration */
  struct ThreadSpecificData;
  
  /*
   * One of the following structures exists for each asynchronous handler:
   */
  
  typedef struct AsyncHandler {
      int ready;			/* Non-zero means this handler should be
  				 * invoked in the next call to
  				 * Tcl_AsyncInvoke. */
      struct AsyncHandler *nextPtr;
  				/* Next in list of all handlers for the
  				 * process. */
      Tcl_AsyncProc *proc;	/* Procedure to call when handler is
  				 * invoked. */
      ClientData clientData;	/* Value to pass to handler when it is
  				 * invoked. */
      struct ThreadSpecificData *originTsd;
  				/* Used in Tcl_AsyncMark to modify thread-
  				 * specific data from outside the thread it is
  				 * associated to. */
      Tcl_ThreadId originThrdId;	/* Origin thread where this token was created
  				 * and where it will be yielded. */
  } AsyncHandler;
  
  typedef struct ThreadSpecificData {
      /*
       * The variables below maintain a list of all existing handlers specific
       * to the calling thread.
       */
      AsyncHandler *firstHandler;	/* First handler defined for process, or NULL
  				 * if none. */
      AsyncHandler *lastHandler;	/* Last handler or NULL. */
      int asyncReady;		/* This is set to 1 whenever a handler becomes
  				 * ready and it is cleared to zero whenever
  				 * Tcl_AsyncInvoke is called. It can be
  				 * checked elsewhere in the application by
  				 * calling Tcl_AsyncReady to see if
  				 * Tcl_AsyncInvoke should be invoked. */
      int asyncActive;		/* Indicates whether Tcl_AsyncInvoke is
  				 * currently working. If so then we won't set
  				 * asyncReady again until Tcl_AsyncInvoke
  				 * returns. */
      Tcl_Mutex asyncMutex;	/* Thread-specific AsyncHandler linked-list
  				 * lock */
  } ThreadSpecificData;
  static Tcl_ThreadDataKey dataKey;
  
  /*
   *----------------------------------------------------------------------
   *
   * TclFinalizeAsync --
   *
   *	Finalizes the mutex in the thread local data structure for the async
   *	subsystem.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	Forgets knowledge of the mutex should it have been created.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclFinalizeAsync(void)
  {
      ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  
      if (tsdPtr->asyncMutex != NULL) {
  	Tcl_MutexFinalize(&tsdPtr->asyncMutex);
      }
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_AsyncCreate --
   *
   *	This procedure creates the data structures for an asynchronous
   *	handler, so that no memory has to be allocated when the handler is
   *	activated.
   *
   * Results:
   *	The return value is a token for the handler, which can be used to
   *	activate it later on.
   *
   * Side effects:
   *	Information about the handler is recorded.
   *
   *----------------------------------------------------------------------
   */
  
  Tcl_AsyncHandler
  Tcl_AsyncCreate(
      Tcl_AsyncProc *proc,	/* Procedure to call when handler is
  				 * invoked. */
      ClientData clientData)	/* Argument to pass to handler. */
  {
      AsyncHandler *asyncPtr;
      ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  
      asyncPtr = Tcl_Alloc(sizeof(AsyncHandler));
      asyncPtr->ready = 0;
      asyncPtr->nextPtr = NULL;
      asyncPtr->proc = proc;
      asyncPtr->clientData = clientData;
      asyncPtr->originTsd = tsdPtr;
      asyncPtr->originThrdId = Tcl_GetCurrentThread();
  
      Tcl_MutexLock(&tsdPtr->asyncMutex);
      if (tsdPtr->firstHandler == NULL) {
  	tsdPtr->firstHandler = asyncPtr;
      } else {
  	tsdPtr->lastHandler->nextPtr = asyncPtr;
      }
      tsdPtr->lastHandler = asyncPtr;
      Tcl_MutexUnlock(&tsdPtr->asyncMutex);
      return (Tcl_AsyncHandler) asyncPtr;
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_AsyncMark --
   *
   *	This procedure is called to request that an asynchronous handler be
   *	invoked as soon as possible. It's typically called from an interrupt
   *	handler, where it isn't safe to do anything that depends on or
   *	modifies application state.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	The handler gets marked for invocation later.
   *
   *----------------------------------------------------------------------
   */
  
  void
  Tcl_AsyncMark(
      Tcl_AsyncHandler async)		/* Token for handler. */
  {
      AsyncHandler *token = (AsyncHandler *) async;
  
      Tcl_MutexLock(&token->originTsd->asyncMutex);
      token->ready = 1;
      if (!token->originTsd->asyncActive) {
  	token->originTsd->asyncReady = 1;
  	Tcl_ThreadAlert(token->originThrdId);
      }
      Tcl_MutexUnlock(&token->originTsd->asyncMutex);
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_AsyncInvoke --
   *
   *	This procedure is called at a "safe" time at background level to
   *	invoke any active asynchronous handlers.
   *
   * Results:
   *	The return value is a normal Tcl result, which is intended to replace
   *	the code argument as the current completion code for interp.
   *
   * Side effects:
   *	Depends on the handlers that are active.
   *
   *----------------------------------------------------------------------
   */
  
  int
  Tcl_AsyncInvoke(
      Tcl_Interp *interp,		/* If invoked from Tcl_Eval just after
  				 * completing a command, points to
  				 * interpreter. Otherwise it is NULL. */
      int code)			/* If interp is non-NULL, this gives
  				 * completion code from command that just
  				 * completed. */
  {
      AsyncHandler *asyncPtr;
      ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  
      Tcl_MutexLock(&tsdPtr->asyncMutex);
  
      if (tsdPtr->asyncReady == 0) {
  	Tcl_MutexUnlock(&tsdPtr->asyncMutex);
  	return code;
      }
      tsdPtr->asyncReady = 0;
      tsdPtr->asyncActive = 1;
      if (interp == NULL) {
  	code = 0;
      }
  
      /*
       * Make one or more passes over the list of handlers, invoking at most one
       * handler in each pass. After invoking a handler, go back to the start of
       * the list again so that (a) if a new higher-priority handler gets marked
       * while executing a lower priority handler, we execute the higher-
       * priority handler next, and (b) if a handler gets deleted during the
       * execution of a handler, then the list structure may change so it isn't
       * safe to continue down the list anyway.
       */
  
      while (1) {
  	for (asyncPtr = tsdPtr->firstHandler; asyncPtr != NULL;
  		asyncPtr = asyncPtr->nextPtr) {
  	    if (asyncPtr->ready) {
  		break;
  	    }
  	}
  	if (asyncPtr == NULL) {
  	    break;
  	}
  	asyncPtr->ready = 0;
  	Tcl_MutexUnlock(&tsdPtr->asyncMutex);
  	code = asyncPtr->proc(asyncPtr->clientData, interp, code);
  	Tcl_MutexLock(&tsdPtr->asyncMutex);
      }
      tsdPtr->asyncActive = 0;
      Tcl_MutexUnlock(&tsdPtr->asyncMutex);
      return code;
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_AsyncDelete --
   *
   *	Frees up all the state for an asynchronous handler. The handler should
   *	never be used again.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	The state associated with the handler is deleted.
   *
   *	Failure to locate the handler in current thread private list
   *	of async handlers will result in panic; exception: the list
   *	is already empty (potential trouble?).
   *	Consequently, threads should create and delete handlers
   *	themselves.  I.e. a handler created by one should not be
   *	deleted by some other thread.
   *
   *----------------------------------------------------------------------
   */
  
  void
  Tcl_AsyncDelete(
      Tcl_AsyncHandler async)		/* Token for handler to delete. */
  {
      ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
      AsyncHandler *asyncPtr = (AsyncHandler *) async;
      AsyncHandler *prevPtr, *thisPtr;
  
      /*
       * Assure early handling of the constraint
       */
  
      if (asyncPtr->originThrdId != Tcl_GetCurrentThread()) {
  	Tcl_Panic("Tcl_AsyncDelete: async handler deleted by the wrong thread");
      }
  
      /*
       * If we come to this point when TSD's for the current
       * thread have already been garbage-collected, we are
       * in the _serious_ trouble. OTOH, we tolerate calling
       * with already cleaned-up handler list (should we?).
       */
  
      Tcl_MutexLock(&tsdPtr->asyncMutex);
      if (tsdPtr->firstHandler != NULL) {
  	prevPtr = thisPtr = tsdPtr->firstHandler;
  	while (thisPtr != NULL && thisPtr != asyncPtr) {
  	    prevPtr = thisPtr;
  	    thisPtr = thisPtr->nextPtr;
  	}
  	if (thisPtr == NULL) {
  	    Tcl_Panic("Tcl_AsyncDelete: cannot find async handler");
  	}
  	if (asyncPtr == tsdPtr->firstHandler) {
  	    tsdPtr->firstHandler = asyncPtr->nextPtr;
  	} else {
  	    prevPtr->nextPtr = asyncPtr->nextPtr;
  	}
  	if (asyncPtr == tsdPtr->lastHandler) {
  	    tsdPtr->lastHandler = prevPtr;
  	}
      }
      Tcl_MutexUnlock(&tsdPtr->asyncMutex);
      Tcl_Free(asyncPtr);
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_AsyncReady --
   *
   *	This procedure can be used to tell whether Tcl_AsyncInvoke needs to be
   *	called. This procedure is the external interface for checking the
   *	thread-specific asyncReady variable.
   *
   * Results:
   *	The return value is 1 whenever a handler is ready and is 0 when no
   *	handlers are ready.
   *
   * Side effects:
   *	None.
   *
   *----------------------------------------------------------------------
   */
  
  int
  Tcl_AsyncReady(void)
  {
      ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
      return tsdPtr->asyncReady;
  }
  
  int *
  TclGetAsyncReadyPtr(void)
  {
      ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
      return &(tsdPtr->asyncReady);
  }
  
  /*
   * Local Variables:
   * mode: c
   * c-basic-offset: 4
   * fill-column: 78
   * End:
   */
  