1 /*
2  * tclTimer.c --
3  *
4  *	This file provides timer event management facilities for Tcl,
5  *	including the "after" command.
6  *
7  * Copyright (c) 1997 by Sun Microsystems, Inc.
8  *
9  * See the file "license.terms" for information on usage and redistribution
10  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11  *
12  * RCS: @(#) $Id: tclTimer.c,v 1.6 2002/03/01 06:22:31 hobbs Exp $
13  */
14 #include "tkPort.h"
15 #include "Lang.h"
16 
17 #ifndef UCHAR
18 #define UCHAR(x) ((unsigned char) (x))
19 #endif
20 
21 /*
22  * This flag indicates whether this module has been initialized.
23  */
24 
25 static int initialized = 0;
26 
27 /*
28  * For each timer callback that's pending there is one record of the following
29  * type.  The normal handlers (created by Tcl_CreateTimerHandler) are chained
30  * together in a list sorted by time (earliest event first).
31  */
32 
33 typedef struct TimerHandler {
34     Tcl_Time time;			/* When timer is to fire. */
35     Tcl_TimerProc *proc;		/* Procedure to call. */
36     ClientData clientData;		/* Argument to pass to proc. */
37     Tcl_TimerToken token;		/* Identifies handler so it can be
38 					 * deleted. */
39     struct TimerHandler *nextPtr;	/* Next event in queue, or NULL for
40 					 * end of queue. */
41 } TimerHandler;
42 
43 /*
44  * The data structure below is used by the "after" command to remember
45  * the command to be executed later.  All of the pending "after" commands
46  * for an interpreter are linked together in a list.
47  */
48 
49 typedef struct AfterInfo {
50     struct AfterAssocData *assocPtr;
51 				/* Pointer to the "tclAfter" assocData for
52 				 * the interp in which command will be
53 				 * executed. */
54     Tcl_Obj *commandPtr;	/* Command to execute. */
55     int id;			/* Integer identifier for command;  used to
56 				 * cancel it. */
57     Tcl_TimerToken token;	/* Used to cancel the "after" command.  NULL
58 				 * means that the command is run as an
59 				 * idle handler rather than as a timer
60 				 * handler.  NULL means this is an "after
61 				 * idle" handler rather than a
62                                  * timer handler. */
63     struct AfterInfo *nextPtr;	/* Next in list of all "after" commands for
64 				 * this interpreter. */
65 } AfterInfo;
66 
67 /*
68  * One of the following structures is associated with each interpreter
69  * for which an "after" command has ever been invoked.  A pointer to
70  * this structure is stored in the AssocData for the "tclAfter" key.
71  */
72 
73 typedef struct AfterAssocData {
74     Tcl_Interp *interp;		/* The interpreter for which this data is
75 				 * registered. */
76     AfterInfo *firstAfterPtr;	/* First in list of all "after" commands
77 				 * still pending for this interpreter, or
78 				 * NULL if none. */
79 } AfterAssocData;
80 
81 /*
82  * There is one of the following structures for each of the
83  * handlers declared in a call to Tcl_DoWhenIdle.  All of the
84  * currently-active handlers are linked together into a list.
85  */
86 
87 typedef struct IdleHandler {
88     Tcl_IdleProc (*proc);	/* Procedure to call. */
89     ClientData clientData;	/* Value to pass to proc. */
90     int generation;		/* Used to distinguish older handlers from
91 				 * recently-created ones. */
92     struct IdleHandler *nextPtr;/* Next in list of active handlers. */
93 } IdleHandler;
94 
95 /*
96  * The timer and idle queues are per-thread because they are associated
97  * with the notifier, which is also per-thread.
98  *
99  * All static variables used in this file are collected into a single
100  * instance of the following structure.  For multi-threaded implementations,
101  * there is one instance of this structure for each thread.
102  *
103  * Notice that different structures with the same name appear in other
104  * files.  The structure defined below is used in this file only.
105  */
106 
107 typedef struct ThreadSpecificData {
108     TimerHandler *firstTimerHandlerPtr;	/* First event in queue. */
109     int lastTimerId;		/* Timer identifier of most recently
110 				 * created timer. */
111     int timerPending;		/* 1 if a timer event is in the queue. */
112     IdleHandler *idleList;	/* First in list of all idle handlers. */
113     IdleHandler *lastIdlePtr;	/* Last in list (or NULL for empty list). */
114     int idleGeneration;		/* Used to fill in the "generation" fields
115 				 * of IdleHandler structures.  Increments
116 				 * each time Tcl_DoOneEvent starts calling
117 				 * idle handlers, so that all old handlers
118 				 * can be called without calling any of the
119 				 * new ones created by old ones. */
120     int afterId;		/* For unique identifiers of after events. */
121 } ThreadSpecificData;
122 
123 static Tcl_ThreadDataKey dataKey;
124 
125 /*
126  * Prototypes for procedures referenced only in this file:
127  */
128 
129 #ifndef TCL_EVENT_IMPLEMENT
130 static void		AfterCleanupProc _ANSI_ARGS_((ClientData clientData,
131 			    Tcl_Interp *interp));
132 static void		AfterProc _ANSI_ARGS_((ClientData clientData));
133 static void		FreeAfterPtr _ANSI_ARGS_((AfterInfo *afterPtr));
134 static AfterInfo *	GetAfterEvent _ANSI_ARGS_((AfterAssocData *assocPtr,
135 			    Tcl_Obj *commandPtr));
136 #else
137 static ThreadSpecificData *InitTimer _ANSI_ARGS_((void));
138 static void		TimerExitProc _ANSI_ARGS_((ClientData clientData));
139 static int		TimerHandlerEventProc _ANSI_ARGS_((Tcl_Event *evPtr,
140 			    int flags));
141 static void		TimerCheckProc _ANSI_ARGS_((ClientData clientData,
142 			    int flags));
143 static void		TimerSetupProc _ANSI_ARGS_((ClientData clientData,
144 			    int flags));
145 
146 #endif
147 #ifdef TCL_EVENT_IMPLEMENT
148 /*
149  *----------------------------------------------------------------------
150  *
151  * InitTimer --
152  *
153  *	This function initializes the timer module.
154  *
155  * Results:
156  *	A pointer to the thread specific data.
157  *
158  * Side effects:
159  *	Registers the idle and timer event sources.
160  *
161  *----------------------------------------------------------------------
162  */
163 
164 static ThreadSpecificData *
InitTimer()165 InitTimer()
166 {
167     ThreadSpecificData *tsdPtr =
168 	(ThreadSpecificData *) TclThreadDataKeyGet(&dataKey);
169 
170     if (tsdPtr == NULL) {
171 	tsdPtr = TCL_TSD_INIT(&dataKey);
172 	Tcl_CreateEventSource(TimerSetupProc, TimerCheckProc, NULL);
173 	Tcl_CreateThreadExitHandler(TimerExitProc, NULL);
174     }
175     return tsdPtr;
176 }
177 
178 void *
TkInitTimer()179 TkInitTimer()
180 {
181  return InitTimer();
182 }
183 /*
184  *----------------------------------------------------------------------
185  *
186  * TimerExitProc --
187  *
188  *	This function is call at exit or unload time to remove the
189  *	timer and idle event sources.
190  *
191  * Results:
192  *	None.
193  *
194  * Side effects:
195  *	Removes the timer and idle event sources and remaining events.
196  *
197  *----------------------------------------------------------------------
198  */
199 
200 static void
TimerExitProc(clientData)201 TimerExitProc(clientData)
202     ClientData clientData;	/* Not used. */
203 {
204     ThreadSpecificData *tsdPtr =
205 	(ThreadSpecificData *) TclThreadDataKeyGet(&dataKey);
206 
207     Tcl_DeleteEventSource(TimerSetupProc, TimerCheckProc, NULL);
208     if (tsdPtr != NULL) {
209 	register TimerHandler *timerHandlerPtr;
210 	timerHandlerPtr = tsdPtr->firstTimerHandlerPtr;
211 	while (timerHandlerPtr != NULL) {
212 	    tsdPtr->firstTimerHandlerPtr = timerHandlerPtr->nextPtr;
213 	    ckfree((char *) timerHandlerPtr);
214 	    timerHandlerPtr = tsdPtr->firstTimerHandlerPtr;
215 	}
216     }
217 }
218 
219 /*
220  *--------------------------------------------------------------
221  *
222  * Tcl_CreateTimerHandler --
223  *
224  *	Arrange for a given procedure to be invoked at a particular
225  *	time in the future.
226  *
227  * Results:
228  *	The return value is a token for the timer event, which
229  *	may be used to delete the event before it fires.
230  *
231  * Side effects:
232  *	When milliseconds have elapsed, proc will be invoked
233  *	exactly once.
234  *
235  *--------------------------------------------------------------
236  */
237 
238 
239 Tcl_TimerToken
Tcl_CreateTimerHandler(milliseconds,proc,clientData)240 Tcl_CreateTimerHandler(milliseconds, proc, clientData)
241     int milliseconds;		/* How many milliseconds to wait
242 				 * before invoking proc. */
243     Tcl_TimerProc *proc;	/* Procedure to invoke. */
244     ClientData clientData;	/* Arbitrary data to pass to proc. */
245 {
246     register TimerHandler *timerHandlerPtr, *tPtr2, *prevPtr;
247     Tcl_Time time;
248     ThreadSpecificData *tsdPtr;
249 
250     tsdPtr = InitTimer();
251 
252     timerHandlerPtr = (TimerHandler *) ckalloc(sizeof(TimerHandler));
253 
254     /*
255      * Compute when the event should fire.
256      */
257 
258     Tcl_GetTime(&time);
259     timerHandlerPtr->time.sec = time.sec + milliseconds/1000;
260     timerHandlerPtr->time.usec = time.usec + (milliseconds%1000)*1000;
261     if (timerHandlerPtr->time.usec >= 1000000) {
262 	timerHandlerPtr->time.usec -= 1000000;
263 	timerHandlerPtr->time.sec += 1;
264     }
265 
266     /*
267      * Fill in other fields for the event.
268      */
269 
270     timerHandlerPtr->proc = proc;
271     timerHandlerPtr->clientData = clientData;
272     tsdPtr->lastTimerId++;
273     timerHandlerPtr->token = (Tcl_TimerToken) tsdPtr->lastTimerId;
274 
275     /*
276      * Add the event to the queue in the correct position
277      * (ordered by event firing time).
278      */
279 
280     for (tPtr2 = tsdPtr->firstTimerHandlerPtr, prevPtr = NULL; tPtr2 != NULL;
281 	    prevPtr = tPtr2, tPtr2 = tPtr2->nextPtr) {
282 	if ((tPtr2->time.sec > timerHandlerPtr->time.sec)
283 		|| ((tPtr2->time.sec == timerHandlerPtr->time.sec)
284 		&& (tPtr2->time.usec > timerHandlerPtr->time.usec))) {
285 	    break;
286 	}
287     }
288     timerHandlerPtr->nextPtr = tPtr2;
289     if (prevPtr == NULL) {
290 	tsdPtr->firstTimerHandlerPtr = timerHandlerPtr;
291     } else {
292 	prevPtr->nextPtr = timerHandlerPtr;
293     }
294 
295     TimerSetupProc(NULL, TCL_ALL_EVENTS);
296 
297     return timerHandlerPtr->token;
298 }
299 
300 /*
301  *--------------------------------------------------------------
302  *
303  * Tcl_DeleteTimerHandler --
304  *
305  *	Delete a previously-registered timer handler.
306  *
307  * Results:
308  *	None.
309  *
310  * Side effects:
311  *	Destroy the timer callback identified by TimerToken,
312  *	so that its associated procedure will not be called.
313  *	If the callback has already fired, or if the given
314  *	token doesn't exist, then nothing happens.
315  *
316  *--------------------------------------------------------------
317  */
318 
319 void
Tcl_DeleteTimerHandler(token)320 Tcl_DeleteTimerHandler(token)
321     Tcl_TimerToken token;	/* Result previously returned by
322 				 * Tcl_DeleteTimerHandler. */
323 {
324     register TimerHandler *timerHandlerPtr, *prevPtr;
325     ThreadSpecificData *tsdPtr;
326 
327     tsdPtr = InitTimer();
328     for (timerHandlerPtr = tsdPtr->firstTimerHandlerPtr, prevPtr = NULL;
329 	    timerHandlerPtr != NULL; prevPtr = timerHandlerPtr,
330 	    timerHandlerPtr = timerHandlerPtr->nextPtr) {
331 	if (timerHandlerPtr->token != token) {
332 	    continue;
333 	}
334 	if (prevPtr == NULL) {
335 	    tsdPtr->firstTimerHandlerPtr = timerHandlerPtr->nextPtr;
336 	} else {
337 	    prevPtr->nextPtr = timerHandlerPtr->nextPtr;
338 	}
339 	ckfree((char *) timerHandlerPtr);
340 	return;
341     }
342 }
343 
344 /*
345  *----------------------------------------------------------------------
346  *
347  * TimerSetupProc --
348  *
349  *	This function is called by Tcl_DoOneEvent to setup the timer
350  *	event source for before blocking.  This routine checks both the
351  *	idle and after timer lists.
352  *
353  * Results:
354  *	None.
355  *
356  * Side effects:
357  *	May update the maximum notifier block time.
358  *
359  *----------------------------------------------------------------------
360  */
361 
362 static void
TimerSetupProc(data,flags)363 TimerSetupProc(data, flags)
364     ClientData data;		/* Not used. */
365     int flags;			/* Event flags as passed to Tcl_DoOneEvent. */
366 {
367     Tcl_Time blockTime;
368     ThreadSpecificData *tsdPtr = InitTimer();
369 
370     if (((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList)
371 	    || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)) {
372 	/*
373 	 * There is an idle handler or a pending timer event, so just poll.
374 	 */
375 
376 	blockTime.sec = 0;
377 	blockTime.usec = 0;
378 
379     } else if ((flags & TCL_TIMER_EVENTS) && tsdPtr->firstTimerHandlerPtr) {
380 	/*
381 	 * Compute the timeout for the next timer on the list.
382 	 */
383 
384 	Tcl_GetTime(&blockTime);
385 	blockTime.sec = tsdPtr->firstTimerHandlerPtr->time.sec - blockTime.sec;
386 	blockTime.usec = tsdPtr->firstTimerHandlerPtr->time.usec -
387 		blockTime.usec;
388 	if (blockTime.usec < 0) {
389 	    blockTime.sec -= 1;
390 	    blockTime.usec += 1000000;
391 	}
392 	if (blockTime.sec < 0) {
393 	    blockTime.sec = 0;
394 	    blockTime.usec = 0;
395 	}
396     } else {
397 	return;
398     }
399 
400     Tcl_SetMaxBlockTime(&blockTime);
401 }
402 
403 /*
404  *----------------------------------------------------------------------
405  *
406  * TimerCheckProc --
407  *
408  *	This function is called by Tcl_DoOneEvent to check the timer
409  *	event source for events.  This routine checks both the
410  *	idle and after timer lists.
411  *
412  * Results:
413  *	None.
414  *
415  * Side effects:
416  *	May queue an event and update the maximum notifier block time.
417  *
418  *----------------------------------------------------------------------
419  */
420 
421 static void
TimerCheckProc(data,flags)422 TimerCheckProc(data, flags)
423     ClientData data;		/* Not used. */
424     int flags;			/* Event flags as passed to Tcl_DoOneEvent. */
425 {
426     Tcl_Event *timerEvPtr;
427     Tcl_Time blockTime;
428     ThreadSpecificData *tsdPtr = InitTimer();
429 
430     if ((flags & TCL_TIMER_EVENTS) && tsdPtr->firstTimerHandlerPtr) {
431 	/*
432 	 * Compute the timeout for the next timer on the list.
433 	 */
434 
435 	Tcl_GetTime(&blockTime);
436 	blockTime.sec = tsdPtr->firstTimerHandlerPtr->time.sec - blockTime.sec;
437 	blockTime.usec = tsdPtr->firstTimerHandlerPtr->time.usec -
438 		blockTime.usec;
439 	if (blockTime.usec < 0) {
440 	    blockTime.sec -= 1;
441 	    blockTime.usec += 1000000;
442 	}
443 	if (blockTime.sec < 0) {
444 	    blockTime.sec = 0;
445 	    blockTime.usec = 0;
446 	}
447 
448 	/*
449 	 * If the first timer has expired, stick an event on the queue.
450 	 */
451 
452 	if (blockTime.sec == 0 && blockTime.usec == 0 &&
453 		!tsdPtr->timerPending) {
454 	    tsdPtr->timerPending = 1;
455 	    timerEvPtr = (Tcl_Event *) ckalloc(sizeof(Tcl_Event));
456 	    timerEvPtr->proc = TimerHandlerEventProc;
457 	    Tcl_QueueEvent(timerEvPtr, TCL_QUEUE_TAIL);
458 	}
459     }
460 }
461 
462 /*
463  *----------------------------------------------------------------------
464  *
465  * TimerHandlerEventProc --
466  *
467  *	This procedure is called by Tcl_ServiceEvent when a timer event
468  *	reaches the front of the event queue.  This procedure handles
469  *	the event by invoking the callbacks for all timers that are
470  *	ready.
471  *
472  * Results:
473  *	Returns 1 if the event was handled, meaning it should be removed
474  *	from the queue.  Returns 0 if the event was not handled, meaning
475  *	it should stay on the queue.  The only time the event isn't
476  *	handled is if the TCL_TIMER_EVENTS flag bit isn't set.
477  *
478  * Side effects:
479  *	Whatever the timer handler callback procedures do.
480  *
481  *----------------------------------------------------------------------
482  */
483 
484 static int
TimerHandlerEventProc(evPtr,flags)485 TimerHandlerEventProc(evPtr, flags)
486     Tcl_Event *evPtr;		/* Event to service. */
487     int flags;			/* Flags that indicate what events to
488 				 * handle, such as TCL_FILE_EVENTS. */
489 {
490     TimerHandler *timerHandlerPtr, **nextPtrPtr;
491     Tcl_Time time;
492     int currentTimerId;
493     ThreadSpecificData *tsdPtr = InitTimer();
494 
495     /*
496      * Do nothing if timers aren't enabled.  This leaves the event on the
497      * queue, so we will get to it as soon as ServiceEvents() is called
498      * with timers enabled.
499      */
500 
501     if (!(flags & TCL_TIMER_EVENTS)) {
502 	return 0;
503     }
504 
505     /*
506      * The code below is trickier than it may look, for the following
507      * reasons:
508      *
509      * 1. New handlers can get added to the list while the current
510      *    one is being processed.  If new ones get added, we don't
511      *    want to process them during this pass through the list to avoid
512      *	  starving other event sources.  This is implemented using the
513      *	  token number in the handler:  new handlers will have a
514      *    newer token than any of the ones currently on the list.
515      * 2. The handler can call Tcl_DoOneEvent, so we have to remove
516      *    the handler from the list before calling it. Otherwise an
517      *    infinite loop could result.
518      * 3. Tcl_DeleteTimerHandler can be called to remove an element from
519      *    the list while a handler is executing, so the list could
520      *    change structure during the call.
521      * 4. Because we only fetch the current time before entering the loop,
522      *    the only way a new timer will even be considered runnable is if
523      *	  its expiration time is within the same millisecond as the
524      *	  current time.  This is fairly likely on Windows, since it has
525      *	  a course granularity clock.  Since timers are placed
526      *	  on the queue in time order with the most recently created
527      *    handler appearing after earlier ones with the same expiration
528      *	  time, we don't have to worry about newer generation timers
529      *	  appearing before later ones.
530      */
531 
532     tsdPtr->timerPending = 0;
533     currentTimerId = tsdPtr->lastTimerId;
534     Tcl_GetTime(&time);
535     while (1) {
536 	nextPtrPtr = &tsdPtr->firstTimerHandlerPtr;
537 	timerHandlerPtr = tsdPtr->firstTimerHandlerPtr;
538 	if (timerHandlerPtr == NULL) {
539 	    break;
540 	}
541 
542 	if ((timerHandlerPtr->time.sec > time.sec)
543 		|| ((timerHandlerPtr->time.sec == time.sec)
544 			&& (timerHandlerPtr->time.usec > time.usec))) {
545 	    break;
546 	}
547 
548 	/*
549 	 * Bail out if the next timer is of a newer generation.
550 	 */
551 
552 	if ((currentTimerId - (int)timerHandlerPtr->token) < 0) {
553 	    break;
554 	}
555 
556 	/*
557 	 * Remove the handler from the queue before invoking it,
558 	 * to avoid potential reentrancy problems.
559 	 */
560 
561 	(*nextPtrPtr) = timerHandlerPtr->nextPtr;
562 	(*timerHandlerPtr->proc)(timerHandlerPtr->clientData);
563 	ckfree((char *) timerHandlerPtr);
564     }
565     TimerSetupProc(NULL, TCL_TIMER_EVENTS);
566     return 1;
567 }
568 
569 /*
570  *--------------------------------------------------------------
571  *
572  * Tcl_DoWhenIdle --
573  *
574  *	Arrange for proc to be invoked the next time the system is
575  *	idle (i.e., just before the next time that Tcl_DoOneEvent
576  *	would have to wait for something to happen).
577  *
578  * Results:
579  *	None.
580  *
581  * Side effects:
582  *	Proc will eventually be called, with clientData as argument.
583  *	See the manual entry for details.
584  *
585  *--------------------------------------------------------------
586  */
587 
588 void
Tcl_DoWhenIdle(proc,clientData)589 Tcl_DoWhenIdle(proc, clientData)
590     Tcl_IdleProc *proc;		/* Procedure to invoke. */
591     ClientData clientData;	/* Arbitrary value to pass to proc. */
592 {
593     register IdleHandler *idlePtr;
594     Tcl_Time blockTime;
595     ThreadSpecificData *tsdPtr = InitTimer();
596 
597     idlePtr = (IdleHandler *) ckalloc(sizeof(IdleHandler));
598     idlePtr->proc = proc;
599     idlePtr->clientData = clientData;
600     idlePtr->generation = tsdPtr->idleGeneration;
601     idlePtr->nextPtr = NULL;
602     if (tsdPtr->lastIdlePtr == NULL) {
603 	tsdPtr->idleList = idlePtr;
604     } else {
605 	tsdPtr->lastIdlePtr->nextPtr = idlePtr;
606     }
607     tsdPtr->lastIdlePtr = idlePtr;
608 
609     blockTime.sec = 0;
610     blockTime.usec = 0;
611     Tcl_SetMaxBlockTime(&blockTime);
612 }
613 
614 /*
615  *----------------------------------------------------------------------
616  *
617  * Tcl_CancelIdleCall --
618  *
619  *	If there are any when-idle calls requested to a given procedure
620  *	with given clientData, cancel all of them.
621  *
622  * Results:
623  *	None.
624  *
625  * Side effects:
626  *	If the proc/clientData combination were on the when-idle list,
627  *	they are removed so that they will never be called.
628  *
629  *----------------------------------------------------------------------
630  */
631 
632 void
Tcl_CancelIdleCall(proc,clientData)633 Tcl_CancelIdleCall(proc, clientData)
634     Tcl_IdleProc *proc;		/* Procedure that was previously registered. */
635     ClientData clientData;	/* Arbitrary value to pass to proc. */
636 {
637     register IdleHandler *idlePtr, *prevPtr;
638     IdleHandler *nextPtr;
639     ThreadSpecificData *tsdPtr = InitTimer();
640 
641     for (prevPtr = NULL, idlePtr = tsdPtr->idleList; idlePtr != NULL;
642 	    prevPtr = idlePtr, idlePtr = idlePtr->nextPtr) {
643 	while ((idlePtr->proc == proc)
644 		&& (idlePtr->clientData == clientData)) {
645 	    nextPtr = idlePtr->nextPtr;
646 	    ckfree((char *) idlePtr);
647 	    idlePtr = nextPtr;
648 	    if (prevPtr == NULL) {
649 		tsdPtr->idleList = idlePtr;
650 	    } else {
651 		prevPtr->nextPtr = idlePtr;
652 	    }
653 	    if (idlePtr == NULL) {
654 		tsdPtr->lastIdlePtr = prevPtr;
655 		return;
656 	    }
657 	}
658     }
659 }
660 
661 /*
662  *----------------------------------------------------------------------
663  *
664  * TclServiceIdle --
665  *
666  *	This procedure is invoked by the notifier when it becomes
667  *	idle.  It will invoke all idle handlers that are present at
668  *	the time the call is invoked, but not those added during idle
669  *	processing.
670  *
671  * Results:
672  *	The return value is 1 if TclServiceIdle found something to
673  *	do, otherwise return value is 0.
674  *
675  * Side effects:
676  *	Invokes all pending idle handlers.
677  *
678  *----------------------------------------------------------------------
679  */
680 
681 int
TclServiceIdle()682 TclServiceIdle()
683 {
684     IdleHandler *idlePtr;
685     int oldGeneration;
686     Tcl_Time blockTime;
687     ThreadSpecificData *tsdPtr = InitTimer();
688 
689     if (tsdPtr->idleList == NULL) {
690 	return 0;
691     }
692 
693     oldGeneration = tsdPtr->idleGeneration;
694     tsdPtr->idleGeneration++;
695 
696     /*
697      * The code below is trickier than it may look, for the following
698      * reasons:
699      *
700      * 1. New handlers can get added to the list while the current
701      *    one is being processed.  If new ones get added, we don't
702      *    want to process them during this pass through the list (want
703      *    to check for other work to do first).  This is implemented
704      *    using the generation number in the handler:  new handlers
705      *    will have a different generation than any of the ones currently
706      *    on the list.
707      * 2. The handler can call Tcl_DoOneEvent, so we have to remove
708      *    the handler from the list before calling it. Otherwise an
709      *    infinite loop could result.
710      * 3. Tcl_CancelIdleCall can be called to remove an element from
711      *    the list while a handler is executing, so the list could
712      *    change structure during the call.
713      */
714 
715     for (idlePtr = tsdPtr->idleList;
716 	    ((idlePtr != NULL)
717 		    && ((oldGeneration - idlePtr->generation) >= 0));
718 	    idlePtr = tsdPtr->idleList) {
719 	tsdPtr->idleList = idlePtr->nextPtr;
720 	if (tsdPtr->idleList == NULL) {
721 	    tsdPtr->lastIdlePtr = NULL;
722 	}
723 	(*idlePtr->proc)(idlePtr->clientData);
724 	ckfree((char *) idlePtr);
725     }
726     if (tsdPtr->idleList) {
727 	blockTime.sec = 0;
728 	blockTime.usec = 0;
729 	Tcl_SetMaxBlockTime(&blockTime);
730     }
731     return 1;
732 }
733 
734 #endif
735 #ifndef TCL_EVENT_IMPLEMENT
736 /*
737  *----------------------------------------------------------------------
738  *
739  * Tcl_AfterObjCmd --
740  *
741  *	This procedure is invoked to process the "after" Tcl command.
742  *	See the user documentation for details on what it does.
743  *
744  * Results:
745  *	A standard Tcl result.
746  *
747  * Side effects:
748  *	See the user documentation.
749  *
750  *----------------------------------------------------------------------
751  */
752 
753 #if ((defined(__STDC__) || defined(SABER)) && !defined(NO_PROTOTYPE)) || defined(__cplusplus) || defined(USE_PROTOTYPE)
754 #   define ICONST const
755 #else
756 #   define ICONST
757 #endif
758 
759 	/* ARGSUSED */
760 int
Tcl_AfterObjCmd(clientData,interp,objc,objv)761 Tcl_AfterObjCmd(clientData, interp, objc, objv)
762     ClientData clientData;	/* Points to the "tclAfter" assocData for
763 				 * this interpreter, or NULL if the assocData
764 				 * hasn't been created yet.*/
765     Tcl_Interp *interp;		/* Current interpreter. */
766     int objc;			/* Number of arguments. */
767     Tcl_Obj *ICONST objv[];	/* Argument objects. */
768 {
769     int ms;
770     AfterInfo *afterPtr;
771     AfterAssocData *assocPtr = (AfterAssocData *) clientData;
772     Tcl_CmdInfo cmdInfo;
773     int length;
774     char *argString;
775     int index;
776     char buf[16 + TCL_INTEGER_SPACE];
777     static CONST char *afterSubCmds[] = {
778 	"cancel", "idle", "info", (char *) NULL
779     };
780     enum afterSubCmds {AFTER_CANCEL, AFTER_IDLE, AFTER_INFO};
781     ThreadSpecificData *tsdPtr = TkInitTimer();
782 
783     if (objc < 2) {
784 	Tcl_WrongNumArgs(interp, 1, (Tcl_Obj **)objv, "option ?arg arg ...?");
785 	return TCL_ERROR;
786     }
787 
788     /*
789      * Create the "after" information associated for this interpreter,
790      * if it doesn't already exist.  Associate it with the command too,
791      * so that it will be passed in as the ClientData argument in the
792      * future.
793      */
794 
795     if (assocPtr == NULL) {
796 	assocPtr = (AfterAssocData *) ckalloc(sizeof(AfterAssocData));
797 	assocPtr->interp = interp;
798 	assocPtr->firstAfterPtr = NULL;
799 	Tcl_SetAssocData(interp, "tclAfter", AfterCleanupProc,
800 		(ClientData) assocPtr);
801 	cmdInfo.proc = NULL;
802 	cmdInfo.clientData = (ClientData) NULL;
803 	cmdInfo.objProc = (Tcl_ObjCmdProc *) Tcl_AfterObjCmd;
804 	cmdInfo.objClientData = (ClientData) assocPtr;
805 	cmdInfo.deleteProc = NULL;
806 	cmdInfo.deleteData = (ClientData) assocPtr;
807 	Tcl_SetCommandInfo(interp, Tcl_GetStringFromObj(objv[0], &length),
808 		&cmdInfo);
809     }
810 
811     /*
812      * First lets see if the command was passed a number as the first argument.
813      */
814 
815     if (TclObjGetType(objv[1]) == &tclIntType) {
816 	ms = (int) TclObjInternal(objv[1])->longValue;
817 	goto processInteger;
818     }
819     argString = Tcl_GetStringFromObj(objv[1], &length);
820     if (isdigit(UCHAR(argString[0]))) {	/* INTL: digit */
821 	if (Tcl_GetIntFromObj(interp, objv[1], &ms) != TCL_OK) {
822 	    return TCL_ERROR;
823 	}
824 processInteger:
825 	if (ms < 0) {
826 	    ms = 0;
827 	}
828 	if (objc == 2) {
829 	    Tcl_Sleep(ms);
830 	    return TCL_OK;
831 	}
832 	afterPtr = (AfterInfo *) ckalloc((unsigned) (sizeof(AfterInfo)));
833 	afterPtr->assocPtr = assocPtr;
834 	if (objc == 3) {
835 	    afterPtr->commandPtr = LangMakeCallback(objv[2]);
836 	} else {
837 	    Tcl_Obj *objPtr = Tcl_ConcatObj(objc-2, (Tcl_Obj **)objv+2);
838 	    afterPtr->commandPtr = LangMakeCallback(objPtr);
839 	    Tcl_DecrRefCount(objPtr);
840 	}
841 	/*
842 	 * The variable below is used to generate unique identifiers for
843 	 * after commands.  This id can wrap around, which can potentially
844 	 * cause problems.  However, there are not likely to be problems
845 	 * in practice, because after commands can only be requested to
846 	 * about a month in the future, and wrap-around is unlikely to
847 	 * occur in less than about 1-10 years.  Thus it's unlikely that
848 	 * any old ids will still be around when wrap-around occurs.
849 	 */
850 	afterPtr->id = tsdPtr->afterId;
851 	tsdPtr->afterId += 1;
852 	afterPtr->token = Tcl_CreateTimerHandler(ms, AfterProc,
853 		(ClientData) afterPtr);
854 	afterPtr->nextPtr = assocPtr->firstAfterPtr;
855 	assocPtr->firstAfterPtr = afterPtr;
856 	sprintf(buf, "after#%d", afterPtr->id);
857 	Tcl_AppendResult(interp, buf, (char *) NULL);
858 	return TCL_OK;
859     }
860 
861     /*
862      * If it's not a number it must be a subcommand.
863      */
864 
865     if (Tcl_GetIndexFromObj(NULL, objv[1], afterSubCmds, "argument",
866             0, &index) != TCL_OK) {
867 	Tcl_AppendResult(interp, "bad argument \"", argString,
868 		"\": must be cancel, idle, info, or a number",
869 		(char *) NULL);
870 	return TCL_ERROR;
871     }
872     switch ((enum afterSubCmds) index) {
873         case AFTER_CANCEL: {
874 	    Tcl_Obj *commandPtr;
875 	    char *command, *tempCommand;
876 	    int tempLength;
877 
878 	    if (objc < 3) {
879 		Tcl_WrongNumArgs(interp, 2, (Tcl_Obj **)objv, "id|command");
880 		return TCL_ERROR;
881 	    }
882 	    if (objc == 3) {
883 		commandPtr = objv[2];
884 		afterPtr = GetAfterEvent(assocPtr, commandPtr);
885 		if (afterPtr != NULL) {
886 		    goto got_after;
887 		}
888 	    } else {
889 		commandPtr = Tcl_ConcatObj(objc-2, (Tcl_Obj **)objv+2);;
890 	    }
891 	    command = Tcl_GetStringFromObj(commandPtr, &length);
892 	    for (afterPtr = assocPtr->firstAfterPtr;  afterPtr != NULL;
893 		    afterPtr = afterPtr->nextPtr) {
894 		tempCommand = Tcl_GetStringFromObj(afterPtr->commandPtr,
895 			&tempLength);
896 		if ((length == tempLength)
897 		        && (memcmp((void*) command, (void*) tempCommand,
898 			        (unsigned) length) == 0)) {
899 		    break;
900 		}
901 	    }
902 	    if (afterPtr == NULL) {
903 		afterPtr = GetAfterEvent(assocPtr, commandPtr);
904 	    }
905 	    if (objc != 3) {
906 		Tcl_DecrRefCount(commandPtr);
907 	    }
908 	got_after:
909 	    if (afterPtr != NULL) {
910 		if (afterPtr->token != NULL) {
911 		    Tcl_DeleteTimerHandler(afterPtr->token);
912 		} else {
913 		    Tcl_CancelIdleCall(AfterProc, (ClientData) afterPtr);
914 		}
915 		FreeAfterPtr(afterPtr);
916 	    }
917 	    break;
918 	}
919 	case AFTER_IDLE:
920 	    if (objc < 3) {
921 		Tcl_WrongNumArgs(interp, 2, (Tcl_Obj **)objv, "script script ...");
922 		return TCL_ERROR;
923 	    }
924 	    afterPtr = (AfterInfo *) ckalloc((unsigned) (sizeof(AfterInfo)));
925 	    afterPtr->assocPtr = assocPtr;
926 	    if (objc == 3) {
927 		afterPtr->commandPtr = LangMakeCallback(objv[2]);
928 	    } else {
929 		Tcl_Obj *objPtr = Tcl_ConcatObj(objc-2, (Tcl_Obj **)objv+2);
930 		afterPtr->commandPtr = LangMakeCallback(objPtr);
931 		Tcl_DecrRefCount(objPtr);
932 	    }
933 #ifndef _LANG
934 	    /* commandPtr already has refcnt==1 */
935 	    Tcl_IncrRefCount(afterPtr->commandPtr);
936 #endif
937 	    afterPtr->id = tsdPtr->afterId;
938 	    tsdPtr->afterId += 1;
939 	    afterPtr->token = NULL;
940 	    afterPtr->nextPtr = assocPtr->firstAfterPtr;
941 	    assocPtr->firstAfterPtr = afterPtr;
942 	    Tcl_DoWhenIdle(AfterProc, (ClientData) afterPtr);
943 	    sprintf(buf, "after#%d", afterPtr->id);
944 	    Tcl_AppendResult(interp, buf, (char *) NULL);
945 	    break;
946 	case AFTER_INFO: {
947 	    Tcl_Obj *resultListPtr;
948 
949 	    if (objc == 2) {
950 		for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL;
951 		     afterPtr = afterPtr->nextPtr) {
952 		    if (assocPtr->interp == interp) {
953 			sprintf(buf, "after#%d", afterPtr->id);
954 			Tcl_AppendElement(interp, buf);
955 		    }
956 		}
957 		return TCL_OK;
958 	    }
959 	    if (objc != 3) {
960 		Tcl_WrongNumArgs(interp, 2, (Tcl_Obj **)objv, "?id?");
961 		return TCL_ERROR;
962 	    }
963 	    afterPtr = GetAfterEvent(assocPtr, objv[2]);
964 	    if (afterPtr == NULL) {
965 		Tcl_AppendResult(interp, "event \"", Tcl_GetString(objv[2]),
966 			"\" doesn't exist", (char *) NULL);
967 		return TCL_ERROR;
968 	    }
969 	    resultListPtr = Tcl_GetObjResult(interp);
970  	    Tcl_ListObjAppendElement(interp, resultListPtr, LangCallbackObj(afterPtr->commandPtr));
971  	    Tcl_ListObjAppendElement(interp, resultListPtr, Tcl_NewStringObj(
972  		(afterPtr->token == NULL) ? "idle" : "timer", -1));
973 	    Tcl_SetObjResult(interp, resultListPtr);
974 	    break;
975 	}
976 	default: {
977 	    panic("Tcl_AfterObjCmd: bad subcommand index to afterSubCmds");
978 	}
979     }
980     return TCL_OK;
981 }
982 
983 /*
984  *----------------------------------------------------------------------
985  *
986  * GetAfterEvent --
987  *
988  *	This procedure parses an "after" id such as "after#4" and
989  *	returns a pointer to the AfterInfo structure.
990  *
991  * Results:
992  *	The return value is either a pointer to an AfterInfo structure,
993  *	if one is found that corresponds to "cmdString" and is for interp,
994  *	or NULL if no corresponding after event can be found.
995  *
996  * Side effects:
997  *	None.
998  *
999  *----------------------------------------------------------------------
1000  */
1001 
1002 static AfterInfo *
GetAfterEvent(assocPtr,commandPtr)1003 GetAfterEvent(assocPtr, commandPtr)
1004     AfterAssocData *assocPtr;	/* Points to "after"-related information for
1005 				 * this interpreter. */
1006     Tcl_Obj *commandPtr;
1007 {
1008     char *cmdString;		/* Textual identifier for after event, such
1009 				 * as "after#6". */
1010     AfterInfo *afterPtr;
1011     int id;
1012     char *end;
1013 
1014     cmdString = Tcl_GetString(commandPtr);
1015     if (strncmp(cmdString, "after#", 6) != 0) {
1016 	return NULL;
1017     }
1018     cmdString += 6;
1019     id = strtoul(cmdString, &end, 10);
1020     if ((end == cmdString) || (*end != 0)) {
1021 	return NULL;
1022     }
1023     for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL;
1024 	    afterPtr = afterPtr->nextPtr) {
1025 	if (afterPtr->id == id) {
1026 	    return afterPtr;
1027 	}
1028     }
1029     return NULL;
1030 }
1031 
1032 /*
1033  *----------------------------------------------------------------------
1034  *
1035  * AfterProc --
1036  *
1037  *	Timer callback to execute commands registered with the
1038  *	"after" command.
1039  *
1040  * Results:
1041  *	None.
1042  *
1043  * Side effects:
1044  *	Executes whatever command was specified.  If the command
1045  *	returns an error, then the command "bgerror" is invoked
1046  *	to process the error;  if bgerror fails then information
1047  *	about the error is output on stderr.
1048  *
1049  *----------------------------------------------------------------------
1050  */
1051 
1052 static void
AfterProc(clientData)1053 AfterProc(clientData)
1054     ClientData clientData;	/* Describes command to execute. */
1055 {
1056     AfterInfo *afterPtr = (AfterInfo *) clientData;
1057     AfterAssocData *assocPtr = afterPtr->assocPtr;
1058     AfterInfo *prevPtr;
1059     int result;
1060     Tcl_Interp *interp;
1061     char *script;
1062     int numBytes;
1063 
1064     /*
1065      * First remove the callback from our list of callbacks;  otherwise
1066      * someone could delete the callback while it's being executed, which
1067      * could cause a core dump.
1068      */
1069 
1070     if (assocPtr->firstAfterPtr == afterPtr) {
1071 	assocPtr->firstAfterPtr = afterPtr->nextPtr;
1072     } else {
1073 	for (prevPtr = assocPtr->firstAfterPtr; prevPtr->nextPtr != afterPtr;
1074 		prevPtr = prevPtr->nextPtr) {
1075 	    /* Empty loop body. */
1076 	}
1077 	prevPtr->nextPtr = afterPtr->nextPtr;
1078     }
1079 
1080     /*
1081      * Execute the callback.
1082      */
1083 
1084     interp = assocPtr->interp;
1085     Tcl_Preserve((ClientData) interp);
1086     result = LangDoCallback(interp, afterPtr->commandPtr, 0, 0);
1087     if (result != TCL_OK) {
1088 	Tcl_AddErrorInfo(interp, "\n    (\"after\" script)");
1089 	Tcl_BackgroundError(interp);
1090     }
1091     Tcl_Release((ClientData) interp);
1092 
1093     /*
1094      * Free the memory for the callback.
1095      */
1096 
1097     Tcl_DecrRefCount(afterPtr->commandPtr);
1098     ckfree((char *) afterPtr);
1099 }
1100 
1101 /*
1102  *----------------------------------------------------------------------
1103  *
1104  * FreeAfterPtr --
1105  *
1106  *	This procedure removes an "after" command from the list of
1107  *	those that are pending and frees its resources.  This procedure
1108  *	does *not* cancel the timer handler;  if that's needed, the
1109  *	caller must do it.
1110  *
1111  * Results:
1112  *	None.
1113  *
1114  * Side effects:
1115  *	The memory associated with afterPtr is released.
1116  *
1117  *----------------------------------------------------------------------
1118  */
1119 
1120 static void
FreeAfterPtr(afterPtr)1121 FreeAfterPtr(afterPtr)
1122     AfterInfo *afterPtr;		/* Command to be deleted. */
1123 {
1124     AfterInfo *prevPtr;
1125     AfterAssocData *assocPtr = afterPtr->assocPtr;
1126 
1127     if (assocPtr->firstAfterPtr == afterPtr) {
1128 	assocPtr->firstAfterPtr = afterPtr->nextPtr;
1129     } else {
1130 	for (prevPtr = assocPtr->firstAfterPtr; prevPtr->nextPtr != afterPtr;
1131 		prevPtr = prevPtr->nextPtr) {
1132 	    /* Empty loop body. */
1133 	}
1134 	prevPtr->nextPtr = afterPtr->nextPtr;
1135     }
1136     Tcl_DecrRefCount(afterPtr->commandPtr);
1137     ckfree((char *) afterPtr);
1138 }
1139 
1140 /*
1141  *----------------------------------------------------------------------
1142  *
1143  * AfterCleanupProc --
1144  *
1145  *	This procedure is invoked whenever an interpreter is deleted
1146  *	to cleanup the AssocData for "tclAfter".
1147  *
1148  * Results:
1149  *	None.
1150  *
1151  * Side effects:
1152  *	After commands are removed.
1153  *
1154  *----------------------------------------------------------------------
1155  */
1156 
1157 	/* ARGSUSED */
1158 static void
AfterCleanupProc(clientData,interp)1159 AfterCleanupProc(clientData, interp)
1160     ClientData clientData;	/* Points to AfterAssocData for the
1161 				 * interpreter. */
1162     Tcl_Interp *interp;		/* Interpreter that is being deleted. */
1163 {
1164     AfterAssocData *assocPtr = (AfterAssocData *) clientData;
1165     AfterInfo *afterPtr;
1166 
1167     while (assocPtr->firstAfterPtr != NULL) {
1168 	afterPtr = assocPtr->firstAfterPtr;
1169 	assocPtr->firstAfterPtr = afterPtr->nextPtr;
1170 	if (afterPtr->token != NULL) {
1171 	    Tcl_DeleteTimerHandler(afterPtr->token);
1172 	} else {
1173 	    Tcl_CancelIdleCall(AfterProc, (ClientData) afterPtr);
1174 	}
1175 	Tcl_DecrRefCount(afterPtr->commandPtr);
1176 	ckfree((char *) afterPtr);
1177     }
1178     ckfree((char *) assocPtr);
1179 }
1180 
1181 #endif
1182 
1183