Tcl Source Code

Artifact [605836e322]
Login

Artifact 605836e322b60a3f7d985d3453d2fdcbafaf00f3:

Attachment "tip143.patch" to ticket [926771ffff] added by dkf 2004-04-20 05:28:33.
Index: generic/tcl.decls
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tcl.decls,v
retrieving revision 1.103
diff -u -b -B -r1.103 tcl.decls
--- generic/tcl.decls	17 Mar 2004 18:14:12 -0000	1.103
+++ generic/tcl.decls	19 Apr 2004 22:18:31 -0000
@@ -1860,6 +1860,56 @@
     Tcl_ExitProc *Tcl_SetExitProc(Tcl_ExitProc *proc)
 }
 
+# TIP#143 API
+declare 520 generic {
+    void Tcl_LimitAddHandler(Tcl_Interp *interp, int type,
+	    Tcl_LimitHandlerProc *handlerProc, ClientData clientData,
+	    Tcl_LimitHandlerDeleteProc *deleteProc)
+}
+declare 521 generic {
+    void Tcl_LimitRemoveHandler(Tcl_Interp *interp, int type,
+	    Tcl_LimitHandlerProc *handlerProc, ClientData clientData)
+}
+declare 522 generic {
+    int Tcl_LimitReady(Tcl_Interp *interp)
+}
+declare 523 generic {
+    int Tcl_LimitCheck(Tcl_Interp *interp)
+}
+declare 524 generic {
+    int Tcl_LimitExceeded(Tcl_Interp *interp)
+}
+declare 525 generic {
+    void Tcl_LimitSetCommands(Tcl_Interp *interp, int commandLimit)
+}
+declare 526 generic {
+    void Tcl_LimitSetTime(Tcl_Interp *interp, Tcl_Time *timeLimitPtr)
+}
+declare 527 generic {
+    void Tcl_LimitSetGranularity(Tcl_Interp *interp, int type, int granularity)
+}
+declare 528 generic {
+    int Tcl_LimitTypeEnabled(Tcl_Interp *interp, int type)
+}
+declare 529 generic {
+    int Tcl_LimitTypeExceeded(Tcl_Interp *interp, int type)
+}
+declare 530 generic {
+    void Tcl_LimitTypeSet(Tcl_Interp *interp, int type)
+}
+declare 531 generic {
+    void Tcl_LimitTypeReset(Tcl_Interp *interp, int type)
+}
+declare 532 generic {
+    int Tcl_LimitGetCommands(Tcl_Interp *interp)
+}
+declare 533 generic {
+    void Tcl_LimitGetTime(Tcl_Interp *interp, Tcl_Time *timeLimitPtr)
+}
+declare 534 generic {
+    int Tcl_LimitGetGranularity(Tcl_Interp *interp, int type)
+}
+
 ##############################################################################
 
 # Define the platform specific public Tcl interface.  These functions are
Index: generic/tcl.h
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tcl.h,v
retrieving revision 1.176
diff -u -b -B -r1.176 tcl.h
--- generic/tcl.h	31 Mar 2004 18:13:24 -0000	1.176
+++ generic/tcl.h	19 Apr 2004 22:18:31 -0000
@@ -2226,6 +2226,24 @@
 } Tcl_Config;
 
 
+/*
+ * Flags for TIP#143 limits, detailing which limits are active in an
+ * interpreter.  Used for Tcl_{Add,Remove}LimitHandler type argument.
+ */
+
+#define TCL_LIMIT_COMMANDS	0x01
+#define TCL_LIMIT_TIME		0x02
+
+/*
+ * Structure containing information about a limit handler to be called
+ * when a command- or time-limit is exceeded by an interpreter.
+ */
+
+typedef void (Tcl_LimitHandlerProc) _ANSI_ARGS_((ClientData clientData,
+	Tcl_Interp *interp));
+typedef void (Tcl_LimitHandlerDeleteProc) _ANSI_ARGS_((ClientData clientData));
+
+
 #ifndef TCL_NO_DEPRECATED
 
     /*
Index: generic/tclBasic.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclBasic.c,v
retrieving revision 1.99
diff -u -b -B -r1.99 tclBasic.c
--- generic/tclBasic.c	6 Apr 2004 22:25:48 -0000	1.99
+++ generic/tclBasic.c	19 Apr 2004 22:18:32 -0000
@@ -411,6 +411,11 @@
 
     iPtr->stubTable = &tclStubs;
 
+    /*
+     * TIP#143: Initialise the resource limit support.
+     */
+
+    TclInitLimitSupport(interp);
     
     /*
      * Create the core commands. Do it here, rather than calling
@@ -3121,7 +3126,7 @@
      */
     cmdPtr->refCount++;
     iPtr->cmdCount++;
-    if ( code == TCL_OK && traceCode == TCL_OK) {
+    if (code == TCL_OK && traceCode == TCL_OK && !Tcl_LimitExceeded(interp)) {
 	savedVarFramePtr = iPtr->varFramePtr;
 	if (flags & TCL_EVAL_GLOBAL) {
 	    iPtr->varFramePtr = NULL;
@@ -3132,6 +3137,9 @@
     if (Tcl_AsyncReady()) {
 	code = Tcl_AsyncInvoke(interp, code);
     }
+    if (code == TCL_OK && Tcl_LimitReady(interp)) {
+	code = Tcl_LimitCheck(interp);
+    }
 
     /*
      * Call 'leave' command traces
@@ -3142,7 +3150,7 @@
 	Tcl_Obj *saveOptions = iPtr->returnOpts;
 	Tcl_IncrRefCount(saveOptions);
         if ((cmdPtr->flags & CMD_HAS_EXEC_TRACES) && (traceCode == TCL_OK)) {
-            traceCode = TclCheckExecutionTraces (interp, command, length,
+            traceCode = TclCheckExecutionTraces(interp, command, length,
                    cmdPtr, code, TCL_TRACE_LEAVE_EXEC, objc, objv);
         }
         if (iPtr->tracePtr != NULL && traceCode == TCL_OK) {
Index: generic/tclCmdAH.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclCmdAH.c,v
retrieving revision 1.44
diff -u -b -B -r1.44 tclCmdAH.c
--- generic/tclCmdAH.c	6 Apr 2004 22:25:48 -0000	1.44
+++ generic/tclCmdAH.c	19 Apr 2004 22:18:32 -0000
@@ -257,6 +257,17 @@
 
     result = Tcl_EvalObjEx(interp, objv[1], 0);
     
+    /*
+     * We disable catch in interpreters where the limit has been exceeded.
+     */
+    if (Tcl_LimitExceeded(interp)) {
+	char msg[32 + TCL_INTEGER_SPACE];
+
+	sprintf(msg, "\n    (\"catch\" body line %d)", interp->errorLine);
+	Tcl_AddErrorInfo(interp, msg);
+	return TCL_ERROR;
+    }
+
     if (objc >= 3) {
 	if (NULL == Tcl_ObjSetVar2(interp, varNamePtr, NULL,
 		Tcl_GetObjResult(interp), 0)) {
Index: generic/tclDecls.h
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclDecls.h,v
retrieving revision 1.102
diff -u -b -B -r1.102 tclDecls.h
--- generic/tclDecls.h	17 Mar 2004 18:14:13 -0000	1.102
+++ generic/tclDecls.h	19 Apr 2004 22:18:32 -0000
@@ -3222,6 +3222,98 @@
 /* 519 */
 EXTERN Tcl_ExitProc *	Tcl_SetExitProc _ANSI_ARGS_((Tcl_ExitProc * proc));
 #endif
+#ifndef Tcl_LimitAddHandler_TCL_DECLARED
+#define Tcl_LimitAddHandler_TCL_DECLARED
+/* 520 */
+EXTERN void		Tcl_LimitAddHandler _ANSI_ARGS_((Tcl_Interp * interp, 
+				int type, Tcl_LimitHandlerProc * handlerProc, 
+				ClientData clientData, 
+				Tcl_LimitHandlerDeleteProc * deleteProc));
+#endif
+#ifndef Tcl_LimitRemoveHandler_TCL_DECLARED
+#define Tcl_LimitRemoveHandler_TCL_DECLARED
+/* 521 */
+EXTERN void		Tcl_LimitRemoveHandler _ANSI_ARGS_((
+				Tcl_Interp * interp, int type, 
+				Tcl_LimitHandlerProc * handlerProc, 
+				ClientData clientData));
+#endif
+#ifndef Tcl_LimitReady_TCL_DECLARED
+#define Tcl_LimitReady_TCL_DECLARED
+/* 522 */
+EXTERN int		Tcl_LimitReady _ANSI_ARGS_((Tcl_Interp * interp));
+#endif
+#ifndef Tcl_LimitCheck_TCL_DECLARED
+#define Tcl_LimitCheck_TCL_DECLARED
+/* 523 */
+EXTERN int		Tcl_LimitCheck _ANSI_ARGS_((Tcl_Interp * interp));
+#endif
+#ifndef Tcl_LimitExceeded_TCL_DECLARED
+#define Tcl_LimitExceeded_TCL_DECLARED
+/* 524 */
+EXTERN int		Tcl_LimitExceeded _ANSI_ARGS_((Tcl_Interp * interp));
+#endif
+#ifndef Tcl_LimitSetCommands_TCL_DECLARED
+#define Tcl_LimitSetCommands_TCL_DECLARED
+/* 525 */
+EXTERN void		Tcl_LimitSetCommands _ANSI_ARGS_((
+				Tcl_Interp * interp, int commandLimit));
+#endif
+#ifndef Tcl_LimitSetTime_TCL_DECLARED
+#define Tcl_LimitSetTime_TCL_DECLARED
+/* 526 */
+EXTERN void		Tcl_LimitSetTime _ANSI_ARGS_((Tcl_Interp * interp, 
+				Tcl_Time * timeLimitPtr));
+#endif
+#ifndef Tcl_LimitSetGranularity_TCL_DECLARED
+#define Tcl_LimitSetGranularity_TCL_DECLARED
+/* 527 */
+EXTERN void		Tcl_LimitSetGranularity _ANSI_ARGS_((
+				Tcl_Interp * interp, int type, 
+				int granularity));
+#endif
+#ifndef Tcl_LimitTypeEnabled_TCL_DECLARED
+#define Tcl_LimitTypeEnabled_TCL_DECLARED
+/* 528 */
+EXTERN int		Tcl_LimitTypeEnabled _ANSI_ARGS_((
+				Tcl_Interp * interp, int type));
+#endif
+#ifndef Tcl_LimitTypeExceeded_TCL_DECLARED
+#define Tcl_LimitTypeExceeded_TCL_DECLARED
+/* 529 */
+EXTERN int		Tcl_LimitTypeExceeded _ANSI_ARGS_((
+				Tcl_Interp * interp, int type));
+#endif
+#ifndef Tcl_LimitTypeSet_TCL_DECLARED
+#define Tcl_LimitTypeSet_TCL_DECLARED
+/* 530 */
+EXTERN void		Tcl_LimitTypeSet _ANSI_ARGS_((Tcl_Interp * interp, 
+				int type));
+#endif
+#ifndef Tcl_LimitTypeReset_TCL_DECLARED
+#define Tcl_LimitTypeReset_TCL_DECLARED
+/* 531 */
+EXTERN void		Tcl_LimitTypeReset _ANSI_ARGS_((Tcl_Interp * interp, 
+				int type));
+#endif
+#ifndef Tcl_LimitGetCommands_TCL_DECLARED
+#define Tcl_LimitGetCommands_TCL_DECLARED
+/* 532 */
+EXTERN int		Tcl_LimitGetCommands _ANSI_ARGS_((
+				Tcl_Interp * interp));
+#endif
+#ifndef Tcl_LimitGetTime_TCL_DECLARED
+#define Tcl_LimitGetTime_TCL_DECLARED
+/* 533 */
+EXTERN void		Tcl_LimitGetTime _ANSI_ARGS_((Tcl_Interp * interp, 
+				Tcl_Time * timeLimitPtr));
+#endif
+#ifndef Tcl_LimitGetGranularity_TCL_DECLARED
+#define Tcl_LimitGetGranularity_TCL_DECLARED
+/* 534 */
+EXTERN int		Tcl_LimitGetGranularity _ANSI_ARGS_((
+				Tcl_Interp * interp, int type));
+#endif
 
 typedef struct TclStubHooks {
     struct TclPlatStubs *tclPlatStubs;
@@ -3783,6 +3875,21 @@
     void (*tcl_GetCommandFullName) _ANSI_ARGS_((Tcl_Interp * interp, Tcl_Command command, Tcl_Obj * objPtr)); /* 517 */
     int (*tcl_FSEvalFileEx) _ANSI_ARGS_((Tcl_Interp * interp, Tcl_Obj * fileName, CONST char * encodingName)); /* 518 */
     Tcl_ExitProc * (*tcl_SetExitProc) _ANSI_ARGS_((Tcl_ExitProc * proc)); /* 519 */
+    void (*tcl_LimitAddHandler) _ANSI_ARGS_((Tcl_Interp * interp, int type, Tcl_LimitHandlerProc * handlerProc, ClientData clientData, Tcl_LimitHandlerDeleteProc * deleteProc)); /* 520 */
+    void (*tcl_LimitRemoveHandler) _ANSI_ARGS_((Tcl_Interp * interp, int type, Tcl_LimitHandlerProc * handlerProc, ClientData clientData)); /* 521 */
+    int (*tcl_LimitReady) _ANSI_ARGS_((Tcl_Interp * interp)); /* 522 */
+    int (*tcl_LimitCheck) _ANSI_ARGS_((Tcl_Interp * interp)); /* 523 */
+    int (*tcl_LimitExceeded) _ANSI_ARGS_((Tcl_Interp * interp)); /* 524 */
+    void (*tcl_LimitSetCommands) _ANSI_ARGS_((Tcl_Interp * interp, int commandLimit)); /* 525 */
+    void (*tcl_LimitSetTime) _ANSI_ARGS_((Tcl_Interp * interp, Tcl_Time * timeLimitPtr)); /* 526 */
+    void (*tcl_LimitSetGranularity) _ANSI_ARGS_((Tcl_Interp * interp, int type, int granularity)); /* 527 */
+    int (*tcl_LimitTypeEnabled) _ANSI_ARGS_((Tcl_Interp * interp, int type)); /* 528 */
+    int (*tcl_LimitTypeExceeded) _ANSI_ARGS_((Tcl_Interp * interp, int type)); /* 529 */
+    void (*tcl_LimitTypeSet) _ANSI_ARGS_((Tcl_Interp * interp, int type)); /* 530 */
+    void (*tcl_LimitTypeReset) _ANSI_ARGS_((Tcl_Interp * interp, int type)); /* 531 */
+    int (*tcl_LimitGetCommands) _ANSI_ARGS_((Tcl_Interp * interp)); /* 532 */
+    void (*tcl_LimitGetTime) _ANSI_ARGS_((Tcl_Interp * interp, Tcl_Time * timeLimitPtr)); /* 533 */
+    int (*tcl_LimitGetGranularity) _ANSI_ARGS_((Tcl_Interp * interp, int type)); /* 534 */
 } TclStubs;
 
 #ifdef __cplusplus
@@ -5903,6 +6010,66 @@
 #define Tcl_SetExitProc \
 	(tclStubsPtr->tcl_SetExitProc) /* 519 */
 #endif
+#ifndef Tcl_LimitAddHandler
+#define Tcl_LimitAddHandler \
+	(tclStubsPtr->tcl_LimitAddHandler) /* 520 */
+#endif
+#ifndef Tcl_LimitRemoveHandler
+#define Tcl_LimitRemoveHandler \
+	(tclStubsPtr->tcl_LimitRemoveHandler) /* 521 */
+#endif
+#ifndef Tcl_LimitReady
+#define Tcl_LimitReady \
+	(tclStubsPtr->tcl_LimitReady) /* 522 */
+#endif
+#ifndef Tcl_LimitCheck
+#define Tcl_LimitCheck \
+	(tclStubsPtr->tcl_LimitCheck) /* 523 */
+#endif
+#ifndef Tcl_LimitExceeded
+#define Tcl_LimitExceeded \
+	(tclStubsPtr->tcl_LimitExceeded) /* 524 */
+#endif
+#ifndef Tcl_LimitSetCommands
+#define Tcl_LimitSetCommands \
+	(tclStubsPtr->tcl_LimitSetCommands) /* 525 */
+#endif
+#ifndef Tcl_LimitSetTime
+#define Tcl_LimitSetTime \
+	(tclStubsPtr->tcl_LimitSetTime) /* 526 */
+#endif
+#ifndef Tcl_LimitSetGranularity
+#define Tcl_LimitSetGranularity \
+	(tclStubsPtr->tcl_LimitSetGranularity) /* 527 */
+#endif
+#ifndef Tcl_LimitTypeEnabled
+#define Tcl_LimitTypeEnabled \
+	(tclStubsPtr->tcl_LimitTypeEnabled) /* 528 */
+#endif
+#ifndef Tcl_LimitTypeExceeded
+#define Tcl_LimitTypeExceeded \
+	(tclStubsPtr->tcl_LimitTypeExceeded) /* 529 */
+#endif
+#ifndef Tcl_LimitTypeSet
+#define Tcl_LimitTypeSet \
+	(tclStubsPtr->tcl_LimitTypeSet) /* 530 */
+#endif
+#ifndef Tcl_LimitTypeReset
+#define Tcl_LimitTypeReset \
+	(tclStubsPtr->tcl_LimitTypeReset) /* 531 */
+#endif
+#ifndef Tcl_LimitGetCommands
+#define Tcl_LimitGetCommands \
+	(tclStubsPtr->tcl_LimitGetCommands) /* 532 */
+#endif
+#ifndef Tcl_LimitGetTime
+#define Tcl_LimitGetTime \
+	(tclStubsPtr->tcl_LimitGetTime) /* 533 */
+#endif
+#ifndef Tcl_LimitGetGranularity
+#define Tcl_LimitGetGranularity \
+	(tclStubsPtr->tcl_LimitGetGranularity) /* 534 */
+#endif
 
 #endif /* defined(USE_TCL_STUBS) && !defined(USE_TCL_STUB_PROCS) */
 
Index: generic/tclEvent.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclEvent.c,v
retrieving revision 1.32
diff -u -b -B -r1.32 tclEvent.c
--- generic/tclEvent.c	6 Apr 2004 22:25:50 -0000	1.32
+++ generic/tclEvent.c	19 Apr 2004 22:18:32 -0000
@@ -1102,6 +1102,9 @@
     foundEvent = 1;
     while (!done && foundEvent) {
 	foundEvent = Tcl_DoOneEvent(TCL_ALL_EVENTS);
+	if (Tcl_LimitExceeded(interp)) {
+	    return TCL_ERROR;
+	}
     }
     Tcl_UntraceVar(interp, nameString,
 	    TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
@@ -1188,7 +1191,9 @@
     }
     
     while (Tcl_DoOneEvent(flags) != 0) {
-	/* Empty loop body */
+	if (Tcl_LimitExceeded(interp)) {
+	    return TCL_ERROR;
+	}
     }
 
     /*
Index: generic/tclExecute.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclExecute.c,v
retrieving revision 1.124
diff -u -b -B -r1.124 tclExecute.c
--- generic/tclExecute.c	6 Apr 2004 22:25:50 -0000	1.124
+++ generic/tclExecute.c	19 Apr 2004 22:18:32 -0000
@@ -1011,21 +1011,20 @@
     }
     iPtr->numLevels--;
 
-
     /*
      * If no commands at all were executed, check for asynchronous
-     * handlers so that they at least get one change to execute.
-     * This is needed to handle event loops written in Tcl with
-     * empty bodies.
+     * handlers and resource limits so that they at least get one
+     * change to execute.  This is needed to handle event loops
+     * written in Tcl with empty bodies.
      */
 
-    if ((oldCount == iPtr->cmdCount) && Tcl_AsyncReady()) {
+    if (oldCount == iPtr->cmdCount) {
+	if (Tcl_AsyncReady()) {
 	result = Tcl_AsyncInvoke(interp, result);
     
-
 	/*
-	 * If an error occurred, record information about what was being
-	 * executed when the error occurred.
+	     * If an error occurred, record information about what was
+	     * being executed when the error occurred.
 	 */
 	
 	if ((result == TCL_ERROR) && !(iPtr->flags & ERR_ALREADY_LOGGED)) {
@@ -1033,6 +1032,20 @@
 	    Tcl_LogCommandInfo(interp, script, script, numSrcBytes);
 	}
     }
+	if (result==TCL_OK && Tcl_LimitReady(interp)) {
+	    result = Tcl_LimitCheck(interp);
+
+	    /*
+	     * If an error occurred, record information about what was
+	     * being executed when the error occurred.
+	     */
+
+	    if (result==TCL_ERROR && !(iPtr->flags & ERR_ALREADY_LOGGED)) {
+		script = Tcl_GetStringFromObj(objPtr, &numSrcBytes);
+		Tcl_LogCommandInfo(interp, script, script, numSrcBytes);
+	    }
+	}
+    }
 
     iPtr->flags &= ~ERR_ALREADY_LOGGED;
     return result;
@@ -1229,7 +1242,8 @@
      * of the form (2**n-1).
      */
 
-    if (!(instructionCount++ & ASYNC_CHECK_COUNT_MASK) && Tcl_AsyncReady()) {
+    if ((instructionCount++ & ASYNC_CHECK_COUNT_MASK) == 0) {
+	if (Tcl_AsyncReady()) {
 	DECACHE_STACK_INFO();
 	result = Tcl_AsyncInvoke(interp, result);
 	CACHE_STACK_INFO();
@@ -1237,6 +1251,15 @@
 	    goto checkForCatch;
 	}
     }
+	if (Tcl_LimitReady(interp)) {
+	    DECACHE_STACK_INFO();
+	    result = Tcl_LimitCheck(interp);
+	    CACHE_STACK_INFO();
+	    if (result == TCL_ERROR) {
+		goto checkForCatch;
+	    }
+	}
+    }
 
     switch (*pc) {
     case INST_START_CMD:
@@ -4549,6 +4572,20 @@
 	    iPtr->flags |= ERR_ALREADY_LOGGED;
 	}
     }
+    /*
+     * We must not catch an exceeded limit.  Instead, it blows
+     * outwards until we either hit another interpreter (presumably
+     * where the limit is not exceeded) or we get to the top-level.
+     */
+    if (Tcl_LimitExceeded(interp)) {
+#ifdef TCL_COMPILE_DEBUG
+	if (traceInstructions) {
+	    fprintf(stdout, "   ... limit exceeded, returning %s\n",
+	            StringForResultCode(result));
+	}
+#endif
+	goto abnormalReturn;
+    }
     if (catchTop == initCatchTop) {
 #ifdef TCL_COMPILE_DEBUG
 	if (traceInstructions) {
Index: generic/tclHistory.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclHistory.c,v
retrieving revision 1.5
diff -u -b -B -r1.5 tclHistory.c
--- generic/tclHistory.c	6 Apr 2004 22:25:51 -0000	1.5
+++ generic/tclHistory.c	19 Apr 2004 22:18:32 -0000
@@ -133,6 +133,14 @@
     Tcl_DecrRefCount(objPtr);
 
     /*
+     * One possible failure mode above: exceeding a resource limit
+     */
+
+    if (Tcl_LimitExceeded(interp)) {
+	return TCL_ERROR;
+    }
+
+    /*
      * Execute the command.
      */
 
Index: generic/tclInt.h
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclInt.h,v
retrieving revision 1.152
diff -u -b -B -r1.152 tclInt.h
--- generic/tclInt.h	7 Apr 2004 22:04:29 -0000	1.152
+++ generic/tclInt.h	19 Apr 2004 22:18:32 -0000
@@ -1126,6 +1126,12 @@
 } ResolverScheme;
 
 /*
+ * Forward declaration of the TIP#143 limit handler structure.
+ */
+
+typedef struct LimitHandler LimitHandler;
+
+/*
  *----------------------------------------------------------------
  * This structure defines an interpreter, which is a collection of
  * commands plus other state information related to interpreting
@@ -1325,6 +1331,39 @@
     Tcl_Obj *returnOptionsKey;	/* holds "-options" */
 
     /*
+     * Resource limiting framework support (TIP#143).
+     */
+
+    struct {
+	int active;		/* Flag values defining which limits have
+				 * been set. */
+	int granularityTicker;	/* Counter used to determine how often to
+				 * check the limits. */
+	int exceeded;		/* Which limits have been exceeded, described
+				 * as flag values the same as the 'active'
+				 * field. */
+
+	int cmdCount;		/* Limit for how many commands to execute
+				 * in the interpreter. */
+	LimitHandler *cmdHandlers; /* Handlers to execute when the limit
+				    * is reached. */
+	int cmdGranularity;	/* Mod factor used to determine how often
+				 * to evaluate the limit check. */
+
+	Tcl_Time time;		/* Time limit for execution within the
+				 * interpreter (in seconds from epoch). */
+	LimitHandler *timeHandlers; /* Handlers to execute when the limit
+				     * is reached. */
+	int timeGranularity;	/* Mod factor used to determine how often
+				 * to evaluate the limit check. */
+
+	Tcl_HashTable callbacks; /* Mapping from (interp,type) pair to data
+				  * used to install a limit handler callback
+				  * to run in _this_ interp when the limit
+				  * is exceeded. */
+    } limit;
+
+    /*
      * Statistical information about the bytecode compiler and interpreter's
      * operation.
      */
@@ -1403,6 +1442,32 @@
 #define MAX_NESTING_DEPTH	1000
 
 /*
+ * TIP#143 limit handler internal representation.
+ */
+
+struct LimitHandler {
+    int flags;			/* The state of this particular handler. */
+    Tcl_LimitHandlerProc *handlerProc; /* The handler callback. */
+    ClientData clientData;	/* Opaque argument to the handler callback. */
+    Tcl_LimitHandlerDeleteProc *deleteProc; /* How to delete the clientData */
+    LimitHandler *prevPtr;	/* Previous item in linked list of handlers */
+    LimitHandler *nextPtr;	/* Next item in linked list of handlers */
+};
+
+/*
+ * Values for the LimitHandler flags field.
+ *	LIMIT_HANDLER_ACTIVE - Whether the handler is currently being
+ *		processed; handlers are never to be entered reentrantly.
+ *	LIMIT_HANDLER_DELETED - Whether the handler has been deleted.  This
+ *		should not normally be observed because when a handler is
+ *		deleted it is also spliced out of the list of handlers, but
+ *		even so we will be careful.
+ */
+
+#define LIMIT_HANDLER_ACTIVE	0x01
+#define LIMIT_HANDLER_DELETED	0x02
+
+/*
  * The macro below is used to modify a "char" value (e.g. by casting
  * it to an unsigned character) so that it can be used safely with
  * macros such as isspace.
@@ -1676,6 +1741,7 @@
 			    int len));
 EXTERN int              TclJoinThread _ANSI_ARGS_((Tcl_ThreadId id,
 			    int* result));
+EXTERN void		TclInitLimitSupport _ANSI_ARGS_((Tcl_Interp *interp));
 EXTERN Tcl_Obj *	TclLindexList _ANSI_ARGS_((Tcl_Interp* interp,
 						   Tcl_Obj* listPtr,
 						   Tcl_Obj* argPtr ));
Index: generic/tclInterp.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclInterp.c,v
retrieving revision 1.26
diff -u -b -B -r1.26 tclInterp.c
--- generic/tclInterp.c	6 Apr 2004 22:25:53 -0000	1.26
+++ generic/tclInterp.c	19 Apr 2004 22:18:32 -0000
@@ -145,6 +145,24 @@
 } InterpInfo;
 
 /*
+ * Limit callbacks handled by scripts are modelled as structures which
+ * are stored in hashes indexed by a two-word key.  Note that the type
+ * of the 'type' field in the key is not int; this is to make sure
+ * that things work properly on 64-bit architectures.
+ */
+
+struct ScriptLimitCallback {
+    Tcl_Interp *interp;
+    Tcl_Obj *scriptObj;
+    int type;
+};
+
+struct ScriptLimitCallbackKey {
+    Tcl_Interp *interp;
+    long type;
+};
+
+/*
  * Prototypes for local static procedures:
  */
 
@@ -196,6 +214,23 @@
 static int		SlaveRecursionLimit _ANSI_ARGS_((Tcl_Interp *interp,
 			    Tcl_Interp *slaveInterp, int objc,
 			    Tcl_Obj *CONST objv[]));
+static int		SlaveCommandLimit _ANSI_ARGS_((Tcl_Interp *interp,
+			    Tcl_Interp *slaveInterp, int consumedObjc,
+			    int objc, Tcl_Obj *CONST objv[]));
+static int		SlaveTimeLimit _ANSI_ARGS_((Tcl_Interp *interp,
+			    Tcl_Interp *slaveInterp, int consumedObjc,
+			    int objc, Tcl_Obj *CONST objv[]));
+static void		InheritLimits _ANSI_ARGS_((Tcl_Interp *slaveInterp,
+			    Tcl_Interp *masterInterp));
+static void		SetLimitCallback _ANSI_ARGS_((Tcl_Interp *interp,
+			    int type, Tcl_Interp *targetInterp,
+			    Tcl_Obj *scriptObj));
+static void		CallScriptLimitCallback _ANSI_ARGS_((
+			    ClientData clientData, Tcl_Interp *interp));
+static void		DeleteScriptLimitCallback _ANSI_ARGS_((
+			    ClientData clientData));
+static void		RunLimitHandlers _ANSI_ARGS_((LimitHandler *handlerPtr,
+			    Tcl_Interp *interp));
 
 
 /*
@@ -357,16 +392,16 @@
     static CONST char *options[] = {
         "alias",	"aliases",	"create",	"delete", 
 	"eval",		"exists",	"expose",	"hide", 
-	"hidden",	"issafe",	"invokehidden",	"marktrusted", 
-	"recursionlimit",		"slaves",	"share",
+	"hidden",	"issafe",	"invokehidden",	"limit",
+	"marktrusted",	"recursionlimit","slaves",	"share",
 	"target",	"transfer",
         NULL
     };
     enum option {
 	OPT_ALIAS,	OPT_ALIASES,	OPT_CREATE,	OPT_DELETE,
 	OPT_EVAL,	OPT_EXISTS,	OPT_EXPOSE,	OPT_HIDE,
-	OPT_HIDDEN,	OPT_ISSAFE,	OPT_INVOKEHID,	OPT_MARKTRUSTED,
-	OPT_RECLIMIT,			OPT_SLAVES,	OPT_SHARE,
+	OPT_HIDDEN,	OPT_ISSAFE,	OPT_INVOKEHID,	OPT_LIMIT,
+	OPT_MARKTRUSTED,OPT_RECLIMIT,	OPT_SLAVES,	OPT_SHARE,
 	OPT_TARGET,	OPT_TRANSFER
     };
 
@@ -628,6 +663,35 @@
 	    return SlaveInvokeHidden(interp, slaveInterp, global, objc - i,
 		    objv + i);
 	}
+	case OPT_LIMIT: {
+	    Tcl_Interp *slaveInterp;
+	    static CONST char *limitTypes[] = {
+		"commands", "time", NULL
+	    };
+	    enum LimitTypes {
+		LIMIT_TYPE_COMMANDS, LIMIT_TYPE_TIME
+	    };
+	    int limitType;
+
+	    if (objc < 4) {
+		Tcl_WrongNumArgs(interp, 2, objv, "path limitType ?options?");
+		return TCL_ERROR;
+	    }
+	    slaveInterp = GetInterp(interp, objv[2]);
+	    if (slaveInterp == NULL) {
+		return TCL_ERROR;
+	    }
+	    if (Tcl_GetIndexFromObj(interp, objv[3], limitTypes, "limit type",
+		    0, &limitType) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	    switch ((enum LimitTypes) limitType) {
+	    case LIMIT_TYPE_COMMANDS:
+		return SlaveCommandLimit(interp, slaveInterp, 4, objc, objv);
+	    case LIMIT_TYPE_TIME:
+		return SlaveTimeLimit(interp, slaveInterp, 4, objc, objv);
+	    }
+	}
 	case OPT_MARKTRUSTED: {
 	    Tcl_Interp *slaveInterp;
 
@@ -1838,6 +1902,12 @@
 	 */
 	Tcl_InitMemory(slaveInterp);
     }
+
+    /*
+     * Inherit the TIP#143 limits.
+     */
+    InheritLimits(slaveInterp, masterInterp);
+
     return slaveInterp;
 
     error:
@@ -1876,12 +1946,12 @@
     static CONST char *options[] = {
         "alias",	"aliases",	"eval",		"expose",
         "hide",		"hidden",	"issafe",	"invokehidden",
-        "marktrusted",	"recursionlimit", NULL
+	"limit",	"marktrusted",	"recursionlimit", NULL
     };
     enum options {
 	OPT_ALIAS,	OPT_ALIASES,	OPT_EVAL,	OPT_EXPOSE,
 	OPT_HIDE,	OPT_HIDDEN,	OPT_ISSAFE,	OPT_INVOKEHIDDEN,
-	OPT_MARKTRUSTED, OPT_RECLIMIT
+	OPT_LIMIT,	OPT_MARKTRUSTED, OPT_RECLIMIT
     };
     
     slaveInterp = (Tcl_Interp *) clientData;
@@ -1992,6 +2062,30 @@
 	    return SlaveInvokeHidden(interp, slaveInterp, global, objc - i,
 		    objv + i);
 	}
+	case OPT_LIMIT: {
+	    static CONST char *limitTypes[] = {
+		"commands", "time", NULL
+	    };
+	    enum LimitTypes {
+		LIMIT_TYPE_COMMANDS, LIMIT_TYPE_TIME
+	    };
+	    int limitType;
+
+	    if (objc < 3) {
+		Tcl_WrongNumArgs(interp, 2, objv, "limitType ?options?");
+		return TCL_ERROR;
+	    }
+	    if (Tcl_GetIndexFromObj(interp, objv[2], limitTypes, "limit type",
+		    0, &limitType) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	    switch ((enum LimitTypes) limitType) {
+	    case LIMIT_TYPE_COMMANDS:
+		return SlaveCommandLimit(interp, slaveInterp, 3, objc, objv);
+	    case LIMIT_TYPE_TIME:
+		return SlaveTimeLimit(interp, slaveInterp, 3, objc, objv);
+	    }
+	}
 	case OPT_MARKTRUSTED: {
 	    if (objc != 2) {
 		Tcl_WrongNumArgs(interp, 2, objv, NULL);
@@ -2486,3 +2580,918 @@
 
     return TCL_OK;
 }
+
+int
+Tcl_LimitExceeded(interp)
+    Tcl_Interp *interp;
+{
+    register Interp *iPtr = (Interp *) interp;
+
+    return iPtr->limit.exceeded != 0;
+}
+
+int
+Tcl_LimitReady(interp)
+    Tcl_Interp *interp;
+{
+    register Interp *iPtr = (Interp *) interp;
+
+    if (iPtr->limit.active != 0) {
+	register int ticker = ++iPtr->limit.granularityTicker;
+
+	if ((iPtr->limit.active & TCL_LIMIT_COMMANDS) &&
+		((iPtr->limit.cmdGranularity == 1) ||
+			(ticker % iPtr->limit.cmdGranularity == 0))) {
+	    return 1;
+	}
+	if ((iPtr->limit.active & TCL_LIMIT_TIME) &&
+		((iPtr->limit.timeGranularity == 1) ||
+			(ticker % iPtr->limit.timeGranularity == 0))) {
+	    return 1;
+	}
+    }
+    return 0;
+}
+
+int
+Tcl_LimitCheck(interp)
+    Tcl_Interp *interp;
+{
+    Interp *iPtr = (Interp *) interp;
+    register int ticker = iPtr->limit.granularityTicker;
+
+    if (Tcl_InterpDeleted(interp)) {
+	return TCL_OK;
+    }
+
+    if ((iPtr->limit.active & TCL_LIMIT_COMMANDS) &&
+	    ((iPtr->limit.cmdGranularity == 1) ||
+		    (ticker % iPtr->limit.cmdGranularity == 0)) &&
+	    (iPtr->limit.cmdCount < iPtr->cmdCount)) {
+	iPtr->limit.exceeded |= TCL_LIMIT_COMMANDS;
+	Tcl_Preserve(interp);
+	RunLimitHandlers(iPtr->limit.cmdHandlers, interp);
+	if (iPtr->limit.cmdCount >= iPtr->cmdCount) {
+	    iPtr->limit.exceeded &= ~TCL_LIMIT_COMMANDS;
+	} else {
+	    Tcl_ResetResult(interp);
+	    Tcl_AppendResult(interp, "command count limit exceeded", NULL);
+	    Tcl_Release(interp);
+	    return TCL_ERROR;
+	}
+    }
+
+    if ((iPtr->limit.active & TCL_LIMIT_TIME) &&
+	    ((iPtr->limit.timeGranularity == 1) ||
+		    (ticker % iPtr->limit.timeGranularity == 0))) {
+	Tcl_Time now;
+
+	Tcl_GetTime(&now);
+	if (iPtr->limit.time.sec < now.sec ||
+		(iPtr->limit.time.sec == now.sec &&
+		iPtr->limit.time.usec < now.usec)) {
+	    iPtr->limit.exceeded |= TCL_LIMIT_TIME;
+	    Tcl_Preserve(interp);
+	    RunLimitHandlers(iPtr->limit.timeHandlers, interp);
+	    if (iPtr->limit.time.sec < now.sec ||
+		    (iPtr->limit.time.sec == now.sec &&
+		    iPtr->limit.time.usec < now.usec)) {
+		iPtr->limit.exceeded &= ~TCL_LIMIT_TIME;
+	    } else {
+		Tcl_ResetResult(interp);
+		Tcl_AppendResult(interp, "time limit exceeded", NULL);
+		Tcl_Release(interp);
+		return TCL_ERROR;
+	    }
+	}
+    }
+
+    return TCL_OK;
+}
+
+static void
+RunLimitHandlers(handlerPtr, interp)
+    LimitHandler *handlerPtr;
+    Tcl_Interp *interp;
+{
+    LimitHandler *nextPtr;
+    for (; handlerPtr!=NULL ; handlerPtr=nextPtr) {
+	if (handlerPtr->flags & (LIMIT_HANDLER_DELETED|LIMIT_HANDLER_ACTIVE)) {
+	    /*
+	     * Reentrant call or something seriously strange in the
+	     * delete code.
+	     */
+	    nextPtr = handlerPtr->nextPtr;
+	    continue;
+	}
+
+	/*
+	 * Set the ACTIVE flag while running the limit handler itself
+	 * so we cannot reentrantly call this handler and know to use
+	 * the alternate method of deletion if necessary.
+	 */
+
+	handlerPtr->flags |= LIMIT_HANDLER_ACTIVE;
+	(handlerPtr->handlerProc)(handlerPtr->clientData, interp);
+	handlerPtr->flags &= ~LIMIT_HANDLER_ACTIVE;
+
+	/*
+	 * Rediscover this value; it might have changed during the
+	 * processing of a limit handler.  We have to record it here
+	 * because we might delete the structure below, and reading a
+	 * value out of a deleted structure is unsafe (even if
+	 * actually legal with some malloc()/free() implementations.)
+	 */
+
+	nextPtr = handlerPtr->nextPtr;
+
+	/*
+	 * If we deleted the current handler while we were executing
+	 * it, we will have spliced it out of the list and set the
+	 * LIMIT_HANDLER_DELETED flag.
+	 */
+	if (handlerPtr->flags & LIMIT_HANDLER_DELETED) {
+	    if (handlerPtr->deleteProc != NULL) {
+		(handlerPtr->deleteProc)(handlerPtr->clientData);
+	    }
+	    ckfree((char *) handlerPtr);
+	}
+    }
+}
+
+void
+Tcl_LimitAddHandler(interp, type, handlerProc, clientData, deleteProc)
+    Tcl_Interp *interp;
+    int type;
+    Tcl_LimitHandlerProc *handlerProc;
+    ClientData clientData;
+    Tcl_LimitHandlerDeleteProc *deleteProc;
+{
+    Interp *iPtr = (Interp *) interp;
+    LimitHandler *handlerPtr;
+
+    if (deleteProc == (Tcl_LimitHandlerDeleteProc *) TCL_DYNAMIC) {
+	deleteProc = (Tcl_LimitHandlerDeleteProc *) Tcl_Alloc;
+    }
+    if (deleteProc == (Tcl_LimitHandlerDeleteProc *) TCL_STATIC) {
+	deleteProc = (Tcl_LimitHandlerDeleteProc *) NULL;
+    }
+
+    switch (type) {
+    case TCL_LIMIT_COMMANDS:
+	handlerPtr = (LimitHandler *) ckalloc(sizeof(LimitHandler));
+
+	handlerPtr->flags = 0;
+	handlerPtr->handlerProc = handlerProc;
+	handlerPtr->clientData = clientData;
+	handlerPtr->deleteProc = deleteProc;
+	handlerPtr->prevPtr = NULL;
+	handlerPtr->nextPtr = iPtr->limit.cmdHandlers;
+	if (handlerPtr->nextPtr != NULL) {
+	    handlerPtr->nextPtr->prevPtr = handlerPtr;
+	}
+	iPtr->limit.cmdHandlers = handlerPtr;
+	return;
+
+    case TCL_LIMIT_TIME:
+	handlerPtr = (LimitHandler *) ckalloc(sizeof(LimitHandler));
+
+	handlerPtr->flags = 0;
+	handlerPtr->handlerProc = handlerProc;
+	handlerPtr->clientData = clientData;
+	handlerPtr->deleteProc = deleteProc;
+	handlerPtr->prevPtr = NULL;
+	handlerPtr->nextPtr = iPtr->limit.timeHandlers;
+	if (handlerPtr->nextPtr != NULL) {
+	    handlerPtr->nextPtr->prevPtr = handlerPtr;
+	}
+	iPtr->limit.timeHandlers = handlerPtr;
+	return;
+    }
+
+    Tcl_Panic("unknown type of resource limit");
+}
+
+void
+Tcl_LimitRemoveHandler(interp, type, handlerProc, clientData)
+    Tcl_Interp *interp;
+    int type;
+    Tcl_LimitHandlerProc *handlerProc;
+    ClientData clientData;
+{
+    Interp *iPtr = (Interp *) interp;
+    LimitHandler *handlerPtr;
+
+    switch (type) {
+    case TCL_LIMIT_COMMANDS:
+	handlerPtr = iPtr->limit.cmdHandlers;
+	break;
+    case TCL_LIMIT_TIME:
+	handlerPtr = iPtr->limit.timeHandlers;
+	break;
+    default:
+	Tcl_Panic("unknown type of resource limit");
+    }
+
+    for (; handlerPtr!=NULL ; handlerPtr=handlerPtr->nextPtr) {
+	if ((handlerPtr->handlerProc != handlerProc) ||
+		(handlerPtr->clientData != clientData)) {
+	    continue;
+	}
+
+	/*
+	 * We've found the handler to delete; mark it as doomed if not
+	 * already so marked (which shouldn't actually happen).
+	 */
+
+	if (handlerPtr->flags & LIMIT_HANDLER_DELETED) {
+	    return;
+	}
+	handlerPtr->flags |= LIMIT_HANDLER_DELETED;
+
+	/*
+	 * Splice the handler out of the doubly-linked list.
+	 */
+
+	if (handlerPtr->prevPtr == NULL) {
+	    switch (type) {
+	    case TCL_LIMIT_COMMANDS:
+		iPtr->limit.cmdHandlers = handlerPtr->nextPtr;
+		break;
+	    case TCL_LIMIT_TIME:
+		iPtr->limit.timeHandlers = handlerPtr->nextPtr;
+		break;
+	    }
+	} else {
+	    handlerPtr->prevPtr->nextPtr = handlerPtr->nextPtr;
+	}
+	if (handlerPtr->nextPtr != NULL) {
+	    handlerPtr->nextPtr->prevPtr = handlerPtr->prevPtr;
+	}
+
+	/*
+	 * If nothing is currently executing the handler, delete its
+	 * client data and the overall handler structure now.
+	 * Otherwise it will all go away when the handler returns.
+	 */
+
+	if (!(handlerPtr->flags & LIMIT_HANDLER_ACTIVE)) {
+	    if (handlerPtr->deleteProc != NULL) {
+		(handlerPtr->deleteProc)(handlerPtr->clientData);
+	    }
+	    ckfree((char *) handlerPtr);
+	}
+	return;
+    }
+}
+
+int
+Tcl_LimitTypeEnabled(interp, type)
+    Tcl_Interp *interp;
+    int type;
+{
+    Interp *iPtr = (Interp *) interp;
+
+    return (iPtr->limit.active & type) != 0;
+}
+
+int
+Tcl_LimitTypeExceeded(interp, type)
+    Tcl_Interp *interp;
+    int type;
+{
+    Interp *iPtr = (Interp *) interp;
+
+    return (iPtr->limit.exceeded & type) != 0;
+}
+
+void
+Tcl_LimitTypeSet(interp, type)
+    Tcl_Interp *interp;
+    int type;
+{
+    Interp *iPtr = (Interp *) interp;
+
+    iPtr->limit.active |= type;
+}
+
+void
+Tcl_LimitTypeReset(interp, type)
+    Tcl_Interp *interp;
+    int type;
+{
+    Interp *iPtr = (Interp *) interp;
+
+    iPtr->limit.active &= ~type;
+}
+
+void
+Tcl_LimitSetCommands(interp, commandLimit)
+    Tcl_Interp *interp;
+    int commandLimit;
+{
+    Interp *iPtr = (Interp *) interp;
+
+    iPtr->limit.cmdCount = commandLimit;
+    iPtr->limit.exceeded &= ~TCL_LIMIT_COMMANDS;
+}
+
+int
+Tcl_LimitGetCommands(interp)
+    Tcl_Interp *interp;
+{
+    Interp *iPtr = (Interp *) interp;
+
+    return iPtr->limit.cmdCount;
+}
+
+void
+Tcl_LimitSetTime(interp, timeLimitPtr)
+    Tcl_Interp *interp;
+    Tcl_Time *timeLimitPtr;
+{
+    Interp *iPtr = (Interp *) interp;
+
+    memcpy(&iPtr->limit.time, timeLimitPtr, sizeof(Tcl_Time));
+    iPtr->limit.exceeded &= ~TCL_LIMIT_COMMANDS;
+}
+
+void
+Tcl_LimitGetTime(interp, timeLimitPtr)
+    Tcl_Interp *interp;
+    Tcl_Time *timeLimitPtr;
+{
+    Interp *iPtr = (Interp *) interp;
+
+    memcpy(timeLimitPtr, &iPtr->limit.time, sizeof(Tcl_Time));
+}
+
+void
+Tcl_LimitSetGranularity(interp, type, granularity)
+    Tcl_Interp *interp;
+    int type;
+    int granularity;
+{
+    Interp *iPtr = (Interp *) interp;
+    if (granularity < 1) {
+	Tcl_Panic("limit granularity must be positive");
+    }
+
+    switch (type) {
+    case TCL_LIMIT_COMMANDS:
+	iPtr->limit.cmdGranularity = granularity;
+	return;
+    case TCL_LIMIT_TIME:
+	iPtr->limit.timeGranularity = granularity;
+	return;
+    }
+    Tcl_Panic("unknown type of resource limit");
+}
+
+int
+Tcl_LimitGetGranularity(interp, type)
+    Tcl_Interp *interp;
+    int type;
+{
+    Interp *iPtr = (Interp *) interp;
+
+    switch (type) {
+    case TCL_LIMIT_COMMANDS:
+	return iPtr->limit.cmdGranularity;
+    case TCL_LIMIT_TIME:
+	return iPtr->limit.timeGranularity;
+    }
+    Tcl_Panic("unknown type of resource limit");
+    return -1; /* NOT REACHED */
+}    
+
+/*
+ * Callback for when a script limit is deleted.
+ */
+static void
+DeleteScriptLimitCallback(clientData)
+    ClientData clientData;
+{
+    struct ScriptLimitCallback *limitCBPtr =
+	    (struct ScriptLimitCallback *) clientData;
+
+    Tcl_DecrRefCount(limitCBPtr->scriptObj);
+    ckfree((char *) limitCBPtr);
+}
+
+/*
+ * Callback for when a script limit is invoked.
+ */
+static void
+CallScriptLimitCallback(clientData, interp)
+    ClientData clientData;
+    Tcl_Interp *interp;		/* Interpreter which failed the limit */
+{
+    struct ScriptLimitCallback *limitCBPtr =
+	    (struct ScriptLimitCallback *) clientData;
+    int code;
+
+    if (Tcl_InterpDeleted(limitCBPtr->interp)) {
+	return;
+    }
+    Tcl_Preserve(limitCBPtr->interp);
+    code = Tcl_EvalObjEx(limitCBPtr->interp, limitCBPtr->scriptObj,
+	    TCL_EVAL_GLOBAL);
+    if (code != TCL_OK && !Tcl_InterpDeleted(limitCBPtr->interp)) {
+	Tcl_BackgroundError(limitCBPtr->interp);
+    }
+    Tcl_Release(limitCBPtr->interp);
+}
+
+/*
+ * Install (or remove, if scriptObj is NULL) a limit callback script
+ * that is called when the target interpreter exceeds the type of
+ * limit specified.
+ */
+static void
+SetLimitCallback(interp, type, targetInterp, scriptObj)
+    Tcl_Interp *interp;
+    int type;
+    Tcl_Interp *targetInterp;
+    Tcl_Obj *scriptObj;
+{
+    struct ScriptLimitCallback *limitCBPtr;
+    Tcl_HashEntry *hashPtr;
+    int isNew;
+    struct ScriptLimitCallbackKey key;
+    Interp *iPtr = (Interp *) interp;
+
+    if (interp == targetInterp) {
+	Tcl_Panic("installing limit callback to the limited interpreter");
+    }
+
+    key.interp = targetInterp;
+    key.type = type;
+
+    if (scriptObj == NULL) {
+	hashPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, (char *) &key);
+	if (hashPtr != NULL) {
+	    Tcl_LimitRemoveHandler(targetInterp, type, CallScriptLimitCallback,
+		    Tcl_GetHashValue(hashPtr));
+	    Tcl_DeleteHashEntry(hashPtr);
+	}
+	return;
+    }
+
+    limitCBPtr = (struct ScriptLimitCallback *)
+	    ckalloc(sizeof(struct ScriptLimitCallback));
+    limitCBPtr->interp = interp;
+    limitCBPtr->scriptObj = scriptObj;
+    limitCBPtr->type = type;
+    Tcl_IncrRefCount(scriptObj);
+
+    hashPtr = Tcl_CreateHashEntry(&iPtr->limit.callbacks, (char *) &key,
+	    &isNew);
+    if (!isNew) {
+	Tcl_LimitRemoveHandler(targetInterp, type, CallScriptLimitCallback,
+		Tcl_GetHashValue(hashPtr));
+    }
+    Tcl_LimitAddHandler(targetInterp, type, CallScriptLimitCallback,
+	    (ClientData) limitCBPtr, DeleteScriptLimitCallback);
+    Tcl_SetHashValue(hashPtr, (ClientData) limitCBPtr);
+}
+
+/*
+ * Remove all limit callback scripts that make callbacks into the
+ * given interpreter.
+ */
+
+void
+TclDecommissionLimitCallbacks(interp)
+    Tcl_Interp *interp;
+{
+    Interp *iPtr = (Interp *) interp;
+    Tcl_HashEntry *hashPtr;
+    Tcl_HashSearch search;
+    struct ScriptLimitCallbackKey *keyPtr;
+
+    hashPtr = Tcl_FirstHashEntry(&iPtr->limit.callbacks, &search);
+    while (hashPtr != NULL) {
+	keyPtr = (struct ScriptLimitCallbackKey *)
+		Tcl_GetHashKey(&iPtr->limit.callbacks, hashPtr);
+	Tcl_LimitRemoveHandler(keyPtr->interp, keyPtr->type,
+		CallScriptLimitCallback, Tcl_GetHashValue(hashPtr));
+	hashPtr = Tcl_NextHashEntry(&search);
+    }
+    Tcl_DeleteHashTable(&iPtr->limit.callbacks);
+}
+
+void
+TclInitLimitSupport(interp)
+    Tcl_Interp *interp;
+{
+    Interp *iPtr = (Interp *) interp;
+
+    iPtr->limit.active = 0;
+    iPtr->limit.granularityTicker = 0;
+    iPtr->limit.exceeded = 0;
+    iPtr->limit.cmdCount = 0;
+    iPtr->limit.cmdHandlers = NULL;
+    iPtr->limit.cmdGranularity = 1;
+    memset(&iPtr->limit.time, 0, sizeof(Tcl_Time));
+    iPtr->limit.timeHandlers = NULL;
+    iPtr->limit.timeGranularity = 10;
+    Tcl_InitHashTable(&iPtr->limit.callbacks,
+	    sizeof(struct ScriptLimitCallbackKey)/sizeof(ClientData));
+}
+
+static void
+InheritLimits(slaveInterp, masterInterp)
+    Tcl_Interp *slaveInterp, *masterInterp;
+{
+    Interp *slavePtr = (Interp *) slaveInterp;
+    Interp *masterPtr = (Interp *) masterInterp;
+
+    if (masterPtr->limit.active & TCL_LIMIT_COMMANDS) {
+	slavePtr->limit.active |= TCL_LIMIT_COMMANDS;
+	slavePtr->limit.cmdCount = 0;
+	slavePtr->limit.cmdGranularity = masterPtr->limit.cmdGranularity;
+    }
+    if (masterPtr->limit.active & TCL_LIMIT_TIME) {
+	slavePtr->limit.active |= TCL_LIMIT_TIME;
+	memcpy(&slavePtr->limit.time, &masterPtr->limit.time,
+		sizeof(Tcl_Time));
+	slavePtr->limit.timeGranularity = masterPtr->limit.timeGranularity;
+    }
+}
+
+static int
+SlaveCommandLimit(interp, slaveInterp, consumedObjc, objc, objv)
+    Tcl_Interp *interp;			/* Current interpreter. */
+    Tcl_Interp *slaveInterp;		/* Interpreter being adjusted. */
+    int consumedObjc;			/* Number of args already parsed. */
+    int objc;				/* Total number of arguments. */
+    Tcl_Obj *CONST objv[];		/* Argument objects. */
+{
+    static CONST char *options[] = {
+	"-command", "-granularity", "-value", NULL
+    };
+    enum Options {
+	OPT_CMD, OPT_GRAN, OPT_VAL
+    };
+    Interp *iPtr = (Interp *) interp;
+    int index;
+    struct ScriptLimitCallbackKey key;
+    struct ScriptLimitCallback *limitCBPtr;
+    Tcl_HashEntry *hPtr;
+
+    if (objc == consumedObjc) {
+	Tcl_Obj *dictPtr;
+
+	TclNewObj(dictPtr);
+	key.interp = slaveInterp;
+	key.type = TCL_LIMIT_COMMANDS;
+	hPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, (char *) &key);
+	if (hPtr != NULL) {
+	    limitCBPtr = (struct ScriptLimitCallback *)
+		    Tcl_GetHashValue(hPtr);
+	    if (limitCBPtr != NULL && limitCBPtr->scriptObj != NULL) {
+		Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[0], -1),
+			limitCBPtr->scriptObj);
+	    } else {
+		goto putEmptyCommandInDict;
+	    }
+	} else {
+	    Tcl_Obj *empty;
+	putEmptyCommandInDict:
+	    TclNewObj(empty);
+	    Tcl_DictObjPut(NULL, dictPtr,
+		    Tcl_NewStringObj(options[0], -1), empty);
+	}
+	Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[1], -1),
+		Tcl_NewIntObj(Tcl_LimitGetGranularity(slaveInterp,
+		TCL_LIMIT_COMMANDS)));
+
+	if (Tcl_LimitTypeEnabled(slaveInterp, TCL_LIMIT_COMMANDS)) {
+	    Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[2], -1),
+		    Tcl_NewIntObj(Tcl_LimitGetCommands(slaveInterp)));
+	} else {
+	    Tcl_Obj *empty;
+
+	    TclNewObj(empty);
+	    Tcl_DictObjPut(NULL, dictPtr,
+		    Tcl_NewStringObj(options[2], -1), empty);
+	}
+	Tcl_SetObjResult(interp, dictPtr);
+	return TCL_OK;
+    } else if (objc == consumedObjc+1) {
+	if (Tcl_GetIndexFromObj(interp, objv[consumedObjc], options, "option",
+		0, &index) != TCL_OK) {
+	    return TCL_ERROR;
+	}
+	switch ((enum Options) index) {
+	case OPT_CMD:
+	    key.interp = slaveInterp;
+	    key.type = TCL_LIMIT_COMMANDS;
+	    hPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, (char *) &key);
+	    if (hPtr != NULL) {
+		limitCBPtr = (struct ScriptLimitCallback *)
+			Tcl_GetHashValue(hPtr);
+		if (limitCBPtr != NULL && limitCBPtr->scriptObj != NULL) {
+		    Tcl_SetObjResult(interp, limitCBPtr->scriptObj);
+		}
+	    }
+	    break;
+	case OPT_GRAN:
+	    Tcl_SetObjResult(interp, Tcl_NewIntObj(
+		    Tcl_LimitGetGranularity(slaveInterp, TCL_LIMIT_COMMANDS)));
+	    break;
+	case OPT_VAL:
+	    if (Tcl_LimitTypeEnabled(slaveInterp, TCL_LIMIT_COMMANDS)) {
+		Tcl_SetObjResult(interp,
+			Tcl_NewIntObj(Tcl_LimitGetCommands(slaveInterp)));
+	    }
+	    break;
+	}
+	return TCL_OK;
+    } else if ((objc-consumedObjc) & 1 /* isOdd(objc-consumedObjc) */) {
+	Tcl_WrongNumArgs(interp, consumedObjc, objv,
+		"?-option? ?value? ?-option value ...?");
+	return TCL_ERROR;
+    } else {
+	int i, scriptLen = 0, limitLen = 0;
+	Tcl_Obj *scriptObj = NULL, *granObj = NULL, *limitObj = NULL;
+	int gran = 0, limit = 0;
+
+	for (i=consumedObjc ; i<objc ; i+=2) {
+	    if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0,
+		    &index) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	    switch ((enum Options) index) {
+	    case OPT_CMD:
+		scriptObj = objv[i+1];
+		(void) Tcl_GetStringFromObj(objv[i+1], &scriptLen);
+		break;
+	    case OPT_GRAN:
+		granObj = objv[i+1];
+		if (Tcl_GetIntFromObj(interp, objv[i+1], &gran) != TCL_OK) {
+		    return TCL_ERROR;
+		}
+		if (gran < 1) {
+		    Tcl_AppendResult(interp, "granularity must be at ",
+			    "least 1", NULL);
+		    return TCL_ERROR;
+		}
+		break;
+	    case OPT_VAL:
+		limitObj = objv[i+1];
+		(void) Tcl_GetStringFromObj(objv[i+1], &limitLen);
+		if (limitLen == 0) {
+		    break;
+		}
+		if (Tcl_GetIntFromObj(interp, objv[i+1], &limit) != TCL_OK) {
+		    return TCL_ERROR;
+		}
+		if (limit < 0) {
+		    Tcl_AppendResult(interp, "command limit value must be at ",
+			    "least 0", NULL);
+		    return TCL_ERROR;
+		}
+		break;
+	    }
+	}
+	if (scriptObj != NULL) {
+	    SetLimitCallback(interp, TCL_LIMIT_COMMANDS, slaveInterp,
+		    (scriptLen > 0 ? scriptObj : NULL));
+	}
+	if (granObj != NULL) {
+	    Tcl_LimitSetGranularity(slaveInterp, TCL_LIMIT_COMMANDS, gran);
+	}
+	if (limitObj != NULL) {
+	    if (limitLen > 0) {
+		Tcl_LimitSetCommands(slaveInterp, limit);
+		Tcl_LimitTypeSet(slaveInterp, TCL_LIMIT_COMMANDS);
+	    } else {
+		Tcl_LimitTypeReset(slaveInterp, TCL_LIMIT_COMMANDS);
+	    }
+	}
+	return TCL_OK;
+    }
+}
+
+static int
+SlaveTimeLimit(interp, slaveInterp, consumedObjc, objc, objv)
+    Tcl_Interp *interp;			/* Current interpreter. */
+    Tcl_Interp *slaveInterp;		/* Interpreter being adjusted. */
+    int consumedObjc;			/* Number of args already parsed. */
+    int objc;				/* Total number of arguments. */
+    Tcl_Obj *CONST objv[];		/* Argument objects. */
+{
+    static CONST char *options[] = {
+	"-command", "-granularity", "-milliseconds", "-seconds", NULL
+    };
+    enum Options {
+	OPT_CMD, OPT_GRAN, OPT_MILLI, OPT_SEC
+    };
+    Interp *iPtr = (Interp *) interp;
+    int index;
+    struct ScriptLimitCallbackKey key;
+    struct ScriptLimitCallback *limitCBPtr;
+    Tcl_HashEntry *hPtr;
+
+    if (objc == consumedObjc) {
+	Tcl_Obj *dictPtr;
+
+	TclNewObj(dictPtr);
+	key.interp = slaveInterp;
+	key.type = TCL_LIMIT_TIME;
+	hPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, (char *) &key);
+	if (hPtr != NULL) {
+	    limitCBPtr = (struct ScriptLimitCallback *)
+		    Tcl_GetHashValue(hPtr);
+	    if (limitCBPtr != NULL && limitCBPtr->scriptObj != NULL) {
+		Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[0], -1),
+			limitCBPtr->scriptObj);
+	    } else {
+		goto putEmptyCommandInDict;
+	    }
+	} else {
+	    Tcl_Obj *empty;
+	putEmptyCommandInDict:
+	    TclNewObj(empty);
+	    Tcl_DictObjPut(NULL, dictPtr,
+		    Tcl_NewStringObj(options[0], -1), empty);
+	}
+	Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[1], -1),
+		Tcl_NewIntObj(Tcl_LimitGetGranularity(slaveInterp,
+		TCL_LIMIT_TIME)));
+
+	if (Tcl_LimitTypeEnabled(slaveInterp, TCL_LIMIT_TIME)) {
+	    Tcl_Time limitMoment;
+
+	    Tcl_LimitGetTime(slaveInterp, &limitMoment);
+	    Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[2], -1),
+		    Tcl_NewLongObj(limitMoment.usec/1000));
+	    Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[3], -1),
+		    Tcl_NewLongObj(limitMoment.sec));
+	} else {
+	    Tcl_Obj *empty;
+
+	    TclNewObj(empty);
+	    Tcl_DictObjPut(NULL, dictPtr,
+		    Tcl_NewStringObj(options[2], -1), empty);
+	    Tcl_DictObjPut(NULL, dictPtr,
+		    Tcl_NewStringObj(options[3], -1), empty);
+	}
+	Tcl_SetObjResult(interp, dictPtr);
+	return TCL_OK;
+    } else if (objc == consumedObjc+1) {
+	if (Tcl_GetIndexFromObj(interp, objv[consumedObjc], options, "option",
+		0, &index) != TCL_OK) {
+	    return TCL_ERROR;
+	}
+	switch ((enum Options) index) {
+	case OPT_CMD:
+	    key.interp = slaveInterp;
+	    key.type = TCL_LIMIT_TIME;
+	    hPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, (char *) &key);
+	    if (hPtr != NULL) {
+		limitCBPtr = (struct ScriptLimitCallback *)
+			Tcl_GetHashValue(hPtr);
+		if (limitCBPtr != NULL && limitCBPtr->scriptObj != NULL) {
+		    Tcl_SetObjResult(interp, limitCBPtr->scriptObj);
+		}
+	    }
+	    break;
+	case OPT_GRAN:
+	    Tcl_SetObjResult(interp, Tcl_NewIntObj(
+		    Tcl_LimitGetGranularity(slaveInterp, TCL_LIMIT_TIME)));
+	    break;
+	case OPT_MILLI:
+	    if (Tcl_LimitTypeEnabled(slaveInterp, TCL_LIMIT_TIME)) {
+		Tcl_Time limitMoment;
+
+		Tcl_LimitGetTime(slaveInterp, &limitMoment);
+		Tcl_SetObjResult(interp,
+			Tcl_NewLongObj(limitMoment.usec/1000));
+	    }
+	    break;
+	case OPT_SEC:
+	    if (Tcl_LimitTypeEnabled(slaveInterp, TCL_LIMIT_TIME)) {
+		Tcl_Time limitMoment;
+
+		Tcl_LimitGetTime(slaveInterp, &limitMoment);
+		Tcl_SetObjResult(interp, Tcl_NewLongObj(limitMoment.sec));
+	    }
+	    break;
+	}
+	return TCL_OK;
+    } else if ((objc-consumedObjc) & 1 /* isOdd(objc-consumedObjc) */) {
+	Tcl_WrongNumArgs(interp, consumedObjc, objv,
+		"?-option? ?value? ?-option value ...?");
+	return TCL_ERROR;
+    } else {
+	int i, scriptLen = 0, milliLen = 0, secLen = 0;
+	Tcl_Obj *scriptObj = NULL, *granObj = NULL;
+	Tcl_Obj *milliObj = NULL, *secObj = NULL;
+	int gran = 0;
+	Tcl_Time limitMoment;
+	int tmp;
+
+	Tcl_LimitGetTime(slaveInterp, &limitMoment);
+	for (i=consumedObjc ; i<objc ; i+=2) {
+	    if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0,
+		    &index) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	    switch ((enum Options) index) {
+	    case OPT_CMD:
+		scriptObj = objv[i+1];
+		(void) Tcl_GetStringFromObj(objv[i+1], &scriptLen);
+		break;
+	    case OPT_GRAN:
+		granObj = objv[i+1];
+		if (Tcl_GetIntFromObj(interp, objv[i+1], &gran) != TCL_OK) {
+		    return TCL_ERROR;
+		}
+		if (gran < 1) {
+		    Tcl_AppendResult(interp, "granularity must be at ",
+			    "least 1", NULL);
+		    return TCL_ERROR;
+		}
+		break;
+	    case OPT_MILLI:
+		milliObj = objv[i+1];
+		(void) Tcl_GetStringFromObj(objv[i+1], &milliLen);
+		if (milliLen == 0) {
+		    break;
+		}
+		if (Tcl_GetIntFromObj(interp, objv[i+1], &tmp) != TCL_OK) {
+		    return TCL_ERROR;
+		}
+		if (tmp < 0) {
+		    Tcl_AppendResult(interp, "milliseconds must be at least 0",
+			    NULL);
+		    return TCL_ERROR;
+		}
+		limitMoment.usec = ((long)tmp)*1000;
+		break;
+	    case OPT_SEC:
+		secObj = objv[i+1];
+		(void) Tcl_GetStringFromObj(objv[i+1], &secLen);
+		if (secLen == 0) {
+		    break;
+		}
+		if (Tcl_GetIntFromObj(interp, objv[i+1], &tmp) != TCL_OK) {
+		    return TCL_ERROR;
+		}
+		if (tmp < 0) {
+		    Tcl_AppendResult(interp, "seconds must be at least 0",
+			    NULL);
+		    return TCL_ERROR;
+		}
+		limitMoment.sec = tmp;
+		break;
+	    }
+	}
+	if (milliObj != NULL || secObj != NULL) {
+	    if (milliObj != NULL) {
+		/*
+		 * Setting -milliseconds but clearing -seconds, or
+		 * resetting -milliseconds but not resetting -seconds?
+		 * Bad voodoo!
+		 */
+		if (secObj != NULL && secLen == 0 && milliLen > 0) {
+		    Tcl_AppendResult(interp, "may only set -milliseconds ",
+			    "if -seconds is not also being reset", NULL);
+		    return TCL_ERROR;
+		}
+		if (milliLen == 0 && (secObj == NULL || secLen > 0)) {
+		    Tcl_AppendResult(interp, "may only reset -milliseconds ",
+			    "if -seconds is also being reset", NULL);
+		    return TCL_ERROR;
+		}
+	    }
+
+	    if (milliLen > 0 || secLen > 0) {
+		/*
+		 * Force usec to be in range [0..1000000), possibly
+		 * incrementing sec in the process.  This makes it
+		 * much easier for people to write scripts that do
+		 * small time increments.
+		 */
+		limitMoment.sec += limitMoment.usec / 1000000;
+		limitMoment.usec %= 1000000;
+
+		Tcl_LimitSetTime(slaveInterp, &limitMoment);
+		Tcl_LimitTypeSet(slaveInterp, TCL_LIMIT_TIME);
+	    } else {
+		Tcl_LimitTypeReset(slaveInterp, TCL_LIMIT_TIME);
+	    }
+	}
+	if (scriptObj != NULL) {
+	    SetLimitCallback(interp, TCL_LIMIT_TIME, slaveInterp,
+		    (scriptLen > 0 ? scriptObj : NULL));
+	}
+	if (granObj != NULL) {
+	    Tcl_LimitSetGranularity(slaveInterp, TCL_LIMIT_TIME, gran);
+	}
+	return TCL_OK;
+    }
+}
Index: generic/tclMain.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclMain.c,v
retrieving revision 1.25
diff -u -b -B -r1.25 tclMain.c
--- generic/tclMain.c	6 Apr 2004 22:25:54 -0000	1.25
+++ generic/tclMain.c	19 Apr 2004 22:18:35 -0000
@@ -363,6 +363,9 @@
     if (Tcl_InterpDeleted(interp)) {
 	goto done;
     }
+    if (Tcl_LimitExceeded(interp)) {
+	goto done;
+    }
 
     /*
      * If a script file was specified then just source that file
@@ -399,6 +402,9 @@
      */
 
     Tcl_SourceRCFile(interp);
+    if (Tcl_LimitExceeded(interp)) {
+	goto done;
+    }
 
     /*
      * Process commands from stdin until there's an end-of-file.  Note
@@ -421,6 +427,9 @@
 	    if (Tcl_InterpDeleted(interp)) {
 		break;
 	    }
+	    if (Tcl_LimitExceeded(interp)) {
+		break;
+	    }
 	    inChannel = Tcl_GetStdChannel(TCL_STDIN);
 	    if (inChannel == (Tcl_Channel) NULL) {
 	        break;
@@ -557,7 +566,8 @@
     }
 
     done:
-    if ((exitCode == 0) && (mainLoopProc != NULL)) {
+    if ((exitCode == 0) && (mainLoopProc != NULL)
+	    && !Tcl_LimitExceeded(interp)) {
 
 	/*
 	 * If everything has gone OK so far, call the main loop proc,
@@ -578,7 +588,7 @@
      * cleanup on exit.  The Tcl_Eval call should never return.
      */
 
-    if (!Tcl_InterpDeleted(interp)) {
+    if (!Tcl_InterpDeleted(interp) && !Tcl_LimitExceeded(interp)) {
         sprintf(buffer, "exit %d", exitCode);
         Tcl_Eval(interp, buffer);
 
Index: generic/tclStubInit.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclStubInit.c,v
retrieving revision 1.94
diff -u -b -B -r1.94 tclStubInit.c
--- generic/tclStubInit.c	6 Apr 2004 22:25:55 -0000	1.94
+++ generic/tclStubInit.c	19 Apr 2004 22:18:35 -0000
@@ -898,6 +898,21 @@
     Tcl_GetCommandFullName, /* 517 */
     Tcl_FSEvalFileEx, /* 518 */
     Tcl_SetExitProc, /* 519 */
+    Tcl_LimitAddHandler, /* 520 */
+    Tcl_LimitRemoveHandler, /* 521 */
+    Tcl_LimitReady, /* 522 */
+    Tcl_LimitCheck, /* 523 */
+    Tcl_LimitExceeded, /* 524 */
+    Tcl_LimitSetCommands, /* 525 */
+    Tcl_LimitSetTime, /* 526 */
+    Tcl_LimitSetGranularity, /* 527 */
+    Tcl_LimitTypeEnabled, /* 528 */
+    Tcl_LimitTypeExceeded, /* 529 */
+    Tcl_LimitTypeSet, /* 530 */
+    Tcl_LimitTypeReset, /* 531 */
+    Tcl_LimitGetCommands, /* 532 */
+    Tcl_LimitGetTime, /* 533 */
+    Tcl_LimitGetGranularity, /* 534 */
 };
 
 /* !END!: Do not edit above this line. */
Index: generic/tclTrace.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclTrace.c,v
retrieving revision 1.9
diff -u -b -B -r1.9 tclTrace.c
--- generic/tclTrace.c	1 Mar 2004 17:33:45 -0000	1.9
+++ generic/tclTrace.c	19 Apr 2004 22:18:35 -0000
@@ -1295,7 +1295,8 @@
     
     tcmdPtr->refCount++;
     
-    if ((tcmdPtr->flags & flags) && !(flags & TCL_INTERP_DESTROYED)) {
+    if ((tcmdPtr->flags & flags) && !(flags & TCL_INTERP_DESTROYED)
+	    && !Tcl_LimitExceeded(interp)) {
 	/*
 	 * Generate a command to execute by appending list elements
 	 * for the old and new command name and the operation.
@@ -1333,6 +1334,7 @@
 		Tcl_DStringLength(&cmd), 0);
 	if (code != TCL_OK) {	     
 	    /* We ignore errors in these traced commands */
+	    /*** QUESTION: Use Tcl_BackgroundError(interp); instead? ***/
 	}
 
 	Tcl_RestoreResult(interp, &state);
@@ -1728,7 +1730,7 @@
 	return traceCode;
     }
     
-    if (!(flags & TCL_INTERP_DESTROYED)) {
+    if (!(flags & TCL_INTERP_DESTROYED) && !Tcl_LimitExceeded(interp)) {
 	/*
 	 * Check whether the current call is going to eval arbitrary
 	 * Tcl code with a generated trace, or whether we are only
@@ -1938,7 +1940,8 @@
     Tcl_Preserve((ClientData) tvarPtr);
 
     result = NULL;
-    if ((tvarPtr->flags & flags) && !(flags & TCL_INTERP_DESTROYED)) {
+    if ((tvarPtr->flags & flags) && !(flags & TCL_INTERP_DESTROYED)
+	    && !Tcl_LimitExceeded(interp)) {
 	if (tvarPtr->length != (size_t) 0) {
 	    /*
 	     * Generate a command to execute by appending list elements