]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/peer_select.cc
Do not use invasive lists to store CachePeers (#1424)
[thirdparty/squid.git] / src / peer_select.cc
index 0ac9a090615c565a0386e4ca0a9d619003ed5b36..c1e0d89612c300a1254f00618bc783789b0a1caa 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
 
 #include "squid.h"
 #include "acl/FilledChecklist.h"
+#include "base/AsyncCbdataCalls.h"
 #include "base/InstanceId.h"
+#include "base/TypeTraits.h"
 #include "CachePeer.h"
+#include "CachePeers.h"
 #include "carp.h"
 #include "client_side.h"
 #include "dns/LookupDetails.h"
@@ -32,9 +35,8 @@
 #include "peer_userhash.h"
 #include "PeerSelectState.h"
 #include "SquidConfig.h"
-#include "SquidTime.h"
 #include "Store.h"
-#include "URL.h"
+#include "time/gadgets.h"
 
 /**
  * A CachePeer which has been selected as a possible destination.
@@ -93,13 +95,139 @@ operator <<(std::ostream &os, const PeerSelectionDumper &fsd)
     os << hier_code_str[fsd.code];
 
     if (fsd.peer)
-        os << '/' << fsd.peer->host;
+        os << '/' << *fsd.peer;
     else if (fsd.selector) // useful for DIRECT and gone PINNED destinations
         os << '#' << fsd.selector->request->url.host();
 
     return os;
 }
 
+/// An ICP ping timeout service.
+/// Protects event.cc (which is designed to handle a few unrelated timeouts)
+/// from exposure to thousands of ping-related timeouts on busy proxies.
+class PeerSelectorPingMonitor
+{
+public:
+    /// registers the given selector to be notified about the IPC ping timeout
+    void monitor(PeerSelector *);
+
+    /// removes a PeerSelector from the waiting list
+    void forget(PeerSelector *);
+
+    /// \returns a (nil) registration of a non-waiting peer selector
+    WaitingPeerSelectorPosition npos() { return selectors.end(); }
+
+private:
+    static void NoteWaitOver(void *monitor);
+
+    void startWaiting();
+    void abortWaiting();
+    void noteWaitOver();
+
+    WaitingPeerSelectors selectors; ///< \see WaitingPeerSelectors
+};
+
+/// monitors all PeerSelector ICP ping timeouts
+static PeerSelectorPingMonitor &
+PingMonitor()
+{
+    static const auto Instance = new PeerSelectorPingMonitor();
+    return *Instance;
+}
+
+/* PeerSelectorPingMonitor */
+
+/// PeerSelectorPingMonitor::noteWaitOver() wrapper
+void
+PeerSelectorPingMonitor::NoteWaitOver(void *raw)
+{
+    assert(raw);
+    static_cast<PeerSelectorPingMonitor*>(raw)->noteWaitOver();
+}
+
+/// schedules a single event to represent all waiting selectors
+void
+PeerSelectorPingMonitor::startWaiting()
+{
+    assert(!selectors.empty());
+    const auto interval = tvSubDsec(current_time, selectors.begin()->first);
+    eventAdd("PeerSelectorPingMonitor::NoteWaitOver", &PeerSelectorPingMonitor::NoteWaitOver, this, interval, 0, false);
+}
+
+/// undoes an earlier startWaiting() call
+void
+PeerSelectorPingMonitor::abortWaiting()
+{
+    // our event may be already in the AsyncCallQueue but that is OK:
+    // such queued calls cannot accumulate, and we ignore any stale ones
+    eventDelete(&PeerSelectorPingMonitor::NoteWaitOver, nullptr);
+}
+
+/// calls back all ready PeerSelectors and continues to wait for others
+void
+PeerSelectorPingMonitor::noteWaitOver()
+{
+    while (!selectors.empty() && current_time >= selectors.begin()->first) {
+        const auto selector = selectors.begin()->second;
+        CallBack(selector->al, [selector,this] {
+            selector->ping.monitorRegistration = npos();
+            AsyncCall::Pointer callback = asyncCall(44, 4, "PeerSelector::HandlePingTimeout",
+                                                    cbdataDialer(PeerSelector::HandlePingTimeout, selector));
+            ScheduleCallHere(callback);
+        });
+        selectors.erase(selectors.begin());
+    }
+
+    if (!selectors.empty()) {
+        // Since abortWaiting() is unreliable, we may have been awakened by a
+        // stale event A after event B has been scheduled. Now we are going to
+        // schedule event C. Prevent event accumulation by deleting B (if any).
+        abortWaiting();
+
+        startWaiting();
+    }
+}
+
+void
+PeerSelectorPingMonitor::monitor(PeerSelector *selector)
+{
+    assert(selector);
+
+    const auto deadline = selector->ping.deadline();
+    const auto position = selectors.emplace(deadline, selector);
+    selector->ping.monitorRegistration = position;
+
+    if (position == selectors.begin()) {
+        if (selectors.size() > 1)
+            abortWaiting(); // remove the previously scheduled earlier event
+        startWaiting();
+    } // else the already scheduled event is still the earliest one
+}
+
+void
+PeerSelectorPingMonitor::forget(PeerSelector *selector)
+{
+    assert(selector);
+
+    if (selector->ping.monitorRegistration == npos())
+        return; // already forgotten
+
+    const auto wasFirst = selector->ping.monitorRegistration == selectors.begin();
+    selectors.erase(selector->ping.monitorRegistration);
+    selector->ping.monitorRegistration = npos();
+
+    if (wasFirst) {
+        // do not reschedule if there are still elements with the same deadline
+        if (!selectors.empty() && selectors.begin()->first == selector->ping.deadline())
+            return;
+        abortWaiting();
+        if (!selectors.empty())
+            startWaiting();
+    } // else do nothing since the old scheduled event is still the earliest one
+}
+
+/* PeerSelector */
+
 PeerSelector::~PeerSelector()
 {
     while (servers) {
@@ -108,17 +236,15 @@ PeerSelector::~PeerSelector()
         servers = next;
     }
 
+    cancelPingTimeoutMonitoring();
+
     if (entry) {
         debugs(44, 3, entry->url());
-
-        if (entry->ping_status == PING_WAITING)
-            eventDelete(HandlePingTimeout, this);
-
         entry->ping_status = PING_DONE;
     }
 
     if (acl_checklist) {
-        debugs(44, DBG_IMPORTANT, "BUG: peer selector gone while waiting for a slow ACL");
+        debugs(44, DBG_IMPORTANT, "ERROR: Squid BUG: peer selector gone while waiting for a slow ACL");
         delete acl_checklist;
     }
 
@@ -127,15 +253,33 @@ PeerSelector::~PeerSelector()
     if (entry) {
         assert(entry->ping_status != PING_WAITING);
         entry->unlock("peerSelect");
-        entry = NULL;
+        entry = nullptr;
     }
 
     delete lastError;
 }
 
+void
+PeerSelector::startPingWaiting()
+{
+    assert(entry);
+    assert(entry->ping_status != PING_WAITING);
+    PingMonitor().monitor(this);
+    entry->ping_status = PING_WAITING;
+}
+
+void
+PeerSelector::cancelPingTimeoutMonitoring()
+{
+    PingMonitor().forget(this);
+}
+
 static int
-peerSelectIcpPing(HttpRequest * request, int direct, StoreEntry * entry)
+peerSelectIcpPing(PeerSelector *ps, int direct, StoreEntry * entry)
 {
+    assert(ps);
+    HttpRequest *request = ps->request;
+
     int n;
     assert(entry);
     assert(entry->ping_status == PING_NONE);
@@ -149,7 +293,7 @@ peerSelectIcpPing(HttpRequest * request, int direct, StoreEntry * entry)
         if (direct != DIRECT_NO)
             return 0;
 
-    n = neighborsCount(request);
+    n = neighborsCount(ps);
 
     debugs(44, 3, "counted " << n << " neighbors");
 
@@ -196,7 +340,7 @@ PeerSelectionInitiator::startSelectingDestinations(HttpRequest *request, const A
 }
 
 void
-PeerSelector::checkNeverDirectDone(const allow_t answer)
+PeerSelector::checkNeverDirectDone(const Acl::Answer answer)
 {
     acl_checklist = nullptr;
     debugs(44, 3, answer);
@@ -218,13 +362,13 @@ PeerSelector::checkNeverDirectDone(const allow_t answer)
 }
 
 void
-PeerSelector::CheckNeverDirectDone(allow_t answer, void *data)
+PeerSelector::CheckNeverDirectDone(Acl::Answer answer, void *data)
 {
     static_cast<PeerSelector*>(data)->checkNeverDirectDone(answer);
 }
 
 void
-PeerSelector::checkAlwaysDirectDone(const allow_t answer)
+PeerSelector::checkAlwaysDirectDone(const Acl::Answer answer)
 {
     acl_checklist = nullptr;
     debugs(44, 3, answer);
@@ -246,7 +390,7 @@ PeerSelector::checkAlwaysDirectDone(const allow_t answer)
 }
 
 void
-PeerSelector::CheckAlwaysDirectDone(allow_t answer, void *data)
+PeerSelector::CheckAlwaysDirectDone(Acl::Answer answer, void *data)
 {
     static_cast<PeerSelector*>(data)->checkAlwaysDirectDone(answer);
 }
@@ -299,6 +443,17 @@ PeerSelector::resolveSelected()
         return;
     }
 
+    if (fs && fs->code == PINNED) {
+        // Nil path signals a PINNED destination selection. Our initiator should
+        // borrow and use clientConnectionManager's pinned connection object
+        // (regardless of that connection destination).
+        handlePath(nullptr, *fs);
+        servers = fs->next;
+        delete fs;
+        resolveSelected();
+        return;
+    }
+
     // convert the list of FwdServer destinations into destinations IP addresses
     if (fs && wantsMoreDestinations()) {
         // send the next one off for DNS lookup.
@@ -376,7 +531,10 @@ PeerSelector::noteIp(const Ip::Address &ip)
 
     Comm::ConnectionPointer p = new Comm::Connection();
     p->remote = ip;
-    p->remote.port(peer ? peer->http_port : request->url.port());
+    // XXX: We return a (non-peer) destination with a zero port if the selection
+    // initiator supplied a request target without a port. If there are no valid
+    // use cases for this behavior, stop _selecting_ such destinations.
+    p->remote.port(peer ? peer->http_port : request->url.port().value_or(0));
     handlePath(p, *servers);
 }
 
@@ -391,9 +549,9 @@ PeerSelector::noteIps(const Dns::CachedIps *ia, const Dns::LookupDetails &detail
         debugs(44, 3, "Unknown host: " << (fs->_peer.valid() ? fs->_peer->host : request->url.host()));
         // discard any previous error.
         delete lastError;
-        lastError = NULL;
+        lastError = nullptr;
         if (fs->code == HIER_DIRECT) {
-            lastError = new ErrorState(ERR_DNS_FAIL, Http::scServiceUnavailable, request);
+            lastError = new ErrorState(ERR_DNS_FAIL, Http::scServiceUnavailable, request, al);
             lastError->dnsError = details.error;
         }
     }
@@ -436,7 +594,7 @@ PeerSelector::checkNetdbDirect()
 
     p = whichPeer(closest_parent_miss);
 
-    if (p == NULL)
+    if (p == nullptr)
         return 0;
 
     debugs(44, 3, "closest_parent_miss RTT = " << ping.p_rtt << " msec");
@@ -462,17 +620,19 @@ PeerSelector::selectMore()
         if (always_direct == ACCESS_DUNNO) {
             debugs(44, 3, "direct = " << DirectStr[direct] << " (always_direct to be checked)");
             /** check always_direct; */
-            ACLFilledChecklist *ch = new ACLFilledChecklist(Config.accessList.AlwaysDirect, request, NULL);
+            ACLFilledChecklist *ch = new ACLFilledChecklist(Config.accessList.AlwaysDirect, request, nullptr);
             ch->al = al;
             acl_checklist = ch;
+            acl_checklist->syncAle(request, nullptr);
             acl_checklist->nonBlockingCheck(CheckAlwaysDirectDone, this);
             return;
         } else if (never_direct == ACCESS_DUNNO) {
             debugs(44, 3, "direct = " << DirectStr[direct] << " (never_direct to be checked)");
             /** check never_direct; */
-            ACLFilledChecklist *ch = new ACLFilledChecklist(Config.accessList.NeverDirect, request, NULL);
+            ACLFilledChecklist *ch = new ACLFilledChecklist(Config.accessList.NeverDirect, request, nullptr);
             ch->al = al;
             acl_checklist = ch;
+            acl_checklist->syncAle(request, nullptr);
             acl_checklist->nonBlockingCheck(CheckNeverDirectDone, this);
             return;
         } else if (request->flags.noDirect) {
@@ -496,7 +656,7 @@ PeerSelector::selectMore()
 
     if (!entry || entry->ping_status == PING_NONE)
         selectPinned();
-    if (entry == NULL) {
+    if (entry == nullptr) {
         (void) 0;
     } else if (entry->ping_status == PING_NONE) {
         selectSomeNeighbor();
@@ -505,6 +665,7 @@ PeerSelector::selectMore()
             return;
     } else if (entry->ping_status == PING_WAITING) {
         selectSomeNeighborReplies();
+        cancelPingTimeoutMonitoring();
         entry->ping_status = PING_DONE;
     }
 
@@ -539,7 +700,7 @@ PeerSelector::selectMore()
     resolveSelected();
 }
 
-bool peerAllowedToUse(const CachePeer * p, HttpRequest * request);
+bool peerAllowedToUse(const CachePeer *, PeerSelector*);
 
 /// Selects a pinned connection if it exists, is valid, and is allowed.
 void
@@ -548,17 +709,16 @@ PeerSelector::selectPinned()
     // TODO: Avoid all repeated calls. Relying on PING_DONE is not enough.
     if (!request->pinnedConnection())
         return;
-    CachePeer *pear = request->pinnedConnection()->pinnedPeer();
-    if (Comm::IsConnOpen(request->pinnedConnection()->validatePinnedConnection(request, pear))) {
-        const bool usePinned = pear ? peerAllowedToUse(pear, request) : (direct != DIRECT_NO);
-        if (usePinned) {
-            addSelection(pear, PINNED);
-            if (entry)
-                entry->ping_status = PING_DONE; // skip ICP
-        }
-    }
-    // If the pinned connection is prohibited (for this request) or gone, then
+
+    const auto peer = request->pinnedConnection()->pinnedPeer();
+    const auto usePinned = peer ? peerAllowedToUse(peer, this) : (direct != DIRECT_NO);
+    // If the pinned connection is prohibited (for this request) then
     // the initiator must decide whether it is OK to open a new one instead.
+    request->pinnedConnection()->pinning.peerAccessDenied = !usePinned;
+
+    addSelection(peer, PINNED);
+    if (entry)
+        entry->ping_status = PING_DONE; // skip ICP
 }
 
 /**
@@ -582,16 +742,16 @@ PeerSelector::selectSomeNeighbor()
     }
 
 #if USE_CACHE_DIGESTS
-    if ((p = neighborsDigestSelect(request))) {
+    if ((p = neighborsDigestSelect(this))) {
         if (neighborType(p, request->url) == PEER_PARENT)
             code = CD_PARENT_HIT;
         else
             code = CD_SIBLING_HIT;
     } else
 #endif
-        if ((p = netdbClosestParent(request))) {
+        if ((p = netdbClosestParent(this))) {
             code = CLOSEST_PARENT;
-        } else if (peerSelectIcpPing(request, direct, entry)) {
+        } else if (peerSelectIcpPing(this, direct, entry)) {
             debugs(44, 3, "Doing ICP pings");
             ping.start = current_time;
             ping.n_sent = neighborsUdpPing(request,
@@ -600,6 +760,9 @@ PeerSelector::selectSomeNeighbor()
                                            this,
                                            &ping.n_replies_expected,
                                            &ping.timeout);
+            // TODO: Refactor neighborsUdpPing() to guarantee positive timeouts.
+            if (ping.timeout < 0)
+                ping.timeout = 0;
 
             if (ping.n_sent == 0)
                 debugs(44, DBG_CRITICAL, "WARNING: neighborsUdpPing returned 0");
@@ -608,12 +771,7 @@ PeerSelector::selectSomeNeighbor()
                    " msec");
 
             if (ping.n_replies_expected > 0) {
-                entry->ping_status = PING_WAITING;
-                eventAdd("PeerSelector::HandlePingTimeout",
-                         HandlePingTimeout,
-                         this,
-                         0.001 * ping.timeout,
-                         0);
+                startPingWaiting();
                 return;
             }
         }
@@ -630,7 +788,7 @@ PeerSelector::selectSomeNeighbor()
 void
 PeerSelector::selectSomeNeighborReplies()
 {
-    CachePeer *p = NULL;
+    CachePeer *p = nullptr;
     hier_code code = HIER_NONE;
     assert(entry->ping_status == PING_WAITING);
     assert(direct != DIRECT_YES);
@@ -681,21 +839,21 @@ PeerSelector::selectSomeParent()
     if (direct == DIRECT_YES)
         return;
 
-    if ((p = peerSourceHashSelectParent(request))) {
+    if ((p = peerSourceHashSelectParent(this))) {
         code = SOURCEHASH_PARENT;
 #if USE_AUTH
-    } else if ((p = peerUserHashSelectParent(request))) {
+    } else if ((p = peerUserHashSelectParent(this))) {
         code = USERHASH_PARENT;
 #endif
-    } else if ((p = carpSelectParent(request))) {
+    } else if ((p = carpSelectParent(this))) {
         code = CARP;
-    } else if ((p = getRoundRobinParent(request))) {
+    } else if ((p = getRoundRobinParent(this))) {
         code = ROUNDROBIN_PARENT;
-    } else if ((p = getWeightedRoundRobinParent(request))) {
+    } else if ((p = getWeightedRoundRobinParent(this))) {
         code = ROUNDROBIN_PARENT;
-    } else if ((p = getFirstUpParent(request))) {
+    } else if ((p = getFirstUpParent(this))) {
         code = FIRSTUP_PARENT;
-    } else if ((p = getDefaultParent(request))) {
+    } else if ((p = getDefaultParent(this))) {
         code = DEFAULT_PARENT;
     }
 
@@ -708,10 +866,10 @@ PeerSelector::selectSomeParent()
 void
 PeerSelector::selectAllParents()
 {
-    CachePeer *p;
     /* Add all alive parents */
 
-    for (p = Config.peers; p; p = p->next) {
+    for (const auto &peer: CurrentCachePeers()) {
+        const auto p = peer.get();
         /* XXX: neighbors.c lacks a public interface for enumerating
          * parents to a request so we have to dig some here..
          */
@@ -719,7 +877,7 @@ PeerSelector::selectAllParents()
         if (neighborType(p, request->url) != PEER_PARENT)
             continue;
 
-        if (!peerHTTPOkay(p, request))
+        if (!peerHTTPOkay(p, this))
             continue;
 
         addSelection(p, ANY_OLD_PARENT);
@@ -730,7 +888,7 @@ PeerSelector::selectAllParents()
      * simply are not configured to handle the request.
      */
     /* Add default parent as a last resort */
-    if ((p = getDefaultParent(request))) {
+    if (const auto p = getDefaultParent(this)) {
         addSelection(p, DEFAULT_PARENT);
     }
 }
@@ -740,8 +898,11 @@ PeerSelector::handlePingTimeout()
 {
     debugs(44, 3, url());
 
-    if (entry)
-        entry->ping_status = PING_DONE;
+    // do nothing if ping reply came while handlePingTimeout() was queued
+    if (!entry || entry->ping_status != PING_WAITING)
+        return;
+
+    entry->ping_status = PING_DONE;
 
     if (selectionAborted())
         return;
@@ -752,9 +913,9 @@ PeerSelector::handlePingTimeout()
 }
 
 void
-PeerSelector::HandlePingTimeout(void *data)
+PeerSelector::HandlePingTimeout(PeerSelector *selector)
 {
-    static_cast<PeerSelector*>(data)->handlePingTimeout();
+    selector->handlePingTimeout();
 }
 
 void
@@ -783,6 +944,8 @@ PeerSelector::handleIcpParentMiss(CachePeer *p, icp_common_t *header)
             }
         }
     }
+#else
+    (void)header;
 #endif /* USE_ICMP */
 
     /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
@@ -814,7 +977,7 @@ PeerSelector::handleIcpReply(CachePeer *p, const peer_t type, icp_common_t *head
 
     if (p && request)
         peerNoteDigestLookup(request, p,
-                             peerDigestLookup(p, request, entry));
+                             peerDigestLookup(p, this));
 
 #endif
 
@@ -877,6 +1040,8 @@ PeerSelector::handleHtcpParentMiss(CachePeer *p, HtcpReplyData *htcp)
             }
         }
     }
+#else
+    (void)htcp;
 #endif /* USE_ICMP */
 
     /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
@@ -926,6 +1091,8 @@ PeerSelector::addSelection(CachePeer *peer, const hier_code code)
         // There can be at most one PINNED destination.
         // Non-PINNED destinations are uniquely identified by their CachePeer
         // (even though a DIRECT destination might match a cache_peer address).
+        // TODO: We may still add duplicates because the same peer could have
+        // been removed from `servers` already (and given to the requestor).
         const bool duplicate = (server->code == PINNED) ?
                                (code == PINNED) : (server->_peer == peer);
         if (duplicate) {
@@ -942,17 +1109,17 @@ PeerSelector::addSelection(CachePeer *peer, const hier_code code)
 
 PeerSelector::PeerSelector(PeerSelectionInitiator *initiator):
     request(nullptr),
-    entry (NULL),
+    entry (nullptr),
     always_direct(Config.accessList.AlwaysDirect?ACCESS_DUNNO:ACCESS_DENIED),
     never_direct(Config.accessList.NeverDirect?ACCESS_DUNNO:ACCESS_DENIED),
     direct(DIRECT_UNKNOWN),
-    lastError(NULL),
-    servers (NULL),
+    lastError(nullptr),
+    servers (nullptr),
     first_parent_miss(),
     closest_parent_miss(),
-    hit(NULL),
+    hit(nullptr),
     hit_type(PEER_NONE),
-    acl_checklist (NULL),
+    acl_checklist (nullptr),
     initiator_(initiator)
 {
     ; // no local defaults.
@@ -994,24 +1161,26 @@ PeerSelector::interestedInitiator()
 bool
 PeerSelector::wantsMoreDestinations() const {
     const auto maxCount = Config.forward_max_tries;
-    return maxCount >= 0 && foundPaths <
-           static_cast<std::make_unsigned<decltype(maxCount)>::type>(maxCount);
+    return maxCount >= 0 && foundPaths < static_cast<size_t>(maxCount);
 }
 
 void
-PeerSelector::handlePath(Comm::ConnectionPointer &path, FwdServer &fs)
+PeerSelector::handlePath(const Comm::ConnectionPointer &path, FwdServer &fs)
 {
     ++foundPaths;
 
-    path->peerType = fs.code;
-    path->setPeer(fs._peer.get());
+    if (path) {
+        path->peerType = fs.code;
+        path->setPeer(fs._peer.get());
 
-    // check for a configured outgoing address for this destination...
-    getOutgoingAddress(request, path);
+        // check for a configured outgoing address for this destination...
+        getOutgoingAddress(request, path);
+        debugs(44, 2, id << " found " << path << ", destination #" << foundPaths << " for " << url());
+    } else
+        debugs(44, 2, id << " found pinned, destination #" << foundPaths << " for " << url());
 
     request->hier.ping = ping; // may be updated later
 
-    debugs(44, 2, id << " found " << path << ", destination #" << foundPaths << " for " << url());
     debugs(44, 2, "  always_direct = " << always_direct);
     debugs(44, 2, "   never_direct = " << never_direct);
     debugs(44, 2, "       timedout = " << ping.timedout);
@@ -1029,7 +1198,8 @@ ping_data::ping_data() :
     timeout(0),
     timedout(0),
     w_rtt(0),
-    p_rtt(0)
+    p_rtt(0),
+    monitorRegistration(PingMonitor().npos())
 {
     start.tv_sec = 0;
     start.tv_usec = 0;
@@ -1037,3 +1207,15 @@ ping_data::ping_data() :
     stop.tv_usec = 0;
 }
 
+timeval
+ping_data::deadline() const
+{
+    timeval timeInterval;
+    timeInterval.tv_sec = timeout / 1000;
+    timeInterval.tv_usec = (timeout % 1000) * 1000;
+
+    timeval result;
+    tvAdd(result, start, timeInterval);
+    return result;
+}
+