summaryrefslogtreecommitdiffstats
path: root/tcl8.6/generic/tclTimer.c
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2017-10-17 19:52:11 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2017-10-17 19:52:11 (GMT)
commitda0aaf36f00f649cd910b1bcb972b4ad476cd5fd (patch)
tree3d5215355c39587ed375451b86b7f9962f739a5f /tcl8.6/generic/tclTimer.c
parent9bfb1e415c87790341c6a3520b081292fcdb058b (diff)
parentc8bc058c566bbcbcd23e732197d64fecc739008f (diff)
downloadblt-da0aaf36f00f649cd910b1bcb972b4ad476cd5fd.zip
blt-da0aaf36f00f649cd910b1bcb972b4ad476cd5fd.tar.gz
blt-da0aaf36f00f649cd910b1bcb972b4ad476cd5fd.tar.bz2
Merge commit 'c8bc058c566bbcbcd23e732197d64fecc739008f' as 'tcl8.6'
Diffstat (limited to 'tcl8.6/generic/tclTimer.c')
-rw-r--r--tcl8.6/generic/tclTimer.c1293
1 files changed, 1293 insertions, 0 deletions
diff --git a/tcl8.6/generic/tclTimer.c b/tcl8.6/generic/tclTimer.c
new file mode 100644
index 0000000..c10986a
--- /dev/null
+++ b/tcl8.6/generic/tclTimer.c
@@ -0,0 +1,1293 @@
+/*
+ * tclTimer.c --
+ *
+ * This file provides timer event management facilities for Tcl,
+ * including the "after" command.
+ *
+ * Copyright (c) 1997 by 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"
+
+/*
+ * For each timer callback that's pending there is one record of the following
+ * type. The normal handlers (created by Tcl_CreateTimerHandler) are chained
+ * together in a list sorted by time (earliest event first).
+ */
+
+typedef struct TimerHandler {
+ Tcl_Time time; /* When timer is to fire. */
+ Tcl_TimerProc *proc; /* Function to call. */
+ ClientData clientData; /* Argument to pass to proc. */
+ Tcl_TimerToken token; /* Identifies handler so it can be deleted. */
+ struct TimerHandler *nextPtr;
+ /* Next event in queue, or NULL for end of
+ * queue. */
+} TimerHandler;
+
+/*
+ * The data structure below is used by the "after" command to remember the
+ * command to be executed later. All of the pending "after" commands for an
+ * interpreter are linked together in a list.
+ */
+
+typedef struct AfterInfo {
+ struct AfterAssocData *assocPtr;
+ /* Pointer to the "tclAfter" assocData for the
+ * interp in which command will be
+ * executed. */
+ Tcl_Obj *commandPtr; /* Command to execute. */
+ int id; /* Integer identifier for command; used to
+ * cancel it. */
+ Tcl_TimerToken token; /* Used to cancel the "after" command. NULL
+ * means that the command is run as an idle
+ * handler rather than as a timer handler.
+ * NULL means this is an "after idle" handler
+ * rather than a timer handler. */
+ struct AfterInfo *nextPtr; /* Next in list of all "after" commands for
+ * this interpreter. */
+} AfterInfo;
+
+/*
+ * One of the following structures is associated with each interpreter for
+ * which an "after" command has ever been invoked. A pointer to this structure
+ * is stored in the AssocData for the "tclAfter" key.
+ */
+
+typedef struct AfterAssocData {
+ Tcl_Interp *interp; /* The interpreter for which this data is
+ * registered. */
+ AfterInfo *firstAfterPtr; /* First in list of all "after" commands still
+ * pending for this interpreter, or NULL if
+ * none. */
+} AfterAssocData;
+
+/*
+ * There is one of the following structures for each of the handlers declared
+ * in a call to Tcl_DoWhenIdle. All of the currently-active handlers are
+ * linked together into a list.
+ */
+
+typedef struct IdleHandler {
+ Tcl_IdleProc *proc; /* Function to call. */
+ ClientData clientData; /* Value to pass to proc. */
+ int generation; /* Used to distinguish older handlers from
+ * recently-created ones. */
+ struct IdleHandler *nextPtr;/* Next in list of active handlers. */
+} IdleHandler;
+
+/*
+ * The timer and idle queues are per-thread because they are associated with
+ * the notifier, which is also per-thread.
+ *
+ * All static variables used in this file are collected into a single instance
+ * of the following structure. For multi-threaded implementations, there is
+ * one instance of this structure for each thread.
+ *
+ * Notice that different structures with the same name appear in other files.
+ * The structure defined below is used in this file only.
+ */
+
+typedef struct ThreadSpecificData {
+ TimerHandler *firstTimerHandlerPtr; /* First event in queue. */
+ int lastTimerId; /* Timer identifier of most recently created
+ * timer. */
+ int timerPending; /* 1 if a timer event is in the queue. */
+ IdleHandler *idleList; /* First in list of all idle handlers. */
+ IdleHandler *lastIdlePtr; /* Last in list (or NULL for empty list). */
+ int idleGeneration; /* Used to fill in the "generation" fields of
+ * IdleHandler structures. Increments each
+ * time Tcl_DoOneEvent starts calling idle
+ * handlers, so that all old handlers can be
+ * called without calling any of the new ones
+ * created by old ones. */
+ int afterId; /* For unique identifiers of after events. */
+} ThreadSpecificData;
+
+static Tcl_ThreadDataKey dataKey;
+
+/*
+ * Helper macros for working with times. TCL_TIME_BEFORE encodes how to write
+ * the ordering relation on (normalized) times, and TCL_TIME_DIFF_MS computes
+ * the number of milliseconds difference between two times. Both macros use
+ * both of their arguments multiple times, so make sure they are cheap and
+ * side-effect free. The "prototypes" for these macros are:
+ *
+ * static int TCL_TIME_BEFORE(Tcl_Time t1, Tcl_Time t2);
+ * static long TCL_TIME_DIFF_MS(Tcl_Time t1, Tcl_Time t2);
+ */
+
+#define TCL_TIME_BEFORE(t1, t2) \
+ (((t1).sec<(t2).sec) || ((t1).sec==(t2).sec && (t1).usec<(t2).usec))
+
+#define TCL_TIME_DIFF_MS(t1, t2) \
+ (1000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \
+ ((long)(t1).usec - (long)(t2).usec)/1000)
+
+#define TCL_TIME_DIFF_MS_CEILING(t1, t2) \
+ (1000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \
+ ((long)(t1).usec - (long)(t2).usec + 999)/1000)
+
+/*
+ * Sleeps under that number of milliseconds don't get double-checked
+ * and are done in exactly one Tcl_Sleep(). This to limit gettimeofday()s.
+ */
+
+#define SLEEP_OFFLOAD_GETTIMEOFDAY 20
+
+/*
+ * The maximum number of milliseconds for each Tcl_Sleep call in AfterDelay.
+ * This is used to limit the maximum lag between interp limit and script
+ * cancellation checks.
+ */
+
+#define TCL_TIME_MAXIMUM_SLICE 500
+
+/*
+ * Prototypes for functions referenced only in this file:
+ */
+
+static void AfterCleanupProc(ClientData clientData,
+ Tcl_Interp *interp);
+static int AfterDelay(Tcl_Interp *interp, Tcl_WideInt ms);
+static void AfterProc(ClientData clientData);
+static void FreeAfterPtr(AfterInfo *afterPtr);
+static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr,
+ Tcl_Obj *commandPtr);
+static ThreadSpecificData *InitTimer(void);
+static void TimerExitProc(ClientData clientData);
+static int TimerHandlerEventProc(Tcl_Event *evPtr, int flags);
+static void TimerCheckProc(ClientData clientData, int flags);
+static void TimerSetupProc(ClientData clientData, int flags);
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * InitTimer --
+ *
+ * This function initializes the timer module.
+ *
+ * Results:
+ * A pointer to the thread specific data.
+ *
+ * Side effects:
+ * Registers the idle and timer event sources.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static ThreadSpecificData *
+InitTimer(void)
+{
+ ThreadSpecificData *tsdPtr = TclThreadDataKeyGet(&dataKey);
+
+ if (tsdPtr == NULL) {
+ tsdPtr = TCL_TSD_INIT(&dataKey);
+ Tcl_CreateEventSource(TimerSetupProc, TimerCheckProc, NULL);
+ Tcl_CreateThreadExitHandler(TimerExitProc, NULL);
+ }
+ return tsdPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TimerExitProc --
+ *
+ * This function is call at exit or unload time to remove the timer and
+ * idle event sources.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Removes the timer and idle event sources and remaining events.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TimerExitProc(
+ ClientData clientData) /* Not used. */
+{
+ ThreadSpecificData *tsdPtr = TclThreadDataKeyGet(&dataKey);
+
+ Tcl_DeleteEventSource(TimerSetupProc, TimerCheckProc, NULL);
+ if (tsdPtr != NULL) {
+ register TimerHandler *timerHandlerPtr;
+
+ timerHandlerPtr = tsdPtr->firstTimerHandlerPtr;
+ while (timerHandlerPtr != NULL) {
+ tsdPtr->firstTimerHandlerPtr = timerHandlerPtr->nextPtr;
+ ckfree(timerHandlerPtr);
+ timerHandlerPtr = tsdPtr->firstTimerHandlerPtr;
+ }
+ }
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * Tcl_CreateTimerHandler --
+ *
+ * Arrange for a given function to be invoked at a particular time in the
+ * future.
+ *
+ * Results:
+ * The return value is a token for the timer event, which may be used to
+ * delete the event before it fires.
+ *
+ * Side effects:
+ * When milliseconds have elapsed, proc will be invoked exactly once.
+ *
+ *--------------------------------------------------------------
+ */
+
+Tcl_TimerToken
+Tcl_CreateTimerHandler(
+ int milliseconds, /* How many milliseconds to wait before
+ * invoking proc. */
+ Tcl_TimerProc *proc, /* Function to invoke. */
+ ClientData clientData) /* Arbitrary data to pass to proc. */
+{
+ Tcl_Time time;
+
+ /*
+ * Compute when the event should fire.
+ */
+
+ Tcl_GetTime(&time);
+ time.sec += milliseconds/1000;
+ time.usec += (milliseconds%1000)*1000;
+ if (time.usec >= 1000000) {
+ time.usec -= 1000000;
+ time.sec += 1;
+ }
+ return TclCreateAbsoluteTimerHandler(&time, proc, clientData);
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TclCreateAbsoluteTimerHandler --
+ *
+ * Arrange for a given function to be invoked at a particular time in the
+ * future.
+ *
+ * Results:
+ * The return value is a token for the timer event, which may be used to
+ * delete the event before it fires.
+ *
+ * Side effects:
+ * When the time in timePtr has been reached, proc will be invoked
+ * exactly once.
+ *
+ *--------------------------------------------------------------
+ */
+
+Tcl_TimerToken
+TclCreateAbsoluteTimerHandler(
+ Tcl_Time *timePtr,
+ Tcl_TimerProc *proc,
+ ClientData clientData)
+{
+ register TimerHandler *timerHandlerPtr, *tPtr2, *prevPtr;
+ ThreadSpecificData *tsdPtr = InitTimer();
+
+ timerHandlerPtr = ckalloc(sizeof(TimerHandler));
+
+ /*
+ * Fill in fields for the event.
+ */
+
+ memcpy(&timerHandlerPtr->time, timePtr, sizeof(Tcl_Time));
+ timerHandlerPtr->proc = proc;
+ timerHandlerPtr->clientData = clientData;
+ tsdPtr->lastTimerId++;
+ timerHandlerPtr->token = (Tcl_TimerToken) INT2PTR(tsdPtr->lastTimerId);
+
+ /*
+ * Add the event to the queue in the correct position
+ * (ordered by event firing time).
+ */
+
+ for (tPtr2 = tsdPtr->firstTimerHandlerPtr, prevPtr = NULL; tPtr2 != NULL;
+ prevPtr = tPtr2, tPtr2 = tPtr2->nextPtr) {
+ if (TCL_TIME_BEFORE(timerHandlerPtr->time, tPtr2->time)) {
+ break;
+ }
+ }
+ timerHandlerPtr->nextPtr = tPtr2;
+ if (prevPtr == NULL) {
+ tsdPtr->firstTimerHandlerPtr = timerHandlerPtr;
+ } else {
+ prevPtr->nextPtr = timerHandlerPtr;
+ }
+
+ TimerSetupProc(NULL, TCL_ALL_EVENTS);
+
+ return timerHandlerPtr->token;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * Tcl_DeleteTimerHandler --
+ *
+ * Delete a previously-registered timer handler.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Destroy the timer callback identified by TimerToken, so that its
+ * associated function will not be called. If the callback has already
+ * fired, or if the given token doesn't exist, then nothing happens.
+ *
+ *--------------------------------------------------------------
+ */
+
+void
+Tcl_DeleteTimerHandler(
+ Tcl_TimerToken token) /* Result previously returned by
+ * Tcl_DeleteTimerHandler. */
+{
+ register TimerHandler *timerHandlerPtr, *prevPtr;
+ ThreadSpecificData *tsdPtr = InitTimer();
+
+ if (token == NULL) {
+ return;
+ }
+
+ for (timerHandlerPtr = tsdPtr->firstTimerHandlerPtr, prevPtr = NULL;
+ timerHandlerPtr != NULL; prevPtr = timerHandlerPtr,
+ timerHandlerPtr = timerHandlerPtr->nextPtr) {
+ if (timerHandlerPtr->token != token) {
+ continue;
+ }
+ if (prevPtr == NULL) {
+ tsdPtr->firstTimerHandlerPtr = timerHandlerPtr->nextPtr;
+ } else {
+ prevPtr->nextPtr = timerHandlerPtr->nextPtr;
+ }
+ ckfree(timerHandlerPtr);
+ return;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TimerSetupProc --
+ *
+ * This function is called by Tcl_DoOneEvent to setup the timer event
+ * source for before blocking. This routine checks both the idle and
+ * after timer lists.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May update the maximum notifier block time.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TimerSetupProc(
+ ClientData data, /* Not used. */
+ int flags) /* Event flags as passed to Tcl_DoOneEvent. */
+{
+ Tcl_Time blockTime;
+ ThreadSpecificData *tsdPtr = InitTimer();
+
+ if (((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList)
+ || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)) {
+ /*
+ * There is an idle handler or a pending timer event, so just poll.
+ */
+
+ blockTime.sec = 0;
+ blockTime.usec = 0;
+ } else if ((flags & TCL_TIMER_EVENTS) && tsdPtr->firstTimerHandlerPtr) {
+ /*
+ * Compute the timeout for the next timer on the list.
+ */
+
+ Tcl_GetTime(&blockTime);
+ blockTime.sec = tsdPtr->firstTimerHandlerPtr->time.sec - blockTime.sec;
+ blockTime.usec = tsdPtr->firstTimerHandlerPtr->time.usec -
+ blockTime.usec;
+ if (blockTime.usec < 0) {
+ blockTime.sec -= 1;
+ blockTime.usec += 1000000;
+ }
+ if (blockTime.sec < 0) {
+ blockTime.sec = 0;
+ blockTime.usec = 0;
+ }
+ } else {
+ return;
+ }
+
+ Tcl_SetMaxBlockTime(&blockTime);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TimerCheckProc --
+ *
+ * This function is called by Tcl_DoOneEvent to check the timer event
+ * source for events. This routine checks both the idle and after timer
+ * lists.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May queue an event and update the maximum notifier block time.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TimerCheckProc(
+ ClientData data, /* Not used. */
+ int flags) /* Event flags as passed to Tcl_DoOneEvent. */
+{
+ Tcl_Event *timerEvPtr;
+ Tcl_Time blockTime;
+ ThreadSpecificData *tsdPtr = InitTimer();
+
+ if ((flags & TCL_TIMER_EVENTS) && tsdPtr->firstTimerHandlerPtr) {
+ /*
+ * Compute the timeout for the next timer on the list.
+ */
+
+ Tcl_GetTime(&blockTime);
+ blockTime.sec = tsdPtr->firstTimerHandlerPtr->time.sec - blockTime.sec;
+ blockTime.usec = tsdPtr->firstTimerHandlerPtr->time.usec -
+ blockTime.usec;
+ if (blockTime.usec < 0) {
+ blockTime.sec -= 1;
+ blockTime.usec += 1000000;
+ }
+ if (blockTime.sec < 0) {
+ blockTime.sec = 0;
+ blockTime.usec = 0;
+ }
+
+ /*
+ * If the first timer has expired, stick an event on the queue.
+ */
+
+ if (blockTime.sec == 0 && blockTime.usec == 0 &&
+ !tsdPtr->timerPending) {
+ tsdPtr->timerPending = 1;
+ timerEvPtr = ckalloc(sizeof(Tcl_Event));
+ timerEvPtr->proc = TimerHandlerEventProc;
+ Tcl_QueueEvent(timerEvPtr, TCL_QUEUE_TAIL);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TimerHandlerEventProc --
+ *
+ * This function is called by Tcl_ServiceEvent when a timer event reaches
+ * the front of the event queue. This function handles the event by
+ * invoking the callbacks for all timers that are ready.
+ *
+ * Results:
+ * Returns 1 if the event was handled, meaning it should be removed from
+ * the queue. Returns 0 if the event was not handled, meaning it should
+ * stay on the queue. The only time the event isn't handled is if the
+ * TCL_TIMER_EVENTS flag bit isn't set.
+ *
+ * Side effects:
+ * Whatever the timer handler callback functions do.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TimerHandlerEventProc(
+ Tcl_Event *evPtr, /* Event to service. */
+ int flags) /* Flags that indicate what events to handle,
+ * such as TCL_FILE_EVENTS. */
+{
+ TimerHandler *timerHandlerPtr, **nextPtrPtr;
+ Tcl_Time time;
+ int currentTimerId;
+ ThreadSpecificData *tsdPtr = InitTimer();
+
+ /*
+ * Do nothing if timers aren't enabled. This leaves the event on the
+ * queue, so we will get to it as soon as ServiceEvents() is called with
+ * timers enabled.
+ */
+
+ if (!(flags & TCL_TIMER_EVENTS)) {
+ return 0;
+ }
+
+ /*
+ * The code below is trickier than it may look, for the following reasons:
+ *
+ * 1. New handlers can get added to the list while the current one is
+ * being processed. If new ones get added, we don't want to process
+ * them during this pass through the list to avoid starving other event
+ * sources. This is implemented using the token number in the handler:
+ * new handlers will have a newer token than any of the ones currently
+ * on the list.
+ * 2. The handler can call Tcl_DoOneEvent, so we have to remove the
+ * handler from the list before calling it. Otherwise an infinite loop
+ * could result.
+ * 3. Tcl_DeleteTimerHandler can be called to remove an element from the
+ * list while a handler is executing, so the list could change
+ * structure during the call.
+ * 4. Because we only fetch the current time before entering the loop, the
+ * only way a new timer will even be considered runnable is if its
+ * expiration time is within the same millisecond as the current time.
+ * This is fairly likely on Windows, since it has a course granularity
+ * clock. Since timers are placed on the queue in time order with the
+ * most recently created handler appearing after earlier ones with the
+ * same expiration time, we don't have to worry about newer generation
+ * timers appearing before later ones.
+ */
+
+ tsdPtr->timerPending = 0;
+ currentTimerId = tsdPtr->lastTimerId;
+ Tcl_GetTime(&time);
+ while (1) {
+ nextPtrPtr = &tsdPtr->firstTimerHandlerPtr;
+ timerHandlerPtr = tsdPtr->firstTimerHandlerPtr;
+ if (timerHandlerPtr == NULL) {
+ break;
+ }
+
+ if (TCL_TIME_BEFORE(time, timerHandlerPtr->time)) {
+ break;
+ }
+
+ /*
+ * Bail out if the next timer is of a newer generation.
+ */
+
+ if ((currentTimerId - PTR2INT(timerHandlerPtr->token)) < 0) {
+ break;
+ }
+
+ /*
+ * Remove the handler from the queue before invoking it, to avoid
+ * potential reentrancy problems.
+ */
+
+ *nextPtrPtr = timerHandlerPtr->nextPtr;
+ timerHandlerPtr->proc(timerHandlerPtr->clientData);
+ ckfree(timerHandlerPtr);
+ }
+ TimerSetupProc(NULL, TCL_TIMER_EVENTS);
+ return 1;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * Tcl_DoWhenIdle --
+ *
+ * Arrange for proc to be invoked the next time the system is idle (i.e.,
+ * just before the next time that Tcl_DoOneEvent would have to wait for
+ * something to happen).
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Proc will eventually be called, with clientData as argument. See the
+ * manual entry for details.
+ *
+ *--------------------------------------------------------------
+ */
+
+void
+Tcl_DoWhenIdle(
+ Tcl_IdleProc *proc, /* Function to invoke. */
+ ClientData clientData) /* Arbitrary value to pass to proc. */
+{
+ register IdleHandler *idlePtr;
+ Tcl_Time blockTime;
+ ThreadSpecificData *tsdPtr = InitTimer();
+
+ idlePtr = ckalloc(sizeof(IdleHandler));
+ idlePtr->proc = proc;
+ idlePtr->clientData = clientData;
+ idlePtr->generation = tsdPtr->idleGeneration;
+ idlePtr->nextPtr = NULL;
+ if (tsdPtr->lastIdlePtr == NULL) {
+ tsdPtr->idleList = idlePtr;
+ } else {
+ tsdPtr->lastIdlePtr->nextPtr = idlePtr;
+ }
+ tsdPtr->lastIdlePtr = idlePtr;
+
+ blockTime.sec = 0;
+ blockTime.usec = 0;
+ Tcl_SetMaxBlockTime(&blockTime);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_CancelIdleCall --
+ *
+ * If there are any when-idle calls requested to a given function with
+ * given clientData, cancel all of them.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * If the proc/clientData combination were on the when-idle list, they
+ * are removed so that they will never be called.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+Tcl_CancelIdleCall(
+ Tcl_IdleProc *proc, /* Function that was previously registered. */
+ ClientData clientData) /* Arbitrary value to pass to proc. */
+{
+ register IdleHandler *idlePtr, *prevPtr;
+ IdleHandler *nextPtr;
+ ThreadSpecificData *tsdPtr = InitTimer();
+
+ for (prevPtr = NULL, idlePtr = tsdPtr->idleList; idlePtr != NULL;
+ prevPtr = idlePtr, idlePtr = idlePtr->nextPtr) {
+ while ((idlePtr->proc == proc)
+ && (idlePtr->clientData == clientData)) {
+ nextPtr = idlePtr->nextPtr;
+ ckfree(idlePtr);
+ idlePtr = nextPtr;
+ if (prevPtr == NULL) {
+ tsdPtr->idleList = idlePtr;
+ } else {
+ prevPtr->nextPtr = idlePtr;
+ }
+ if (idlePtr == NULL) {
+ tsdPtr->lastIdlePtr = prevPtr;
+ return;
+ }
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclServiceIdle --
+ *
+ * This function is invoked by the notifier when it becomes idle. It will
+ * invoke all idle handlers that are present at the time the call is
+ * invoked, but not those added during idle processing.
+ *
+ * Results:
+ * The return value is 1 if TclServiceIdle found something to do,
+ * otherwise return value is 0.
+ *
+ * Side effects:
+ * Invokes all pending idle handlers.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TclServiceIdle(void)
+{
+ IdleHandler *idlePtr;
+ int oldGeneration;
+ Tcl_Time blockTime;
+ ThreadSpecificData *tsdPtr = InitTimer();
+
+ if (tsdPtr->idleList == NULL) {
+ return 0;
+ }
+
+ oldGeneration = tsdPtr->idleGeneration;
+ tsdPtr->idleGeneration++;
+
+ /*
+ * The code below is trickier than it may look, for the following reasons:
+ *
+ * 1. New handlers can get added to the list while the current one is
+ * being processed. If new ones get added, we don't want to process
+ * them during this pass through the list (want to check for other work
+ * to do first). This is implemented using the generation number in the
+ * handler: new handlers will have a different generation than any of
+ * the ones currently on the list.
+ * 2. The handler can call Tcl_DoOneEvent, so we have to remove the
+ * handler from the list before calling it. Otherwise an infinite loop
+ * could result.
+ * 3. Tcl_CancelIdleCall can be called to remove an element from the list
+ * while a handler is executing, so the list could change structure
+ * during the call.
+ */
+
+ for (idlePtr = tsdPtr->idleList;
+ ((idlePtr != NULL)
+ && ((oldGeneration - idlePtr->generation) >= 0));
+ idlePtr = tsdPtr->idleList) {
+ tsdPtr->idleList = idlePtr->nextPtr;
+ if (tsdPtr->idleList == NULL) {
+ tsdPtr->lastIdlePtr = NULL;
+ }
+ idlePtr->proc(idlePtr->clientData);
+ ckfree(idlePtr);
+ }
+ if (tsdPtr->idleList) {
+ blockTime.sec = 0;
+ blockTime.usec = 0;
+ Tcl_SetMaxBlockTime(&blockTime);
+ }
+ return 1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_AfterObjCmd --
+ *
+ * This function is invoked to process the "after" Tcl command. See the
+ * user documentation for details on what it does.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+ /* ARGSUSED */
+int
+Tcl_AfterObjCmd(
+ ClientData clientData, /* Unused */
+ Tcl_Interp *interp, /* Current interpreter. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[]) /* Argument objects. */
+{
+ Tcl_WideInt ms = 0; /* Number of milliseconds to wait */
+ Tcl_Time wakeup;
+ AfterInfo *afterPtr;
+ AfterAssocData *assocPtr;
+ int length;
+ int index;
+ static const char *const afterSubCmds[] = {
+ "cancel", "idle", "info", NULL
+ };
+ enum afterSubCmds {AFTER_CANCEL, AFTER_IDLE, AFTER_INFO};
+ ThreadSpecificData *tsdPtr = InitTimer();
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
+ return TCL_ERROR;
+ }
+
+ /*
+ * Create the "after" information associated for this interpreter, if it
+ * doesn't already exist.
+ */
+
+ assocPtr = Tcl_GetAssocData(interp, "tclAfter", NULL);
+ if (assocPtr == NULL) {
+ assocPtr = ckalloc(sizeof(AfterAssocData));
+ assocPtr->interp = interp;
+ assocPtr->firstAfterPtr = NULL;
+ Tcl_SetAssocData(interp, "tclAfter", AfterCleanupProc, assocPtr);
+ }
+
+ /*
+ * First lets see if the command was passed a number as the first argument.
+ */
+
+ if (objv[1]->typePtr == &tclIntType
+#ifndef TCL_WIDE_INT_IS_LONG
+ || objv[1]->typePtr == &tclWideIntType
+#endif
+ || objv[1]->typePtr == &tclBignumType
+ || (Tcl_GetIndexFromObj(NULL, objv[1], afterSubCmds, "", 0,
+ &index) != TCL_OK)) {
+ index = -1;
+ if (Tcl_GetWideIntFromObj(NULL, objv[1], &ms) != TCL_OK) {
+ const char *arg = Tcl_GetString(objv[1]);
+
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad argument \"%s\": must be"
+ " cancel, idle, info, or an integer", arg));
+ Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INDEX", "argument",
+ arg, NULL);
+ return TCL_ERROR;
+ }
+ }
+
+ /*
+ * At this point, either index = -1 and ms contains the number of ms
+ * to wait, or else index is the index of a subcommand.
+ */
+
+ switch (index) {
+ case -1: {
+ if (ms < 0) {
+ ms = 0;
+ }
+ if (objc == 2) {
+ return AfterDelay(interp, ms);
+ }
+ afterPtr = ckalloc(sizeof(AfterInfo));
+ afterPtr->assocPtr = assocPtr;
+ if (objc == 3) {
+ afterPtr->commandPtr = objv[2];
+ } else {
+ afterPtr->commandPtr = Tcl_ConcatObj(objc-2, objv+2);
+ }
+ Tcl_IncrRefCount(afterPtr->commandPtr);
+
+ /*
+ * The variable below is used to generate unique identifiers for after
+ * commands. This id can wrap around, which can potentially cause
+ * problems. However, there are not likely to be problems in practice,
+ * because after commands can only be requested to about a month in
+ * the future, and wrap-around is unlikely to occur in less than about
+ * 1-10 years. Thus it's unlikely that any old ids will still be
+ * around when wrap-around occurs.
+ */
+
+ afterPtr->id = tsdPtr->afterId;
+ tsdPtr->afterId += 1;
+ Tcl_GetTime(&wakeup);
+ wakeup.sec += (long)(ms / 1000);
+ wakeup.usec += ((long)(ms % 1000)) * 1000;
+ if (wakeup.usec > 1000000) {
+ wakeup.sec++;
+ wakeup.usec -= 1000000;
+ }
+ afterPtr->token = TclCreateAbsoluteTimerHandler(&wakeup,
+ AfterProc, afterPtr);
+ afterPtr->nextPtr = assocPtr->firstAfterPtr;
+ assocPtr->firstAfterPtr = afterPtr;
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("after#%d", afterPtr->id));
+ return TCL_OK;
+ }
+ case AFTER_CANCEL: {
+ Tcl_Obj *commandPtr;
+ const char *command, *tempCommand;
+ int tempLength;
+
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "id|command");
+ return TCL_ERROR;
+ }
+ if (objc == 3) {
+ commandPtr = objv[2];
+ } else {
+ commandPtr = Tcl_ConcatObj(objc-2, objv+2);;
+ }
+ command = Tcl_GetStringFromObj(commandPtr, &length);
+ for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL;
+ afterPtr = afterPtr->nextPtr) {
+ tempCommand = Tcl_GetStringFromObj(afterPtr->commandPtr,
+ &tempLength);
+ if ((length == tempLength)
+ && !memcmp(command, tempCommand, (unsigned) length)) {
+ break;
+ }
+ }
+ if (afterPtr == NULL) {
+ afterPtr = GetAfterEvent(assocPtr, commandPtr);
+ }
+ if (objc != 3) {
+ Tcl_DecrRefCount(commandPtr);
+ }
+ if (afterPtr != NULL) {
+ if (afterPtr->token != NULL) {
+ Tcl_DeleteTimerHandler(afterPtr->token);
+ } else {
+ Tcl_CancelIdleCall(AfterProc, afterPtr);
+ }
+ FreeAfterPtr(afterPtr);
+ }
+ break;
+ }
+ case AFTER_IDLE:
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "script ?script ...?");
+ return TCL_ERROR;
+ }
+ afterPtr = ckalloc(sizeof(AfterInfo));
+ afterPtr->assocPtr = assocPtr;
+ if (objc == 3) {
+ afterPtr->commandPtr = objv[2];
+ } else {
+ afterPtr->commandPtr = Tcl_ConcatObj(objc-2, objv+2);
+ }
+ Tcl_IncrRefCount(afterPtr->commandPtr);
+ afterPtr->id = tsdPtr->afterId;
+ tsdPtr->afterId += 1;
+ afterPtr->token = NULL;
+ afterPtr->nextPtr = assocPtr->firstAfterPtr;
+ assocPtr->firstAfterPtr = afterPtr;
+ Tcl_DoWhenIdle(AfterProc, afterPtr);
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("after#%d", afterPtr->id));
+ break;
+ case AFTER_INFO:
+ if (objc == 2) {
+ Tcl_Obj *resultObj = Tcl_NewObj();
+
+ for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL;
+ afterPtr = afterPtr->nextPtr) {
+ if (assocPtr->interp == interp) {
+ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_ObjPrintf(
+ "after#%d", afterPtr->id));
+ }
+ }
+ Tcl_SetObjResult(interp, resultObj);
+ return TCL_OK;
+ }
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?id?");
+ return TCL_ERROR;
+ }
+ afterPtr = GetAfterEvent(assocPtr, objv[2]);
+ if (afterPtr == NULL) {
+ const char *eventStr = TclGetString(objv[2]);
+
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "event \"%s\" doesn't exist", eventStr));
+ Tcl_SetErrorCode(interp, "TCL","LOOKUP","EVENT", eventStr, NULL);
+ return TCL_ERROR;
+ } else {
+ Tcl_Obj *resultListPtr = Tcl_NewObj();
+
+ Tcl_ListObjAppendElement(interp, resultListPtr,
+ afterPtr->commandPtr);
+ Tcl_ListObjAppendElement(interp, resultListPtr, Tcl_NewStringObj(
+ (afterPtr->token == NULL) ? "idle" : "timer", -1));
+ Tcl_SetObjResult(interp, resultListPtr);
+ }
+ break;
+ default:
+ Tcl_Panic("Tcl_AfterObjCmd: bad subcommand index to afterSubCmds");
+ }
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * AfterDelay --
+ *
+ * Implements the blocking delay behaviour of [after $time]. Tricky
+ * because it has to take into account any time limit that has been set.
+ *
+ * Results:
+ * Standard Tcl result code (with error set if an error occurred due to a
+ * time limit being exceeded or being canceled).
+ *
+ * Side effects:
+ * May adjust the time limit granularity marker.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+AfterDelay(
+ Tcl_Interp *interp,
+ Tcl_WideInt ms)
+{
+ Interp *iPtr = (Interp *) interp;
+
+ Tcl_Time endTime, now;
+ Tcl_WideInt diff;
+
+ Tcl_GetTime(&now);
+ endTime = now;
+ endTime.sec += (long)(ms/1000);
+ endTime.usec += ((int)(ms%1000))*1000;
+ if (endTime.usec >= 1000000) {
+ endTime.sec++;
+ endTime.usec -= 1000000;
+ }
+
+ do {
+ if (Tcl_AsyncReady()) {
+ if (Tcl_AsyncInvoke(interp, TCL_OK) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ if (Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+ if (iPtr->limit.timeEvent != NULL
+ && TCL_TIME_BEFORE(iPtr->limit.time, now)) {
+ iPtr->limit.granularityTicker = 0;
+ if (Tcl_LimitCheck(interp) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ if (iPtr->limit.timeEvent == NULL
+ || TCL_TIME_BEFORE(endTime, iPtr->limit.time)) {
+ diff = TCL_TIME_DIFF_MS_CEILING(endTime, now);
+#ifndef TCL_WIDE_INT_IS_LONG
+ if (diff > LONG_MAX) {
+ diff = LONG_MAX;
+ }
+#endif
+ if (diff > TCL_TIME_MAXIMUM_SLICE) {
+ diff = TCL_TIME_MAXIMUM_SLICE;
+ }
+ if (diff == 0 && TCL_TIME_BEFORE(now, endTime)) diff = 1;
+ if (diff > 0) {
+ Tcl_Sleep((long) diff);
+ if (diff < SLEEP_OFFLOAD_GETTIMEOFDAY) break;
+ } else break;
+ } else {
+ diff = TCL_TIME_DIFF_MS(iPtr->limit.time, now);
+#ifndef TCL_WIDE_INT_IS_LONG
+ if (diff > LONG_MAX) {
+ diff = LONG_MAX;
+ }
+#endif
+ if (diff > TCL_TIME_MAXIMUM_SLICE) {
+ diff = TCL_TIME_MAXIMUM_SLICE;
+ }
+ if (diff > 0) {
+ Tcl_Sleep((long) diff);
+ }
+ if (Tcl_AsyncReady()) {
+ if (Tcl_AsyncInvoke(interp, TCL_OK) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ if (Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+ if (Tcl_LimitCheck(interp) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ Tcl_GetTime(&now);
+ } while (TCL_TIME_BEFORE(now, endTime));
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * GetAfterEvent --
+ *
+ * This function parses an "after" id such as "after#4" and returns a
+ * pointer to the AfterInfo structure.
+ *
+ * Results:
+ * The return value is either a pointer to an AfterInfo structure, if one
+ * is found that corresponds to "cmdString" and is for interp, or NULL if
+ * no corresponding after event can be found.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static AfterInfo *
+GetAfterEvent(
+ AfterAssocData *assocPtr, /* Points to "after"-related information for
+ * this interpreter. */
+ Tcl_Obj *commandPtr)
+{
+ const char *cmdString; /* Textual identifier for after event, such as
+ * "after#6". */
+ AfterInfo *afterPtr;
+ int id;
+ char *end;
+
+ cmdString = TclGetString(commandPtr);
+ if (strncmp(cmdString, "after#", 6) != 0) {
+ return NULL;
+ }
+ cmdString += 6;
+ id = strtoul(cmdString, &end, 10);
+ if ((end == cmdString) || (*end != 0)) {
+ return NULL;
+ }
+ for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL;
+ afterPtr = afterPtr->nextPtr) {
+ if (afterPtr->id == id) {
+ return afterPtr;
+ }
+ }
+ return NULL;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * AfterProc --
+ *
+ * Timer callback to execute commands registered with the "after"
+ * command.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Executes whatever command was specified. If the command returns an
+ * error, then the command "bgerror" is invoked to process the error; if
+ * bgerror fails then information about the error is output on stderr.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+AfterProc(
+ ClientData clientData) /* Describes command to execute. */
+{
+ AfterInfo *afterPtr = clientData;
+ AfterAssocData *assocPtr = afterPtr->assocPtr;
+ AfterInfo *prevPtr;
+ int result;
+ Tcl_Interp *interp;
+
+ /*
+ * First remove the callback from our list of callbacks; otherwise someone
+ * could delete the callback while it's being executed, which could cause
+ * a core dump.
+ */
+
+ if (assocPtr->firstAfterPtr == afterPtr) {
+ assocPtr->firstAfterPtr = afterPtr->nextPtr;
+ } else {
+ for (prevPtr = assocPtr->firstAfterPtr; prevPtr->nextPtr != afterPtr;
+ prevPtr = prevPtr->nextPtr) {
+ /* Empty loop body. */
+ }
+ prevPtr->nextPtr = afterPtr->nextPtr;
+ }
+
+ /*
+ * Execute the callback.
+ */
+
+ interp = assocPtr->interp;
+ Tcl_Preserve(interp);
+ result = Tcl_EvalObjEx(interp, afterPtr->commandPtr, TCL_EVAL_GLOBAL);
+ if (result != TCL_OK) {
+ Tcl_AddErrorInfo(interp, "\n (\"after\" script)");
+ Tcl_BackgroundException(interp, result);
+ }
+ Tcl_Release(interp);
+
+ /*
+ * Free the memory for the callback.
+ */
+
+ Tcl_DecrRefCount(afterPtr->commandPtr);
+ ckfree(afterPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FreeAfterPtr --
+ *
+ * This function removes an "after" command from the list of those that
+ * are pending and frees its resources. This function does *not* cancel
+ * the timer handler; if that's needed, the caller must do it.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The memory associated with afterPtr is released.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FreeAfterPtr(
+ AfterInfo *afterPtr) /* Command to be deleted. */
+{
+ AfterInfo *prevPtr;
+ AfterAssocData *assocPtr = afterPtr->assocPtr;
+
+ if (assocPtr->firstAfterPtr == afterPtr) {
+ assocPtr->firstAfterPtr = afterPtr->nextPtr;
+ } else {
+ for (prevPtr = assocPtr->firstAfterPtr; prevPtr->nextPtr != afterPtr;
+ prevPtr = prevPtr->nextPtr) {
+ /* Empty loop body. */
+ }
+ prevPtr->nextPtr = afterPtr->nextPtr;
+ }
+ Tcl_DecrRefCount(afterPtr->commandPtr);
+ ckfree(afterPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * AfterCleanupProc --
+ *
+ * This function is invoked whenever an interpreter is deleted
+ * to cleanup the AssocData for "tclAfter".
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * After commands are removed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+ /* ARGSUSED */
+static void
+AfterCleanupProc(
+ ClientData clientData, /* Points to AfterAssocData for the
+ * interpreter. */
+ Tcl_Interp *interp) /* Interpreter that is being deleted. */
+{
+ AfterAssocData *assocPtr = clientData;
+ AfterInfo *afterPtr;
+
+ while (assocPtr->firstAfterPtr != NULL) {
+ afterPtr = assocPtr->firstAfterPtr;
+ assocPtr->firstAfterPtr = afterPtr->nextPtr;
+ if (afterPtr->token != NULL) {
+ Tcl_DeleteTimerHandler(afterPtr->token);
+ } else {
+ Tcl_CancelIdleCall(AfterProc, afterPtr);
+ }
+ Tcl_DecrRefCount(afterPtr->commandPtr);
+ ckfree(afterPtr);
+ }
+ ckfree(assocPtr);
+}
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-basic-offset: 4
+ * fill-column: 78
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ */