tclTimer.c

Go to the documentation of this file.
00001 /*
00002  * tclTimer.c --
00003  *
00004  *      This file provides timer event management facilities for Tcl,
00005  *      including the "after" command.
00006  *
00007  * Copyright (c) 1997 by Sun Microsystems, Inc.
00008  *
00009  * See the file "license.terms" for information on usage and redistribution of
00010  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
00011  *
00012  * RCS: @(#) $Id: tclTimer.c,v 1.31 2008/01/22 20:52:10 dgp Exp $
00013  */
00014 
00015 #include "tclInt.h"
00016 
00017 /*
00018  * For each timer callback that's pending there is one record of the following
00019  * type. The normal handlers (created by Tcl_CreateTimerHandler) are chained
00020  * together in a list sorted by time (earliest event first).
00021  */
00022 
00023 typedef struct TimerHandler {
00024     Tcl_Time time;              /* When timer is to fire. */
00025     Tcl_TimerProc *proc;        /* Function to call. */
00026     ClientData clientData;      /* Argument to pass to proc. */
00027     Tcl_TimerToken token;       /* Identifies handler so it can be deleted. */
00028     struct TimerHandler *nextPtr;
00029                                 /* Next event in queue, or NULL for end of
00030                                  * queue. */
00031 } TimerHandler;
00032 
00033 /*
00034  * The data structure below is used by the "after" command to remember the
00035  * command to be executed later. All of the pending "after" commands for an
00036  * interpreter are linked together in a list.
00037  */
00038 
00039 typedef struct AfterInfo {
00040     struct AfterAssocData *assocPtr;
00041                                 /* Pointer to the "tclAfter" assocData for the
00042                                  * interp in which command will be
00043                                  * executed. */
00044     Tcl_Obj *commandPtr;        /* Command to execute. */
00045     int id;                     /* Integer identifier for command; used to
00046                                  * cancel it. */
00047     Tcl_TimerToken token;       /* Used to cancel the "after" command. NULL
00048                                  * means that the command is run as an idle
00049                                  * handler rather than as a timer handler.
00050                                  * NULL means this is an "after idle" handler
00051                                  * rather than a timer handler. */
00052     struct AfterInfo *nextPtr;  /* Next in list of all "after" commands for
00053                                  * this interpreter. */
00054 } AfterInfo;
00055 
00056 /*
00057  * One of the following structures is associated with each interpreter for
00058  * which an "after" command has ever been invoked. A pointer to this structure
00059  * is stored in the AssocData for the "tclAfter" key.
00060  */
00061 
00062 typedef struct AfterAssocData {
00063     Tcl_Interp *interp;         /* The interpreter for which this data is
00064                                  * registered. */
00065     AfterInfo *firstAfterPtr;   /* First in list of all "after" commands still
00066                                  * pending for this interpreter, or NULL if
00067                                  * none. */
00068 } AfterAssocData;
00069 
00070 /*
00071  * There is one of the following structures for each of the handlers declared
00072  * in a call to Tcl_DoWhenIdle. All of the currently-active handlers are
00073  * linked together into a list.
00074  */
00075 
00076 typedef struct IdleHandler {
00077     Tcl_IdleProc (*proc);       /* Function to call. */
00078     ClientData clientData;      /* Value to pass to proc. */
00079     int generation;             /* Used to distinguish older handlers from
00080                                  * recently-created ones. */
00081     struct IdleHandler *nextPtr;/* Next in list of active handlers. */
00082 } IdleHandler;
00083 
00084 /*
00085  * The timer and idle queues are per-thread because they are associated with
00086  * the notifier, which is also per-thread.
00087  *
00088  * All static variables used in this file are collected into a single instance
00089  * of the following structure. For multi-threaded implementations, there is
00090  * one instance of this structure for each thread.
00091  *
00092  * Notice that different structures with the same name appear in other files.
00093  * The structure defined below is used in this file only.
00094  */
00095 
00096 typedef struct ThreadSpecificData {
00097     TimerHandler *firstTimerHandlerPtr; /* First event in queue. */
00098     int lastTimerId;            /* Timer identifier of most recently created
00099                                  * timer. */
00100     int timerPending;           /* 1 if a timer event is in the queue. */
00101     IdleHandler *idleList;      /* First in list of all idle handlers. */
00102     IdleHandler *lastIdlePtr;   /* Last in list (or NULL for empty list). */
00103     int idleGeneration;         /* Used to fill in the "generation" fields of
00104                                  * IdleHandler structures. Increments each
00105                                  * time Tcl_DoOneEvent starts calling idle
00106                                  * handlers, so that all old handlers can be
00107                                  * called without calling any of the new ones
00108                                  * created by old ones. */
00109     int afterId;                /* For unique identifiers of after events. */
00110 } ThreadSpecificData;
00111 
00112 static Tcl_ThreadDataKey dataKey;
00113 
00114 /*
00115  * Helper macros for working with times. TCL_TIME_BEFORE encodes how to write
00116  * the ordering relation on (normalized) times, and TCL_TIME_DIFF_MS computes
00117  * the number of milliseconds difference between two times. Both macros use
00118  * both of their arguments multiple times, so make sure they are cheap and
00119  * side-effect free. The "prototypes" for these macros are:
00120  *
00121  * static int   TCL_TIME_BEFORE(Tcl_Time t1, Tcl_Time t2);
00122  * static long  TCL_TIME_DIFF_MS(Tcl_Time t1, Tcl_Time t2);
00123  */
00124 
00125 #define TCL_TIME_BEFORE(t1, t2) \
00126     (((t1).sec<(t2).sec) || ((t1).sec==(t2).sec && (t1).usec<(t2).usec))
00127 
00128 #define TCL_TIME_DIFF_MS(t1, t2) \
00129     (1000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \
00130             ((long)(t1).usec - (long)(t2).usec)/1000)
00131 
00132 /*
00133  * Prototypes for functions referenced only in this file:
00134  */
00135 
00136 static void             AfterCleanupProc(ClientData clientData,
00137                             Tcl_Interp *interp);
00138 static int              AfterDelay(Tcl_Interp *interp, Tcl_WideInt ms);
00139 static void             AfterProc(ClientData clientData);
00140 static void             FreeAfterPtr(AfterInfo *afterPtr);
00141 static AfterInfo *      GetAfterEvent(AfterAssocData *assocPtr,
00142                             Tcl_Obj *commandPtr);
00143 static ThreadSpecificData *InitTimer(void);
00144 static void             TimerExitProc(ClientData clientData);
00145 static int              TimerHandlerEventProc(Tcl_Event *evPtr, int flags);
00146 static void             TimerCheckProc(ClientData clientData, int flags);
00147 static void             TimerSetupProc(ClientData clientData, int flags);
00148 
00149 /*
00150  *----------------------------------------------------------------------
00151  *
00152  * InitTimer --
00153  *
00154  *      This function initializes the timer module.
00155  *
00156  * Results:
00157  *      A pointer to the thread specific data.
00158  *
00159  * Side effects:
00160  *      Registers the idle and timer event sources.
00161  *
00162  *----------------------------------------------------------------------
00163  */
00164 
00165 static ThreadSpecificData *
00166 InitTimer(void)
00167 {
00168     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
00169             TclThreadDataKeyGet(&dataKey);
00170 
00171     if (tsdPtr == NULL) {
00172         tsdPtr = TCL_TSD_INIT(&dataKey);
00173         Tcl_CreateEventSource(TimerSetupProc, TimerCheckProc, NULL);
00174         Tcl_CreateThreadExitHandler(TimerExitProc, NULL);
00175     }
00176     return tsdPtr;
00177 }
00178 
00179 /*
00180  *----------------------------------------------------------------------
00181  *
00182  * TimerExitProc --
00183  *
00184  *      This function is call at exit or unload time to remove the timer and
00185  *      idle event sources.
00186  *
00187  * Results:
00188  *      None.
00189  *
00190  * Side effects:
00191  *      Removes the timer and idle event sources and remaining events.
00192  *
00193  *----------------------------------------------------------------------
00194  */
00195 
00196 static void
00197 TimerExitProc(
00198     ClientData clientData)      /* Not used. */
00199 {
00200     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
00201             TclThreadDataKeyGet(&dataKey);
00202 
00203     Tcl_DeleteEventSource(TimerSetupProc, TimerCheckProc, NULL);
00204     if (tsdPtr != NULL) {
00205         register TimerHandler *timerHandlerPtr;
00206 
00207         timerHandlerPtr = tsdPtr->firstTimerHandlerPtr;
00208         while (timerHandlerPtr != NULL) {
00209             tsdPtr->firstTimerHandlerPtr = timerHandlerPtr->nextPtr;
00210             ckfree((char *) timerHandlerPtr);
00211             timerHandlerPtr = tsdPtr->firstTimerHandlerPtr;
00212         }
00213     }
00214 }
00215 
00216 /*
00217  *--------------------------------------------------------------
00218  *
00219  * Tcl_CreateTimerHandler --
00220  *
00221  *      Arrange for a given function to be invoked at a particular time in the
00222  *      future.
00223  *
00224  * Results:
00225  *      The return value is a token for the timer event, which may be used to
00226  *      delete the event before it fires.
00227  *
00228  * Side effects:
00229  *      When milliseconds have elapsed, proc will be invoked exactly once.
00230  *
00231  *--------------------------------------------------------------
00232  */
00233 
00234 Tcl_TimerToken
00235 Tcl_CreateTimerHandler(
00236     int milliseconds,           /* How many milliseconds to wait before
00237                                  * invoking proc. */
00238     Tcl_TimerProc *proc,        /* Function to invoke. */
00239     ClientData clientData)      /* Arbitrary data to pass to proc. */
00240 {
00241     Tcl_Time time;
00242 
00243     /*
00244      * Compute when the event should fire.
00245      */
00246 
00247     Tcl_GetTime(&time);
00248     time.sec += milliseconds/1000;
00249     time.usec += (milliseconds%1000)*1000;
00250     if (time.usec >= 1000000) {
00251         time.usec -= 1000000;
00252         time.sec += 1;
00253     }
00254     return TclCreateAbsoluteTimerHandler(&time, proc, clientData);
00255 }
00256 
00257 /*
00258  *--------------------------------------------------------------
00259  *
00260  * TclCreateAbsoluteTimerHandler --
00261  *
00262  *      Arrange for a given function to be invoked at a particular time in the
00263  *      future.
00264  *
00265  * Results:
00266  *      The return value is a token for the timer event, which may be used to
00267  *      delete the event before it fires.
00268  *
00269  * Side effects:
00270  *      When the time in timePtr has been reached, proc will be invoked
00271  *      exactly once.
00272  *
00273  *--------------------------------------------------------------
00274  */
00275 
00276 Tcl_TimerToken
00277 TclCreateAbsoluteTimerHandler(
00278     Tcl_Time *timePtr,
00279     Tcl_TimerProc *proc,
00280     ClientData clientData)
00281 {
00282     register TimerHandler *timerHandlerPtr, *tPtr2, *prevPtr;
00283     ThreadSpecificData *tsdPtr;
00284 
00285     tsdPtr = InitTimer();
00286     timerHandlerPtr = (TimerHandler *) ckalloc(sizeof(TimerHandler));
00287 
00288     /*
00289      * Fill in fields for the event.
00290      */
00291 
00292     memcpy((void *)&timerHandlerPtr->time, (void *)timePtr, sizeof(Tcl_Time));
00293     timerHandlerPtr->proc = proc;
00294     timerHandlerPtr->clientData = clientData;
00295     tsdPtr->lastTimerId++;
00296     timerHandlerPtr->token = (Tcl_TimerToken) INT2PTR(tsdPtr->lastTimerId);
00297 
00298     /*
00299      * Add the event to the queue in the correct position
00300      * (ordered by event firing time).
00301      */
00302 
00303     for (tPtr2 = tsdPtr->firstTimerHandlerPtr, prevPtr = NULL; tPtr2 != NULL;
00304             prevPtr = tPtr2, tPtr2 = tPtr2->nextPtr) {
00305         if (TCL_TIME_BEFORE(timerHandlerPtr->time, tPtr2->time)) {
00306             break;
00307         }
00308     }
00309     timerHandlerPtr->nextPtr = tPtr2;
00310     if (prevPtr == NULL) {
00311         tsdPtr->firstTimerHandlerPtr = timerHandlerPtr;
00312     } else {
00313         prevPtr->nextPtr = timerHandlerPtr;
00314     }
00315 
00316     TimerSetupProc(NULL, TCL_ALL_EVENTS);
00317 
00318     return timerHandlerPtr->token;
00319 }
00320 
00321 /*
00322  *--------------------------------------------------------------
00323  *
00324  * Tcl_DeleteTimerHandler --
00325  *
00326  *      Delete a previously-registered timer handler.
00327  *
00328  * Results:
00329  *      None.
00330  *
00331  * Side effects:
00332  *      Destroy the timer callback identified by TimerToken, so that its
00333  *      associated function will not be called. If the callback has already
00334  *      fired, or if the given token doesn't exist, then nothing happens.
00335  *
00336  *--------------------------------------------------------------
00337  */
00338 
00339 void
00340 Tcl_DeleteTimerHandler(
00341     Tcl_TimerToken token)       /* Result previously returned by
00342                                  * Tcl_DeleteTimerHandler. */
00343 {
00344     register TimerHandler *timerHandlerPtr, *prevPtr;
00345     ThreadSpecificData *tsdPtr = InitTimer();
00346 
00347     if (token == NULL) {
00348         return;
00349     }
00350 
00351     for (timerHandlerPtr = tsdPtr->firstTimerHandlerPtr, prevPtr = NULL;
00352             timerHandlerPtr != NULL; prevPtr = timerHandlerPtr,
00353             timerHandlerPtr = timerHandlerPtr->nextPtr) {
00354         if (timerHandlerPtr->token != token) {
00355             continue;
00356         }
00357         if (prevPtr == NULL) {
00358             tsdPtr->firstTimerHandlerPtr = timerHandlerPtr->nextPtr;
00359         } else {
00360             prevPtr->nextPtr = timerHandlerPtr->nextPtr;
00361         }
00362         ckfree((char *) timerHandlerPtr);
00363         return;
00364     }
00365 }
00366 
00367 /*
00368  *----------------------------------------------------------------------
00369  *
00370  * TimerSetupProc --
00371  *
00372  *      This function is called by Tcl_DoOneEvent to setup the timer event
00373  *      source for before blocking. This routine checks both the idle and
00374  *      after timer lists.
00375  *
00376  * Results:
00377  *      None.
00378  *
00379  * Side effects:
00380  *      May update the maximum notifier block time.
00381  *
00382  *----------------------------------------------------------------------
00383  */
00384 
00385 static void
00386 TimerSetupProc(
00387     ClientData data,            /* Not used. */
00388     int flags)                  /* Event flags as passed to Tcl_DoOneEvent. */
00389 {
00390     Tcl_Time blockTime;
00391     ThreadSpecificData *tsdPtr = InitTimer();
00392 
00393     if (((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList)
00394             || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)) {
00395         /*
00396          * There is an idle handler or a pending timer event, so just poll.
00397          */
00398 
00399         blockTime.sec = 0;
00400         blockTime.usec = 0;
00401 
00402     } else if ((flags & TCL_TIMER_EVENTS) && tsdPtr->firstTimerHandlerPtr) {
00403         /*
00404          * Compute the timeout for the next timer on the list.
00405          */
00406 
00407         Tcl_GetTime(&blockTime);
00408         blockTime.sec = tsdPtr->firstTimerHandlerPtr->time.sec - blockTime.sec;
00409         blockTime.usec = tsdPtr->firstTimerHandlerPtr->time.usec -
00410                 blockTime.usec;
00411         if (blockTime.usec < 0) {
00412             blockTime.sec -= 1;
00413             blockTime.usec += 1000000;
00414         }
00415         if (blockTime.sec < 0) {
00416             blockTime.sec = 0;
00417             blockTime.usec = 0;
00418         }
00419     } else {
00420         return;
00421     }
00422 
00423     Tcl_SetMaxBlockTime(&blockTime);
00424 }
00425 
00426 /*
00427  *----------------------------------------------------------------------
00428  *
00429  * TimerCheckProc --
00430  *
00431  *      This function is called by Tcl_DoOneEvent to check the timer event
00432  *      source for events. This routine checks both the idle and after timer
00433  *      lists.
00434  *
00435  * Results:
00436  *      None.
00437  *
00438  * Side effects:
00439  *      May queue an event and update the maximum notifier block time.
00440  *
00441  *----------------------------------------------------------------------
00442  */
00443 
00444 static void
00445 TimerCheckProc(
00446     ClientData data,            /* Not used. */
00447     int flags)                  /* Event flags as passed to Tcl_DoOneEvent. */
00448 {
00449     Tcl_Event *timerEvPtr;
00450     Tcl_Time blockTime;
00451     ThreadSpecificData *tsdPtr = InitTimer();
00452 
00453     if ((flags & TCL_TIMER_EVENTS) && tsdPtr->firstTimerHandlerPtr) {
00454         /*
00455          * Compute the timeout for the next timer on the list.
00456          */
00457 
00458         Tcl_GetTime(&blockTime);
00459         blockTime.sec = tsdPtr->firstTimerHandlerPtr->time.sec - blockTime.sec;
00460         blockTime.usec = tsdPtr->firstTimerHandlerPtr->time.usec -
00461                 blockTime.usec;
00462         if (blockTime.usec < 0) {
00463             blockTime.sec -= 1;
00464             blockTime.usec += 1000000;
00465         }
00466         if (blockTime.sec < 0) {
00467             blockTime.sec = 0;
00468             blockTime.usec = 0;
00469         }
00470 
00471         /*
00472          * If the first timer has expired, stick an event on the queue.
00473          */
00474 
00475         if (blockTime.sec == 0 && blockTime.usec == 0 &&
00476                 !tsdPtr->timerPending) {
00477             tsdPtr->timerPending = 1;
00478             timerEvPtr = (Tcl_Event *) ckalloc(sizeof(Tcl_Event));
00479             timerEvPtr->proc = TimerHandlerEventProc;
00480             Tcl_QueueEvent(timerEvPtr, TCL_QUEUE_TAIL);
00481         }
00482     }
00483 }
00484 
00485 /*
00486  *----------------------------------------------------------------------
00487  *
00488  * TimerHandlerEventProc --
00489  *
00490  *      This function is called by Tcl_ServiceEvent when a timer event reaches
00491  *      the front of the event queue. This function handles the event by
00492  *      invoking the callbacks for all timers that are ready.
00493  *
00494  * Results:
00495  *      Returns 1 if the event was handled, meaning it should be removed from
00496  *      the queue. Returns 0 if the event was not handled, meaning it should
00497  *      stay on the queue. The only time the event isn't handled is if the
00498  *      TCL_TIMER_EVENTS flag bit isn't set.
00499  *
00500  * Side effects:
00501  *      Whatever the timer handler callback functions do.
00502  *
00503  *----------------------------------------------------------------------
00504  */
00505 
00506 static int
00507 TimerHandlerEventProc(
00508     Tcl_Event *evPtr,           /* Event to service. */
00509     int flags)                  /* Flags that indicate what events to handle,
00510                                  * such as TCL_FILE_EVENTS. */
00511 {
00512     TimerHandler *timerHandlerPtr, **nextPtrPtr;
00513     Tcl_Time time;
00514     int currentTimerId;
00515     ThreadSpecificData *tsdPtr = InitTimer();
00516 
00517     /*
00518      * Do nothing if timers aren't enabled. This leaves the event on the
00519      * queue, so we will get to it as soon as ServiceEvents() is called with
00520      * timers enabled.
00521      */
00522 
00523     if (!(flags & TCL_TIMER_EVENTS)) {
00524         return 0;
00525     }
00526 
00527     /*
00528      * The code below is trickier than it may look, for the following reasons:
00529      *
00530      * 1. New handlers can get added to the list while the current one is
00531      *    being processed. If new ones get added, we don't want to process
00532      *    them during this pass through the list to avoid starving other event
00533      *    sources. This is implemented using the token number in the handler:
00534      *    new handlers will have a newer token than any of the ones currently
00535      *    on the list.
00536      * 2. The handler can call Tcl_DoOneEvent, so we have to remove the
00537      *    handler from the list before calling it. Otherwise an infinite loop
00538      *    could result.
00539      * 3. Tcl_DeleteTimerHandler can be called to remove an element from the
00540      *    list while a handler is executing, so the list could change
00541      *    structure during the call.
00542      * 4. Because we only fetch the current time before entering the loop, the
00543      *    only way a new timer will even be considered runnable is if its
00544      *    expiration time is within the same millisecond as the current time.
00545      *    This is fairly likely on Windows, since it has a course granularity
00546      *    clock. Since timers are placed on the queue in time order with the
00547      *    most recently created handler appearing after earlier ones with the
00548      *    same expiration time, we don't have to worry about newer generation
00549      *    timers appearing before later ones.
00550      */
00551 
00552     tsdPtr->timerPending = 0;
00553     currentTimerId = tsdPtr->lastTimerId;
00554     Tcl_GetTime(&time);
00555     while (1) {
00556         nextPtrPtr = &tsdPtr->firstTimerHandlerPtr;
00557         timerHandlerPtr = tsdPtr->firstTimerHandlerPtr;
00558         if (timerHandlerPtr == NULL) {
00559             break;
00560         }
00561 
00562         if (TCL_TIME_BEFORE(time, timerHandlerPtr->time)) {
00563             break;
00564         }
00565 
00566         /*
00567          * Bail out if the next timer is of a newer generation.
00568          */
00569 
00570         if ((currentTimerId - PTR2INT(timerHandlerPtr->token)) < 0) {
00571             break;
00572         }
00573 
00574         /*
00575          * Remove the handler from the queue before invoking it, to avoid
00576          * potential reentrancy problems.
00577          */
00578 
00579         (*nextPtrPtr) = timerHandlerPtr->nextPtr;
00580         (*timerHandlerPtr->proc)(timerHandlerPtr->clientData);
00581         ckfree((char *) timerHandlerPtr);
00582     }
00583     TimerSetupProc(NULL, TCL_TIMER_EVENTS);
00584     return 1;
00585 }
00586 
00587 /*
00588  *--------------------------------------------------------------
00589  *
00590  * Tcl_DoWhenIdle --
00591  *
00592  *      Arrange for proc to be invoked the next time the system is idle (i.e.,
00593  *      just before the next time that Tcl_DoOneEvent would have to wait for
00594  *      something to happen).
00595  *
00596  * Results:
00597  *      None.
00598  *
00599  * Side effects:
00600  *      Proc will eventually be called, with clientData as argument. See the
00601  *      manual entry for details.
00602  *
00603  *--------------------------------------------------------------
00604  */
00605 
00606 void
00607 Tcl_DoWhenIdle(
00608     Tcl_IdleProc *proc,         /* Function to invoke. */
00609     ClientData clientData)      /* Arbitrary value to pass to proc. */
00610 {
00611     register IdleHandler *idlePtr;
00612     Tcl_Time blockTime;
00613     ThreadSpecificData *tsdPtr = InitTimer();
00614 
00615     idlePtr = (IdleHandler *) ckalloc(sizeof(IdleHandler));
00616     idlePtr->proc = proc;
00617     idlePtr->clientData = clientData;
00618     idlePtr->generation = tsdPtr->idleGeneration;
00619     idlePtr->nextPtr = NULL;
00620     if (tsdPtr->lastIdlePtr == NULL) {
00621         tsdPtr->idleList = idlePtr;
00622     } else {
00623         tsdPtr->lastIdlePtr->nextPtr = idlePtr;
00624     }
00625     tsdPtr->lastIdlePtr = idlePtr;
00626 
00627     blockTime.sec = 0;
00628     blockTime.usec = 0;
00629     Tcl_SetMaxBlockTime(&blockTime);
00630 }
00631 
00632 /*
00633  *----------------------------------------------------------------------
00634  *
00635  * Tcl_CancelIdleCall --
00636  *
00637  *      If there are any when-idle calls requested to a given function with
00638  *      given clientData, cancel all of them.
00639  *
00640  * Results:
00641  *      None.
00642  *
00643  * Side effects:
00644  *      If the proc/clientData combination were on the when-idle list, they
00645  *      are removed so that they will never be called.
00646  *
00647  *----------------------------------------------------------------------
00648  */
00649 
00650 void
00651 Tcl_CancelIdleCall(
00652     Tcl_IdleProc *proc,         /* Function that was previously registered. */
00653     ClientData clientData)      /* Arbitrary value to pass to proc. */
00654 {
00655     register IdleHandler *idlePtr, *prevPtr;
00656     IdleHandler *nextPtr;
00657     ThreadSpecificData *tsdPtr = InitTimer();
00658 
00659     for (prevPtr = NULL, idlePtr = tsdPtr->idleList; idlePtr != NULL;
00660             prevPtr = idlePtr, idlePtr = idlePtr->nextPtr) {
00661         while ((idlePtr->proc == proc)
00662                 && (idlePtr->clientData == clientData)) {
00663             nextPtr = idlePtr->nextPtr;
00664             ckfree((char *) idlePtr);
00665             idlePtr = nextPtr;
00666             if (prevPtr == NULL) {
00667                 tsdPtr->idleList = idlePtr;
00668             } else {
00669                 prevPtr->nextPtr = idlePtr;
00670             }
00671             if (idlePtr == NULL) {
00672                 tsdPtr->lastIdlePtr = prevPtr;
00673                 return;
00674             }
00675         }
00676     }
00677 }
00678 
00679 /*
00680  *----------------------------------------------------------------------
00681  *
00682  * TclServiceIdle --
00683  *
00684  *      This function is invoked by the notifier when it becomes idle. It will
00685  *      invoke all idle handlers that are present at the time the call is
00686  *      invoked, but not those added during idle processing.
00687  *
00688  * Results:
00689  *      The return value is 1 if TclServiceIdle found something to do,
00690  *      otherwise return value is 0.
00691  *
00692  * Side effects:
00693  *      Invokes all pending idle handlers.
00694  *
00695  *----------------------------------------------------------------------
00696  */
00697 
00698 int
00699 TclServiceIdle(void)
00700 {
00701     IdleHandler *idlePtr;
00702     int oldGeneration;
00703     Tcl_Time blockTime;
00704     ThreadSpecificData *tsdPtr = InitTimer();
00705 
00706     if (tsdPtr->idleList == NULL) {
00707         return 0;
00708     }
00709 
00710     oldGeneration = tsdPtr->idleGeneration;
00711     tsdPtr->idleGeneration++;
00712 
00713     /*
00714      * The code below is trickier than it may look, for the following reasons:
00715      *
00716      * 1. New handlers can get added to the list while the current one is
00717      *    being processed. If new ones get added, we don't want to process
00718      *    them during this pass through the list (want to check for other work
00719      *    to do first). This is implemented using the generation number in the
00720      *    handler: new handlers will have a different generation than any of
00721      *    the ones currently on the list.
00722      * 2. The handler can call Tcl_DoOneEvent, so we have to remove the
00723      *    handler from the list before calling it. Otherwise an infinite loop
00724      *    could result.
00725      * 3. Tcl_CancelIdleCall can be called to remove an element from the list
00726      *    while a handler is executing, so the list could change structure
00727      *    during the call.
00728      */
00729 
00730     for (idlePtr = tsdPtr->idleList;
00731             ((idlePtr != NULL)
00732                     && ((oldGeneration - idlePtr->generation) >= 0));
00733             idlePtr = tsdPtr->idleList) {
00734         tsdPtr->idleList = idlePtr->nextPtr;
00735         if (tsdPtr->idleList == NULL) {
00736             tsdPtr->lastIdlePtr = NULL;
00737         }
00738         (*idlePtr->proc)(idlePtr->clientData);
00739         ckfree((char *) idlePtr);
00740     }
00741     if (tsdPtr->idleList) {
00742         blockTime.sec = 0;
00743         blockTime.usec = 0;
00744         Tcl_SetMaxBlockTime(&blockTime);
00745     }
00746     return 1;
00747 }
00748 
00749 /*
00750  *----------------------------------------------------------------------
00751  *
00752  * Tcl_AfterObjCmd --
00753  *
00754  *      This function is invoked to process the "after" Tcl command. See the
00755  *      user documentation for details on what it does.
00756  *
00757  * Results:
00758  *      A standard Tcl result.
00759  *
00760  * Side effects:
00761  *      See the user documentation.
00762  *
00763  *----------------------------------------------------------------------
00764  */
00765 
00766         /* ARGSUSED */
00767 int
00768 Tcl_AfterObjCmd(
00769     ClientData clientData,      /* Unused */
00770     Tcl_Interp *interp,         /* Current interpreter. */
00771     int objc,                   /* Number of arguments. */
00772     Tcl_Obj *CONST objv[])      /* Argument objects. */
00773 {
00774     Tcl_WideInt ms;             /* Number of milliseconds to wait */
00775     Tcl_Time wakeup;
00776     AfterInfo *afterPtr;
00777     AfterAssocData *assocPtr;
00778     int length;
00779     int index;
00780     char buf[16 + TCL_INTEGER_SPACE];
00781     static CONST char *afterSubCmds[] = {
00782         "cancel", "idle", "info", NULL
00783     };
00784     enum afterSubCmds {AFTER_CANCEL, AFTER_IDLE, AFTER_INFO};
00785     ThreadSpecificData *tsdPtr = InitTimer();
00786 
00787     if (objc < 2) {
00788         Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
00789         return TCL_ERROR;
00790     }
00791 
00792     /*
00793      * Create the "after" information associated for this interpreter, if it
00794      * doesn't already exist.
00795      */
00796 
00797     assocPtr = Tcl_GetAssocData(interp, "tclAfter", NULL);
00798     if (assocPtr == NULL) {
00799         assocPtr = (AfterAssocData *) ckalloc(sizeof(AfterAssocData));
00800         assocPtr->interp = interp;
00801         assocPtr->firstAfterPtr = NULL;
00802         Tcl_SetAssocData(interp, "tclAfter", AfterCleanupProc,
00803                 (ClientData) assocPtr);
00804     }
00805 
00806     /*
00807      * First lets see if the command was passed a number as the first argument.
00808      */
00809 
00810     if (objv[1]->typePtr == &tclIntType
00811 #ifndef NO_WIDE_TYPE
00812         || objv[1]->typePtr == &tclWideIntType
00813 #endif
00814         || objv[1]->typePtr == &tclBignumType
00815         || ( Tcl_GetIndexFromObj(NULL, objv[1], afterSubCmds, "", 0, 
00816                                  &index) != TCL_OK )) {
00817         index = -1;
00818         if (Tcl_GetWideIntFromObj(NULL, objv[1], &ms) != TCL_OK) {
00819             Tcl_AppendResult(interp, "bad argument \"",
00820                              Tcl_GetString(objv[1]),
00821                              "\": must be cancel, idle, info, or an integer",
00822                              NULL);
00823             return TCL_ERROR;
00824         }
00825     }
00826 
00827     /* 
00828      * At this point, either index = -1 and ms contains the number of ms
00829      * to wait, or else index is the index of a subcommand.
00830      */
00831 
00832     switch (index) {
00833     case -1: {
00834         if (ms < 0) {
00835             ms = 0;
00836         }
00837         if (objc == 2) {
00838             return AfterDelay(interp, ms);
00839         }
00840         afterPtr = (AfterInfo *) ckalloc((unsigned) (sizeof(AfterInfo)));
00841         afterPtr->assocPtr = assocPtr;
00842         if (objc == 3) {
00843             afterPtr->commandPtr = objv[2];
00844         } else {
00845             afterPtr->commandPtr = Tcl_ConcatObj(objc-2, objv+2);
00846         }
00847         Tcl_IncrRefCount(afterPtr->commandPtr);
00848 
00849         /*
00850          * The variable below is used to generate unique identifiers for after
00851          * commands. This id can wrap around, which can potentially cause
00852          * problems. However, there are not likely to be problems in practice,
00853          * because after commands can only be requested to about a month in
00854          * the future, and wrap-around is unlikely to occur in less than about
00855          * 1-10 years. Thus it's unlikely that any old ids will still be
00856          * around when wrap-around occurs.
00857          */
00858 
00859         afterPtr->id = tsdPtr->afterId;
00860         tsdPtr->afterId += 1;
00861         Tcl_GetTime(&wakeup);
00862         wakeup.sec += (long)(ms / 1000);
00863         wakeup.usec += ((long)(ms % 1000)) * 1000;
00864         if (wakeup.usec > 1000000) {
00865             wakeup.sec++;
00866             wakeup.usec -= 1000000;
00867         }
00868         afterPtr->token = TclCreateAbsoluteTimerHandler(&wakeup, AfterProc,
00869                                                         (ClientData) afterPtr);
00870         afterPtr->nextPtr = assocPtr->firstAfterPtr;
00871         assocPtr->firstAfterPtr = afterPtr;
00872         Tcl_SetObjResult(interp, Tcl_ObjPrintf("after#%d", afterPtr->id));
00873         return TCL_OK;
00874     }
00875     case AFTER_CANCEL: {
00876         Tcl_Obj *commandPtr;
00877         char *command, *tempCommand;
00878         int tempLength;
00879 
00880         if (objc < 3) {
00881             Tcl_WrongNumArgs(interp, 2, objv, "id|command");
00882             return TCL_ERROR;
00883         }
00884         if (objc == 3) {
00885             commandPtr = objv[2];
00886         } else {
00887             commandPtr = Tcl_ConcatObj(objc-2, objv+2);;
00888         }
00889         command = Tcl_GetStringFromObj(commandPtr, &length);
00890         for (afterPtr = assocPtr->firstAfterPtr;  afterPtr != NULL;
00891                 afterPtr = afterPtr->nextPtr) {
00892             tempCommand = Tcl_GetStringFromObj(afterPtr->commandPtr,
00893                     &tempLength);
00894             if ((length == tempLength)
00895                     && (memcmp((void*) command, (void*) tempCommand,
00896                             (unsigned) length) == 0)) {
00897                 break;
00898             }
00899         }
00900         if (afterPtr == NULL) {
00901             afterPtr = GetAfterEvent(assocPtr, commandPtr);
00902         }
00903         if (objc != 3) {
00904             Tcl_DecrRefCount(commandPtr);
00905         }
00906         if (afterPtr != NULL) {
00907             if (afterPtr->token != NULL) {
00908                 Tcl_DeleteTimerHandler(afterPtr->token);
00909             } else {
00910                 Tcl_CancelIdleCall(AfterProc, (ClientData) afterPtr);
00911             }
00912             FreeAfterPtr(afterPtr);
00913         }
00914         break;
00915     }
00916     case AFTER_IDLE:
00917         if (objc < 3) {
00918             Tcl_WrongNumArgs(interp, 2, objv, "script script ...");
00919             return TCL_ERROR;
00920         }
00921         afterPtr = (AfterInfo *) ckalloc((unsigned) (sizeof(AfterInfo)));
00922         afterPtr->assocPtr = assocPtr;
00923         if (objc == 3) {
00924             afterPtr->commandPtr = objv[2];
00925         } else {
00926             afterPtr->commandPtr = Tcl_ConcatObj(objc-2, objv+2);
00927         }
00928         Tcl_IncrRefCount(afterPtr->commandPtr);
00929         afterPtr->id = tsdPtr->afterId;
00930         tsdPtr->afterId += 1;
00931         afterPtr->token = NULL;
00932         afterPtr->nextPtr = assocPtr->firstAfterPtr;
00933         assocPtr->firstAfterPtr = afterPtr;
00934         Tcl_DoWhenIdle(AfterProc, (ClientData) afterPtr);
00935         Tcl_SetObjResult(interp, Tcl_ObjPrintf("after#%d", afterPtr->id));
00936         break;
00937     case AFTER_INFO: {
00938         Tcl_Obj *resultListPtr;
00939 
00940         if (objc == 2) {
00941             for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL;
00942                     afterPtr = afterPtr->nextPtr) {
00943                 if (assocPtr->interp == interp) {
00944                     sprintf(buf, "after#%d", afterPtr->id);
00945                     Tcl_AppendElement(interp, buf);
00946                 }
00947             }
00948             return TCL_OK;
00949         }
00950         if (objc != 3) {
00951             Tcl_WrongNumArgs(interp, 2, objv, "?id?");
00952             return TCL_ERROR;
00953         }
00954         afterPtr = GetAfterEvent(assocPtr, objv[2]);
00955         if (afterPtr == NULL) {
00956             Tcl_AppendResult(interp, "event \"", TclGetString(objv[2]),
00957                     "\" doesn't exist", NULL);
00958             return TCL_ERROR;
00959         }
00960         resultListPtr = Tcl_NewObj();
00961         Tcl_ListObjAppendElement(interp, resultListPtr, afterPtr->commandPtr);
00962         Tcl_ListObjAppendElement(interp, resultListPtr, Tcl_NewStringObj(
00963                 (afterPtr->token == NULL) ? "idle" : "timer", -1));
00964         Tcl_SetObjResult(interp, resultListPtr);
00965         break;
00966     }
00967     default:
00968         Tcl_Panic("Tcl_AfterObjCmd: bad subcommand index to afterSubCmds");
00969     }
00970     return TCL_OK;
00971 }
00972 
00973 /*
00974  *----------------------------------------------------------------------
00975  *
00976  * AfterDelay --
00977  *
00978  *      Implements the blocking delay behaviour of [after $time]. Tricky
00979  *      because it has to take into account any time limit that has been set.
00980  *
00981  * Results:
00982  *      Standard Tcl result code (with error set if an error occurred due to a
00983  *      time limit being exceeded).
00984  *
00985  * Side effects:
00986  *      May adjust the time limit granularity marker.
00987  *
00988  *----------------------------------------------------------------------
00989  */
00990 
00991 static int
00992 AfterDelay(
00993     Tcl_Interp *interp,
00994     Tcl_WideInt ms)
00995 {
00996     Interp *iPtr = (Interp *) interp;
00997 
00998     Tcl_Time endTime, now;
00999     Tcl_WideInt diff;
01000 
01001     Tcl_GetTime(&endTime);
01002     endTime.sec += (long)(ms/1000);
01003     endTime.usec += ((int)(ms%1000))*1000;
01004     if (endTime.usec >= 1000000) {
01005         endTime.sec++;
01006         endTime.usec -= 1000000;
01007     }
01008 
01009     do {
01010         Tcl_GetTime(&now);
01011         if (iPtr->limit.timeEvent != NULL
01012             && TCL_TIME_BEFORE(iPtr->limit.time, now)) {
01013             iPtr->limit.granularityTicker = 0;
01014             if (Tcl_LimitCheck(interp) != TCL_OK) {
01015                 return TCL_ERROR;
01016             }
01017         }
01018         if (iPtr->limit.timeEvent == NULL
01019             || TCL_TIME_BEFORE(endTime, iPtr->limit.time)) {
01020             diff = TCL_TIME_DIFF_MS(endTime, now);
01021 #ifndef TCL_WIDE_INT_IS_LONG
01022             if (diff > LONG_MAX) {
01023                 diff = LONG_MAX;
01024             }
01025 #endif
01026             if (diff > 0) {
01027                 Tcl_Sleep((long)diff);
01028             }
01029         } else {
01030             diff = TCL_TIME_DIFF_MS(iPtr->limit.time, now);
01031 #ifndef TCL_WIDE_INT_IS_LONG
01032             if (diff > LONG_MAX) {
01033                 diff = LONG_MAX;
01034             }
01035 #endif
01036             if (diff > 0) {
01037                 Tcl_Sleep((long)diff);
01038             }
01039             if (Tcl_LimitCheck(interp) != TCL_OK) {
01040                 return TCL_ERROR;
01041             }
01042         }
01043     } while (TCL_TIME_BEFORE(now, endTime));
01044     return TCL_OK;
01045 }
01046 
01047 /*
01048  *----------------------------------------------------------------------
01049  *
01050  * GetAfterEvent --
01051  *
01052  *      This function parses an "after" id such as "after#4" and returns a
01053  *      pointer to the AfterInfo structure.
01054  *
01055  * Results:
01056  *      The return value is either a pointer to an AfterInfo structure, if one
01057  *      is found that corresponds to "cmdString" and is for interp, or NULL if
01058  *      no corresponding after event can be found.
01059  *
01060  * Side effects:
01061  *      None.
01062  *
01063  *----------------------------------------------------------------------
01064  */
01065 
01066 static AfterInfo *
01067 GetAfterEvent(
01068     AfterAssocData *assocPtr,   /* Points to "after"-related information for
01069                                  * this interpreter. */
01070     Tcl_Obj *commandPtr)
01071 {
01072     char *cmdString;            /* Textual identifier for after event, such as
01073                                  * "after#6". */
01074     AfterInfo *afterPtr;
01075     int id;
01076     char *end;
01077 
01078     cmdString = TclGetString(commandPtr);
01079     if (strncmp(cmdString, "after#", 6) != 0) {
01080         return NULL;
01081     }
01082     cmdString += 6;
01083     id = strtoul(cmdString, &end, 10);
01084     if ((end == cmdString) || (*end != 0)) {
01085         return NULL;
01086     }
01087     for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL;
01088             afterPtr = afterPtr->nextPtr) {
01089         if (afterPtr->id == id) {
01090             return afterPtr;
01091         }
01092     }
01093     return NULL;
01094 }
01095 
01096 /*
01097  *----------------------------------------------------------------------
01098  *
01099  * AfterProc --
01100  *
01101  *      Timer callback to execute commands registered with the "after"
01102  *      command.
01103  *
01104  * Results:
01105  *      None.
01106  *
01107  * Side effects:
01108  *      Executes whatever command was specified. If the command returns an
01109  *      error, then the command "bgerror" is invoked to process the error; if
01110  *      bgerror fails then information about the error is output on stderr.
01111  *
01112  *----------------------------------------------------------------------
01113  */
01114 
01115 static void
01116 AfterProc(
01117     ClientData clientData)      /* Describes command to execute. */
01118 {
01119     AfterInfo *afterPtr = (AfterInfo *) clientData;
01120     AfterAssocData *assocPtr = afterPtr->assocPtr;
01121     AfterInfo *prevPtr;
01122     int result;
01123     Tcl_Interp *interp;
01124 
01125     /*
01126      * First remove the callback from our list of callbacks; otherwise someone
01127      * could delete the callback while it's being executed, which could cause
01128      * a core dump.
01129      */
01130 
01131     if (assocPtr->firstAfterPtr == afterPtr) {
01132         assocPtr->firstAfterPtr = afterPtr->nextPtr;
01133     } else {
01134         for (prevPtr = assocPtr->firstAfterPtr; prevPtr->nextPtr != afterPtr;
01135                 prevPtr = prevPtr->nextPtr) {
01136             /* Empty loop body. */
01137         }
01138         prevPtr->nextPtr = afterPtr->nextPtr;
01139     }
01140 
01141     /*
01142      * Execute the callback.
01143      */
01144 
01145     interp = assocPtr->interp;
01146     Tcl_Preserve((ClientData) interp);
01147     result = Tcl_EvalObjEx(interp, afterPtr->commandPtr, TCL_EVAL_GLOBAL);
01148     if (result != TCL_OK) {
01149         Tcl_AddErrorInfo(interp, "\n    (\"after\" script)");
01150         TclBackgroundException(interp, result);
01151     }
01152     Tcl_Release((ClientData) interp);
01153 
01154     /*
01155      * Free the memory for the callback.
01156      */
01157 
01158     Tcl_DecrRefCount(afterPtr->commandPtr);
01159     ckfree((char *) afterPtr);
01160 }
01161 
01162 /*
01163  *----------------------------------------------------------------------
01164  *
01165  * FreeAfterPtr --
01166  *
01167  *      This function removes an "after" command from the list of those that
01168  *      are pending and frees its resources. This function does *not* cancel
01169  *      the timer handler; if that's needed, the caller must do it.
01170  *
01171  * Results:
01172  *      None.
01173  *
01174  * Side effects:
01175  *      The memory associated with afterPtr is released.
01176  *
01177  *----------------------------------------------------------------------
01178  */
01179 
01180 static void
01181 FreeAfterPtr(
01182     AfterInfo *afterPtr)                /* Command to be deleted. */
01183 {
01184     AfterInfo *prevPtr;
01185     AfterAssocData *assocPtr = afterPtr->assocPtr;
01186 
01187     if (assocPtr->firstAfterPtr == afterPtr) {
01188         assocPtr->firstAfterPtr = afterPtr->nextPtr;
01189     } else {
01190         for (prevPtr = assocPtr->firstAfterPtr; prevPtr->nextPtr != afterPtr;
01191                 prevPtr = prevPtr->nextPtr) {
01192             /* Empty loop body. */
01193         }
01194         prevPtr->nextPtr = afterPtr->nextPtr;
01195     }
01196     Tcl_DecrRefCount(afterPtr->commandPtr);
01197     ckfree((char *) afterPtr);
01198 }
01199 
01200 /*
01201  *----------------------------------------------------------------------
01202  *
01203  * AfterCleanupProc --
01204  *
01205  *      This function is invoked whenever an interpreter is deleted
01206  *      to cleanup the AssocData for "tclAfter".
01207  *
01208  * Results:
01209  *      None.
01210  *
01211  * Side effects:
01212  *      After commands are removed.
01213  *
01214  *----------------------------------------------------------------------
01215  */
01216 
01217         /* ARGSUSED */
01218 static void
01219 AfterCleanupProc(
01220     ClientData clientData,      /* Points to AfterAssocData for the
01221                                  * interpreter. */
01222     Tcl_Interp *interp)         /* Interpreter that is being deleted. */
01223 {
01224     AfterAssocData *assocPtr = (AfterAssocData *) clientData;
01225     AfterInfo *afterPtr;
01226 
01227     while (assocPtr->firstAfterPtr != NULL) {
01228         afterPtr = assocPtr->firstAfterPtr;
01229         assocPtr->firstAfterPtr = afterPtr->nextPtr;
01230         if (afterPtr->token != NULL) {
01231             Tcl_DeleteTimerHandler(afterPtr->token);
01232         } else {
01233             Tcl_CancelIdleCall(AfterProc, (ClientData) afterPtr);
01234         }
01235         Tcl_DecrRefCount(afterPtr->commandPtr);
01236         ckfree((char *) afterPtr);
01237     }
01238     ckfree((char *) assocPtr);
01239 }
01240 
01241 /*
01242  * Local Variables:
01243  * mode: c
01244  * c-basic-offset: 4
01245  * fill-column: 78
01246  * End:
01247  */



Generated on Wed Mar 12 12:18:23 2008 by  doxygen 1.5.1