diff options
Diffstat (limited to 'generic/tclNotify.c')
-rw-r--r-- | generic/tclNotify.c | 910 |
1 files changed, 739 insertions, 171 deletions
diff --git a/generic/tclNotify.c b/generic/tclNotify.c index e76bca8..9d7c225 100644 --- a/generic/tclNotify.c +++ b/generic/tclNotify.c @@ -39,6 +39,15 @@ typedef struct EventSource { } EventSource; /* + * Used for performance purposes, threshold to bypass check source (if don't wait) + * Value should be approximately correspond 100-ns ranges, if the wide-clicks + * supported, it is more precise so e. g. 5 is ca. 0.5 microseconds (500-ns). + */ +#ifndef TCL_CHECK_EVENT_SOURCE_THRESHOLD + #define TCL_CHECK_EVENT_SOURCE_THRESHOLD 5 +#endif + +/* * The following structure keeps track of the state of the notifier on a * per-thread basis. The first three elements keep track of the event queue. * In addition to the first (next to be serviced) and last events in the @@ -56,10 +65,18 @@ typedef struct ThreadSpecificData { Tcl_Event *lastEventPtr; /* Last pending event, or NULL if none. */ Tcl_Event *markerEventPtr; /* Last high-priority event in queue, or NULL * if none. */ + Tcl_Event *timerMarkerPtr; /* Weak pointer to last event in the queue, + * before timer event generation */ + Tcl_Event *firstRetardEv; /* First retarded event, or NULL if none. */ + Tcl_Event *lastRetardEv; /* Last retarded event, or NULL if none. */ Tcl_Mutex queueMutex; /* Mutex to protect access to the previous * three fields. */ + size_t queueEpoch; /* Epoch of the queue (incremented if changed + * using TCL_QUEUE_HEAD or TCL_QUEUE_MARK). */ int serviceMode; /* One of TCL_SERVICE_NONE or * TCL_SERVICE_ALL. */ + size_t serviceLevel; /* Current (nested) level of event cycle. */ + size_t blockTimeServLev; /* Level of the event cycle block time was set. */ int blockTimeSet; /* 0 means there is no maximum block time: * block forever. */ Tcl_Time blockTime; /* If blockTimeSet is 1, gives the maximum @@ -77,6 +94,15 @@ typedef struct ThreadSpecificData { /* Next notifier in global list of notifiers. * Access is controlled by the listLock global * mutex. */ +#if TCL_CHECK_EVENT_SOURCE_THRESHOLD + /* Last "time" source checked, used as threshold + * to avoid checking for events too often */ + #ifndef TCL_WIDE_CLICKS + unsigned long lastCheckClicks; + #else + Tcl_WideInt lastCheckClicks; + #endif +#endif } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; @@ -96,7 +122,8 @@ TCL_DECLARE_MUTEX(listLock) static void QueueEvent(ThreadSpecificData *tsdPtr, Tcl_Event *evPtr, Tcl_QueuePosition position); - + + /* *---------------------------------------------------------------------- * @@ -126,7 +153,7 @@ TclInitNotifier(void) /* Empty loop body. */ } - if (NULL == tsdPtr) { + if (NULL == tsdPtr || !tsdPtr->initialized) { /* * Notifier not yet initialized in this thread. */ @@ -140,7 +167,8 @@ TclInitNotifier(void) } Tcl_MutexUnlock(&listLock); } - + + /* *---------------------------------------------------------------------- * @@ -183,8 +211,17 @@ TclFinalizeNotifier(void) evPtr = evPtr->nextPtr; ckfree(hold); } + for (evPtr = tsdPtr->firstRetardEv; evPtr != NULL; ) { + hold = evPtr; + evPtr = evPtr->nextPtr; + ckfree(hold); + } tsdPtr->firstEventPtr = NULL; tsdPtr->lastEventPtr = NULL; + tsdPtr->markerEventPtr = NULL; + tsdPtr->timerMarkerPtr = NULL; + tsdPtr->firstRetardEv = NULL; + tsdPtr->lastRetardEv = NULL; Tcl_MutexUnlock(&(tsdPtr->queueMutex)); Tcl_MutexLock(&listLock); @@ -202,7 +239,8 @@ TclFinalizeNotifier(void) Tcl_MutexUnlock(&listLock); } - + + /* *---------------------------------------------------------------------- * @@ -228,7 +266,8 @@ Tcl_SetNotifier( { tclNotifierHooks = *notifierProcPtr; } - + + /* *---------------------------------------------------------------------- * @@ -284,7 +323,8 @@ Tcl_CreateEventSource( sourcePtr->nextPtr = tsdPtr->firstEventSourcePtr; tsdPtr->firstEventSourcePtr = sourcePtr; } - + + /* *---------------------------------------------------------------------- * @@ -334,7 +374,8 @@ Tcl_DeleteEventSource( return; } } - + + /* *---------------------------------------------------------------------- * @@ -359,13 +400,14 @@ Tcl_QueueEvent( * property of the event queue. It will be * freed after the event has been handled. */ Tcl_QueuePosition position) /* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, - * TCL_QUEUE_MARK. */ + * TCL_QUEUE_MARK or TCL_QUEUE_RETARDED. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); QueueEvent(tsdPtr, evPtr, position); } - + + /* *---------------------------------------------------------------------- * @@ -391,7 +433,7 @@ Tcl_ThreadQueueEvent( * property of the event queue. It will be * freed after the event has been handled. */ Tcl_QueuePosition position) /* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, - * TCL_QUEUE_MARK. */ + * TCL_QUEUE_MARK or TCL_QUEUE_RETARDED. */ { ThreadSpecificData *tsdPtr; @@ -416,7 +458,43 @@ Tcl_ThreadQueueEvent( } Tcl_MutexUnlock(&listLock); } - + + +static inline void +SpliceEventTail( + Tcl_Event *evPtr, + Tcl_Event **firstEvPtr, + Tcl_Event **lastEvPtr) +{ + evPtr->nextPtr = NULL; + if (*firstEvPtr == NULL) { + *firstEvPtr = evPtr; + } else { + (*lastEvPtr)->nextPtr = evPtr; + } + *lastEvPtr = evPtr; +} + + +static inline void +LinkEvent( + ThreadSpecificData *tsdPtr, + Tcl_Event *evPtr, + Tcl_Event *prevPtr) +{ + if (prevPtr) { + evPtr->nextPtr = prevPtr->nextPtr; + prevPtr->nextPtr = evPtr; + } else { + evPtr->nextPtr = tsdPtr->firstEventPtr; + tsdPtr->firstEventPtr = evPtr; + } + if (evPtr->nextPtr == NULL) { + tsdPtr->lastEventPtr = evPtr; + } +} + + /* *---------------------------------------------------------------------- * @@ -448,22 +526,19 @@ QueueEvent( * property of the event queue. It will be * freed after the event has been handled. */ Tcl_QueuePosition position) /* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, - * TCL_QUEUE_MARK. */ + * TCL_QUEUE_MARK or TCL_QUEUE_RETARDED. */ { Tcl_MutexLock(&(tsdPtr->queueMutex)); - if (position == TCL_QUEUE_TAIL) { + switch (position) { + case TCL_QUEUE_TAIL: /* * Append the event on the end of the queue. */ - evPtr->nextPtr = NULL; - if (tsdPtr->firstEventPtr == NULL) { - tsdPtr->firstEventPtr = evPtr; - } else { - tsdPtr->lastEventPtr->nextPtr = evPtr; - } - tsdPtr->lastEventPtr = evPtr; - } else if (position == TCL_QUEUE_HEAD) { + SpliceEventTail(evPtr, &tsdPtr->firstEventPtr, &tsdPtr->lastEventPtr); + + break; + case TCL_QUEUE_HEAD: /* * Push the event on the head of the queue. */ @@ -473,27 +548,143 @@ QueueEvent( tsdPtr->lastEventPtr = evPtr; } tsdPtr->firstEventPtr = evPtr; - } else if (position == TCL_QUEUE_MARK) { + + /* move timer event hereafter */ + if (tsdPtr->timerMarkerPtr == INT2PTR(-1)) { + tsdPtr->timerMarkerPtr = evPtr; + } + + tsdPtr->queueEpoch++; /* queue may be changed in the middle */ + + break; + case TCL_QUEUE_MARK: /* * Insert the event after the current marker event and advance the * marker to the new event. */ - if (tsdPtr->markerEventPtr == NULL) { - evPtr->nextPtr = tsdPtr->firstEventPtr; - tsdPtr->firstEventPtr = evPtr; - } else { - evPtr->nextPtr = tsdPtr->markerEventPtr->nextPtr; - tsdPtr->markerEventPtr->nextPtr = evPtr; - } + LinkEvent(tsdPtr, evPtr, tsdPtr->markerEventPtr); tsdPtr->markerEventPtr = evPtr; - if (evPtr->nextPtr == NULL) { - tsdPtr->lastEventPtr = evPtr; + + /* move timer event hereafter */ + if (tsdPtr->timerMarkerPtr == INT2PTR(-1)) { + tsdPtr->timerMarkerPtr = evPtr; } + + tsdPtr->queueEpoch++; /* queue may be changed in the middle */ + break; + case TCL_QUEUE_RETARDED: + /* + * Append the event on the end of the retarded list. + * This guarantees the service earliest at the next event-cycle. + */ + + SpliceEventTail(evPtr, &tsdPtr->firstRetardEv, &tsdPtr->lastRetardEv); + break; } Tcl_MutexUnlock(&(tsdPtr->queueMutex)); } - + + +static Tcl_Event * +SearchEventInQueue( + Tcl_Event *firstEvPtr, + Tcl_Event *evPtr, + Tcl_Event **prevEvPtr) +{ + Tcl_Event *prevPtr = NULL; + + /* + * Search event in the queue (if not first one). + */ + + if (evPtr != firstEvPtr) { + + for (prevPtr = firstEvPtr; + prevPtr && prevPtr->nextPtr != evPtr; + prevPtr = prevPtr->nextPtr) { + /* Empty loop body. */ + } + if (!prevPtr) { + /* not in queue */ + evPtr = NULL; + } + } + if (prevEvPtr) { + *prevEvPtr = prevPtr; + } + return evPtr; +} + + +static void +UnlinkEvent( + ThreadSpecificData *tsdPtr, + Tcl_Event *evPtr, + Tcl_Event *prevPtr) +{ + /* + * Unlink it. + */ + + if (prevPtr == NULL) { + tsdPtr->firstEventPtr = evPtr->nextPtr; + } else { + prevPtr->nextPtr = evPtr->nextPtr; + } + if (evPtr->nextPtr == NULL) { + tsdPtr->lastEventPtr = prevPtr; + } + + /* queue may be changed in the middle */ + tsdPtr->queueEpoch++; + + /* + * Update 'marker' events if either has been deleted. + */ + + if (tsdPtr->markerEventPtr == evPtr) { + tsdPtr->markerEventPtr = prevPtr; + } + if (tsdPtr->timerMarkerPtr == evPtr) { + tsdPtr->timerMarkerPtr = prevPtr ? prevPtr : INT2PTR(-1); + } +} + + +static void +InvolveRetardedEvents( + ThreadSpecificData *tsdPtr) +{ + /* move retarded events at end of the queue */ + if (tsdPtr->firstEventPtr == NULL) { + tsdPtr->firstEventPtr = tsdPtr->firstRetardEv; + } else { + tsdPtr->lastEventPtr->nextPtr = tsdPtr->firstRetardEv; + } + tsdPtr->lastEventPtr = tsdPtr->lastRetardEv; + /* reset retarded list */ + tsdPtr->lastRetardEv = tsdPtr->firstRetardEv = NULL; +} + + +static void +UnlinkRetardedEvent( + ThreadSpecificData *tsdPtr, + Tcl_Event *evPtr, + Tcl_Event *prevPtr) +{ + if (prevPtr == NULL) { + tsdPtr->firstRetardEv = evPtr->nextPtr; + } else { + prevPtr->nextPtr = evPtr->nextPtr; + } + if (evPtr->nextPtr == NULL) { + tsdPtr->lastRetardEv = prevPtr; + } +} + + /* *---------------------------------------------------------------------- * @@ -522,7 +713,6 @@ Tcl_DeleteEvents( Tcl_Event *prevPtr; /* Pointer to evPtr's predecessor, or NULL if * evPtr designates the first event in the * queue for the thread. */ - Tcl_Event *hold; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_MutexLock(&(tsdPtr->queueMutex)); @@ -531,51 +721,71 @@ Tcl_DeleteEvents( * Walk the queue of events for the thread, applying 'proc' to each to * decide whether to eliminate the event. */ - prevPtr = NULL; evPtr = tsdPtr->firstEventPtr; while (evPtr != NULL) { + Tcl_Event *nextPtr = evPtr->nextPtr; if (proc(evPtr, clientData) == 1) { - /* - * This event should be deleted. Unlink it. - */ + /* This event should be deleted. Unlink and delete it. */ + UnlinkEvent(tsdPtr, evPtr, prevPtr); + ckfree(evPtr); + } else { + /* Event is to be retained. */ + prevPtr = evPtr; + } + evPtr = nextPtr; + } + + /* + * Do the same for the retarded list. + */ + prevPtr = NULL; + evPtr = tsdPtr->firstRetardEv; + while (evPtr != NULL) { + Tcl_Event *nextPtr = evPtr->nextPtr; + if (proc(evPtr, clientData) == 1) { + /* This event should be deleted. Unlink and delete it. */ + UnlinkRetardedEvent(tsdPtr, evPtr, prevPtr); + ckfree(evPtr); + } else { + /* Event is to be retained. */ + prevPtr = evPtr; + } + evPtr = nextPtr; + } - if (prevPtr == NULL) { - tsdPtr->firstEventPtr = evPtr->nextPtr; - } else { - prevPtr->nextPtr = evPtr->nextPtr; - } + Tcl_MutexUnlock(&(tsdPtr->queueMutex)); +} - /* - * Update 'last' and 'marker' events if either has been deleted. - */ - if (evPtr->nextPtr == NULL) { - tsdPtr->lastEventPtr = prevPtr; - } - if (tsdPtr->markerEventPtr == evPtr) { - tsdPtr->markerEventPtr = prevPtr; - } +void +TclpCancelEvent( + Tcl_Event *evPtr) /* Event to remove from queue. */ +{ + Tcl_Event *prevPtr = NULL; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - /* - * Delete the event data structure. - */ + Tcl_MutexLock(&(tsdPtr->queueMutex)); - hold = evPtr; - evPtr = evPtr->nextPtr; - ckfree(hold); - } else { - /* - * Event is to be retained. - */ + /* + * Search event to unlink from queue and delete it. + * Note the event can be in retarded list. + */ - prevPtr = evPtr; - evPtr = evPtr->nextPtr; - } + if (SearchEventInQueue(tsdPtr->firstEventPtr, evPtr, &prevPtr)) { + UnlinkEvent(tsdPtr, evPtr, prevPtr); + ckfree(evPtr); + } + else + if (!SearchEventInQueue(tsdPtr->firstRetardEv, evPtr, &prevPtr)) { + UnlinkRetardedEvent(tsdPtr, evPtr, prevPtr); + ckfree(evPtr); } + Tcl_MutexUnlock(&(tsdPtr->queueMutex)); } - + + /* *---------------------------------------------------------------------- * @@ -608,35 +818,67 @@ Tcl_ServiceEvent( Tcl_Event *evPtr, *prevPtr; Tcl_EventProc *proc; int result; + size_t queueEpoch; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* + * No event flags is equivalent to TCL_ALL_EVENTS. + */ + + if ((flags & TCL_ALL_EVENTS) == 0) { + flags |= TCL_ALL_EVENTS; + } + + /* * Asynchronous event handlers are considered to be the highest priority * events, and so must be invoked before we process events on the event * queue. */ - if (Tcl_AsyncReady()) { - (void) Tcl_AsyncInvoke(NULL, 0); - return 1; + if ((flags & TCL_ASYNC_EVENTS)) { + if (Tcl_AsyncReady()) { + (void) Tcl_AsyncInvoke(NULL, 0); + return 1; + } + /* Async only */ + if ((flags & TCL_ALL_EVENTS) == TCL_ASYNC_EVENTS) { + return 0; + } + } + + /* Fast bypass case */ + if ( !tsdPtr->firstEventPtr /* no other events */ + || ((flags & TCL_ALL_EVENTS) == TCL_TIMER_EVENTS) /* timers only */ + ) { + goto timer; } /* - * No event flags is equivalent to TCL_ALL_EVENTS. + * If timer marker reached, process timer events now. */ - - if ((flags & TCL_ALL_EVENTS) == 0) { - flags |= TCL_ALL_EVENTS; + if ((flags & TCL_TIMER_EVENTS) && (tsdPtr->timerMarkerPtr == INT2PTR(-1))) { + goto processTimer; } + /* Lock queue to process events */ + Tcl_MutexLock(&(tsdPtr->queueMutex)); + /* * Loop through all the events in the queue until we find one that can * actually be handled. */ - Tcl_MutexLock(&(tsdPtr->queueMutex)); - for (evPtr = tsdPtr->firstEventPtr; evPtr != NULL; - evPtr = evPtr->nextPtr) { + for (prevPtr = NULL, evPtr = tsdPtr->firstEventPtr; + evPtr != NULL && tsdPtr->timerMarkerPtr != INT2PTR(-1); + prevPtr = evPtr, evPtr = evPtr->nextPtr + ) { + + repeatCycle: + + if (tsdPtr->timerMarkerPtr == evPtr) { + tsdPtr->timerMarkerPtr = INT2PTR(-1); /* timer marker reached */ + } + /* * Call the handler for the event. If it actually handles the event * then free the storage for the event. There are two tricky things @@ -659,6 +901,9 @@ Tcl_ServiceEvent( } evPtr->proc = NULL; + /* Save current queue epoch (if unchanged - the same prevPtr) */ + queueEpoch = tsdPtr->queueEpoch; + /* * Release the lock before calling the event function. This allows * other threads to post events if we enter a recursive event loop in @@ -670,55 +915,337 @@ Tcl_ServiceEvent( result = proc(evPtr, flags); Tcl_MutexLock(&(tsdPtr->queueMutex)); - if (result) { + /* If event processed or scheduled to be executed later (retarding) */ + if (result || evPtr->proc) { + /* - * The event was processed, so remove it from the queue. + * Check the queue was changed. */ - if (tsdPtr->firstEventPtr == evPtr) { - tsdPtr->firstEventPtr = evPtr->nextPtr; - if (evPtr->nextPtr == NULL) { - tsdPtr->lastEventPtr = NULL; - } - if (tsdPtr->markerEventPtr == evPtr) { - tsdPtr->markerEventPtr = NULL; - } - } else { - for (prevPtr = tsdPtr->firstEventPtr; - prevPtr && prevPtr->nextPtr != evPtr; - prevPtr = prevPtr->nextPtr) { - /* Empty loop body. */ - } - if (prevPtr) { - prevPtr->nextPtr = evPtr->nextPtr; - if (evPtr->nextPtr == NULL) { - tsdPtr->lastEventPtr = prevPtr; - } - if (tsdPtr->markerEventPtr == evPtr) { - tsdPtr->markerEventPtr = prevPtr; - } + if (queueEpoch != tsdPtr->queueEpoch) { + /* queue may be changed in the middle */ + queueEpoch = tsdPtr->queueEpoch; + /* try to find event */ + evPtr = SearchEventInQueue(tsdPtr->firstEventPtr, + evPtr, &prevPtr); + } + + /* + * If the handler set another function to process it later, + * do retarding of the event. + */ + if (evPtr && evPtr->proc) { + /* + * Reattach the event on the end of the retarded list. + */ + UnlinkEvent(tsdPtr, evPtr, prevPtr); + SpliceEventTail(evPtr, + &tsdPtr->firstRetardEv, &tsdPtr->lastRetardEv); + + /* next event to service */ + if (prevPtr == NULL) { + /* we stood on begin of list - just repeat from new begin */ + evPtr = tsdPtr->firstEventPtr; } else { - evPtr = NULL; + /* continue from next of previous event */ + evPtr = prevPtr->nextPtr; } + goto repeatCycle; } + + /* + * The event was processed, so remove it. + */ if (evPtr) { + /* Detach event from queue */ + UnlinkEvent(tsdPtr, evPtr, prevPtr); + + /* Free event */ ckfree(evPtr); } + + /* event processed - return with 1 */ Tcl_MutexUnlock(&(tsdPtr->queueMutex)); return 1; + } else { /* * The event wasn't actually handled, so we have to restore the * proc field to allow the event to be attempted again. */ - evPtr->proc = proc; } } Tcl_MutexUnlock(&(tsdPtr->queueMutex)); + + timer: + /* + * Process timer queue, if alloved and timers are enabled. + */ + + if (flags & TCL_TIMER_EVENTS) { + + /* If available pending timer-events of new generation */ + if (tsdPtr->timerMarkerPtr == INT2PTR(-2)) { /* pending */ + /* no other events - process timer-events (next cycle) */ + if (!(tsdPtr->timerMarkerPtr = tsdPtr->lastEventPtr)) { /* no other events */ + tsdPtr->timerMarkerPtr = INT2PTR(-1); + } + return 0; + } + + if (tsdPtr->timerMarkerPtr == INT2PTR(-1)) { + + processTimer: + /* reset marker */ + tsdPtr->timerMarkerPtr = NULL; + + result = TclServiceTimerEvents(); + if (result < 0) { + /* + * Events processed, but still pending timers (of new generation) + * set marker to process timer, if setup- resp. check-proc will + * not generate new events. + */ + if (tsdPtr->timerMarkerPtr == NULL) { + /* marker to last event in the queue */ + if (!(tsdPtr->timerMarkerPtr = tsdPtr->lastEventPtr)) { + /* + * Marker as "pending" - queue is empty, so timers events are first, + * if setup-proc resp. check-proc will not generate new events. + */ + tsdPtr->timerMarkerPtr = INT2PTR(-2); + }; + } + result = 1; + } + return result; + } + } + return 0; } - + + +#if TCL_CHECK_EVENT_SOURCE_THRESHOLD +/* + *---------------------------------------------------------------------- + * + * CheckSourceThreshold -- + * + * Check whether we should iterate over event sources for availability. + * + * This is used to avoid too unneeded overhead (too often call checkProc). + * + * Results: + * Returns 1 if threshold reached (check event sources), 0 otherwise. + * + *---------------------------------------------------------------------- + */ + +static inline int +CheckSourceThreshold( + ThreadSpecificData *tsdPtr) +{ + /* don't need to wait/check for events too often */ +#ifndef TCL_WIDE_CLICKS + unsigned long clickdiff, clicks = TclpGetClicks(); +#else + Tcl_WideInt clickdiff, clicks; + /* in 100-ns */ + clicks = TclpGetWideClicks() * (TclpWideClickInMicrosec() * 10); +#endif + /* considering possible clicks-jump */ + if ( (clickdiff = (clicks - tsdPtr->lastCheckClicks)) >= 0 + && clickdiff <= TCL_CHECK_EVENT_SOURCE_THRESHOLD) { + return 0; + } + tsdPtr->lastCheckClicks = clicks; + return 1; +} +#endif + + +static int +SetUpEventSources( + ThreadSpecificData *tsdPtr, + int flags) +{ + int res = 0; + EventSource *sourcePtr; + + /* + * Set up all the event sources for new events. This will cause the + * block time to be updated if necessary. + */ + tsdPtr->inTraversal++; + for (sourcePtr = tsdPtr->firstEventSourcePtr; + sourcePtr != NULL; + sourcePtr = sourcePtr->nextPtr + ) { + if (sourcePtr->checkProc) { + sourcePtr->setupProc(sourcePtr->clientData, flags); + res++; + } + } + tsdPtr->inTraversal--; + + /* + * If we've some retarded events (from last event-cycle), wait non-blocking. + */ + if ( tsdPtr->firstRetardEv + && ( !tsdPtr->blockTimeSet + || tsdPtr->blockTimeServLev < tsdPtr->serviceLevel ) + ) { + tsdPtr->blockTime.sec = 0; + tsdPtr->blockTime.usec = 0; + tsdPtr->blockTimeSet = 1; + } + + return res; +} + + +static int +CheckEventSources( + ThreadSpecificData *tsdPtr, + int flags) +{ + int res = 0; + EventSource *sourcePtr; + + /* + * Check all the event sources for new events. + */ + for (sourcePtr = tsdPtr->firstEventSourcePtr; + sourcePtr != NULL; + sourcePtr = sourcePtr->nextPtr + ) { + if (sourcePtr->checkProc) { + sourcePtr->checkProc(sourcePtr->clientData, flags); + res++; + } + } + + /* + * If we've some retarded events (from last event-cycle), attach they here + * to the tail of the event queue (new event-cycle). + */ + if (tsdPtr->firstRetardEv) { + Tcl_MutexLock(&(tsdPtr->queueMutex)); + if (tsdPtr->firstRetardEv) { + InvolveRetardedEvents(tsdPtr); + res++; + } + Tcl_MutexUnlock(&(tsdPtr->queueMutex)); + } + + return res; +} + + +/* + *---------------------------------------------------------------------- + * + * TclPeekEventQueued -- + * + * Check whether some event (except idle) available (async, queued, timer). + * + * This will be used e. g. in TclServiceIdle to stop the processing of the + * the idle events if some "normal" event occurred. + * + * Results: + * Returns 1 if some event queued, 0 otherwise. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclPeekEventQueued( + int flags) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int repeat = 1; + + do { + /* + * Events already pending ? + */ + if ( Tcl_AsyncReady() + || (tsdPtr->firstEventPtr) + || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerMarkerPtr) + ) { + return 1; + } + + /* once from here */ + if (!repeat) { + break; + } + + if (flags & TCL_DONT_WAIT) { + /* don't need to wait/check for events too often */ + #if TCL_CHECK_EVENT_SOURCE_THRESHOLD + if (!CheckSourceThreshold(tsdPtr)) { + return 0; + } + #endif + } + + /* + * Check all the event sources for new events. + */ + if (!CheckEventSources(tsdPtr, flags)) { + return 0; /* no sources - no events could be created at all */ + } + + } while (repeat--); + + return 0; +} + + +/* + *---------------------------------------------------------------------- + * + * TclSetTimerEventMarker -- + * + * Set timer event marker to the last pending event in the queue. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TclSetTimerEventMarker( + int flags) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (tsdPtr->timerMarkerPtr == NULL || tsdPtr->timerMarkerPtr == INT2PTR(-2)) { + /* marker to last event in the queue */ + if ( !(tsdPtr->timerMarkerPtr = tsdPtr->lastEventPtr) /* no other events */ + || ((flags & TCL_ALL_EVENTS) == TCL_TIMER_EVENTS) /* timers only */ + ) { + /* + * Marker as "pending" - queue is empty, so timers events are first, + * if setup-proc resp. check-proc will not generate new events. + * Force timer execution if flags specified (from checkProc). + */ + tsdPtr->timerMarkerPtr = flags ? INT2PTR(-1) : INT2PTR(-2); + }; + } +} + + /* *---------------------------------------------------------------------- * @@ -742,7 +1269,8 @@ Tcl_GetServiceMode(void) return tsdPtr->serviceMode; } - + + /* *---------------------------------------------------------------------- * @@ -772,7 +1300,8 @@ Tcl_SetServiceMode( Tcl_ServiceModeHook(mode); return oldMode; } - + + /* *---------------------------------------------------------------------- * @@ -813,10 +1342,15 @@ Tcl_SetMaxBlockTime( */ if (!tsdPtr->inTraversal) { + if (tsdPtr->blockTimeServLev < tsdPtr->serviceLevel) { + /* avoid resetting the blockTime set outside of traversal. */ + tsdPtr->blockTimeServLev = tsdPtr->serviceLevel; + } Tcl_SetTimer(&tsdPtr->blockTime); } } - + + /* *---------------------------------------------------------------------- * @@ -828,14 +1362,18 @@ Tcl_SetMaxBlockTime( * Results: * The return value is 1 if the function actually found an event to * process. If no processing occurred, then 0 is returned (this can - * happen if the TCL_DONT_WAIT flag is set or if there are no event - * handlers to wait for in the set specified by flags). + * happen if the TCL_DONT_WAIT flag is set or block time was set using + * Tcl_SetMaxBlockTime before or if there are no event handlers to wait + * for in the set specified by flags). * * Side effects: * May delay execution of process while waiting for an event, unless * TCL_DONT_WAIT is set in the flags argument. Event sources are invoked * to check for and queue events. Event handlers may produce arbitrary * side effects. + * If block time was set (Tcl_SetMaxBlockTime) but another event occurs + * and interrupt wait, the function can return early, thereby it resets + * the block time (caller should use Tcl_SetMaxBlockTime again). * *---------------------------------------------------------------------- */ @@ -849,18 +1387,9 @@ Tcl_DoOneEvent( * others defined by event sources. */ { int result = 0, oldMode; - EventSource *sourcePtr; Tcl_Time *timePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - - /* - * The first thing we do is to service any asynchronous event handlers. - */ - - if (Tcl_AsyncReady()) { - (void) Tcl_AsyncInvoke(NULL, 0); - return 1; - } + int blockTimeWasSet; /* * No event flags is equivalent to TCL_ALL_EVENTS. @@ -870,6 +1399,9 @@ Tcl_DoOneEvent( flags |= TCL_ALL_EVENTS; } + /* Block time was set outside an event source traversal, caller has specified a waittime */ + blockTimeWasSet = tsdPtr->blockTimeSet; + /* * Set the service mode to none so notifier event routines won't try to * service events recursively. @@ -877,14 +1409,33 @@ Tcl_DoOneEvent( oldMode = tsdPtr->serviceMode; tsdPtr->serviceMode = TCL_SERVICE_NONE; + tsdPtr->serviceLevel++; /* - * The core of this function is an infinite loop, even though we only - * service one event. The reason for this is that we may be processing - * events that don't do anything inside of Tcl. + * Asynchronous event handlers are considered to be the highest priority + * events, and so must be invoked before we process events on the event + * queue. */ - while (1) { + if (flags & TCL_ASYNC_EVENTS) { + if (Tcl_AsyncReady()) { + (void) Tcl_AsyncInvoke(NULL, 0); + result = 1; + goto done; + } + + /* Async only and don't wait - return */ + if ( (flags & (TCL_ALL_EVENTS|TCL_DONT_WAIT)) + == (TCL_ASYNC_EVENTS|TCL_DONT_WAIT) ) { + goto done; + } + } + + /* + * Main loop until servicing exact one event or block time resp. + * TCL_DONT_WAIT specified (infinite loop otherwise). + */ + do { /* * If idle events are the only things to service, skip the main part * of the loop and go directly to handle idle events (i.e. don't wait @@ -892,12 +1443,12 @@ Tcl_DoOneEvent( */ if ((flags & TCL_ALL_EVENTS) == TCL_IDLE_EVENTS) { - flags = TCL_IDLE_EVENTS | TCL_DONT_WAIT; goto idleEvents; } /* - * Ask Tcl to service a queued event, if there are any. + * Ask Tcl to service any asynchronous event handlers or + * queued event, if there are any. */ if (Tcl_ServiceEvent(flags)) { @@ -911,28 +1462,27 @@ Tcl_DoOneEvent( */ if (flags & TCL_DONT_WAIT) { + + /* don't need to wait/check for events too often */ + #if TCL_CHECK_EVENT_SOURCE_THRESHOLD + if (!CheckSourceThreshold(tsdPtr)) { + goto idleEvents; + } + #endif tsdPtr->blockTime.sec = 0; tsdPtr->blockTime.usec = 0; tsdPtr->blockTimeSet = 1; - } else { - tsdPtr->blockTimeSet = 0; + timePtr = &tsdPtr->blockTime; + goto wait; /* for notifier resp. system events */ } /* * Set up all the event sources for new events. This will cause the * block time to be updated if necessary. */ + SetUpEventSources(tsdPtr, flags); - tsdPtr->inTraversal = 1; - for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; - sourcePtr = sourcePtr->nextPtr) { - if (sourcePtr->setupProc) { - sourcePtr->setupProc(sourcePtr->clientData, flags); - } - } - tsdPtr->inTraversal = 0; - - if ((flags & TCL_DONT_WAIT) || tsdPtr->blockTimeSet) { + if (tsdPtr->blockTimeSet) { timePtr = &tsdPtr->blockTime; } else { timePtr = NULL; @@ -942,23 +1492,20 @@ Tcl_DoOneEvent( * Wait for a new event or a timeout. If Tcl_WaitForEvent returns -1, * we should abort Tcl_DoOneEvent. */ - + wait: result = Tcl_WaitForEvent(timePtr); + tsdPtr->blockTimeServLev = 0; /* reset block-time level (processed). */ if (result < 0) { - result = 0; + if (blockTimeWasSet) { + result = 0; + } break; } /* * Check all the event sources for new events. */ - - for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; - sourcePtr = sourcePtr->nextPtr) { - if (sourcePtr->checkProc) { - sourcePtr->checkProc(sourcePtr->clientData, flags); - } - } + CheckEventSources(tsdPtr, flags); /* * Check for events queued by the notifier or event sources. @@ -977,14 +1524,11 @@ Tcl_DoOneEvent( idleEvents: if (flags & TCL_IDLE_EVENTS) { - if (TclServiceIdle()) { + if (TclServiceIdleEx(flags, INT_MAX)) { result = 1; break; } } - if (flags & TCL_DONT_WAIT) { - break; - } /* * If Tcl_WaitForEvent has returned 1, indicating that one system @@ -994,20 +1538,31 @@ Tcl_DoOneEvent( * had the side effect of changing the variable (so the vwait can * return and unwind properly). * - * NB: We will process idle events if any first, because otherwise we - * might never do the idle events if the notifier always gets - * system events. + * We can stop also if works in block to event mode (e. g. block time was + * set outside an event source, that means timeout was set so exit loop + * also without event/result). */ - if (result) { + result = 0; + if (blockTimeWasSet) { break; } - } + } while ( !(flags & TCL_DONT_WAIT) ); +done: + /* + * Reset block time earliest at the end of event cycle and restore mode. + */ + if (tsdPtr->blockTimeServLev < tsdPtr->serviceLevel) { + tsdPtr->blockTimeSet = 0; + tsdPtr->blockTimeServLev = 0; + } tsdPtr->serviceMode = oldMode; + tsdPtr->serviceLevel--; return result; } - + + /* *---------------------------------------------------------------------- * @@ -1032,7 +1587,6 @@ int Tcl_ServiceAll(void) { int result = 0; - EventSource *sourcePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (tsdPtr->serviceMode == TCL_SERVICE_NONE) { @@ -1060,21 +1614,10 @@ Tcl_ServiceAll(void) * so we can avoid multiple changes. */ - tsdPtr->inTraversal = 1; tsdPtr->blockTimeSet = 0; - for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; - sourcePtr = sourcePtr->nextPtr) { - if (sourcePtr->setupProc) { - sourcePtr->setupProc(sourcePtr->clientData, TCL_ALL_EVENTS); - } - } - for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; - sourcePtr = sourcePtr->nextPtr) { - if (sourcePtr->checkProc) { - sourcePtr->checkProc(sourcePtr->clientData, TCL_ALL_EVENTS); - } - } + SetUpEventSources(tsdPtr, TCL_ALL_EVENTS); + CheckEventSources(tsdPtr, TCL_ALL_EVENTS); while (Tcl_ServiceEvent(0)) { result = 1; @@ -1088,11 +1631,11 @@ Tcl_ServiceAll(void) } else { Tcl_SetTimer(&tsdPtr->blockTime); } - tsdPtr->inTraversal = 0; tsdPtr->serviceMode = TCL_SERVICE_ALL; return result; } - + + /* *---------------------------------------------------------------------- * @@ -1131,7 +1674,32 @@ Tcl_ThreadAlert( } Tcl_MutexUnlock(&listLock); } - + + +/* + *---------------------------------------------------------------------- + * + * Tcl_Sleep -- + * + * Delay execution for the specified number of milliseconds. + * + * Results: + * None. + * + * Side effects: + * Time passes. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_Sleep( + int ms) /* Number of milliseconds to sleep. */ +{ + TclpUSleep((Tcl_WideInt)ms * 1000); +} + + /* * Local Variables: * mode: c |