From 982921e495b4f2f86a656139d0ab7bcd28742067 Mon Sep 17 00:00:00 2001 From: Aleric Inglewood Date: Mon, 1 Jul 2013 15:41:44 +0200 Subject: [PATCH] Add AICurlTimer - timer support for the curl thread. Not used yet. Deliberately not threadsafe. Usage the same as AIFrameTimer, upon which is was based. --- indra/llmessage/CMakeLists.txt | 2 + indra/llmessage/aicurlthread.cpp | 55 +++++++++--- indra/llmessage/aicurlthread.h | 3 + indra/llmessage/aicurltimer.cpp | 110 +++++++++++++++++++++++ indra/llmessage/aicurltimer.h | 144 +++++++++++++++++++++++++++++++ 5 files changed, 302 insertions(+), 12 deletions(-) create mode 100644 indra/llmessage/aicurltimer.cpp create mode 100644 indra/llmessage/aicurltimer.h 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 +