Add AICurlTimer - timer support for the curl thread.
Not used yet. Deliberately not threadsafe. Usage the same as AIFrameTimer, upon which is was based.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
110
indra/llmessage/aicurltimer.cpp
Normal file
110
indra/llmessage/aicurltimer.cpp
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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, <optional params>)); // Call the_callback(<optional params>) 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);
|
||||
}
|
||||
}
|
||||
|
||||
144
indra/llmessage/aicurltimer.h
Normal file
144
indra/llmessage/aicurltimer.h
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <boost/signals2.hpp>
|
||||
#include <set>
|
||||
|
||||
class AICurlTimer
|
||||
{
|
||||
protected:
|
||||
typedef boost::signals2::signal<void (void)> 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<AIRunningCurlTimer> 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<deltams_type>(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
|
||||
|
||||
Reference in New Issue
Block a user