summaryrefslogtreecommitdiffstats
path: root/generic
diff options
context:
space:
mode:
authorsebres <sebres@users.sourceforge.net>2017-07-03 13:24:36 (GMT)
committersebres <sebres@users.sourceforge.net>2017-07-03 13:24:36 (GMT)
commit5daa7f610ab6e2ea43bca023cb3cfe96811b48b4 (patch)
tree19638c59cf680b3130f5960f2b1bb2dd8928f39d /generic
parentee8dccacb4206a0ac43f8cb427b86775cedc4684 (diff)
downloadtcl-5daa7f610ab6e2ea43bca023cb3cfe96811b48b4.zip
tcl-5daa7f610ab6e2ea43bca023cb3cfe96811b48b4.tar.gz
tcl-5daa7f610ab6e2ea43bca023cb3cfe96811b48b4.tar.bz2
[performance] do one event (update / event servicing) cycle optimized (introduced threshold to prevent sourcing resp. waiting for new events by no-wait).
[enhancement] new event type introduced: TCL_ASYNC_EVENTS, command "update" becomes options to process only specified types, resp. to bypass some event types (including -idle/-noidle that in opposite to "idletasks" does not included window events); test cases extended.
Diffstat (limited to 'generic')
-rw-r--r--generic/tcl.h1
-rw-r--r--generic/tclEvent.c55
-rw-r--r--generic/tclInt.h2
-rw-r--r--generic/tclNotify.c154
-rw-r--r--generic/tclTimer.c122
5 files changed, 269 insertions, 65 deletions
diff --git a/generic/tcl.h b/generic/tcl.h
index 6a3c66a..b65a5cb 100644
--- a/generic/tcl.h
+++ b/generic/tcl.h
@@ -1297,6 +1297,7 @@ typedef struct {
* events:
*/
+#define TCL_ASYNC_EVENTS (1<<0)
#define TCL_DONT_WAIT (1<<1)
#define TCL_WINDOW_EVENTS (1<<2)
#define TCL_FILE_EVENTS (1<<3)
diff --git a/generic/tclEvent.c b/generic/tclEvent.c
index 27953a4..d18836b 100644
--- a/generic/tclEvent.c
+++ b/generic/tclEvent.c
@@ -1468,30 +1468,42 @@ Tcl_UpdateObjCmd(
int objc, /* Number of arguments. */
Tcl_Obj *CONST objv[]) /* Argument objects. */
{
- int optionIndex;
- int flags = TCL_ALL_EVENTS|TCL_DONT_WAIT;
- static CONST char *updateOptions[] = {"idletasks", "noidletasks", NULL};
- enum updateOptions {UPDATE_IDLETASKS, UPDATE_NOIDLETASKS};
-
- if (objc == 1) {
- } else if (objc == 2) {
- if (Tcl_GetIndexFromObj(interp, objv[1], updateOptions,
+ int i, optionIndex;
+ static CONST defUpdateFlags = TCL_ALL_EVENTS|TCL_DONT_WAIT;
+ int flags = defUpdateFlags;
+ static CONST char *updateOptions[] = {"idletasks", /* backwards compat. */
+ "-nowait", "-wait", /* new options */
+ "-idle", "-noidle", "-timer", "-notimer",
+ "-file", "-nofile", "-window", "-nowindow",
+ "-async", "-noasync",
+ NULL};
+ static CONST struct {
+ int minus;
+ int plus;
+ } *updateFlag, updateFlags[] = {
+ {TCL_ALL_EVENTS,
+ TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS}, /* idletasks */
+ {0, TCL_DONT_WAIT}, {TCL_DONT_WAIT, 0}, /* -nowait, -wait */
+ {0, TCL_IDLE_EVENTS}, {TCL_IDLE_EVENTS, 0}, /* -idle, -noidle */
+ {0, TCL_TIMER_EVENTS}, {TCL_TIMER_EVENTS, 0}, /* -file, -nofile */
+ {0, TCL_FILE_EVENTS}, {TCL_FILE_EVENTS, 0}, /* -file, -nofile */
+ {0, TCL_WINDOW_EVENTS}, {TCL_WINDOW_EVENTS, 0}, /* -window, -nowindow */
+ {0, TCL_ASYNC_EVENTS}, {TCL_ASYNC_EVENTS, 0}, /* -async, -noasync */
+ {0, 0} /* dummy / place holder */
+ };
+
+ for (i = 1; i < objc; i++) {
+ if (Tcl_GetIndexFromObj(interp, objv[i], updateOptions,
"option", 0, &optionIndex) != TCL_OK) {
return TCL_ERROR;
}
- switch ((enum updateOptions) optionIndex) {
- case UPDATE_IDLETASKS:
- flags = TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS|TCL_DONT_WAIT;
- break;
- case UPDATE_NOIDLETASKS:
- flags &= ~TCL_IDLE_EVENTS;
- break;
- default:
- Tcl_Panic("Tcl_UpdateObjCmd: bad option index to UpdateOptions");
+ updateFlag = &updateFlags[optionIndex];
+ /* pure positive option and still default - reset all events */
+ if (flags == defUpdateFlags && !updateFlag->minus) {
+ flags &= ~TCL_ALL_EVENTS;
}
- } else {
- Tcl_WrongNumArgs(interp, 1, objv, "?option?");
- return TCL_ERROR;
+ flags &= ~updateFlag->minus;
+ flags |= updateFlag->plus;
}
while (Tcl_DoOneEvent(flags) != 0) {
@@ -1500,6 +1512,9 @@ Tcl_UpdateObjCmd(
Tcl_AppendResult(interp, "limit exceeded", NULL);
return TCL_ERROR;
}
+
+ /* be sure not to produce infinite wait (wait only once) */
+ flags |= TCL_DONT_WAIT;
}
/*
diff --git a/generic/tclInt.h b/generic/tclInt.h
index d0d1240..dd73eac 100644
--- a/generic/tclInt.h
+++ b/generic/tclInt.h
@@ -2922,6 +2922,7 @@ MODULE_SCOPE int Tcl_ContinueObjCmd(ClientData clientData,
Tcl_Obj *const objv[]);
MODULE_SCOPE void TclSetTimerEventMarker(void);
MODULE_SCOPE int TclServiceTimerEvents(void);
+MODULE_SCOPE int TclServiceIdleEx(int flags, int count);
MODULE_SCOPE Tcl_TimerToken TclCreateAbsoluteTimerHandler(
Tcl_Time *timePtr, Tcl_TimerProc *proc,
ClientData clientData);
@@ -2933,6 +2934,7 @@ MODULE_SCOPE TimerEntry* TclCreateTimerEntryEx(
Tcl_TimerProc *proc, Tcl_TimerDeleteProc *deleteProc,
size_t extraDataSize, int flags);
MODULE_SCOPE void TclDeleteTimerEntry(TimerEntry *entryPtr);
+MODULE_SCOPE int TclPeekEventQueued(int flags);
MODULE_SCOPE int TclDefaultBgErrorHandlerObjCmd(
ClientData clientData, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
diff --git a/generic/tclNotify.c b/generic/tclNotify.c
index 89182ea..f13fca3 100644
--- a/generic/tclNotify.c
+++ b/generic/tclNotify.c
@@ -72,11 +72,25 @@ typedef struct ThreadSpecificData {
/* Next notifier in global list of notifiers.
* Access is controlled by the listLock global
* mutex. */
+#ifndef TCL_WIDE_CLICKS /* Last "time" source checked, used as threshold */
+ unsigned long lastCheckClicks;
+#else
+ Tcl_WideInt lastCheckClicks;
+#endif
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;
/*
+ * Used for performance purposes, threshold to bypass check source (if don't wait)
+ * Values under 1000 should be approximately under 1ms, e. g. 10 is ca. 0.01ms
+ */
+#ifndef CHECK_EVENT_SOURCE_THRESHOLD
+ #define CHECK_EVENT_SOURCE_THRESHOLD 10
+#endif
+
+
+/*
* Global list of notifiers. Access to this list is controlled by the listLock
* mutex. If this becomes a performance bottleneck, this could be replaced
* with a hashtable.
@@ -618,28 +632,36 @@ Tcl_ServiceEvent(
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()) {
+ if ((flags & TCL_ASYNC_EVENTS) && Tcl_AsyncReady()) {
(void) Tcl_AsyncInvoke(NULL, 0);
return 1;
}
- /*
- * No event flags is equivalent to TCL_ALL_EVENTS.
- */
-
- if ((flags & TCL_ALL_EVENTS) == 0) {
- flags |= TCL_ALL_EVENTS;
+ /* Async only */
+ if ((flags & TCL_ALL_EVENTS) == TCL_ASYNC_EVENTS) {
+ return 0;
}
/*
* If timer marker reached, process timer events now.
*/
- if (tsdPtr->timerMarkerPtr == INT2PTR(-1)) {
+ if ( tsdPtr->timerMarkerPtr == INT2PTR(-1)
+ || !tsdPtr->firstEventPtr
+ || ((flags & TCL_ALL_EVENTS) == TCL_TIMER_EVENTS)
+ ) {
goto timer;
}
@@ -743,7 +765,7 @@ timer:
/*
* Process timer queue, if alloved and timers are enabled.
*/
- if (flags & TCL_TIMER_EVENTS && tsdPtr->timerMarkerPtr) {
+ if ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerMarkerPtr) {
/* reset marker */
tsdPtr->timerMarkerPtr = NULL;
@@ -762,6 +784,73 @@ timer:
/*
*----------------------------------------------------------------------
*
+ * 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)
+{
+ EventSource *sourcePtr;
+ 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;
+ }
+
+ if (flags & TCL_DONT_WAIT) {
+ /* don't need to wait/check for events too often */
+#ifndef TCL_WIDE_CLICKS
+ unsigned long clicks = TclpGetClicks();
+#else
+ Tcl_WideInt clicks = TclpGetWideClicks();
+#endif
+
+ if ((clicks - tsdPtr->lastCheckClicks) <= CHECK_EVENT_SOURCE_THRESHOLD) {
+ return 0;
+ }
+ tsdPtr->lastCheckClicks = clicks;
+ }
+
+ /*
+ * 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);
+ }
+ }
+
+ } while (repeat--);
+
+ return 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TclSetTimerEventMarker --
*
* Set timer event marker to the last pending event in the queue.
@@ -920,26 +1009,33 @@ Tcl_DoOneEvent(
* TCL_TIMER_EVENTS, TCL_IDLE_EVENTS, or
* others defined by event sources. */
{
- int result = 0, oldMode;
+ int result = 0, oldMode, i = 0;
EventSource *sourcePtr;
Tcl_Time *timePtr;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
/*
- * The first thing we do is to service any asynchronous event handlers.
+ * No event flags is equivalent to TCL_ALL_EVENTS.
*/
- if (Tcl_AsyncReady()) {
- (void) Tcl_AsyncInvoke(NULL, 0);
- return 1;
+ if ((flags & TCL_ALL_EVENTS) == 0) {
+ flags |= TCL_ALL_EVENTS;
}
/*
- * No event flags is equivalent to 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 ((flags & TCL_ALL_EVENTS) == 0) {
- flags |= TCL_ALL_EVENTS;
+ if ((flags & TCL_ASYNC_EVENTS) && Tcl_AsyncReady()) {
+ (void) Tcl_AsyncInvoke(NULL, 0);
+ return 1;
+ }
+
+ /* Async only */
+ if ((flags & TCL_ALL_EVENTS) == TCL_ASYNC_EVENTS) {
+ return 0;
}
/*
@@ -964,12 +1060,13 @@ Tcl_DoOneEvent(
*/
if ((flags & TCL_ALL_EVENTS) == TCL_IDLE_EVENTS) {
- flags = TCL_IDLE_EVENTS | TCL_DONT_WAIT;
+ flags |= 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)) {
@@ -983,9 +1080,22 @@ Tcl_DoOneEvent(
*/
if (flags & TCL_DONT_WAIT) {
+ /* don't need to wait/check for events too often */
+#ifndef TCL_WIDE_CLICKS
+ unsigned long clicks = TclpGetClicks();
+#else
+ Tcl_WideInt clicks = TclpGetWideClicks();
+#endif
+ if ((clicks - tsdPtr->lastCheckClicks) <= CHECK_EVENT_SOURCE_THRESHOLD) {
+ goto idleEvents;
+ }
+ tsdPtr->lastCheckClicks = clicks;
+
tsdPtr->blockTime.sec = 0;
tsdPtr->blockTime.usec = 0;
tsdPtr->blockTimeSet = 1;
+ timePtr = &tsdPtr->blockTime;
+ goto wait; /* for notifier resp. system events */
} else {
tsdPtr->blockTimeSet = 0;
}
@@ -1004,7 +1114,7 @@ Tcl_DoOneEvent(
}
tsdPtr->inTraversal = 0;
- if ((flags & TCL_DONT_WAIT) || tsdPtr->blockTimeSet) {
+ if (tsdPtr->blockTimeSet) {
timePtr = &tsdPtr->blockTime;
} else {
timePtr = NULL;
@@ -1014,7 +1124,7 @@ 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);
if (result < 0) {
result = 0;
@@ -1049,7 +1159,7 @@ Tcl_DoOneEvent(
idleEvents:
if (flags & TCL_IDLE_EVENTS) {
- if (TclServiceIdle()) {
+ if (TclServiceIdleEx(flags, INT_MAX)) {
result = 1;
break;
}
diff --git a/generic/tclTimer.c b/generic/tclTimer.c
index 12b039f..52a3073 100644
--- a/generic/tclTimer.c
+++ b/generic/tclTimer.c
@@ -121,6 +121,7 @@ static void FreeAfterPtr(ClientData clientData);
static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *objPtr);
static ThreadSpecificData *InitTimer(void);
static void TimerExitProc(ClientData clientData);
+static void TimerCheckProc(ClientData clientData, int flags);
static void TimerSetupProc(ClientData clientData, int flags);
static void AfterObj_DupInternalRep(Tcl_Obj *, Tcl_Obj *);
@@ -258,7 +259,7 @@ InitTimer(void)
if (tsdPtr == NULL) {
tsdPtr = TCL_TSD_INIT(&dataKey);
- Tcl_CreateEventSource(TimerSetupProc, NULL, tsdPtr);
+ Tcl_CreateEventSource(TimerSetupProc, TimerCheckProc, tsdPtr);
Tcl_CreateThreadExitHandler(TimerExitProc, NULL);
}
return tsdPtr;
@@ -289,7 +290,7 @@ TimerExitProc(
TclThreadDataKeyGet(&dataKey);
if (tsdPtr != NULL) {
- Tcl_DeleteEventSource(TimerSetupProc, NULL, tsdPtr);
+ Tcl_DeleteEventSource(TimerSetupProc, TimerCheckProc, tsdPtr);
while ((tsdPtr->lastPromptPtr) != NULL) {
TclDeleteTimerEntry(tsdPtr->lastPromptPtr);
@@ -582,7 +583,7 @@ TclDeleteTimerEntry(
static void
TimerSetupProc(
- ClientData data, /* Not used. */
+ ClientData data, /* Specific data. */
int flags) /* Event flags as passed to Tcl_DoOneEvent. */
{
Tcl_Time blockTime, *firstTime;
@@ -590,10 +591,11 @@ TimerSetupProc(
if (tsdPtr == NULL) { tsdPtr = InitTimer(); };
- if (((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList )
- || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)) {
+ if ( ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)
+ || ((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList )
+ ) {
/*
- * There is an idle handler or a pending timer event, so just poll.
+ * There is a pending timer event or an idle handler, so just poll.
*/
blockTime.sec = 0;
@@ -616,11 +618,10 @@ TimerSetupProc(
blockTime.sec = 0;
blockTime.usec = 0;
}
-
+
/*
- * If the first timer has expired, stick an event on the queue.
- */
-
+ * If the first timer has expired, stick an event on the queue right now.
+ */
if (!tsdPtr->timerPending && blockTime.sec == 0 && blockTime.usec == 0) {
TclSetTimerEventMarker();
tsdPtr->timerPending = 1;
@@ -636,6 +637,67 @@ TimerSetupProc(
/*
*----------------------------------------------------------------------
*
+ * TimerCheckProc --
+ *
+ * This function is called by Tcl_DoOneEvent to check the timer event
+ * source for events. This routine checks the first timer in the list.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May queue an event and update the maximum notifier block time.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TimerCheckProc(
+ ClientData data, /* Specific data. */
+ int flags) /* Event flags as passed to Tcl_DoOneEvent. */
+{
+ Tcl_Time blockTime, *firstTime;
+ ThreadSpecificData *tsdPtr = (ThreadSpecificData *)data;
+
+ if (!(flags & TCL_TIMER_EVENTS)) {
+ return;
+ }
+
+ if (tsdPtr == NULL) { tsdPtr = InitTimer(); };
+
+ /* If already pending */
+ if (!tsdPtr->timerList || tsdPtr->timerPending) {
+ return;
+ }
+
+ /*
+ * Verify the first timer on the queue.
+ */
+ Tcl_GetTime(&blockTime);
+ firstTime = &(TimerEntry2TimerHandler(tsdPtr->timerList)->time);
+ blockTime.sec = firstTime->sec - blockTime.sec;
+ blockTime.usec = firstTime->usec - blockTime.usec;
+ if (blockTime.usec < 0) {
+ blockTime.sec -= 1;
+ blockTime.usec += 1000000;
+ }
+ if (blockTime.sec < 0) {
+ blockTime.sec = 0;
+ blockTime.usec = 0;
+ }
+
+ /*
+ * If the first timer has expired, stick an event on the queue.
+ */
+ if (blockTime.sec == 0 && blockTime.usec == 0) {
+ TclSetTimerEventMarker();
+ tsdPtr->timerPending = 1;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TclServiceTimerEvents --
*
* This function is called by Tcl_ServiceEvent when a timer events should
@@ -785,7 +847,7 @@ done:
/* Compute the next timeout (later via TimerSetupProc using the first timer). */
tsdPtr->timerPending = 0;
- return 1; /* processing done, again later via TimerSetupProc */
+ return 1; /* processing done, again later via TimerCheckProc */
}
/*
@@ -928,7 +990,7 @@ Tcl_CancelIdleCall(
/*
*----------------------------------------------------------------------
*
- * TclServiceIdle --
+ * TclServiceIdle -- , TclServiceIdleEx --
*
* This function is invoked by the notifier when it becomes idle. It will
* invoke all idle handlers that are present at the time the call is
@@ -945,14 +1007,15 @@ Tcl_CancelIdleCall(
*/
int
-TclServiceIdle(void)
+TclServiceIdleEx(
+ int flags,
+ int count)
{
TimerEntry *idlePtr;
size_t currentGeneration;
- Tcl_Time blockTime;
ThreadSpecificData *tsdPtr = InitTimer();
- if (tsdPtr->idleList == NULL) {
+ if ((idlePtr = tsdPtr->idleList) == NULL) {
return 0;
}
@@ -975,9 +1038,7 @@ TclServiceIdle(void)
* during the call.
*/
- while ((idlePtr = tsdPtr->idleList) != NULL
- && idlePtr->generation <= currentGeneration
- ) {
+ while (idlePtr->generation <= currentGeneration) {
/* detach entry from the owner's list */
TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr);
@@ -989,18 +1050,33 @@ TclServiceIdle(void)
(*idlePtr->deleteProc)(idlePtr->clientData);
}
ckfree((char *) idlePtr);
+
+ /*
+ * Stop processing idle if idle queue empty, count reached or other
+ * events queued (only if not idle events only to service).
+ */
+
+ if ( (idlePtr = tsdPtr->idleList) == NULL
+ || !--count
+ || ((flags & TCL_ALL_EVENTS) != TCL_IDLE_EVENTS
+ && TclPeekEventQueued(flags))
+ ) {
+ break;
+ }
}
- if (tsdPtr->idleList) {
- blockTime.sec = 0;
- blockTime.usec = 0;
- Tcl_SetMaxBlockTime(&blockTime);
- }
+
/* Reset generation */
if (!tsdPtr->idleList) {
tsdPtr->idleGeneration = 0;
}
return 1;
}
+
+int
+TclServiceIdle(void)
+{
+ return TclServiceIdleEx(TCL_ALL_EVENTS, INT_MAX);
+}
/*
*----------------------------------------------------------------------