summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsebres <sebres@users.sourceforge.net>2017-07-03 13:27:33 (GMT)
committersebres <sebres@users.sourceforge.net>2017-07-03 13:27:33 (GMT)
commit86897b9570309f5109b17429741bf6ce57f69d2b (patch)
treeeced058d4a4e37be1016001c03c57769d915d661
parentd21bae7857c761d57f933bcc4b2256edd5fe7e11 (diff)
downloadtcl-86897b9570309f5109b17429741bf6ce57f69d2b.zip
tcl-86897b9570309f5109b17429741bf6ce57f69d2b.tar.gz
tcl-86897b9570309f5109b17429741bf6ce57f69d2b.tar.bz2
[unix] optimized Tcl_WaitForEvent similar to windows changes (makes Tcl for *nix more "RTS" resp. NRT-capable):
- more precise waiting now (e.g. still microseconds by time up to 0.005 ms), important since after/vwait accepting microseconds (double); - avoids too long waiting on *nix wait/sleep primitives, e. g. by `timerate {vwait 0 a}` - 1.5µs now vs. 31.9µs before; - extended with new internal function TclpSleep (in contrast to Tcl_Sleep accept Tcl_Time, so microseconds);
-rw-r--r--unix/tclUnixEvent.c58
-rw-r--r--unix/tclUnixNotfy.c257
2 files changed, 226 insertions, 89 deletions
diff --git a/unix/tclUnixEvent.c b/unix/tclUnixEvent.c
index e4d922d..7e75357 100644
--- a/unix/tclUnixEvent.c
+++ b/unix/tclUnixEvent.c
@@ -12,7 +12,10 @@
#include "tclInt.h"
#ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is
* in tclMacOSXNotify.c */
-
+
+/* declaration of TclpSleep() from tclUnixNotfy.c */
+void TclpSleep(const Tcl_Time *timePtr);
+
/*
*----------------------------------------------------------------------
*
@@ -33,56 +36,11 @@ void
Tcl_Sleep(
int ms) /* Number of milliseconds to sleep. */
{
- struct timeval delay;
- Tcl_Time before, after, vdelay;
-
- /*
- * The only trick here is that select appears to return early under some
- * conditions, so we have to check to make sure that the right amount of
- * time really has elapsed. If it's too early, go back to sleep again.
- */
-
- Tcl_GetTime(&before);
- after = before;
- after.sec += ms/1000;
- after.usec += (ms%1000)*1000;
- if (after.usec > 1000000) {
- after.usec -= 1000000;
- after.sec += 1;
- }
- while (1) {
- /*
- * TIP #233: Scale from virtual time to real-time for select.
- */
-
- vdelay.sec = after.sec - before.sec;
- vdelay.usec = after.usec - before.usec;
-
- if (vdelay.usec < 0) {
- vdelay.usec += 1000000;
- vdelay.sec -= 1;
- }
-
- if ((vdelay.sec != 0) || (vdelay.usec != 0)) {
- (*tclScaleTimeProcPtr) (&vdelay, tclTimeClientData);
- }
-
- delay.tv_sec = vdelay.sec;
- delay.tv_usec = vdelay.usec;
-
- /*
- * Special note: must convert delay.tv_sec to int before comparing to
- * zero, since delay.tv_usec is unsigned on some platforms.
- */
+ Tcl_Time delay;
- if ((((int) delay.tv_sec) < 0)
- || ((delay.tv_usec == 0) && (delay.tv_sec == 0))) {
- break;
- }
- (void) select(0, (SELECT_MASK *) 0, (SELECT_MASK *) 0,
- (SELECT_MASK *) 0, &delay);
- Tcl_GetTime(&before);
- }
+ delay.sec = ms/1000;
+ delay.usec = (ms%1000)*1000;
+ TclpSleep(&delay);
}
#endif /* HAVE_COREFOUNDATION */
diff --git a/unix/tclUnixNotfy.c b/unix/tclUnixNotfy.c
index e7ea7a1..a5ee159 100644
--- a/unix/tclUnixNotfy.c
+++ b/unix/tclUnixNotfy.c
@@ -112,7 +112,7 @@ typedef struct ThreadSpecificData {
* this condition variable. */
#endif /* __CYGWIN__ */
int waitCVinitialized; /* Variable to flag initialization of the structure */
- int eventReady; /* True if an event is ready to be processed.
+ int eventReady; /* > 0 if an event is ready to be processed.
* Used as condition flag together with waitCV
* above. */
#endif /* TCL_THREADS */
@@ -492,7 +492,7 @@ Tcl_AlertNotifier(
ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData;
pthread_mutex_lock(&notifierMutex);
- tsdPtr->eventReady = 1;
+ tsdPtr->eventReady++;
#ifdef __CYGWIN__
PostMessageW(tsdPtr->hwnd, 1024, 0, 0);
#else /* __CYGWIN__ */
@@ -822,12 +822,107 @@ NotifierProc(
* Process all of the runnable events.
*/
- tsdPtr->eventReady = 1;
+ tsdPtr->eventReady++;
Tcl_ServiceAll();
return 0;
}
#endif /* TCL_THREADS && __CYGWIN__ */
+
+/*
+ * Several minimal sleep and wait values, corresponding unix timer resolution
+ *
+ * Note: Adjusting of this values may increase NRT-capability, but also may
+ * increase CPU load (resp. busy waits on brief intervals).
+ */
+
+#ifndef TCL_TMR_MIN_DELAY
+# define TCL_TMR_MIN_DELAY 100
+# define TCL_TMR_MIN_SLEEP 50
+# define TCL_TMR_OVERHEAD 50
+#endif
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclpSleep --
+ *
+ * Delay execution for the specified time.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Time passes.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclpSleep(
+ const Tcl_Time *timePtr) /* Time to sleep. */
+{
+ struct timeval delay;
+ Tcl_Time before, after, vdelay;
+
+ /*
+ * The only trick here is that select appears to return early under some
+ * conditions, so we have to check to make sure that the right amount of
+ * time really has elapsed. If it's too early, go back to sleep again.
+ */
+
+ Tcl_GetTime(&before);
+ after = before;
+ if (timePtr) { /* if given calculate, otherwise - 0 usec */
+
+ /*
+ * TIP #233: Scale from virtual time to real-time for select/usleep.
+ */
+
+ vdelay = *timePtr;
+ if ((vdelay.sec != 0) || (vdelay.usec != 0)) {
+ tclScaleTimeProcPtr(&vdelay, tclTimeClientData);
+ }
+
+ after.sec += vdelay.sec;
+ after.usec += vdelay.usec;
+ if (after.usec > 1000000) {
+ after.usec -= 1000000;
+ after.sec += 1;
+ }
+ }
+ while (1) {
+ vdelay.sec = after.sec - before.sec;
+ vdelay.usec = after.usec - before.usec;
+
+ if (vdelay.usec < 0) {
+ vdelay.usec += 1000000;
+ vdelay.sec -= 1;
+ }
+
+ delay.tv_sec = vdelay.sec;
+ delay.tv_usec = vdelay.usec;
+
+ /*
+ * Special note: must convert delay.tv_sec to int before comparing to
+ * zero, since delay.tv_usec is unsigned on some platforms.
+ */
+
+ if ((((int) delay.tv_sec) < 0)
+ || ((delay.tv_usec == 0) && (delay.tv_sec == 0))) {
+ break;
+ }
+
+ if (delay.tv_sec || delay.tv_usec >= TCL_TMR_MIN_DELAY) {
+ (void) select(0, (SELECT_MASK *) 0, (SELECT_MASK *) 0,
+ (SELECT_MASK *) 0, &delay);
+ } else if (delay.tv_usec >= TCL_TMR_MIN_SLEEP) {
+ usleep(delay.tv_usec - TCL_TMR_MIN_SLEEP);
+ }
+ Tcl_GetTime(&before);
+ }
+}
+
/*
*----------------------------------------------------------------------
*
@@ -851,10 +946,10 @@ Tcl_WaitForEvent(
Tcl_Time *timePtr) /* Maximum block time, or NULL. */
{
FileHandler *filePtr;
- int mask;
- Tcl_Time vTime;
+ int mask, canWait = 1;
+ Tcl_Time now, endTime, waitTime;
#ifdef TCL_THREADS
- int waitForFiles;
+ int waitForFiles = 0;
#ifdef __CYGWIN__
MSG msg;
#endif /* __CYGWIN__ */
@@ -868,12 +963,16 @@ Tcl_WaitForEvent(
struct timeval timeout, *timeoutPtr;
int numFound;
#endif /* TCL_THREADS */
- ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
+ ThreadSpecificData *tsdPtr;
if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) {
return tclStubs.tcl_WaitForEvent(timePtr);
}
+ Tcl_GetTime(&now);
+
+ tsdPtr = TCL_TSD_INIT(&dataKey);
+
/*
* Set up the timeout structure. Note that if there are no events to
* check for, we return with a negative result rather than blocking
@@ -881,17 +980,22 @@ Tcl_WaitForEvent(
*/
if (timePtr != NULL) {
- /*
- * TIP #233 (Virtualized Time). Is virtual time in effect? And do
- * we actually have something to scale? If yes to both then we
- * call the handler to do this scaling.
- */
- if (timePtr->sec != 0 || timePtr->usec != 0) {
- vTime = *timePtr;
- tclScaleTimeProcPtr(&vTime, tclTimeClientData);
- timePtr = &vTime;
+ endTime = now;
+ endTime.sec += timePtr->sec;
+ endTime.usec += timePtr->usec;
+ if (endTime.usec > 1000000) {
+ endTime.usec -= 1000000;
+ endTime.sec++;
+ }
+ /*
+ * If short wait or no wait at all, just process events already available
+ * right now, avoid waiting too long somewhere (NRT-capability fix).
+ */
+ if (!timePtr->sec && timePtr->usec < TCL_TMR_MIN_DELAY) {
+ canWait = 0;
}
+
#ifndef TCL_THREADS
timeout.tv_sec = timePtr->sec;
timeout.tv_usec = timePtr->usec;
@@ -921,18 +1025,12 @@ Tcl_WaitForEvent(
pthread_mutex_lock(&notifierMutex);
- if (timePtr != NULL && timePtr->sec == 0 && (timePtr->usec == 0
-#if defined(__APPLE__) && defined(__LP64__)
- /*
- * On 64-bit Darwin, pthread_cond_timedwait() appears to have
- * a bug that causes it to wait forever when passed an
- * absolute time which has already been exceeded by the system
- * time; as a workaround, when given a very brief timeout,
- * just do a poll. [Bug 1457797]
- */
- || timePtr->usec < 10
-#endif /* __APPLE__ && __LP64__ */
- )) {
+ /* if cannot wait (but not really necessary to wait), bypass triggering pipe */
+ if (!canWait && (!tsdPtr->numFdBits || tsdPtr->eventReady)) {
+ goto nowait;
+ }
+
+ if (!canWait) {
/*
* Cannot emulate a polling select with a polling condition
* variable. Instead, pretend to wait for files and tell the
@@ -943,7 +1041,6 @@ Tcl_WaitForEvent(
waitForFiles = 1;
tsdPtr->pollState = POLL_WANT;
- timePtr = NULL;
} else {
waitForFiles = (tsdPtr->numFdBits > 0);
tsdPtr->pollState = 0;
@@ -970,17 +1067,46 @@ Tcl_WaitForEvent(
}
}
+ nowait:
+
FD_ZERO(&tsdPtr->readyMasks.readable);
FD_ZERO(&tsdPtr->readyMasks.writable);
FD_ZERO(&tsdPtr->readyMasks.exception);
- if (!tsdPtr->eventReady) {
+ while (!tsdPtr->eventReady) {
+
+ if (timePtr) {
+ Tcl_GetTime(&now);
+ waitTime = endTime;
+ waitTime.sec -= now.sec;
+ waitTime.usec -= now.usec;
+ if (waitTime.usec < 0) {
+ waitTime.usec += 1000000;
+ waitTime.sec--;
+ }
+
+ if (now.sec > endTime.sec) {
+ break; /* end of wait */
+ }
+ if (now.sec == endTime.sec) {
+ if (now.usec > endTime.usec) {
+ break; /* end of wait */
+ }
+ if (now.usec > endTime.usec + TCL_TMR_OVERHEAD) {
+ canWait = 0;
+ }
+ }
+ }
+
#ifdef __CYGWIN__
if (!PeekMessageW(&msg, NULL, 0, 0, 0)) {
DWORD timeout;
if (timePtr) {
- timeout = timePtr->sec * 1000 + timePtr->usec / 1000;
+ /* TIP #233: Scale from virtual time to real-time */
+ tclScaleTimeProcPtr(&waitTime, tclTimeClientData);
+
+ timeout = waitTime.sec * 1000 + waitTime.usec / 1000;
} else {
timeout = 0xFFFFFFFF;
}
@@ -989,21 +1115,74 @@ Tcl_WaitForEvent(
pthread_mutex_lock(&notifierMutex);
}
#else
- if (timePtr != NULL) {
- Tcl_Time now;
+ /* prevent too long waiting (NRT-capability) */
+ if ( !canWait ) {
+ /* short sleep */
+ TclpSleep(&waitTime);
+ break; /* end of wait */
+ }
+ else
+ if (timePtr) {
struct timespec ptime;
- Tcl_GetTime(&now);
- ptime.tv_sec = timePtr->sec + now.sec + (timePtr->usec + now.usec) / 1000000;
- ptime.tv_nsec = 1000 * ((timePtr->usec + now.usec) % 1000000);
+#if 1
+ /* TIP #233: Scale from virtual time to real-time */
+ tclScaleTimeProcPtr(&waitTime, tclTimeClientData);
- pthread_cond_timedwait(&tsdPtr->waitCV, &notifierMutex, &ptime);
+ ptime.tv_sec = now.sec;
+ ptime.tv_nsec = (now.usec + waitTime.usec);
+ if (ptime.tv_nsec > 1000000) {
+ ptime.tv_nsec -= 1000000;
+ ptime.tv_sec++;
+ }
+ ptime.tv_nsec *= 1000; /* usec to nsec */
+#else
+ ptime.tv_sec = endTime.sec;
+ ptime.tv_nsec = 1000 * endTime.usec;
+#endif
+
+#if defined(__APPLE__) && defined(__LP64__)
+ /*
+ * On 64-bit Darwin, pthread_cond_timedwait() appears to have
+ * a bug that causes it to wait forever when passed an
+ * absolute time which has already been exceeded by the system
+ * time; as a workaround, when given a very brief timeout,
+ * just increment a bit the waiting-time from now. [Bug 1457797]
+ */
+ if ( now.sec > endTime.sec
+ || (now.sec == endTime.sec && now.usec > endTime.usec)
+ ) {
+ ptime.tv_sec = now.sec;
+ ptime.tv_nsec = 1000 * now.usec + 10; /* + 10 nanosecond */
+ }
+#else
+ /* remove overhead in nsec */
+ if (ptime.tv_nsec < TCL_TMR_OVERHEAD * 1000) {
+ ptime.tv_nsec += 1000000000;
+ ptime.tv_sec--;
+ }
+ ptime.tv_nsec -= TCL_TMR_OVERHEAD * 1000;
+
+#endif /* __APPLE__ && __LP64__ */
+
+ if (ptime.tv_nsec > 1000000000) {
+ ptime.tv_nsec -= 1000000000;
+ ptime.tv_sec++;
+ }
+ if (pthread_cond_timedwait(&tsdPtr->waitCV, &notifierMutex,
+ &ptime) == ETIMEDOUT) {
+ continue; /* repeat wait (if not yet real timeout) */
+ };
} else {
pthread_cond_wait(&tsdPtr->waitCV, &notifierMutex);
}
#endif /* __CYGWIN__ */
+
+ break; /* end of wait */
+ }
+ if (tsdPtr->eventReady > 0) {
+ tsdPtr->eventReady--;
}
- tsdPtr->eventReady = 0;
#ifdef __CYGWIN__
while (PeekMessageW(&msg, NULL, 0, 0, 0)) {
@@ -1272,7 +1451,7 @@ NotifierThreadProc(
}
if (found || (tsdPtr->pollState & POLL_DONE)) {
- tsdPtr->eventReady = 1;
+ tsdPtr->eventReady++;
if (tsdPtr->onList) {
/*
* Remove the ThreadSpecificData structure of this thread