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