diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt
index 53e890040..2f9963706 100644
--- a/indra/llmessage/CMakeLists.txt
+++ b/indra/llmessage/CMakeLists.txt
@@ -27,6 +27,7 @@ set(llmessage_SOURCE_FILES
aicurleasyrequeststatemachine.cpp
aicurlperservice.cpp
aicurlthread.cpp
+ aicurltimer.cpp
aihttpheaders.cpp
aihttptimeout.cpp
aihttptimeoutpolicy.cpp
@@ -116,6 +117,7 @@ set(llmessage_HEADER_FILES
aicurlperservice.h
aicurlprivate.h
aicurlthread.h
+ aicurltimer.h
aihttpheaders.h
aihttptimeout.h
aihttptimeoutpolicy.h
diff --git a/indra/llmessage/aicurlthread.cpp b/indra/llmessage/aicurlthread.cpp
index a29859048..70c4d5a37 100644
--- a/indra/llmessage/aicurlthread.cpp
+++ b/indra/llmessage/aicurlthread.cpp
@@ -34,6 +34,7 @@
#include "aihttptimeout.h"
#include "aicurlperservice.h"
#include "aiaverage.h"
+#include "aicurltimer.h"
#include "lltimer.h" // ms_sleep, get_clock_count
#include "llhttpstatuscodes.h"
#include "llbuffer.h"
@@ -1433,21 +1434,35 @@ void AICurlThread::run(void)
#endif
int ready = 0;
struct timeval timeout;
+ // Update AICurlTimer::sTime_1ms.
+ AICurlTimer::sTime_1ms = get_clock_count() * AICurlTimer::sClockWidth_1ms;
+ Dout(dc::curl, "AICurlTimer::sTime_1ms = " << AICurlTimer::sTime_1ms);
+ // Get the time in ms that libcurl wants us to wait for socket actions - at most - before proceeding.
long timeout_ms = multi_handle_w->getTimeout();
- // If no timeout is set, sleep 1 second.
+ // Set libcurl_timeout iff the shortest timeout is that of libcurl.
+ bool libcurl_timeout = timeout_ms == 0 || (timeout_ms > 0 && !AICurlTimer::expiresBefore(timeout_ms));
+ // If no curl timeout is set, sleep at most 4 seconds.
if (LL_UNLIKELY(timeout_ms < 0))
- timeout_ms = 1000;
- if (LL_UNLIKELY(timeout_ms == 0))
+ timeout_ms = 4000;
+ // Check if some AICurlTimer expires first.
+ if (AICurlTimer::expiresBefore(timeout_ms))
{
- if (mZeroTimeout >= 10000)
+ timeout_ms = AICurlTimer::nextExpiration();
+ }
+ // If we have to continue immediately, then just set a zero timeout, but only for 100 calls on a row;
+ // after that start sleeping 1ms and later even 10ms (this should never happen).
+ if (LL_UNLIKELY(timeout_ms <= 0))
+ {
+ if (mZeroTimeout >= 1000)
{
- if (mZeroTimeout == 10000)
- llwarns << "Detected more than 10000 zero-timeout calls of select() by curl thread (more than 101 seconds)!" << llendl;
- }
- else if (mZeroTimeout >= 1000)
+ if (mZeroTimeout % 10000 == 0)
+ llwarns << "Detected " << mZeroTimeout << " zero-timeout calls of select() by curl thread (more than 101 seconds)!" << llendl;
timeout_ms = 10;
+ }
else if (mZeroTimeout >= 100)
timeout_ms = 1;
+ else
+ timeout_ms = 0;
}
else
{
@@ -1554,12 +1569,28 @@ void AICurlThread::run(void)
}
continue;
}
- // Clock count used for timeouts.
- HTTPTimeout::sTime_10ms = get_clock_count() * HTTPTimeout::sClockWidth_10ms;
- Dout(dc::curl, "HTTPTimeout::sTime_10ms = " << HTTPTimeout::sTime_10ms);
+ // Update the clocks.
+ AICurlTimer::sTime_1ms = get_clock_count() * AICurlTimer::sClockWidth_1ms;
+ Dout(dc::curl, "AICurlTimer::sTime_1ms = " << AICurlTimer::sTime_1ms);
+ HTTPTimeout::sTime_10ms = AICurlTimer::sTime_1ms / 10;
if (ready == 0)
{
- multi_handle_w->socket_action(CURL_SOCKET_TIMEOUT, 0);
+ if (libcurl_timeout)
+ {
+ multi_handle_w->socket_action(CURL_SOCKET_TIMEOUT, 0);
+ }
+ else
+ {
+ // Update MultiHandle::mTimeout because next loop we need to sleep timeout_ms shorter.
+ multi_handle_w->update_timeout(timeout_ms);
+ Dout(dc::curl, "MultiHandle::mTimeout set to " << multi_handle_w->getTimeout() << " ms.");
+ }
+ // Handle timers.
+ if (AICurlTimer::expiresBefore(1))
+ {
+ AICurlTimer::handleExpiration();
+ }
+ // Handle stalling transactions.
multi_handle_w->handle_stalls();
}
else
diff --git a/indra/llmessage/aicurlthread.h b/indra/llmessage/aicurlthread.h
index 080456652..908456b13 100644
--- a/indra/llmessage/aicurlthread.h
+++ b/indra/llmessage/aicurlthread.h
@@ -93,6 +93,9 @@ class MultiHandle : public CurlMultiHandle
// Returns how long to wait for socket action before calling socket_action(CURL_SOCKET_TIMEOUT, 0), in ms.
int getTimeout(void) const { return mTimeout; }
+ // We slept delta_ms instead of mTimeout ms. Update mTimeout to be the remaining time.
+ void update_timeout(long delta_ms) { mTimeout -= delta_ms; }
+
// This is called before sleeping, after calling (one or more times) socket_action.
void check_msg_queue(void);
diff --git a/indra/llmessage/aicurltimer.cpp b/indra/llmessage/aicurltimer.cpp
new file mode 100644
index 000000000..7c2e3dc51
--- /dev/null
+++ b/indra/llmessage/aicurltimer.cpp
@@ -0,0 +1,110 @@
+/**
+ * @file aicurltimer.cpp
+ *
+ * Copyright (c) 2013, Aleric Inglewood.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution.
+ *
+ * CHANGELOG
+ * and additional copyright holders.
+ *
+ * 29/06/2013
+ * - Initial version, written by Aleric Inglewood @ SL
+ */
+
+// An AICurlTimer object provides a callback API for timer events
+// by and from the curl thread. It is NOT threadsafe. Everything
+// has to be called from the curl thread.
+//
+// Typical usage:
+//
+// AICurlTimer timer;
+// ...
+// // In curl thread (after successful construction is guaranteed).
+// timer.create(5.5, boost::bind(&the_callback, )); // Call the_callback() in 5.5 seconds.
+//
+// The callback function is always called by the curl thread.
+//
+// If timer.cancel() is called before the timer expires, then the callback
+// function isn't called. Calling cancel() multiple times is not a problem.
+// Note: It is not allowed to call cancel() is from the callback function
+// (and rather pointless).
+//
+// The timer object can be reused (by calling create() again), but
+// only after either the callback function was called, or after cancel()
+// returned. Most notably, you can call create() again from inside the
+// callback function to "restart" the timer.
+//
+
+#include "aicurltimer.h"
+#include "lltimer.h"
+
+static U64 const NEVER = ((U64)1) << 60; // The year 36,560,871.
+
+//static
+F64 const AICurlTimer::sClockWidth_1ms = 1000.0 / calc_clock_frequency(); // Time between two clock ticks, in 1ms units.
+U64 AICurlTimer::sTime_1ms; // Time in 1ms units, set once per select() entry.
+U64 AICurlTimer::sNextExpiration;
+AICurlTimer::timer_list_type AICurlTimer::sTimerList;
+
+void AICurlTimer::create(deltams_type expiration, signal_type::slot_type const& slot)
+{
+ AIRunningCurlTimer new_timer(expiration, this);
+ llassert(mHandle.mRunningTimer == sTimerList.end()); // Create may only be called when the timer isn't already running.
+ mHandle.init(sTimerList.insert(new_timer), slot);
+ sNextExpiration = sTimerList.begin()->expiration();
+}
+
+void AICurlTimer::cancel(void)
+{
+ if (mHandle.mRunningTimer != sTimerList.end())
+ {
+ sTimerList.erase(mHandle.mRunningTimer);
+ mHandle.mRunningTimer = sTimerList.end();
+ sNextExpiration = sTimerList.empty() ? NEVER : sTimerList.begin()->expiration();
+ }
+}
+
+void AICurlTimer::handleExpiration(void)
+{
+ for(;;)
+ {
+ if (sTimerList.empty())
+ {
+ // No running timers left.
+ sNextExpiration = NEVER;
+ break;
+ }
+ timer_list_type::iterator running_timer = sTimerList.begin();
+ sNextExpiration = running_timer->expiration();
+ if (sNextExpiration > sTime_1ms)
+ {
+ // Didn't expire yet.
+ break;
+ }
+
+ Handle& handle(running_timer->getTimer()->mHandle);
+ llassert_always(running_timer == handle.mRunningTimer);
+ handle.mRunningTimer = sTimerList.end();
+ running_timer->do_callback(); // May not throw exceptions.
+
+ // Erase the timer from the running list.
+ sTimerList.erase(running_timer);
+ }
+}
+
diff --git a/indra/llmessage/aicurltimer.h b/indra/llmessage/aicurltimer.h
new file mode 100644
index 000000000..8b30a9ac6
--- /dev/null
+++ b/indra/llmessage/aicurltimer.h
@@ -0,0 +1,144 @@
+/**
+ * @file aicurltimer.h
+ * @brief Implementation of AICurlTimer.
+ *
+ * Copyright (c) 2013, Aleric Inglewood.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution.
+ *
+ * CHANGELOG
+ * and additional copyright holders.
+ *
+ * 29/06/2013
+ * Initial version, written by Aleric Inglewood @ SL
+ */
+
+#ifndef AICURLTIMER_H
+#define AICURLTIMER_H
+
+#include "llerror.h" // llassert
+#include "stdtypes.h" // U64, F64
+#include
+#include
+
+class AICurlTimer
+{
+ protected:
+ typedef boost::signals2::signal signal_type;
+ typedef long deltams_type;
+
+ private:
+ // Use separate struct for this object because it is non-copyable.
+ struct Signal {
+ signal_type mSignal;
+ };
+
+ class AIRunningCurlTimer {
+ private:
+ U64 mExpire; // Time at which the timer expires, in miliseconds since the epoch (compared to LLCurlTimer::sTime_1ms).
+ AICurlTimer* mTimer; // The actual timer.
+ // Can be mutable, since only the mExpire is used for ordering this object in the multiset AICurlTimer::sTimerList.
+ mutable Signal* mCallback; // Pointer to callback struct, or NULL when the object wasn't added to sTimerList yet.
+
+ public:
+ AIRunningCurlTimer(deltams_type expiration, AICurlTimer* timer) : mExpire(AICurlTimer::sTime_1ms + expiration), mTimer(timer), mCallback(NULL) { }
+ ~AIRunningCurlTimer() { delete mCallback; }
+
+ // This function is called after the final object was added to sTimerList (where it is initialized in-place).
+ void init(signal_type::slot_type const& slot) const
+ {
+ // We may only call init() once.
+ llassert(!mCallback);
+ mCallback = new Signal;
+ mCallback->mSignal.connect(slot);
+ }
+
+ // Order AICurlTimer::sTimerList so that the timer that expires first is up front.
+ friend bool operator<(AIRunningCurlTimer const& ft1, AIRunningCurlTimer const& ft2) { return ft1.mExpire < ft2.mExpire; }
+
+ void do_callback(void) const { mCallback->mSignal(); }
+ U64 expiration(void) const { return mExpire; }
+ AICurlTimer* getTimer(void) const { return mTimer; }
+
+#if LL_DEBUG
+ // May not copy this object after it was initialized.
+ AIRunningCurlTimer(AIRunningCurlTimer const& running_curl_timer) :
+ mExpire(running_curl_timer.mExpire), mTimer(running_curl_timer.mTimer), mCallback(running_curl_timer.mCallback)
+ { llassert(!mCallback); }
+#endif
+ };
+
+ typedef std::multiset timer_list_type;
+ static timer_list_type sTimerList; // List with all running timers.
+ static U64 sNextExpiration; // Cache of smallest value in sTimerList.
+
+ public:
+ static F64 const sClockWidth_1ms; // Time between two clock ticks in 1 ms units.
+ static U64 sTime_1ms; // Time since the epoch in 1 ms units. Set by AICurlThread::run().
+
+ static deltams_type nextExpiration(void) { return static_cast(sNextExpiration - sTime_1ms); }
+ static bool expiresBefore(deltams_type timeout_ms) { return sNextExpiration < sTime_1ms + timeout_ms; }
+
+ private:
+ class Handle {
+ public:
+ timer_list_type::iterator mRunningTimer; // Points to the running timer, or to sTimerList.end() when not running.
+ // Access to this iterator is protected by the AICurlTimer::sMutex!
+
+ // Constructor for a not-running timer.
+ Handle(void) : mRunningTimer(sTimerList.end()) { }
+
+ // Actual initialization used by AICurlTimer::create.
+ void init(timer_list_type::iterator const& running_timer, signal_type::slot_type const& slot)
+ {
+ mRunningTimer = running_timer;
+ mRunningTimer->init(slot);
+ }
+
+ private:
+ // No assignment operator.
+ Handle& operator=(Handle const&) { return *this; }
+ };
+
+ Handle mHandle;
+
+ public:
+ // Construct an AICurlTimer that is not running.
+ AICurlTimer(void) { }
+
+ // Construction of a running AICurlTimer with expiration time expiration in miliseconds, and callback slot.
+ AICurlTimer(deltams_type expiration, signal_type::slot_type const& slot) { create(expiration, slot); }
+
+ // Destructing the AICurlTimer object terminates the running timer (if still running).
+ // Note that cancel() must have returned BEFORE anything is destructed that would disallow the callback function to be called.
+ // So, if the AICurlTimer is a member of an object whose callback function is called then cancel() has
+ // to be called manually (or from the destructor of THAT object), before that object is destructed.
+ // Cancel may be called multiple times.
+ ~AICurlTimer() { cancel(); }
+
+ void create(deltams_type expiration, signal_type::slot_type const& slot);
+ void cancel(void);
+
+ bool isRunning(void) const { return mHandle.mRunningTimer != sTimerList.end(); }
+
+ public:
+ static void handleExpiration(void);
+};
+
+#endif
+