diff options
Diffstat (limited to 'generic')
-rw-r--r-- | generic/tclEvent.c | 67 | ||||
-rw-r--r-- | generic/tclInt.h | 11 | ||||
-rw-r--r-- | generic/tclInterp.c | 11 | ||||
-rw-r--r-- | generic/tclTimer.c | 239 |
4 files changed, 180 insertions, 148 deletions
diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 53668d0..9a20cc2 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1384,9 +1384,10 @@ Tcl_VwaitObjCmd( int flags = TCL_ALL_EVENTS; /* default flags */ char *nameString; int optc = objc - 2; /* options count without cmd and varname */ - double ms = -1; - Tcl_Time lastNow, wakeup; + Tcl_WideInt usec = -1; + Tcl_WideInt lastNow = 0, wakeup = 0; long tolerance = 0; + size_t timeJumpEpoch = 0; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "?options? ?timeout? name"); @@ -1399,7 +1400,8 @@ Tcl_VwaitObjCmd( * we assume that option is not an integer, try to get numeric timeout */ if (!TclObjIsIndexOfTable(objv[optc], updateEventOptions) - && Tcl_GetDoubleFromObj(NULL, objv[optc], &ms) == TCL_OK) { + && TclpGetUTimeFromObj(NULL, objv[optc], &usec) == TCL_OK) { + if (usec < 0) { usec = 0; }; optc--; } @@ -1414,16 +1416,17 @@ Tcl_VwaitObjCmd( done = 0; /* if timeout specified - create timer event or no-wait by 0ms */ - if (ms != -1) { - if (ms > 0) { - Tcl_GetTime(&lastNow); - wakeup = lastNow; - TclTimeAddMilliseconds(&wakeup, ms); + if (usec != -1) { + if (usec > 0) { + lastNow = TclpGetMicroseconds(); + timeJumpEpoch = TclpGetLastTimeJumpEpoch(); #ifdef TMR_RES_TOLERANCE - tolerance = (ms < 1000 ? ms : 1000) * - (1000 * TMR_RES_TOLERANCE / 100); + tolerance = (usec < 1000000 ? usec : 1000000) * + TMR_RES_TOLERANCE / 100; + usec += tolerance / 3; #endif - } else if (ms == 0) { + wakeup = lastNow + usec; + } else { flags |= TCL_DONT_WAIT; } } @@ -1437,43 +1440,37 @@ Tcl_VwaitObjCmd( do { /* if wait - set blocking time */ - if (ms > 0) { + if (usec > 0) { Tcl_Time blockTime; - Tcl_GetTime(&blockTime); + Tcl_WideInt diff, now = TclpGetMicroseconds(); /* * Note time can be switched backwards, certainly adjust end-time * by possible time-jumps back. */ - if (TCL_TIME_BEFORE(blockTime, lastNow)) { - /* backwards time-jump - simply shift wakeup-time */ - wakeup.sec -= (lastNow.sec - blockTime.sec); - wakeup.usec -= (lastNow.usec - blockTime.usec); - if (wakeup.usec < 0) { - wakeup.usec += 1000000; - wakeup.sec--; - } + + if ( (diff = TclpGetLastTimeJump(&timeJumpEpoch)) != 0 + || (diff = (now - lastNow)) < 0 + ) { + /* recognized time-jump - simply shift wakeup-time */ + wakeup += diff; } /* calculate blocking time */ - lastNow = blockTime; - blockTime.sec = wakeup.sec - blockTime.sec; - blockTime.usec = wakeup.usec - blockTime.usec; - if (blockTime.usec < 0) { - blockTime.usec += 1000000; - blockTime.sec--; - } + lastNow = now; + diff = wakeup - now; + diff -= 1; /* overhead for Tcl_TraceVar / Tcl_UntraceVar */ /* be sure process at least one event */ - if ( blockTime.sec < 0 - || (blockTime.sec == 0 && blockTime.usec <= tolerance) - ) { + if (diff <= tolerance) { /* timeout occurs */ if (checktime) { done = -1; break; } /* expired, be sure non-negative values here */ - blockTime.usec = blockTime.sec = 0; + diff = 0; checktime = 1; } + blockTime.sec = diff / 1000000; + blockTime.usec = diff % 1000000; Tcl_SetMaxBlockTime(&blockTime); } if ((foundEvent = Tcl_DoOneEvent(flags)) <= 0) { @@ -1485,9 +1482,9 @@ Tcl_VwaitObjCmd( if (flags & TCL_DONT_WAIT) { foundEvent = 1; done = -2; - } else if (ms > 0 && foundEvent == 0) { + } else if (usec > 0 && foundEvent == 0) { foundEvent = 1; - } + } /* don't stop wait - no event expected here * (stop only on error case foundEvent < 0). */ if (foundEvent < 0) { @@ -1507,7 +1504,7 @@ Tcl_VwaitObjCmd( VwaitVarProc, (ClientData) &done); /* if timeout specified (and no errors) */ - if (ms != -1 && foundEvent > 0) { + if (usec != -1 && foundEvent > 0) { Tcl_Obj *objPtr; /* done - true, timeout false */ diff --git a/generic/tclInt.h b/generic/tclInt.h index d270042..fcb4752 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2886,19 +2886,28 @@ MODULE_SCOPE double TclpWideClickInMicrosec(void); # endif # endif #endif +MODULE_SCOPE Tcl_WideInt TclpGetLastTimeJump(size_t *epoch); +MODULE_SCOPE size_t TclpGetLastTimeJumpEpoch(void); MODULE_SCOPE Tcl_WideInt TclpGetMicroseconds(void); +MODULE_SCOPE int TclpGetUTimeFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + Tcl_WideInt *timePtr); +MODULE_SCOPE Tcl_WideInt TclpScaleUTime(Tcl_WideInt usec); + +MODULE_SCOPE void TclpUSleep(Tcl_WideInt usec); /* * Helper macros for working with times. TCL_TIME_BEFORE encodes how to write * the ordering relation on (normalized) times, and TCL_TIME_DIFF_MS resp. * TCL_TIME_DIFF_US compute the number of milliseconds or microseconds difference * between two times. Both macros use both of their arguments multiple times, * so make sure they are cheap and side-effect free. + * Macro TCL_TIME_TO_USEC converts Tcl_Time to microseconds. * The "prototypes" for these macros are: * * static int TCL_TIME_BEFORE(Tcl_Time t1, Tcl_Time t2); * static Tcl_WideInt TCL_TIME_DIFF_MS(Tcl_Time t1, Tcl_Time t2); * static Tcl_WideInt TCL_TIME_DIFF_US(Tcl_Time t1, Tcl_Time t2); + * static Tcl_WideInt TCL_TIME_TO_USEC(Tcl_Time t) */ #define TCL_TIME_BEFORE(t1, t2) \ @@ -2910,6 +2919,8 @@ MODULE_SCOPE Tcl_WideInt TclpGetMicroseconds(void); #define TCL_TIME_DIFF_US(t1, t2) \ (1000000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \ ((long)(t1).usec - (long)(t2).usec)) +#define TCL_TIME_TO_USEC(t) \ + ((Tcl_WideInt)(t).sec * 1000000 + (t).usec) static inline void TclTimeSetMilliseconds( diff --git a/generic/tclInterp.c b/generic/tclInterp.c index b461f40..03a9fe2 100644 --- a/generic/tclInterp.c +++ b/generic/tclInterp.c @@ -3718,19 +3718,14 @@ Tcl_LimitSetTime( Tcl_Time *timeLimitPtr) { Interp *iPtr = (Interp *) interp; - Tcl_Time nextMoment; + Tcl_WideInt nextMoment; memcpy(&iPtr->limit.time, timeLimitPtr, sizeof(Tcl_Time)); if (iPtr->limit.timeEvent != NULL) { TclDeleteTimerEntry(iPtr->limit.timeEvent); } - nextMoment.sec = timeLimitPtr->sec; - nextMoment.usec = timeLimitPtr->usec+10; - if (nextMoment.usec >= 1000000) { - nextMoment.sec++; - nextMoment.usec -= 1000000; - } - iPtr->limit.timeEvent = TclCreateTimerHandlerEx(&nextMoment, + nextMoment = TCL_TIME_TO_USEC(*timeLimitPtr) + 10; + iPtr->limit.timeEvent = TclpCreateTimerHandlerEx(nextMoment, TimeLimitCallback, TimeLimitDeleteCallback, 0, TCL_ABSTMR_EVENT); iPtr->limit.timeEvent->clientData = interp; iPtr->limit.exceeded &= ~TCL_LIMIT_TIME; diff --git a/generic/tclTimer.c b/generic/tclTimer.c index 9eaf944..aad8ee3 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -60,8 +60,10 @@ typedef struct AfterAssocData { */ typedef struct { - Tcl_WideInt relTimerBase; /* Time base (offset) of the last known relative, - * timer, used to revert all events to the new + Tcl_WideInt knownTime; /* Last know time */ + size_t knownTimeJumpEpoch; /* Epoch of the last time-jump */ + Tcl_WideInt relTimerBase; /* Time base of the first known relative */ + /* timer, used to revert all events to the new * base after possible time-jump (adjustment).*/ TimerEntry *relTimerList; /* First event in queue of relative timers. */ TimerEntry *relTimerTail; /* Last event in queue of relative timers. */ @@ -102,7 +104,8 @@ static Tcl_ThreadDataKey dataKey; static void AfterCleanupProc(ClientData clientData, Tcl_Interp *interp); -static int AfterDelay(Tcl_Interp *interp, double ms); +static int AfterDelay(Tcl_Interp *interp, Tcl_WideInt usec, + int absolute); static void AfterProc(ClientData clientData); static void FreeAfterPtr(ClientData clientData); static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *objPtr); @@ -366,7 +369,8 @@ TclpCreateTimerHandlerEx( int flags) /* If TCL_ABSTMR_EVENT, time is absolute */ { register TimerEntry *entryPtr, *entryPtrPos; - register TimerHandler *timerPtr, **tmrList, **tmrTail; + TimerEntry **tmrList, **tmrTail; + register TimerHandler *timerPtr; ThreadSpecificData *tsdPtr; tsdPtr = InitTimer(); @@ -391,18 +395,32 @@ TclpCreateTimerHandlerEx( if (flags & TCL_ABSTMR_EVENT) { tmrList = &tsdPtr->absTimerList; - tmrTail = &tsdPte->absTimerTail; + tmrTail = &tsdPtr->absTimerTail; } else { Tcl_WideInt now = TclpGetMicroseconds(); - tmrList = &tsdPtr->relTimerList; - tmrTail = &tsdPtr->relTimerTail; - if (tsdPtr->relTimerList) { - /* usec is relative current base (to now) */ - usec -= now - tsdPtr->relTimerBase; - } else { - tsdPtr->relTimerBase = now; - } + tmrList = &tsdPtr->relTimerList; + tmrTail = &tsdPtr->relTimerTail; + /* + * We should have the ability to ajust end-time of relative events, + * for possible time-jumps. + */ + if (tsdPtr->relTimerList) { + /* + * end-time = now + usec + * Adjust value of usec relative current base (to now), so + * end-time = base + relative event-time, which corresponds + * original end-time. + */ + Tcl_WideInt diff = TclpGetLastTimeJump(&tsdPtr->knownTimeJumpEpoch); + if (diff != 0) { /* jump recognized */ + tsdPtr->relTimerBase += diff; /* shift the base of relative events*/ + } + usec += now - tsdPtr->relTimerBase; + } else { + tsdPtr->knownTime = tsdPtr->relTimerBase = now; + tsdPtr->knownTimeJumpEpoch = TclpGetLastTimeJumpEpoch(); + } } timerPtr->time = usec; @@ -626,7 +644,7 @@ TclDeleteTimerEntry( tsdPtr->timerListEpoch++; /* signal-timer list was changed */ if (entryPtr->flags & TCL_ABSTMR_EVENT) { TclSpliceOutEx(entryPtr, tsdPtr->absTimerList, tsdPtr->absTimerTail); - } else e + } else { TclSpliceOutEx(entryPtr, tsdPtr->relTimerList, tsdPtr->relTimerTail); } } @@ -645,37 +663,51 @@ TclDeleteTimerEntry( } static Tcl_WideInt -TimerGetFirstTimeOffs( +TimerGetFirstTime( ThreadSpecificData *tsdPtr, + TimerEntry *relTimerList, + TimerEntry *absTimerList, + Tcl_WideInt now, TimerEntry **entryPtr) { Tcl_WideInt firstTime = -0x7FFFFFFFFFFFFFFFL; - Tcl_WideInt now = TclpGetMicroseconds(); - /* consider time-jump back */ - if (tsdPtr->relTimerList) { - if (now < tsdPtr->relTimerBase) { /* switched back */ + /* + * Consider time-jump back - if time jumped forwards, nothing to be done, + * because event will be executed early as specified. But for backwards + * jumps we should adjust relative base to avoid too long waiting for + * relative events. + */ + if (relTimerList) { + Tcl_WideInt diff; + if ( (diff = TclpGetLastTimeJump(&tsdPtr->knownTimeJumpEpoch)) != 0 + || (diff = (now - tsdPtr->knownTime)) < 0 /* switched back */ + ) { /* - * Because the real jump is unknown (resp. too complex to retrieve + * If the real jump is unknown (resp. too complex to retrieve * accross all threads), we simply accept possible small increment * of the real wait-time. */ - tsdPtr->relTimerBase = now; /* just shift the base back */ + tsdPtr->relTimerBase += diff; /* shift the base */ } + tsdPtr->knownTime = now; + /* end-time = base + relative event-time */ firstTime = tsdPtr->relTimerBase - + TimerEntry2TimerHandler(tsdPtr->absTimerList)->time; - if (entryPtr) { *entryPtr = tsdPtr->relTimerBase; } + + TimerEntry2TimerHandler(relTimerList)->time; + if (entryPtr) { *entryPtr = relTimerList; } } - if ( tsdPtr->absTimerList - && firstTime < TimerEntry2TimerHandler(tsdPtr->absTimerList)->time + if ( absTimerList + && firstTime < TimerEntry2TimerHandler(absTimerList)->time ) { - firstTime = TimerEntry2TimerHandler(tsdPtr->absTimerList)->time; - if (entryPtr) { *entryPtr = tsdPtr->absTimerList; } + /* end-time = absolute event-time */ + firstTime = TimerEntry2TimerHandler(absTimerList)->time; + if (entryPtr) { *entryPtr = absTimerList; } } - return firstTime - now; + return firstTime; } + /* *---------------------------------------------------------------------- * @@ -723,7 +755,9 @@ TimerSetupProc( * Compute the timeout for the next timer on the list. */ - Tcl_WideInt timeOffs = TimerGetFirstTimeOffs(tsdPtr, NULL); + Tcl_WideInt now = TclpGetMicroseconds(); + Tcl_WideInt timeOffs = TimerGetFirstTime(tsdPtr, + tsdPtr->relTimerList, tsdPtr->absTimerList, now, NULL) - now; if (timeOffs > 0) { blockTime.sec = (long) (timeOffs / 1000000); @@ -776,7 +810,7 @@ TimerCheckProc( ClientData data, /* Specific data. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { - Tcl_WideInt timeOffs; + Tcl_WideInt now, timeOffs; ThreadSpecificData *tsdPtr = (ThreadSpecificData *)data; long tolerance = 0; @@ -794,8 +828,10 @@ TimerCheckProc( /* * Verify the first timer on the queue. */ - timeOffs = TimerGetFirstTimeOffs(tsdPtr, NULL); - + now = TclpGetMicroseconds(); + timeOffs = TimerGetFirstTime(tsdPtr, + tsdPtr->relTimerList, tsdPtr->absTimerList, now, NULL) - now; + #ifdef TMR_RES_TOLERANCE /* consider timer resolution tolerance (avoid busy wait) */ tolerance = ((timeOffs <= 1000000) ? timeOffs : 1000000) * @@ -836,8 +872,8 @@ TimerCheckProc( int TclServiceTimerEvents(void) { - TimerEntry *entryPtr, *nextPtr; - Tcl_Time time, entrytm; + TimerEntry *entryPtr, *relTimerList, *absTimerList; + Tcl_WideInt now, entryTime; size_t currentGeneration, currentEpoch; int prevTmrPending; ThreadSpecificData *tsdPtr = InitTimer(); @@ -900,30 +936,28 @@ TclServiceTimerEvents(void) return -1; } - /* Hereafter all timer events with time before now */ - if (!tsdPtr->absTimerList) { - goto done; - } - Tcl_GetTime(&time); - for (entryPtr = tsdPtr->absTimerList; - entryPtr != NULLe - entryPtr = nextPtr - ) { - nextPtr = entryPtr->nextPtr; + /* Hereafter all relative and absolute timer events with time before now */ + relTimerList = tsdPtr->relTimerList; + absTimerList = tsdPtr->absTimerList; + while (relTimerList || absTimerList) + { + now = TclpGetMicroseconds(); + entryTime = TimerGetFirstTime(tsdPtr, relTimerList, absTimerList, now, &entryPtr); - entrytm = TimerEntry2TimerHandler(entryPtr)->time; #ifdef TMR_RES_TOLERANCE - entrytm.usec -= ((entrytm.sec <= 0) ? entrytm.usec : 1000000) * - (TMR_RES_TOLERANCE / 100); - if (entrytm.usec < 0) { - entrytm.usec += 1000000; - entrytm.sec--; - } + entryTime -= ((entryTime <= 1000000) ? entryTime : 1000000) * + TMR_RES_TOLERANCE / 100; #endif - if (TCL_TIME_BEFORE(time, entrytm)) { + if (now < entryTime) { break; } + /* Current list head to next entry */ + if (entryPtr == relTimerList) { + relTimerList = relTimerList->nextPtr; + } else { + absTimerList = absTimerList->nextPtr; + } /* * Bypass timers of newer generation. */ @@ -935,16 +969,19 @@ TclServiceTimerEvents(void) } tsdPtr->timerListEpoch++; /* signal-timer list was changed */ + currentEpoch = tsdPtr->timerListEpoch; /* * Remove the handler from the queue before invoking it, to avoid * potential reentrancy problems. */ - - TclSpliceOutEx(entryPtr, - tsdPtr->absTimerLise, tsdPtr->absTimerTail); - - currentEpoch = tsdPtr->timerListEpoch; + if (!(entryPtr->flags & TCL_ABSTMR_EVENT)) { + TclSpliceOutEx(entryPtr, + tsdPtr->relTimerList, tsdPtr->relTimerTail); + } else { + TclSpliceOutEx(entryPtr, + tsdPtr->absTimerList, tsdPtr->absTimerTail); + } /* reset current timer pending (correct process nested wait event) */ prevTmrPending = tsdPtr->timerPending; @@ -969,17 +1006,15 @@ TclServiceTimerEvents(void) } } -done: /* pending timer events, so mark (queue) timer events */ if (tsdPtr->timerPending > 1) { tsdPtr->timerPending = 1; - return -1; } /* Reset generation if both timer queue are empty */ - if (!tsdPtr->absTimerList) { - tsdPtr->timerGeneratioe = 0; + if (!tsdPtr->relTimerList && !tsdPtr->absTimerList) { + tsdPtr->timerGeneration = 0; } /* Compute the next timeout (later via TimerSetupProc using the first timer). */ @@ -1239,7 +1274,7 @@ TclServiceIdle(void) int TclpGetUTimeFromObj( Tcl_Interp *interp, /* Current interpreter or NULL. */ - Tcl_Obj *CONST objPtr, /* Object to read numeric time (in milliseconds). */ + Tcl_Obj *objPtr, /* Object to read numeric time (in milliseconds). */ Tcl_WideInt *timePtr) /* Resulting time if converted (in microseconds). */ { if (objPtr->typePtr != &tclDoubleType) { @@ -1255,7 +1290,7 @@ TclpGetUTimeFromObj( } if (1) { double ms; - if (Tcl_GetDoubleFromObj(interp, objv[1], &ms) == TCL_OK) { + if (Tcl_GetDoubleFromObj(interp, objPtr, &ms) == TCL_OK) { if (ms < 0x7FFFFFFFFFFFFFFFL / 1000) { /* avoid overflow */ /* use precise as possible calculation by double (microseconds) */ *timePtr = ((Tcl_WideInt)ms) * 1000 + (((long)(ms*1000)) % 1000); @@ -1563,16 +1598,16 @@ Tcl_AfterObjCmd( static int AfterDelay( Tcl_Interp *interp, - double ms, + Tcl_WideInt usec, int absolute) { Interp *iPtr = (Interp *) interp; - Tcl_Time endTime, now, lastNow; - Tcl_WideInt diff; + Tcl_WideInt endTime, now, lastNow, diff; long tolerance = 0; + size_t timeJumpEpoch = 0; - if (ms <= 0) { + if (usec <= 0) { /* to cause a context switch only */ Tcl_Sleep(0); return TCL_OK; @@ -1580,44 +1615,42 @@ AfterDelay( /* calculate possible maximal tolerance (in usec) of original wait-time */ #ifdef TMR_RES_TOLERANCE - tolerance = ((ms < 1000) ? ms : 1000) * (1000 * TMR_RES_TOLERANCE / 100); + tolerance = ((usec < 1000000) ? usec : 1000000) * TMR_RES_TOLERANCE / 100; #endif - Tcl_GetTime(&now); - lastNow = endTime = now; - if (absolute) - TclTimeAddMilliseconds(&endTime, ms); + lastNow = now = TclpGetMicroseconds(); + endTime = usec; + timeJumpEpoch = TclpGetLastTimeJumpEpoch(); + + if (!absolute) { + endTime += now; + if (endTime < now) { /* overflow */ + endTime = 0x7FFFFFFFFFFFFFFFL; + } + } do { - if (iPtr->limit.timeEvent != NULL - && TCL_TIME_BEFORE(iPtr->limit.time, now)) { + if ( iPtr->limit.timeEvent != NULL + && now > TCL_TIME_TO_USEC(iPtr->limit.time) + ) { 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(endTime, now); -#ifndef TCL_WIDE_INT_IS_LONG - if (diff > LONG_MAX) { - diff = LONG_MAX; - } -#endif + if ( iPtr->limit.timeEvent == NULL + || endTime < (diff = TCL_TIME_TO_USEC(iPtr->limit.time)) + ) { + diff = endTime - now; if (diff > 0) { - Tcl_Sleep((long)diff); - Tcl_GetTime(&now); + TclpUSleep(diff); + now = TclpGetMicroseconds(); } } else { - diff = TCL_TIME_DIFF_MS(iPtr->limit.time, now); -#ifndef TCL_WIDE_INT_IS_LONG - if (diff > LONG_MAX) { - diff = LONG_MAX; - } -#endif + diff -= now; if (diff > 0) { - Tcl_Sleep((long)diff); - Tcl_GetTime(&now); + TclpUSleep(diff); + now = TclpGetMicroseconds(); } if (Tcl_LimitCheck(interp) != TCL_OK) { return TCL_ERROR; @@ -1626,24 +1659,20 @@ AfterDelay( /* * Note time can be switched backwards, certainly adjust end-time - * by possible time-jumps back. + * by possible time-jumps (for relative sleep). */ - if (!absolute && TCL_TIME_BEFORE(now, lastNow)) { - /* backwards time-jump - simply shift wakeup-time */ - endTime.sec -= (lastNow.sec - now.sec); - endTime.usec -= (lastNow.usec - now.usec); - if (endTime.usec < 0) { - endTime.usec += 1000000; - endTime.sec--; - } + if (!absolute + && ( (diff = TclpGetLastTimeJump(&timeJumpEpoch)) != 0 + || (diff = (now - lastNow)) < 0 + ) + ) { + /* recognized time-jump - simply shift wakeup-time */ + endTime += diff; } lastNow = now; /* consider timer resolution tolerance (avoid busy wait) */ - } while ( - (now.sec > endTime.sec) - || (now.sec == endTime.sec && now.usec >= endTime.usec - tolerance) - ); + } while (now < endTime); return TCL_OK; } |