]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Avoid "hot idle": A series of rapid select() calls with zero timeout.
authorAlex Rousskov <rousskov@measurement-factory.com>
Sun, 3 Nov 2013 08:37:57 +0000 (01:37 -0700)
committerAmos Jeffries <squid3@treenet.co.nz>
Sun, 3 Nov 2013 08:37:57 +0000 (01:37 -0700)
Squid uses "infinite" precision when it comes to deciding whether the next
timed event is ready but uses millisecond (1e-3) precision when deciding how
long to wait before the next event will be ready. This inconsistency results
in the EventScheduler engine telling the main loop that it has 0 milliseconds
to poll pending I/O, but when asked again (after the I/O is quickly polled),
the EventScheduler engine often does not schedule the promised event and tells
the main loop to poll for another 0 milliseconds again. This cycling may
happen many times in a row (until enough time is wasted for the next event to
become ready using higher precision).

The fixed code adds a minimum 1ms delay for not-yet-ready events. It also
places both decisions into one method (EventScheduler::timeRemaining), and
tries to polish/document decision logic (which is more complex than it may
seem) because the code has to avoid both inconsistent decisions and hot idle
loops while maintaining the traditional "no event is fired before it is due"
guarantee.

TODO: Idle Squid still runs hotter than it should because the maximum waiting
time is artificially capped outside the event queue to EVENT_LOOP_TIMEOUT=1s.
This causes at most one extra loop iteration per second.

src/event.cc
src/event.h
src/tests/stub_event.cc

index a812705904251460f38a5ca048e4a2f2600efc86..e6390a1c14f32fc3c5f10cac75fc13539e20fd31 100644 (file)
 #include "profiler/Profiler.h"
 #include "tools.h"
 
+#if HAVE_MATH_H
+#include <math.h>
+#endif
+
 /* The list of event processes */
 
 static OBJH eventDump;
@@ -219,39 +223,37 @@ EventScheduler::cancel(EVH * func, void *arg)
         debug_trap("eventDelete: event not found");
 }
 
+// The event API does not guarantee exact timing, but guarantees that no event
+// is fired before it is due. We may delay firing, but never fire too early.
 int
-EventScheduler::checkDelay()
+EventScheduler::timeRemaining() const
 {
     if (!tasks)
         return EVENT_IDLE;
 
-    int result = (int) ((tasks->when - current_dtime) * 1000);
-
-    if (result < 0)
-        return 0;
+    if (tasks->when <= current_dtime) // we are on time or late
+        return 0; // fire the event ASAP
 
-    return result;
+    const double diff = tasks->when - current_dtime; // microseconds
+    // Round UP: If we come back a nanosecond earlier, we will wait again!
+    const int timeLeft = static_cast<int>(ceil(1000*diff)); // milliseconds
+    // Avoid hot idle: A series of rapid select() calls with zero timeout.
+    const int minDelay = 1; // millisecond
+    return max(minDelay, timeLeft);
 }
 
 int
 EventScheduler::checkEvents(int timeout)
 {
-
-    ev_entry *event = NULL;
-
-    if (NULL == tasks)
-        return checkDelay();
-
-    if (tasks->when > current_dtime)
-        return checkDelay();
+    int result = timeRemaining();
+    if (result != 0)
+        return result;
 
     PROF_start(eventRun);
 
-    debugs(41, 5, HERE << "checkEvents");
-
-    while ((event = tasks)) {
-        if (event->when > current_dtime)
-            break;
+    do {
+        ev_entry *event = tasks;
+        assert(event);
 
         /* XXX assumes event->name is static memory! */
         AsyncCall::Pointer call = asyncCall(41,5, event->name,
@@ -265,14 +267,16 @@ EventScheduler::checkEvents(int timeout)
         tasks = event->next;
         delete event;
 
+        result = timeRemaining();
+
         // XXX: We may be called again during the same event loop iteration.
         // Is there a point in breaking now?
         if (heavy)
             break; // do not dequeue events following a heavy event
-    }
+    } while (result == 0);
 
     PROF_stop(eventRun);
-    return checkDelay();
+    return result;
 }
 
 void
index aa269292a26b21612b004852d4cb80594f397e40..d57d0d4412d5109ac495e8baf3ad346bed9dc9f6 100644 (file)
@@ -80,8 +80,8 @@ public:
     void cancel(EVH * func, void * arg);
     /* clean up the used memory in the scheduler */
     void clean();
-    /* how long until the next event ? */
-    int checkDelay();
+    /* either EVENT_IDLE or milliseconds remaining until the next event */
+    int timeRemaining() const;
     /* cache manager output for the event queue */
     void dump(StoreEntry *);
     /* find a scheduled event */
index 1bdb14a48f22da04a0de63cffdec81a63dd60abb..8c02f711bbf50734441a3da7c1d905b2db8efcea 100644 (file)
@@ -21,8 +21,8 @@ int eventFind(EVH *, void *) STUB_RETVAL(-1)
 EventScheduler::EventScheduler() STUB
 EventScheduler::~EventScheduler() STUB
 void EventScheduler::cancel(EVH * func, void * arg) STUB
+int EventScheduler::timeRemaining() const STUB_RETVAL(1)
 void EventScheduler::clean() STUB
-int EventScheduler::checkDelay() STUB_RETVAL(-1)
 void EventScheduler::dump(StoreEntry *) STUB
 bool EventScheduler::find(EVH * func, void * arg) STUB_RETVAL(false)
 void EventScheduler::schedule(const char *name, EVH * func, void *arg, double when, int weight, bool cbdata) STUB