Merge remote-tracking branch 'Aleric/master' into Cupcake

This commit is contained in:
Drake Arconis
2013-03-11 19:55:38 -04:00
47 changed files with 1925 additions and 1178 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
* @file aistatemachine.h * @file aistatemachine.h
* @brief State machine base class * @brief State machine base class
* *
* Copyright (c) 2010, Aleric Inglewood. * Copyright (c) 2010 - 2013, Aleric Inglewood.
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -26,397 +26,290 @@
* *
* 01/03/2010 * 01/03/2010
* Initial version, written by Aleric Inglewood @ SL * Initial version, written by Aleric Inglewood @ SL
*
* 28/02/2013
* Rewritten from scratch to fully support threading.
*/ */
#ifndef AISTATEMACHINE_H #ifndef AISTATEMACHINE_H
#define AISTATEMACHINE_H #define AISTATEMACHINE_H
#include "aithreadsafe.h" #include "aithreadsafe.h"
#include "lltimer.h" #include <llpointer.h>
#include <list>
#include <boost/signals2.hpp> #include <boost/signals2.hpp>
//! class AIStateMachine;
// A AIStateMachine is a base class that allows derived classes to
// go through asynchronous states, while the code still appears to class AIEngine
// be more or less sequential. {
// private:
// These state machine objects can be reused to build more complex struct QueueElementComp;
// objects. class QueueElement {
// private:
// It is important to note that each state has a duality: the object LLPointer<AIStateMachine> mStateMachine;
// can have a state that will cause a corresponding function to be
// called; and often that function will end with changing the state public:
// again, to signal that it was handled. It is easy to confuse the QueueElement(AIStateMachine* statemachine) : mStateMachine(statemachine) { }
// function of a state with the state at the end of the function. friend bool operator==(QueueElement const& e1, QueueElement const& e2) { return e1.mStateMachine == e2.mStateMachine; }
// For example, the state "initialize" could cause the member friend bool operator!=(QueueElement const& e1, QueueElement const& e2) { return e1.mStateMachine != e2.mStateMachine; }
// function 'init()' to be called, and at the end one would be friend struct QueueElementComp;
// inclined to set the state to "initialized". However, this is the
// wrong approach: the correct use of state names does reflect the AIStateMachine const& statemachine(void) const { return *mStateMachine; }
// functions that will be called, never the function that just was AIStateMachine& statemachine(void) { return *mStateMachine; }
// called. };
// struct QueueElementComp {
// Each (derived) class goes through a series of states as follows: inline bool operator()(QueueElement const& e1, QueueElement const& e2) const;
// };
// Creation
// | public:
// v typedef std::list<QueueElement> queued_type;
// (idle) <----. Idle until run() is called. struct engine_state_type {
// | | queued_type list;
// Initialize | Calls initialize_impl(). bool waiting;
// | | engine_state_type(void) : waiting(false) { }
// | (idle) | Idle until cont() or set_state() is called. };
// | | ^ |
// v v | | private:
// .-------. | | AIThreadSafeSimpleDC<engine_state_type, LLCondition> mEngineState;
// | Run |_, | Call multiplex_impl() until idle(), abort() or finish() is called. typedef AIAccessConst<engine_state_type, LLCondition> engine_state_type_crat;
// '-------' | typedef AIAccess<engine_state_type, LLCondition> engine_state_type_rat;
// | | | typedef AIAccess<engine_state_type, LLCondition> engine_state_type_wat;
// v | | char const* mName;
// Abort | | Calls abort_impl().
// | | | static U64 sMaxCount;
// v v |
// Finish | Calls finish_impl() (which may call kill()) or public:
// | | | the callback function passed to run(), if any, AIEngine(char const* name) : mName(name) { }
// | v |
// | Callback | which may call kill() and/or run(). void add(AIStateMachine* state_machine);
// | | | |
// | | `-----' void mainloop(void);
// v v void threadloop(void);
// Killed Delete the statemachine (all statemachines must be allocated with new). void wake_up(void);
// void flush(void);
// Each state causes corresponding code to be called.
// Finish cleans up whatever is done by Initialize. char const* name(void) const { return mName; }
// Abort should clean up additional things done by Run.
// static void setMaxCount(F32 StateMachineMaxTime);
// The Run state is entered by calling run(). };
//
// While the base class is in the Run state, it is the derived class extern AIEngine gMainThreadEngine;
// that goes through different states. The state variable of the derived extern AIEngine gStateMachineThreadEngine;
// class is only valid while the base class is in the state Run.
// class AIStateMachine : public LLThreadSafeRefCount
// A derived class can exit the Run state by calling one of two methods: {
// abort() in case of failure, or finish() in case of success. public:
// Respectively these set the state to Abort and Finish. typedef U32 state_type; //!< The type of run_state
//
// finish_impl may call kill() for a (default) destruction upon finish. protected:
// Even in that case the callback (passed to run()) may call run() again, // The type of event that causes multiplex() to be called.
// which overrides the request for a default kill. Or, if finish_impl enum event_type {
// doesn't call kill() the callback may call kill() to request the initial_run,
// destruction of the state machine object. schedule_run,
// normal_run,
// State machines are run from the "idle" part of the viewer main loop. insert_abort
// Often a state machine has nothing to do however. In that case it can };
// call the method idle(). This will stop the state machine until // The type of mState
// external code changes it's state (by calling set_state()), or calls
// cont() to continue with the last state.
//
// The methods of the derived class call set_state() to change their
// own state within the bs_run state, or by calling either abort()
// or finish().
//
// Restarting a finished state machine can also be done by calling run(),
// which will cause a re-initialize.
//
// Derived classes should implement the following constants:
//
// static state_type const min_state = first_state;
// static state_type const max_state = last_state + 1;
//
// Where first_state should be equal to BaseClass::max_state.
// These should represent the minimum and (one past) the maximum
// values of mRunState.
//
// virtual void initialize_impl(void)
//
// Initializes the derived class.
//
// virtual void multiplex_impl(void);
//
// This method should handle mRunState in a switch.
// For example:
//
// switch(mRunState)
// {
// case foo:
// handle_foo();
// break;
// case wait_state:
// if (still_waiting())
// {
// idle();
// break;
// }
// set_state(working);
// /*fall-through*/
// case working:
// do_work();
// if (failure())
// abort();
// break;
// case etc:
// etc();
// finish();
// break;
// }
//
// virtual void abort_impl(void);
//
// A call to this method should bring the object to a state
// where finish_impl() can be called.
//
// virtual void finish_impl(void);
//
// Should cleanup whatever init_impl() did, or any of the
// states of the object where multiplex_impl() calls finish().
// Call kill() from here to make that the default behavior
// (state machine is deleted unless the callback calls run()).
//
// virtual char const* state_str_impl(state_type run_state);
//
// Should return a stringified value of run_state.
//
class AIStateMachine {
//! The type of mState
enum base_state_type { enum base_state_type {
bs_initialize, bs_reset, // Idle state before run() is called. Reference count is zero (except for a possible external LLPointer).
bs_run, bs_initialize, // State after run() and before/during initialize_impl().
bs_multiplex, // State after initialize_impl() before finish() or abort().
bs_abort, bs_abort,
bs_finish, bs_finish,
bs_callback, bs_callback,
bs_killed bs_killed
}; };
//! The type of mActive
enum active_type {
as_idle, // State machine is on neither list.
as_queued, // State machine is on continued_statemachines list.
as_active // State machine is on active_statemachines list.
};
//! Type of continued_statemachines.
typedef std::vector<AIStateMachine*> continued_statemachines_type;
//! Type of sContinuedStateMachinesAndMainloopEnabled.
struct csme_type
{
continued_statemachines_type continued_statemachines;
bool mainloop_enabled;
};
public: public:
typedef U32 state_type; //!< The type of mRunState
//! Integral value equal to the state with the lowest value.
static state_type const min_state = bs_initialize;
//! Integral value one more than the state with the highest value.
static state_type const max_state = bs_killed + 1; static state_type const max_state = bs_killed + 1;
protected:
struct multiplex_state_type {
base_state_type base_state;
AIEngine* current_engine; // Current engine.
multiplex_state_type(void) : base_state(bs_reset), current_engine(NULL) { }
};
struct sub_state_type {
state_type run_state;
state_type advance_state;
bool reset;
bool need_run;
bool idle;
bool skip_idle;
bool aborted;
bool finished;
};
private: private:
base_state_type mState; //!< State of the base class. // Base state.
bool mIdle; //!< True if this state machine is not running. AIThreadSafeSimpleDC<multiplex_state_type> mState;
bool mAborted; //!< True after calling abort() and before calling run(). typedef AIAccessConst<multiplex_state_type> multiplex_state_type_crat;
active_type mActive; //!< Whether statemachine is idle, queued to be added to the active list, or already on the active list. typedef AIAccess<multiplex_state_type> multiplex_state_type_rat;
S64 mSleep; //!< Non-zero while the state machine is sleeping. typedef AIAccess<multiplex_state_type> multiplex_state_type_wat;
LLMutex mIdleActive; //!< Used for atomic operations on the pair mIdle / mActive. // Sub state.
#ifdef SHOW_ASSERT AIThreadSafeSimpleDC<sub_state_type> mSubState;
AIThreadID mContThread; //!< Thread that last called locked_cont(). typedef AIAccessConst<sub_state_type> sub_state_type_crat;
bool mCalledThreadUnsafeIdle; //!< Set to true when idle() is called. typedef AIAccess<sub_state_type> sub_state_type_rat;
#endif typedef AIAccess<sub_state_type> sub_state_type_wat;
// Mutex protecting everything below and making sure only one thread runs the state machine at a time.
LLMutex mMultiplexMutex;
// Mutex that is locked while calling *_impl() functions and the call back.
LLMutex mRunMutex;
S64 mSleep; //!< Non-zero while the state machine is sleeping.
// Callback facilities. // Callback facilities.
// From within an other state machine: // From within an other state machine:
AIStateMachine* mParent; //!< The parent object that started this state machine, or NULL if there isn't any. LLPointer<AIStateMachine> mParent; // The parent object that started this state machine, or NULL if there isn't any.
state_type mNewParentState; //!< The state at which the parent should continue upon a successful finish. state_type mNewParentState; // The state at which the parent should continue upon a successful finish.
bool mAbortParent; //!< If true, abort parent on abort(). Otherwise continue as normal. bool mAbortParent; // If true, abort parent on abort(). Otherwise continue as normal.
bool mOnAbortSignalParent; //!< If true and mAbortParent is false, change state of parent even on abort. bool mOnAbortSignalParent; // If true and mAbortParent is false, change state of parent even on abort.
// From outside a state machine: // From outside a state machine:
struct callback_type { struct callback_type {
typedef boost::signals2::signal<void (bool)> signal_type; typedef boost::signals2::signal<void (bool)> signal_type;
callback_type(signal_type::slot_type const& slot) { connection = signal.connect(slot); } callback_type(signal_type::slot_type const& slot) { connection = signal.connect(slot); }
~callback_type() { connection.disconnect(); } ~callback_type() { connection.disconnect(); }
void callback(bool success) const { signal(success); } void callback(bool success) const { signal(success); }
private: private:
boost::signals2::connection connection; boost::signals2::connection connection;
signal_type signal; signal_type signal;
}; };
callback_type* mCallback; //!< Pointer to signal/connection, or NULL when not connected. callback_type* mCallback; // Pointer to signal/connection, or NULL when not connected.
static U64 sMaxCount; //!< Number of cpu clocks below which we start a new state machine within the same frame. // Engine stuff.
static AIThreadSafeDC<csme_type> sContinuedStateMachinesAndMainloopEnabled; //!< Read/write locked variable pair. AIEngine* mDefaultEngine; // Default engine.
AIEngine* mYieldEngine; // Requested engine.
protected:
LLMutex mSetStateLock; //!< For critical areas in set_state() and locked_cont().
//! State of the derived class. Only valid if mState == bs_run. Call set_state to change.
volatile state_type mRunState;
public:
//! Create a non-running state machine.
AIStateMachine(void) : mState(bs_initialize), mIdle(true), mAborted(true), mActive(as_idle), mSleep(0), mParent(NULL), mCallback(NULL)
#ifdef SHOW_ASSERT #ifdef SHOW_ASSERT
, mContThread(AIThreadID::none), mCalledThreadUnsafeIdle(false) // Debug stuff.
AIThreadID mThreadId; // The thread currently running multiplex().
base_state_type mDebugLastState; // The previous state that multiplex() had a normal run with.
bool mDebugShouldRun; // Set if we found evidence that we should indeed call multiplex_impl().
bool mDebugAborted; // True when abort() was called.
bool mDebugContPending; // True while cont() was called by not handled yet.
bool mDebugSetStatePending; // True while set_state() was called by not handled yet.
bool mDebugAdvanceStatePending; // True while advance_state() was called by not handled yet.
bool mDebugRefCalled; // True when ref() is called (or will be called within the critial area of mMultiplexMutex).
#endif #endif
{ } U64 mRuntime; // Total time spent running in the main thread (in clocks).
public:
AIStateMachine(void) : mCallback(NULL), mDefaultEngine(NULL), mYieldEngine(NULL),
#ifdef SHOW_ASSERT
mThreadId(AIThreadID::none), mDebugLastState(bs_killed), mDebugShouldRun(false), mDebugAborted(false), mDebugContPending(false),
mDebugSetStatePending(false), mDebugAdvanceStatePending(false), mDebugRefCalled(false),
#endif
mRuntime(0)
{ }
protected: protected:
//! The user should call 'kill()', not delete a AIStateMachine (derived) directly. // The user should call finish() (or abort(), or kill() from the call back when finish_impl() calls run()), not delete a class derived from AIStateMachine directly.
virtual ~AIStateMachine() { llassert((mState == bs_killed && mActive == as_idle) || mState == bs_initialize); } virtual ~AIStateMachine() { llassert(multiplex_state_type_rat(mState)->base_state == bs_killed); }
public: public:
//! Halt the state machine until cont() is called (not thread-safe). // These functions may be called directly after creation, or from within finish_impl(), or from the call back function.
void idle(void); void run(LLPointer<AIStateMachine> parent, state_type new_parent_state, bool abort_parent = true, bool on_abort_signal_parent = true, AIEngine* default_engine = &gMainThreadEngine);
void run(callback_type::signal_type::slot_type const& slot, AIEngine* default_engine = &gMainThreadEngine);
void run(void) { run(NULL, 0, false, true, mDefaultEngine); }
//! Halt the state machine until cont() is called, provided it is still in 'current_run_state'. // This function may only be called from the call back function (and cancels a call to run() from finish_impl()).
void idle(state_type current_run_state);
//! Temporarily halt the state machine.
void yield_frame(unsigned int frames) { mSleep = -(S64)frames; }
//! Temporarily halt the state machine.
void yield_ms(unsigned int ms) { mSleep = get_clock_count() + calc_clock_frequency() * ms / 1000; }
//! Continue running after calling idle.
void cont(void)
{
mSetStateLock.lock();
// Ignore calls to cont() if the statemachine isn't idle. See comments in set_state().
// Calling cont() twice or after calling set_state(), without first calling idle(), is an error.
if (mState != bs_run || !mIdle) { llassert(mState != bs_run || !mContThread.equals_current_thread()); mSetStateLock.unlock(); return; }
locked_cont();
}
private:
void locked_cont(void);
public:
//---------------------------------------
// Changing the state.
//! Change state to <code>bs_run</code>. May only be called after creation or after returning from finish().
// If <code>parent</code> is non-NULL, change the parent state machine's state to <code>new_parent_state</code>
// upon finish, or in the case of an abort and when <code>abort_parent</code> is true, call parent->abort() instead.
void run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent = true, bool on_abort_signal_parent = true);
//! Change state to 'bs_run'. May only be called after creation or after returning from finish().
// Does not cause a callback.
void run(void) { run(NULL, 0, false); }
//! The same as above, but pass the result of a boost::bind with _1.
//
// Here _1, if present, will be replaced with a bool indicating success.
//
// For example:
//
// <code>
// struct Foo { void callback(AIStateMachineDerived* ptr, bool success); };
// ...
// AIStateMachineDerived* magic = new AIStateMachineDerived; // Deleted by callback
// // Call foo_ptr->callback(magic, _1) on finish.
// state_machine->run(boost::bind(&Foo::callback, foo_ptr, magic, _1));
// </code>
//
// or
//
// <code>
// struct Foo { void callback(bool success, AIStateMachineDerived const& magic); };
// ...
// AIStateMachineDerived magic;
// // Call foo_ptr->callback(_1, magic) on finish.
// magic.run(boost::bind(&Foo::callback, foo_ptr, _1, magic));
// </code>
//
// or
//
// <code>
// static void callback(void* userdata);
// ...
// AIStateMachineDerived magic;
// // Call callback(userdata) on finish.
// magic.run(boost::bind(&callback, userdata));
// </code>
void run(callback_type::signal_type::slot_type const& slot);
//! Change state to 'bs_abort'. May only be called while in the bs_run state.
void abort(void);
//! Change state to 'bs_finish'. May only be called while in the bs_run state.
void finish(void);
//! Refine state while in the bs_run state. May only be called while in the bs_run state.
void set_state(state_type run_state);
//! Change state to 'bs_killed'. May only be called while in the bs_finish state.
void kill(void); void kill(void);
//--------------------------------------- protected:
// Other. // This function can be called from initialize_impl() and multiplex_impl() (both called from within multiplex()).
void set_state(state_type new_state); // Run this state the NEXT loop.
//! Called whenever the StateMachineMaxTime setting is changed. // These functions can only be called from within multiplex_impl().
static void setMaxCount(F32 StateMachineMaxTime); void idle(void); // Go idle unless cont() or advance_state() were called since the start of the current loop, or until they are called.
void finish(void); // Mark that the state machine finished and schedule the call back.
//--------------------------------------- void yield(void); // Yield to give CPU to other state machines, but do not go idle.
// Accessors. void yield(AIEngine* engine); // Yield to give CPU to other state machines, but do not go idle. Continue running from engine 'engine'.
void yield_frame(unsigned int frames); // Run from the main-thread engine after at least 'frames' frames have passed.
//! Return true if state machine was aborted (can be used in finish_impl). void yield_ms(unsigned int ms); // Run from the main-thread engine after roughly 'ms' miliseconds have passed.
bool aborted(void) const { return mAborted; }
//! Return true if the derived class is running (also when we are idle).
bool running(void) const { return mState == bs_run; }
//! Return true if it's safe to call abort.
bool abortable(void) const { return mState == bs_run || mState == bs_initialize; }
//! Return true if the derived class is running but idle.
bool waiting(void) const { return mState == bs_run && mIdle; }
// Use some safebool idiom (http://www.artima.com/cppsource/safebool.html) rather than operator bool.
typedef volatile state_type AIStateMachine::* const bool_type;
//! Return true if state machine successfully finished.
operator bool_type() const { return ((mState == bs_initialize || mState == bs_callback) && !mAborted) ? &AIStateMachine::mRunState : 0; }
//! Return a stringified state, for debugging purposes.
char const* state_str(state_type state);
private:
static void add_continued_statemachines(AIReadAccess<csme_type>& csme_r);
static void dowork(void);
void multiplex(U64 current_time);
public: public:
//! Call this once per frame to give the statemachines CPU cycles. // This function can be called from multiplex_imp(), but also by a child state machine and
static void mainloop(void) // therefore by any thread. The child state machine should use an LLPointer<AIStateMachine>
// to access this state machine.
void abort(void); // Abort the state machine (unsuccessful finish).
// These are the only two functions that can be called by any thread at any moment.
// Those threads should use an LLPointer<AIStateMachine> to access this state machine.
void cont(void); // Guarantee at least one full run of multiplex() after this function is called. Cancels the last call to idle().
void advance_state(state_type new_state); // Guarantee at least one full run of multiplex() after this function is called
// iff new_state is larger than the last state that was processed.
public:
// Accessors.
// Return true if the derived class is running (also when we are idle).
bool running(void) const { return multiplex_state_type_crat(mState)->base_state == bs_multiplex; }
// Return true if the derived class is running and idle.
bool waiting(void) const
{ {
{ multiplex_state_type_crat state_r(mState);
AIReadAccess<csme_type> csme_r(sContinuedStateMachinesAndMainloopEnabled, true); return state_r->base_state == bs_multiplex && sub_state_type_crat(mSubState)->idle;
if (!csme_r->mainloop_enabled) }
return; // Return true if the derived class is running and idle or already being aborted.
if (!csme_r->continued_statemachines.empty()) bool waiting_or_aborting(void) const
add_continued_statemachines(csme_r); {
} multiplex_state_type_crat state_r(mState);
dowork(); return state_r->base_state == bs_abort || ( state_r->base_state == bs_multiplex && sub_state_type_crat(mSubState)->idle);
}
// Return true if are added to the engine.
bool active(AIEngine const* engine) const { return multiplex_state_type_crat(mState)->current_engine == engine; }
bool aborted(void) const { return sub_state_type_crat(mSubState)->aborted; }
// Use some safebool idiom (http://www.artima.com/cppsource/safebool.html) rather than operator bool.
typedef state_type AIStateMachine::* const bool_type;
// Return true if state machine successfully finished.
operator bool_type() const
{
sub_state_type_crat sub_state_r(mSubState);
return (sub_state_r->finished && !sub_state_r->aborted) ? &AIStateMachine::mNewParentState : 0;
} }
//! Abort all running state machines and then run mainloop until all state machines are idle (called when application is exiting). // Return stringified state, for debugging purposes.
static void flush(void); char const* state_str(base_state_type state);
#ifdef CWDEBUG
char const* event_str(event_type event);
#endif
void add(U64 count) { mRuntime += count; }
U64 getRuntime(void) const { return mRuntime; }
protected: protected:
//---------------------------------------
// Derived class implementations.
// Handle initializing the object.
virtual void initialize_impl(void) = 0; virtual void initialize_impl(void) = 0;
virtual void multiplex_impl(state_type run_state) = 0;
// Handle mRunState. virtual void abort_impl(void) { }
virtual void multiplex_impl(void) = 0; virtual void finish_impl(void) { }
// Handle aborting from current bs_run state.
virtual void abort_impl(void) = 0;
// Handle cleaning up from initialization (or post abort) state.
virtual void finish_impl(void) = 0;
// Implemenation of state_str for run states.
virtual char const* state_str_impl(state_type run_state) const = 0; virtual char const* state_str_impl(state_type run_state) const = 0;
private:
void reset(void); // Called from run() to (re)initialize a (re)start.
void multiplex(event_type event); // Called from AIEngine to step through the states (and from reset() to kick start the state machine).
state_type begin_loop(base_state_type base_state); // Called from multiplex() at the start of a loop.
void callback(void); // Called when the state machine finished.
bool sleep(U64 current_time) // Count frames if necessary and return true when the state machine is still sleeping.
{
if (mSleep == 0)
return false;
else if (mSleep < 0)
++mSleep;
else if ((U64)mSleep >= current_time)
mSleep = 0;
return mSleep != 0;
}
friend class AIEngine; // Calls multiplex().
}; };
// This case be used in state_str_impl. bool AIEngine::QueueElementComp::operator()(QueueElement const& e1, QueueElement const& e2) const
{
return e1.mStateMachine->getRuntime() < e2.mStateMachine->getRuntime();
}
// This can be used in state_str_impl.
#define AI_CASE_RETURN(x) do { case x: return #x; } while(0) #define AI_CASE_RETURN(x) do { case x: return #x; } while(0)
#endif #endif

View File

@@ -88,15 +88,15 @@ void AIStateMachineThreadBase::initialize_impl(void)
set_state(start_thread); set_state(start_thread);
} }
void AIStateMachineThreadBase::multiplex_impl(void) void AIStateMachineThreadBase::multiplex_impl(state_type run_state)
{ {
switch(mRunState) switch(run_state)
{ {
case start_thread: case start_thread:
mThread = Thread::allocate(mImpl); mThread = Thread::allocate(mImpl);
// Set next state. // Set next state.
set_state(wait_stopped); set_state(wait_stopped);
idle(wait_stopped); // Wait till the thread returns. idle(); // Wait till the thread returns.
mThread->start(); mThread->start();
break; break;
case wait_stopped: case wait_stopped:
@@ -179,12 +179,8 @@ bool AIThreadImpl::thread_done(bool result)
{ {
// If state_machine_thread is non-NULL then AIThreadImpl::abort_impl wasn't called, // If state_machine_thread is non-NULL then AIThreadImpl::abort_impl wasn't called,
// which means the state machine still exists. In fact, it should be in the waiting() state. // which means the state machine still exists. In fact, it should be in the waiting() state.
// It can also happen that the state machine is being aborted right now (but it will still exist). // It can also happen that the state machine is being aborted right now.
// (Note that waiting() and running() aren't strictly thread-safe (we should really lock llassert(state_machine_thread->waiting_or_aborting());
// mSetStateLock here) but by first calling waiting() and then running(), and assuming that
// changing an int from the value 1 to the value 2 is atomic, this will work since the
// only possible transition is from waiting to not running).
llassert(state_machine_thread->waiting() || !state_machine_thread->running());
state_machine_thread->schedule_abort(!result); state_machine_thread->schedule_abort(!result);
// Note that if the state machine is not running (being aborted, ie - hanging in abort_impl // Note that if the state machine is not running (being aborted, ie - hanging in abort_impl
// waiting for the lock on mStateMachineThread) then this is simply ignored. // waiting for the lock on mStateMachineThread) then this is simply ignored.

View File

@@ -89,19 +89,8 @@ class HelloWorld : public AIStateMachine {
// Handle initializing the object. // Handle initializing the object.
/*virtual*/ void initialize_impl(void); /*virtual*/ void initialize_impl(void);
// Handle mRunState. // Handle run_state.
/*virtual*/ void multiplex_impl(void); /*virtual*/ void multiplex_impl(state_type run_state);
// Handle aborting from current bs_run state.
/*virtual*/ void abort_impl(void) { }
// Handle cleaning up from initialization (or post abort) state.
/*virtual*/ void finish_impl(void)
{
// Kill object by default.
// This can be overridden by calling run() from the callback function.
kill();
}
// Implemenation of state_str for run states. // Implemenation of state_str for run states.
/*virtual*/ char const* state_str_impl(state_type run_state) const /*virtual*/ char const* state_str_impl(state_type run_state) const
@@ -123,9 +112,9 @@ void HelloWorld::initialize_impl(void)
set_state(HelloWorld_start); set_state(HelloWorld_start);
} }
void HelloWorld::multiplex_impl(void) void HelloWorld::multiplex_impl(state_type run_state)
{ {
switch (mRunState) switch (run_state)
{ {
case HelloWorld_start: case HelloWorld_start:
{ {
@@ -177,28 +166,30 @@ class AIStateMachineThreadBase : public AIStateMachine {
// The actual thread (derived from LLThread). // The actual thread (derived from LLThread).
class Thread; class Thread;
protected:
typedef AIStateMachine direct_base_type;
// The states of this state machine. // The states of this state machine.
enum thread_state_type { enum thread_state_type {
start_thread = AIStateMachine::max_state, // Start the thread (if necessary create it first). start_thread = direct_base_type::max_state, // Start the thread (if necessary create it first).
wait_stopped // Wait till the thread is stopped. wait_stopped // Wait till the thread is stopped.
}; };
public:
static state_type const max_state = wait_stopped + 1;
protected: protected:
AIStateMachineThreadBase(AIThreadImpl* impl) : mImpl(impl) { } AIStateMachineThreadBase(AIThreadImpl* impl) : mImpl(impl) { ref(); /* Never call delete */ }
private: private:
// Handle initializing the object. // Handle initializing the object.
/*virtual*/ void initialize_impl(void); /*virtual*/ void initialize_impl(void);
// Handle mRunState. // Handle mRunState.
/*virtual*/ void multiplex_impl(void); /*virtual*/ void multiplex_impl(state_type run_state);
// Handle aborting from current bs_run state. // Handle aborting from current bs_run state.
/*virtual*/ void abort_impl(void); /*virtual*/ void abort_impl(void);
// Handle cleaning up from initialization (or post abort) state.
/*virtual*/ void finish_impl(void) { }
// Implemenation of state_str for run states. // Implemenation of state_str for run states.
/*virtual*/ char const* state_str_impl(state_type run_state) const; /*virtual*/ char const* state_str_impl(state_type run_state) const;

View File

@@ -31,11 +31,6 @@
#include "linden_common.h" #include "linden_common.h"
#include "aitimer.h" #include "aitimer.h"
enum timer_state_type {
AITimer_start = AIStateMachine::max_state,
AITimer_expired
};
char const* AITimer::state_str_impl(state_type run_state) const char const* AITimer::state_str_impl(state_type run_state) const
{ {
switch(run_state) switch(run_state)
@@ -43,6 +38,7 @@ char const* AITimer::state_str_impl(state_type run_state) const
AI_CASE_RETURN(AITimer_start); AI_CASE_RETURN(AITimer_start);
AI_CASE_RETURN(AITimer_expired); AI_CASE_RETURN(AITimer_expired);
} }
llassert(false);
return "UNKNOWN STATE"; return "UNKNOWN STATE";
} }
@@ -54,12 +50,12 @@ void AITimer::initialize_impl(void)
void AITimer::expired(void) void AITimer::expired(void)
{ {
set_state(AITimer_expired); advance_state(AITimer_expired);
} }
void AITimer::multiplex_impl(void) void AITimer::multiplex_impl(state_type run_state)
{ {
switch (mRunState) switch (run_state)
{ {
case AITimer_start: case AITimer_start:
{ {
@@ -79,18 +75,3 @@ void AITimer::abort_impl(void)
{ {
mFrameTimer.cancel(); mFrameTimer.cancel();
} }
void AITimer::finish_impl(void)
{
// Kill object by default.
// This can be overridden by calling run() from the callback function.
kill();
}
void AIPersistentTimer::finish_impl(void)
{
// Don't kill object by default.
if (aborted())
kill();
// Callback function should always call kill() or run().
}

View File

@@ -59,6 +59,18 @@
// just reuse the old ones (call the same callback). // just reuse the old ones (call the same callback).
// //
class AITimer : public AIStateMachine { class AITimer : public AIStateMachine {
protected:
// The base class of this state machine.
typedef AIStateMachine direct_base_type;
// The different states of the state machine.
enum timer_state_type {
AITimer_start = direct_base_type::max_state,
AITimer_expired
};
public:
static state_type const max_state = AITimer_expired + 1;
private: private:
AIFrameTimer mFrameTimer; //!< The actual timer that this object wraps. AIFrameTimer mFrameTimer; //!< The actual timer that this object wraps.
F64 mInterval; //!< Input variable: interval after which the event will be generated, in seconds. F64 mInterval; //!< Input variable: interval after which the event will be generated, in seconds.
@@ -90,14 +102,11 @@ class AITimer : public AIStateMachine {
/*virtual*/ void initialize_impl(void); /*virtual*/ void initialize_impl(void);
// Handle mRunState. // Handle mRunState.
/*virtual*/ void multiplex_impl(void); /*virtual*/ void multiplex_impl(state_type run_state);
// Handle aborting from current bs_run state. // Handle aborting from current bs_run state.
/*virtual*/ void abort_impl(void); /*virtual*/ void abort_impl(void);
// Handle cleaning up from initialization (or post abort) state.
/*virtual*/ void finish_impl(void);
// Implemenation of state_str for run states. // Implemenation of state_str for run states.
/*virtual*/ char const* state_str_impl(state_type run_state) const; /*virtual*/ char const* state_str_impl(state_type run_state) const;
@@ -106,12 +115,4 @@ class AITimer : public AIStateMachine {
void expired(void); void expired(void);
}; };
// Same as above but does not delete itself automatically by default after use.
// Call kill() on it yourself (from the callback function) when you're done with it!
class AIPersistentTimer : public AITimer {
protected:
// Handle cleaning up from initialization (or post abort) state.
/*virtual*/ void finish_impl(void);
};
#endif #endif

View File

@@ -51,6 +51,11 @@ void AIThreadID::set_current_thread_id(void)
} }
#ifndef LL_DARWIN #ifndef LL_DARWIN
void AIThreadID::clear(void)
{
mID = undefinedID;
}
void AIThreadID::reset(void) void AIThreadID::reset(void)
{ {
mID = lCurrentThread; mID = lCurrentThread;

View File

@@ -63,6 +63,7 @@ public:
static void set_main_thread_id(void); // Called once to set sMainThreadID. static void set_main_thread_id(void); // Called once to set sMainThreadID.
static void set_current_thread_id(void); // Called once for every thread to set lCurrentThread. static void set_current_thread_id(void); // Called once for every thread to set lCurrentThread.
#ifndef LL_DARWIN #ifndef LL_DARWIN
LL_COMMON_API void clear(void);
LL_COMMON_API void reset(void); LL_COMMON_API void reset(void);
LL_COMMON_API bool equals_current_thread(void) const; LL_COMMON_API bool equals_current_thread(void) const;
LL_COMMON_API static bool in_main_thread(void); LL_COMMON_API static bool in_main_thread(void);
@@ -74,6 +75,7 @@ public:
static apr_os_thread_t getCurrentThread_inline(void) { return lCurrentThread; } static apr_os_thread_t getCurrentThread_inline(void) { return lCurrentThread; }
#else #else
// Both variants are inline on OS X. // Both variants are inline on OS X.
void clear(void) { mID = undefinedID; }
void reset(void) { mID = apr_os_thread_current(); } void reset(void) { mID = apr_os_thread_current(); }
void reset_inline(void) { mID = apr_os_thread_current(); } void reset_inline(void) { mID = apr_os_thread_current(); }
bool equals_current_thread(void) const { return apr_os_thread_equal(mID, apr_os_thread_current()); } bool equals_current_thread(void) const { return apr_os_thread_equal(mID, apr_os_thread_current()); }

View File

@@ -479,9 +479,6 @@ public:
// Only for use by AITHREADSAFESIMPLE, see below. // Only for use by AITHREADSAFESIMPLE, see below.
AIThreadSafeSimple(T* object) { llassert(object == AIThreadSafeBits<T>::ptr()); } AIThreadSafeSimple(T* object) { llassert(object == AIThreadSafeBits<T>::ptr()); }
// If MUTEX is a LLCondition then this can be used to wake up the waiting thread.
void signal() { mMutex.signal(); }
#if LL_DEBUG #if LL_DEBUG
// Can only be locked when there still exists an AIAccess object that // Can only be locked when there still exists an AIAccess object that
// references this object and will access it upon destruction. // references this object and will access it upon destruction.
@@ -622,6 +619,8 @@ struct AIAccessConst
// If MUTEX is an LLCondition, then this can be used to wait for a signal. // If MUTEX is an LLCondition, then this can be used to wait for a signal.
void wait() { this->mWrapper.mMutex.wait(); } void wait() { this->mWrapper.mMutex.wait(); }
// If MUTEX is a LLCondition then this can be used to wake up the waiting thread.
void signal() { this->mWrapper.mMutex.signal(); }
protected: protected:
AIThreadSafeSimple<T, MUTEX>& mWrapper; //!< Reference to the object that we provide access to. AIThreadSafeSimple<T, MUTEX>& mWrapper; //!< Reference to the object that we provide access to.

View File

@@ -90,6 +90,16 @@ const S32 HTTP_VERSION_NOT_SUPPORTED = 505;
// These status codes should not be sent over the wire // These status codes should not be sent over the wire
// and indicate something went wrong internally. // and indicate something went wrong internally.
// If you get these they are not normal. // If you get these they are not normal.
const S32 HTTP_INTERNAL_ERROR = 499; // Note that these are only related to curl, not to webkit.
const S32 HTTP_INTERNAL_ERROR_LOW_SPEED = 494; // The transfer (receiving data) stalled or was too slow.
const S32 HTTP_INTERNAL_ERROR_CURL_LOCKUP = 495; // Curl never returned at all for 10 minutes!?!
const S32 HTTP_INTERNAL_ERROR_CURL_BADSOCKET = 496; // Curl was aborted because the socket went bad!?!
const S32 HTTP_INTERNAL_ERROR_CURL_TIMEOUT = 497; // Curl returned a timeout error.
const S32 HTTP_INTERNAL_ERROR_CURL_OTHER = 498; // Any other curl error.
const S32 HTTP_INTERNAL_ERROR_OTHER = 499; // Every other internal error.
// Return true if status is an internal error (not received from a server but generated internally).
bool inline is_internal_http_error(S32 status) { return status >= HTTP_INTERNAL_ERROR_LOW_SPEED && status <= HTTP_INTERNAL_ERROR_OTHER; }
bool inline is_internal_http_error_that_warrants_a_retry(S32 status) { return status >= HTTP_INTERNAL_ERROR_LOW_SPEED && status <= HTTP_INTERNAL_ERROR_CURL_OTHER; }
#endif #endif

View File

@@ -470,6 +470,7 @@ public:
void unref() void unref()
{ {
llassert(mRef > 0);
if (!--mRef) delete this; if (!--mRef) delete this;
} }
S32 getNumRefs() const S32 getNumRefs() const

View File

@@ -49,6 +49,7 @@
#include "llhttpclient.h" #include "llhttpclient.h"
#include "llsdserialize.h" #include "llsdserialize.h"
#include "llcurl.h" #include "llcurl.h"
#include "aistatemachine.h"
LLPumpIO* gServicePump; LLPumpIO* gServicePump;
BOOL gBreak = false; BOOL gBreak = false;
@@ -56,6 +57,7 @@ BOOL gSent = false;
class AIHTTPTimeoutPolicy; class AIHTTPTimeoutPolicy;
extern AIHTTPTimeoutPolicy crashLoggerResponder_timeout; extern AIHTTPTimeoutPolicy crashLoggerResponder_timeout;
extern void startEngineThread(void);
class LLCrashLoggerResponder : public LLHTTPClient::ResponderWithResult class LLCrashLoggerResponder : public LLHTTPClient::ResponderWithResult
{ {
@@ -374,7 +376,7 @@ void LLCrashLogger::updateApplication(const std::string& message)
{ {
gServicePump->pump(); gServicePump->pump();
gServicePump->callback(); gServicePump->callback();
//FIXME: AIStateMachine::mainloop(); needs CPU cycles. Can't call it from here though, because it uses gSavedSettings which is part of newview. gMainThreadEngine.mainloop();
} }
bool LLCrashLogger::init() bool LLCrashLogger::init()
@@ -382,6 +384,17 @@ bool LLCrashLogger::init()
// Initialize curl // Initialize curl
AICurlInterface::initCurl(); AICurlInterface::initCurl();
// Initialize state machine engines.
AIEngine::setMaxCount(100); // StateMachineMaxTime
// Start state machine thread.
startEngineThread();
// Start curl thread.
AICurlInterface::startCurlThread(64, // CurlMaxTotalConcurrentConnections
8, // CurlConcurrentConnectionsPerHost
true); // NoVerifySSLCert
// We assume that all the logs we're looking for reside on the current drive // We assume that all the logs we're looking for reside on the current drive
gDirUtilp->initAppDirs("SecondLife"); gDirUtilp->initAppDirs("SecondLife");

View File

@@ -418,7 +418,8 @@ void cleanupCurl(void)
stopCurlThread(); stopCurlThread();
if (CurlMultiHandle::getTotalMultiHandles() != 0) if (CurlMultiHandle::getTotalMultiHandles() != 0)
llwarns << "Not all CurlMultiHandle objects were destroyed!" << llendl; llwarns << "Not all CurlMultiHandle objects were destroyed!" << llendl;
AIStateMachine::flush(); gMainThreadEngine.flush(); // Not really related to curl, but why not.
gStateMachineThreadEngine.flush();
clearCommandQueue(); clearCommandQueue();
Stats::print(); Stats::print();
ssl_cleanup(); ssl_cleanup();
@@ -770,6 +771,22 @@ void CurlEasyRequest::setoptString(CURLoption option, std::string const& value)
setopt(option, value.c_str()); setopt(option, value.c_str());
} }
void CurlEasyRequest::setPut(U32 size, bool keepalive)
{
DoutCurl("PUT size is " << size << " bytes.");
mContentLength = size;
// The server never replies with 100-continue, so suppress the "Expect: 100-continue" header that libcurl adds by default.
addHeader("Expect:");
if (size > 0 && keepalive)
{
addHeader("Connection: keep-alive");
addHeader("Keep-alive: 300");
}
setopt(CURLOPT_UPLOAD, 1);
setopt(CURLOPT_INFILESIZE, size);
}
void CurlEasyRequest::setPost(AIPostFieldPtr const& postdata, U32 size, bool keepalive) void CurlEasyRequest::setPost(AIPostFieldPtr const& postdata, U32 size, bool keepalive)
{ {
llassert_always(postdata->data()); llassert_always(postdata->data());
@@ -787,6 +804,7 @@ void CurlEasyRequest::setPost_raw(U32 size, char const* data, bool keepalive)
// data == NULL when we're going to read the data using CURLOPT_READFUNCTION. // data == NULL when we're going to read the data using CURLOPT_READFUNCTION.
DoutCurl("POST size is " << size << " bytes."); DoutCurl("POST size is " << size << " bytes.");
} }
mContentLength = size;
// The server never replies with 100-continue, so suppress the "Expect: 100-continue" header that libcurl adds by default. // The server never replies with 100-continue, so suppress the "Expect: 100-continue" header that libcurl adds by default.
addHeader("Expect:"); addHeader("Expect:");
@@ -872,13 +890,13 @@ void CurlEasyRequest::setSSLCtxCallback(curl_ssl_ctx_callback callback, void* us
static size_t noHeaderCallback(char* ptr, size_t size, size_t nmemb, void* userdata) static size_t noHeaderCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
{ {
llmaybewarns << "Calling noHeaderCallback(); curl session aborted." << llendl; llmaybewarns << "Calling noHeaderCallback(); curl session aborted." << llendl;
return 0; // Cause a CURL_WRITE_ERROR return 0; // Cause a CURLE_WRITE_ERROR
} }
static size_t noWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata) static size_t noWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
{ {
llmaybewarns << "Calling noWriteCallback(); curl session aborted." << llendl; llmaybewarns << "Calling noWriteCallback(); curl session aborted." << llendl;
return 0; // Cause a CURL_WRITE_ERROR return 0; // Cause a CURLE_WRITE_ERROR
} }
static size_t noReadCallback(char* ptr, size_t size, size_t nmemb, void* userdata) static size_t noReadCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
@@ -1276,7 +1294,7 @@ static int const HTTP_REDIRECTS_DEFAULT = 10;
LLChannelDescriptors const BufferedCurlEasyRequest::sChannels; LLChannelDescriptors const BufferedCurlEasyRequest::sChannels;
BufferedCurlEasyRequest::BufferedCurlEasyRequest() : mRequestTransferedBytes(0), mResponseTransferedBytes(0), mBufferEventsTarget(NULL), mStatus(HTTP_INTERNAL_ERROR) BufferedCurlEasyRequest::BufferedCurlEasyRequest() : mRequestTransferedBytes(0), mResponseTransferedBytes(0), mBufferEventsTarget(NULL), mStatus(HTTP_INTERNAL_ERROR_OTHER)
{ {
AICurlInterface::Stats::BufferedCurlEasyRequest_count++; AICurlInterface::Stats::BufferedCurlEasyRequest_count++;
} }
@@ -1311,7 +1329,7 @@ BufferedCurlEasyRequest::~BufferedCurlEasyRequest()
void BufferedCurlEasyRequest::timed_out(void) void BufferedCurlEasyRequest::timed_out(void)
{ {
mResponder->finished(CURLE_OK, HTTP_INTERNAL_ERROR, "Request timeout, aborted.", sChannels, mOutput); mResponder->finished(CURLE_OK, HTTP_INTERNAL_ERROR_CURL_LOCKUP, "Request timeout, aborted.", sChannels, mOutput);
if (mResponder->needsHeaders()) if (mResponder->needsHeaders())
{ {
send_buffer_events_to(NULL); // Revoke buffer events: we send them to the responder. send_buffer_events_to(NULL); // Revoke buffer events: we send them to the responder.
@@ -1321,7 +1339,7 @@ void BufferedCurlEasyRequest::timed_out(void)
void BufferedCurlEasyRequest::bad_socket(void) void BufferedCurlEasyRequest::bad_socket(void)
{ {
mResponder->finished(CURLE_OK, HTTP_INTERNAL_ERROR, "File descriptor went bad! Aborted.", sChannels, mOutput); mResponder->finished(CURLE_OK, HTTP_INTERNAL_ERROR_CURL_BADSOCKET, "File descriptor went bad! Aborted.", sChannels, mOutput);
if (mResponder->needsHeaders()) if (mResponder->needsHeaders())
{ {
send_buffer_events_to(NULL); // Revoke buffer events: we send them to the responder. send_buffer_events_to(NULL); // Revoke buffer events: we send them to the responder.
@@ -1342,7 +1360,7 @@ void BufferedCurlEasyRequest::resetState(void)
mRequestTransferedBytes = 0; mRequestTransferedBytes = 0;
mResponseTransferedBytes = 0; mResponseTransferedBytes = 0;
mBufferEventsTarget = NULL; mBufferEventsTarget = NULL;
mStatus = HTTP_INTERNAL_ERROR; mStatus = HTTP_INTERNAL_ERROR_OTHER;
} }
void BufferedCurlEasyRequest::print_diagnostics(CURLcode code) void BufferedCurlEasyRequest::print_diagnostics(CURLcode code)

View File

@@ -52,8 +52,6 @@
#include "stdtypes.h" // U16, S32, U32, F64 #include "stdtypes.h" // U16, S32, U32, F64
#include "llatomic.h" // LLAtomicU32 #include "llatomic.h" // LLAtomicU32
#include "aithreadsafe.h" #include "aithreadsafe.h"
#include "llhttpstatuscodes.h"
#include "llhttpclient.h"
// Debug Settings. // Debug Settings.
extern bool gNoVerifySSLCert; extern bool gNoVerifySSLCert;

View File

@@ -36,10 +36,8 @@
enum curleasyrequeststatemachine_state_type { enum curleasyrequeststatemachine_state_type {
AICurlEasyRequestStateMachine_addRequest = AIStateMachine::max_state, AICurlEasyRequestStateMachine_addRequest = AIStateMachine::max_state,
AICurlEasyRequestStateMachine_waitAdded, AICurlEasyRequestStateMachine_waitAdded,
AICurlEasyRequestStateMachine_added, AICurlEasyRequestStateMachine_timedOut, // This must be smaller than the rest, so they always overrule.
AICurlEasyRequestStateMachine_timedOut, // This must be smaller than the rest, so they always overrule. AICurlEasyRequestStateMachine_removed, // The removed states must be largest two, so they are never ignored.
AICurlEasyRequestStateMachine_finished,
AICurlEasyRequestStateMachine_removed, // The removed states must be largest two, so they are never ignored.
AICurlEasyRequestStateMachine_removed_after_finished, AICurlEasyRequestStateMachine_removed_after_finished,
AICurlEasyRequestStateMachine_bad_file_descriptor AICurlEasyRequestStateMachine_bad_file_descriptor
}; };
@@ -50,9 +48,7 @@ char const* AICurlEasyRequestStateMachine::state_str_impl(state_type run_state)
{ {
AI_CASE_RETURN(AICurlEasyRequestStateMachine_addRequest); AI_CASE_RETURN(AICurlEasyRequestStateMachine_addRequest);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_waitAdded); AI_CASE_RETURN(AICurlEasyRequestStateMachine_waitAdded);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_added);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_timedOut); AI_CASE_RETURN(AICurlEasyRequestStateMachine_timedOut);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_finished);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed); AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed_after_finished); AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed_after_finished);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_bad_file_descriptor); AI_CASE_RETURN(AICurlEasyRequestStateMachine_bad_file_descriptor);
@@ -77,14 +73,12 @@ void AICurlEasyRequestStateMachine::initialize_impl(void)
// CURL-THREAD // CURL-THREAD
void AICurlEasyRequestStateMachine::added_to_multi_handle(AICurlEasyRequest_wat&) void AICurlEasyRequestStateMachine::added_to_multi_handle(AICurlEasyRequest_wat&)
{ {
set_state(AICurlEasyRequestStateMachine_added);
} }
// CURL-THREAD // CURL-THREAD
void AICurlEasyRequestStateMachine::finished(AICurlEasyRequest_wat&) void AICurlEasyRequestStateMachine::finished(AICurlEasyRequest_wat&)
{ {
mFinished = true; mFinished = true;
set_state(AICurlEasyRequestStateMachine_finished);
} }
// CURL-THREAD // CURL-THREAD
@@ -93,7 +87,7 @@ void AICurlEasyRequestStateMachine::removed_from_multi_handle(AICurlEasyRequest_
llassert(mFinished || mTimedOut); // If we neither finished nor timed out, then why is this being removed? llassert(mFinished || mTimedOut); // If we neither finished nor timed out, then why is this being removed?
// Note that allowing this would cause an assertion later on for removing // Note that allowing this would cause an assertion later on for removing
// a BufferedCurlEasyRequest with a still active Responder. // a BufferedCurlEasyRequest with a still active Responder.
set_state(mFinished ? AICurlEasyRequestStateMachine_removed_after_finished : AICurlEasyRequestStateMachine_removed); advance_state(mFinished ? AICurlEasyRequestStateMachine_removed_after_finished : AICurlEasyRequestStateMachine_removed);
} }
// CURL-THREAD // CURL-THREAD
@@ -102,7 +96,7 @@ void AICurlEasyRequestStateMachine::bad_file_descriptor(AICurlEasyRequest_wat&)
if (!mFinished) if (!mFinished)
{ {
mFinished = true; mFinished = true;
set_state(AICurlEasyRequestStateMachine_bad_file_descriptor); advance_state(AICurlEasyRequestStateMachine_bad_file_descriptor);
} }
} }
@@ -114,59 +108,46 @@ void AICurlEasyRequestStateMachine::queued_for_removal(AICurlEasyRequest_wat&)
} }
#endif #endif
void AICurlEasyRequestStateMachine::multiplex_impl(void) void AICurlEasyRequestStateMachine::multiplex_impl(state_type run_state)
{ {
mSetStateLock.lock(); switch (run_state)
state_type current_state = mRunState;
mSetStateLock.unlock();
switch (current_state)
{ {
case AICurlEasyRequestStateMachine_addRequest: case AICurlEasyRequestStateMachine_addRequest:
{ {
set_state(AICurlEasyRequestStateMachine_waitAdded); set_state(AICurlEasyRequestStateMachine_waitAdded);
idle(AICurlEasyRequestStateMachine_waitAdded); // Wait till AICurlEasyRequestStateMachine::added_to_multi_handle() is called. idle(); // Wait till AICurlEasyRequestStateMachine::added_to_multi_handle() is called.
// Only AFTER going idle, add request to curl thread; this is needed because calls to set_state() are // Only AFTER going idle, add request to curl thread; this is needed because calls to set_state() are
// ignored when the statemachine is not idle, and theoretically the callbacks could be called // ignored when the statemachine is not idle, and theoretically the callbacks could be called
// immediately after this call. // immediately after this call.
mAdded = true; mAdded = true;
mCurlEasyRequest.addRequest(); // This causes the state to be changed, now or later, to mCurlEasyRequest.addRequest(); // This causes the state to be changed, now or later, to
// AICurlEasyRequestStateMachine_added, then
// AICurlEasyRequestStateMachine_finished and then
// AICurlEasyRequestStateMachine_removed_after_finished. // AICurlEasyRequestStateMachine_removed_after_finished.
// The first two states might be skipped thus, and the state at this point is one of // The state at this point is thus one of
// 1) AICurlEasyRequestStateMachine_waitAdded (idle) // 1) AICurlEasyRequestStateMachine_waitAdded (idle)
// 2) AICurlEasyRequestStateMachine_added (running) // 2) AICurlEasyRequestStateMachine_removed_after_finished (running)
// 3) AICurlEasyRequestStateMachine_finished (running)
// 4) AICurlEasyRequestStateMachine_removed_after_finished (running)
if (mTotalDelayTimeout > 0.f) if (mTotalDelayTimeout > 0.f)
{ {
// Set an inactivity timer. // Set an inactivity timer.
// This shouldn't really be necessary, except in the case of a bug // This shouldn't really be necessary, except in the case of a bug
// in libcurl; but lets be sure and set a timer for inactivity. // in libcurl; but lets be sure and set a timer for inactivity.
mTimer = new AIPersistentTimer; // Do not delete timer upon expiration. mTimer = new AITimer;
mTimer->setInterval(mTotalDelayTimeout); mTimer->setInterval(mTotalDelayTimeout);
mTimer->run(this, AICurlEasyRequestStateMachine_timedOut, false, false); mTimer->run(this, AICurlEasyRequestStateMachine_timedOut, false, false);
} }
break; break;
} }
case AICurlEasyRequestStateMachine_added: case AICurlEasyRequestStateMachine_waitAdded:
{ {
// The request was added to the multi handle. This is a no-op, which is good cause // Nothing to do.
// this state might be skipped anyway ;). idle();
idle(current_state); // Wait for the next event.
// The state at this point is one of
// 1) AICurlEasyRequestStateMachine_added (idle)
// 2) AICurlEasyRequestStateMachine_finished (running)
// 3) AICurlEasyRequestStateMachine_removed_after_finished (running)
break; break;
} }
case AICurlEasyRequestStateMachine_timedOut: case AICurlEasyRequestStateMachine_timedOut:
{ {
// It is possible that exactly at this point the state changes into // It is possible that exactly at this point the state changes into
// AICurlEasyRequestStateMachine_finished, with as result that mTimedOut // AICurlEasyRequestStateMachine_removed_after_finished, with as result that mTimedOut
// is set while we will continue with that state. Hence that mTimedOut // is set while we will continue with that state. Hence that mTimedOut
// is explicitly reset in that state. // is explicitly reset in that state.
@@ -176,10 +157,9 @@ void AICurlEasyRequestStateMachine::multiplex_impl(void)
llassert(mAdded); llassert(mAdded);
mAdded = false; mAdded = false;
mCurlEasyRequest.removeRequest(); mCurlEasyRequest.removeRequest();
idle(current_state); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called. idle(); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
break; break;
} }
case AICurlEasyRequestStateMachine_finished:
case AICurlEasyRequestStateMachine_removed_after_finished: case AICurlEasyRequestStateMachine_removed_after_finished:
{ {
if (!mHandled) if (!mHandled)
@@ -199,12 +179,6 @@ void AICurlEasyRequestStateMachine::multiplex_impl(void)
easy_request_w->processOutput(); easy_request_w->processOutput();
} }
if (current_state == AICurlEasyRequestStateMachine_finished)
{
idle(current_state); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
break;
}
// See above. // See above.
mTimedOut = false; mTimedOut = false;
/* Fall-Through */ /* Fall-Through */
@@ -261,17 +235,14 @@ void AICurlEasyRequestStateMachine::finish_impl(void)
} }
if (mTimer) if (mTimer)
{ {
// Note that even if the timer expired, it wasn't deleted because we used AIPersistentTimer; so mTimer is still valid.
// Stop the timer, if it's still running. // Stop the timer, if it's still running.
if (!mHandled) if (!mHandled)
mTimer->abort(); mTimer->abort();
} }
// Auto clean up ourselves.
kill();
} }
AICurlEasyRequestStateMachine::AICurlEasyRequestStateMachine(void) : AICurlEasyRequestStateMachine::AICurlEasyRequestStateMachine(void) :
mTimer(NULL), mTotalDelayTimeout(AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout().getTotalDelay()) mTotalDelayTimeout(AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout().getTotalDelay())
{ {
Dout(dc::statemachine, "Calling AICurlEasyRequestStateMachine(void) [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); Dout(dc::statemachine, "Calling AICurlEasyRequestStateMachine(void) [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
AICurlInterface::Stats::AICurlEasyRequestStateMachine_count++; AICurlInterface::Stats::AICurlEasyRequestStateMachine_count++;

View File

@@ -62,7 +62,7 @@ class AICurlEasyRequestStateMachine : public AIStateMachine, public AICurlEasyHa
bool mTimedOut; // Set if the expiration timer timed out. bool mTimedOut; // Set if the expiration timer timed out.
bool mFinished; // Set by the curl thread to signal it finished. bool mFinished; // Set by the curl thread to signal it finished.
bool mHandled; // Set when we processed the received data. bool mHandled; // Set when we processed the received data.
AITimer* mTimer; // Expiration timer. LLPointer<AITimer> mTimer; // Expiration timer.
F32 mTotalDelayTimeout; // The time out value for mTimer. F32 mTotalDelayTimeout; // The time out value for mTimer.
public: public:
@@ -99,7 +99,7 @@ class AICurlEasyRequestStateMachine : public AIStateMachine, public AICurlEasyHa
/*virtual*/ void initialize_impl(void); /*virtual*/ void initialize_impl(void);
// Handle mRunState. // Handle mRunState.
/*virtual*/ void multiplex_impl(void); /*virtual*/ void multiplex_impl(state_type run_state);
// Handle aborting from current bs_run state. // Handle aborting from current bs_run state.
/*virtual*/ void abort_impl(void); /*virtual*/ void abort_impl(void);

View File

@@ -36,9 +36,11 @@
#include "llrefcount.h" #include "llrefcount.h"
#include "aicurlperhost.h" #include "aicurlperhost.h"
#include "aihttptimeout.h" #include "aihttptimeout.h"
#include "llhttpclient.h"
class AIHTTPHeaders; class AIHTTPHeaders;
class AICurlEasyRequestStateMachine; class AICurlEasyRequestStateMachine;
class AITransferInfo;
namespace AICurlPrivate { namespace AICurlPrivate {
@@ -212,6 +214,7 @@ class CurlEasyRequest : public CurlEasyHandle {
private: private:
void setPost_raw(U32 size, char const* data, bool keepalive); void setPost_raw(U32 size, char const* data, bool keepalive);
public: public:
void setPut(U32 size, bool keepalive = true);
void setPost(U32 size, bool keepalive = true) { setPost_raw(size, NULL, keepalive); } void setPost(U32 size, bool keepalive = true) { setPost_raw(size, NULL, keepalive); }
void setPost(AIPostFieldPtr const& postdata, U32 size, bool keepalive = true); void setPost(AIPostFieldPtr const& postdata, U32 size, bool keepalive = true);
void setPost(char const* data, U32 size, bool keepalive = true) { setPost(new AIPostField(data), size, keepalive); } void setPost(char const* data, U32 size, bool keepalive = true) { setPost(new AIPostField(data), size, keepalive); }
@@ -297,6 +300,7 @@ class CurlEasyRequest : public CurlEasyHandle {
protected: protected:
curl_slist* mHeaders; curl_slist* mHeaders;
AICurlEasyHandleEvents* mHandleEventsTarget; AICurlEasyHandleEvents* mHandleEventsTarget;
U32 mContentLength; // Non-zero if known (only set for PUT and POST).
CURLcode mResult; //AIFIXME: this does not belong in the request object, but belongs in the response object. CURLcode mResult; //AIFIXME: this does not belong in the request object, but belongs in the response object.
AIHTTPTimeoutPolicy const* mTimeoutPolicy; AIHTTPTimeoutPolicy const* mTimeoutPolicy;
@@ -319,12 +323,12 @@ class CurlEasyRequest : public CurlEasyHandle {
// Accessor for mTimeout with optional creation of orphaned object (if lockobj != NULL). // Accessor for mTimeout with optional creation of orphaned object (if lockobj != NULL).
LLPointer<curlthread::HTTPTimeout>& httptimeout(void) { if (!mTimeout) { create_timeout_object(); mTimeoutIsOrphan = true; } return mTimeout; } LLPointer<curlthread::HTTPTimeout>& httptimeout(void) { if (!mTimeout) { create_timeout_object(); mTimeoutIsOrphan = true; } return mTimeout; }
// Return true if no data has been received on the latest socket (if any) for too long. // Return true if no data has been received on the latest socket (if any) for too long.
bool has_stalled(void) const { return mTimeout && mTimeout->has_stalled(); } bool has_stalled(void) { return mTimeout && mTimeout->has_stalled(); }
protected: protected:
// This class may only be created as base class of BufferedCurlEasyRequest. // This class may only be created as base class of BufferedCurlEasyRequest.
// Throws AICurlNoEasyHandle. // Throws AICurlNoEasyHandle.
CurlEasyRequest(void) : mHeaders(NULL), mHandleEventsTarget(NULL), mResult(CURLE_FAILED_INIT), mTimeoutPolicy(NULL), mTimeoutIsOrphan(false) CurlEasyRequest(void) : mHeaders(NULL), mHandleEventsTarget(NULL), mContentLength(0), mResult(CURLE_FAILED_INIT), mTimeoutPolicy(NULL), mTimeoutIsOrphan(false)
#if defined(CWDEBUG) || defined(DEBUG_CURLIO) #if defined(CWDEBUG) || defined(DEBUG_CURLIO)
, mDebugIsHeadOrGetMethod(false) , mDebugIsHeadOrGetMethod(false)
#endif #endif
@@ -433,7 +437,7 @@ class BufferedCurlEasyRequest : public CurlEasyRequest {
ThreadSafeBufferedCurlEasyRequest const* get_lockobj(void) const; ThreadSafeBufferedCurlEasyRequest const* get_lockobj(void) const;
// Return true when an error code was received that can occur before the upload finished. // Return true when an error code was received that can occur before the upload finished.
// So far the only such error I've seen is HTTP_BAD_REQUEST. // So far the only such error I've seen is HTTP_BAD_REQUEST.
bool upload_error_status(void) const { return mStatus == HTTP_BAD_REQUEST /*&& mStatus != HTTP_INTERNAL_ERROR*/; } bool upload_error_status(void) const { return mStatus == HTTP_BAD_REQUEST; }
// Return true when prepRequest was already called and the object has not been // Return true when prepRequest was already called and the object has not been
// invalidated as a result of calling timed_out(). // invalidated as a result of calling timed_out().

View File

@@ -832,8 +832,9 @@ class AICurlThread : public LLThread
{ {
public: public:
static AICurlThread* sInstance; static AICurlThread* sInstance;
LLMutex mWakeUpMutex; LLMutex mWakeUpMutex; // Set while a thread is waking up the curl thread.
bool mWakeUpFlag; // Protected by mWakeUpMutex. LLMutex mWakeUpFlagMutex; // Set when the curl thread is sleeping (in or about to enter select()).
bool mWakeUpFlag; // Protected by mWakeUpFlagMutex.
public: public:
// MAIN-THREAD // MAIN-THREAD
@@ -1067,11 +1068,10 @@ void AICurlThread::cleanup_wakeup_fds(void)
#endif #endif
} }
// MAIN-THREAD // OTHER THREADS
void AICurlThread::wakeup_thread(bool stop_thread) void AICurlThread::wakeup_thread(bool stop_thread)
{ {
DoutEntering(dc::curl, "AICurlThread::wakeup_thread"); DoutEntering(dc::curl, "AICurlThread::wakeup_thread");
llassert(is_main_thread());
// If we are already exiting the viewer then return immediately. // If we are already exiting the viewer then return immediately.
if (!mRunning) if (!mRunning)
@@ -1079,12 +1079,29 @@ void AICurlThread::wakeup_thread(bool stop_thread)
// Last time we are run? // Last time we are run?
if (stop_thread) if (stop_thread)
mRunning = false; mRunning = false; // Thread-safe because all other threads were already stopped.
// Note, we do not want this function to be blocking the calling thread; therefore we only use tryLock()s.
// Stop two threads running the following code concurrently.
if (!mWakeUpMutex.tryLock())
{
// If we failed to obtain mWakeUpMutex then another thread is (or was) in AICurlThread::wakeup_thread,
// or curl was holding the lock for a micro second at the start of process_commands.
// In the first case, curl might or might not yet have been woken up because of that, but if it was
// then it could not have started processing the commands yet, because it needs to obtain mWakeUpMutex
// between being woken up and processing the commands.
// Either way, the command that this thread called this function for was already in the queue (it's
// added before this function is called) but the command(s) that another thread called this function
// for were not processed yet. Hence, it's safe to exit here as our command(s) will be processed too.
return;
}
// Try if curl thread is still awake and if so, pass the new commands directly. // Try if curl thread is still awake and if so, pass the new commands directly.
if (mWakeUpMutex.tryLock()) if (mWakeUpFlagMutex.tryLock())
{ {
mWakeUpFlag = true; mWakeUpFlag = true;
mWakeUpFlagMutex.unlock();
mWakeUpMutex.unlock(); mWakeUpMutex.unlock();
return; return;
} }
@@ -1111,7 +1128,10 @@ void AICurlThread::wakeup_thread(bool stop_thread)
{ {
len = write(mWakeUpFd_in, "!", 1); len = write(mWakeUpFd_in, "!", 1);
if (len == -1 && errno == EAGAIN) if (len == -1 && errno == EAGAIN)
{
mWakeUpMutex.unlock();
return; // Unread characters are still in the pipe, so no need to add more. return; // Unread characters are still in the pipe, so no need to add more.
}
} }
while(len == -1 && errno == EINTR); while(len == -1 && errno == EINTR);
if (len == -1) if (len == -1)
@@ -1120,6 +1140,12 @@ void AICurlThread::wakeup_thread(bool stop_thread)
} }
llassert_always(len == 1); llassert_always(len == 1);
#endif #endif
// Release the lock here and not sooner, for the sole purpose of making sure
// that not two threads execute the above code concurrently. If the above code
// is thread-safe (maybe it is?) then we could release this lock arbitrarily
// sooner indeed - or even not lock it at all.
mWakeUpMutex.unlock();
} }
apr_status_t AICurlThread::join_thread(void) apr_status_t AICurlThread::join_thread(void)
@@ -1239,6 +1265,16 @@ void AICurlThread::process_commands(AICurlMultiHandle_wat const& multi_handle_w)
{ {
DoutEntering(dc::curl, "AICurlThread::process_commands(void)"); DoutEntering(dc::curl, "AICurlThread::process_commands(void)");
// Block here until the thread that woke us up released mWakeUpMutex.
// This is necessary to make sure that a third thread added commands
// too then either it will signal us later, or we process those commands
// now, too.
mWakeUpMutex.lock();
// Note that if at THIS point another thread tries to obtain mWakeUpMutex in AICurlThread::wakeup_thread
// and fails, it is ok that it leaves that function without waking us up too: we're awake and
// about to process any commands!
mWakeUpMutex.unlock();
// If we get here then the main thread called wakeup_thread() recently. // If we get here then the main thread called wakeup_thread() recently.
for(;;) for(;;)
{ {
@@ -1247,9 +1283,9 @@ void AICurlThread::process_commands(AICurlMultiHandle_wat const& multi_handle_w)
command_queue_wat command_queue_w(command_queue); command_queue_wat command_queue_w(command_queue);
if (command_queue_w->empty()) if (command_queue_w->empty())
{ {
mWakeUpMutex.lock(); mWakeUpFlagMutex.lock();
mWakeUpFlag = false; mWakeUpFlag = false;
mWakeUpMutex.unlock(); mWakeUpFlagMutex.unlock();
break; break;
} }
// Move the next command from the queue into command_being_processed. // Move the next command from the queue into command_being_processed.
@@ -1312,10 +1348,10 @@ void AICurlThread::run(void)
// Process every command in command_queue before filling the fd_set passed to select(). // Process every command in command_queue before filling the fd_set passed to select().
for(;;) for(;;)
{ {
mWakeUpMutex.lock(); mWakeUpFlagMutex.lock();
if (mWakeUpFlag) if (mWakeUpFlag)
{ {
mWakeUpMutex.unlock(); mWakeUpFlagMutex.unlock();
process_commands(multi_handle_w); process_commands(multi_handle_w);
continue; continue;
} }
@@ -1324,7 +1360,7 @@ void AICurlThread::run(void)
// wakeup_thread() is also called after setting mRunning to false. // wakeup_thread() is also called after setting mRunning to false.
if (!mRunning) if (!mRunning)
{ {
mWakeUpMutex.unlock(); mWakeUpFlagMutex.unlock();
break; break;
} }
@@ -1400,7 +1436,7 @@ void AICurlThread::run(void)
#endif #endif
#endif #endif
ready = select(nfds, read_fd_set, write_fd_set, NULL, &timeout); ready = select(nfds, read_fd_set, write_fd_set, NULL, &timeout);
mWakeUpMutex.unlock(); mWakeUpFlagMutex.unlock();
#ifdef CWDEBUG #ifdef CWDEBUG
#ifdef DEBUG_CURLIO #ifdef DEBUG_CURLIO
Dout(dc::finish|cond_error_cf(ready == -1), ready); Dout(dc::finish|cond_error_cf(ready == -1), ready);
@@ -1901,7 +1937,7 @@ void BufferedCurlEasyRequest::setStatusAndReason(U32 status, std::string const&
// Sanity check. If the server replies with a redirect status then we better have that option turned on! // Sanity check. If the server replies with a redirect status then we better have that option turned on!
if ((status >= 300 && status < 400) && mResponder && !mResponder->redirect_status_ok()) if ((status >= 300 && status < 400) && mResponder && !mResponder->redirect_status_ok())
{ {
llerrs << "Received " << status << " (" << reason << ") for responder \"" << mTimeoutPolicy->name() << "\" which has no followRedir()!" << llendl; llerrs << "Received " << status << " (" << reason << ") for responder \"" << mResponder->getName() << "\" which has no followRedir()!" << llendl;
} }
} }
@@ -1913,7 +1949,7 @@ void BufferedCurlEasyRequest::processOutput(void)
CURLcode code; CURLcode code;
AITransferInfo info; AITransferInfo info;
getResult(&code, &info); getResult(&code, &info);
if (code == CURLE_OK && mStatus != HTTP_INTERNAL_ERROR) if (code == CURLE_OK && !is_internal_http_error(mStatus))
{ {
getinfo(CURLINFO_RESPONSE_CODE, &responseCode); getinfo(CURLINFO_RESPONSE_CODE, &responseCode);
// If getResult code is CURLE_OK then we should have decoded the first header line ourselves. // If getResult code is CURLE_OK then we should have decoded the first header line ourselves.
@@ -1925,8 +1961,30 @@ void BufferedCurlEasyRequest::processOutput(void)
} }
else else
{ {
responseCode = HTTP_INTERNAL_ERROR;
responseReason = (code == CURLE_OK) ? mReason : std::string(curl_easy_strerror(code)); responseReason = (code == CURLE_OK) ? mReason : std::string(curl_easy_strerror(code));
switch (code)
{
case CURLE_FAILED_INIT:
responseCode = HTTP_INTERNAL_ERROR_OTHER;
break;
case CURLE_OPERATION_TIMEDOUT:
responseCode = HTTP_INTERNAL_ERROR_CURL_TIMEOUT;
break;
case CURLE_WRITE_ERROR:
responseCode = HTTP_INTERNAL_ERROR_LOW_SPEED;
break;
default:
responseCode = HTTP_INTERNAL_ERROR_CURL_OTHER;
break;
}
if (responseCode == HTTP_INTERNAL_ERROR_LOW_SPEED)
{
// Rewrite error to something understandable.
responseReason = llformat("Connection to \"%s\" stalled: download speed dropped below %u bytes/s for %u seconds (up till that point, %s received a total of %u bytes). "
"To change these values, go to Advanced --> Debug Settings and change CurlTimeoutLowSpeedLimit and CurlTimeoutLowSpeedTime respectively.",
mResponder->getURL().c_str(), mResponder->getHTTPTimeoutPolicy().getLowSpeedLimit(), mResponder->getHTTPTimeoutPolicy().getLowSpeedTime(),
mResponder->getName(), mResponseTransferedBytes);
}
setopt(CURLOPT_FRESH_CONNECT, TRUE); setopt(CURLOPT_FRESH_CONNECT, TRUE);
} }
@@ -2000,8 +2058,9 @@ size_t BufferedCurlEasyRequest::curlReadCallback(char* data, size_t size, size_t
S32 bytes = size * nmemb; // The maximum amount to read. S32 bytes = size * nmemb; // The maximum amount to read.
self_w->mLastRead = self_w->getInput()->readAfter(sChannels.out(), self_w->mLastRead, (U8*)data, bytes); self_w->mLastRead = self_w->getInput()->readAfter(sChannels.out(), self_w->mLastRead, (U8*)data, bytes);
self_w->mRequestTransferedBytes += bytes; // Accumulate data sent to the server. self_w->mRequestTransferedBytes += bytes; // Accumulate data sent to the server.
llassert(self_w->mRequestTransferedBytes <= self_w->mContentLength); // Content-Length should always be known, and we should never be sending more.
// Timeout administration. // Timeout administration.
if (self_w->httptimeout()->data_sent(bytes)) if (self_w->httptimeout()->data_sent(bytes, self_w->mRequestTransferedBytes >= self_w->mContentLength))
{ {
// Transfer timed out. Return CURL_READFUNC_ABORT which will abort with error CURLE_ABORTED_BY_CALLBACK. // Transfer timed out. Return CURL_READFUNC_ABORT which will abort with error CURLE_ABORTED_BY_CALLBACK.
return CURL_READFUNC_ABORT; return CURL_READFUNC_ABORT;
@@ -2063,7 +2122,7 @@ size_t BufferedCurlEasyRequest::curlHeaderCallback(char* data, size_t size, size
} }
// Either way, this status value is not understood (or taken into account). // Either way, this status value is not understood (or taken into account).
// Set it to internal error so that the rest of code treats it as an error. // Set it to internal error so that the rest of code treats it as an error.
status = HTTP_INTERNAL_ERROR; status = HTTP_INTERNAL_ERROR_OTHER;
} }
self_w->received_HTTP_header(); self_w->received_HTTP_header();
self_w->setStatusAndReason(status, reason); self_w->setStatusAndReason(status, reason);

View File

@@ -66,7 +66,7 @@ struct AIAccess {
struct AIHTTPTimeoutPolicy { struct AIHTTPTimeoutPolicy {
U16 getReplyDelay(void) const { return 60; } U16 getReplyDelay(void) const { return 60; }
U16 getLowSpeedTime(void) const { return 30; } U16 getLowSpeedTime(void) const { return 30; }
U32 getLowSpeedLimit(void) const { return 56000; } U32 getLowSpeedLimit(void) const { return 7000; }
static bool connect_timed_out(std::string const&) { return false; } static bool connect_timed_out(std::string const&) { return false; }
}; };
@@ -84,6 +84,11 @@ public:
#include "aihttptimeout.h" #include "aihttptimeout.h"
// If this is set, treat dc::curlio as off in the assertion below.
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
bool gCurlIo;
#endif
namespace AICurlPrivate { namespace AICurlPrivate {
namespace curlthread { namespace curlthread {
@@ -100,7 +105,7 @@ U64 HTTPTimeout::sClockCount; // Clock count, set once per select() exi
// queued--><--DNS lookup + connect + send headers-->[<--send body (if any)-->]<--replydelay--><--receive headers + body--><--done // queued--><--DNS lookup + connect + send headers-->[<--send body (if any)-->]<--replydelay--><--receive headers + body--><--done
// ^ ^ ^ ^ ^ ^ // ^ ^ ^ ^ ^ ^
// | | | | | | // | | | | | |
bool HTTPTimeout::data_sent(size_t n) bool HTTPTimeout::data_sent(size_t n, bool finished)
{ {
// Generate events. // Generate events.
if (!mLowSpeedOn) if (!mLowSpeedOn)
@@ -109,7 +114,7 @@ bool HTTPTimeout::data_sent(size_t n)
reset_lowspeed(); reset_lowspeed();
} }
// Detect low speed. // Detect low speed.
return lowspeed(n); return lowspeed(n, finished);
} }
// CURL-THREAD // CURL-THREAD
@@ -122,6 +127,7 @@ void HTTPTimeout::reset_lowspeed(void)
{ {
mLowSpeedClock = sClockCount; mLowSpeedClock = sClockCount;
mLowSpeedOn = true; mLowSpeedOn = true;
mLastBytesSent = false; // We're just starting!
mLastSecond = -1; // This causes lowspeed to initialize the rest. mLastSecond = -1; // This causes lowspeed to initialize the rest.
mStalled = (U64)-1; // Stop reply delay timer. mStalled = (U64)-1; // Stop reply delay timer.
DoutCurl("reset_lowspeed: mLowSpeedClock = " << mLowSpeedClock << "; mStalled = -1"); DoutCurl("reset_lowspeed: mLowSpeedClock = " << mLowSpeedClock << "; mStalled = -1");
@@ -169,7 +175,7 @@ bool HTTPTimeout::data_received(size_t n/*,*/
// using CURLOPT_DEBUGFUNCTION. Note that mDebugIsHeadOrGetMethod is only valid when the debug channel 'curlio' is on, // using CURLOPT_DEBUGFUNCTION. Note that mDebugIsHeadOrGetMethod is only valid when the debug channel 'curlio' is on,
// because it is set in the debug callback function. // because it is set in the debug callback function.
// This is also normal if we received a HTTP header with an error status, since that can interrupt our upload. // This is also normal if we received a HTTP header with an error status, since that can interrupt our upload.
Debug(llassert(upload_error_status || AICurlEasyRequest_wat(*mLockObj)->mDebugIsHeadOrGetMethod || !dc::curlio.is_on())); Debug(llassert(upload_error_status || AICurlEasyRequest_wat(*mLockObj)->mDebugIsHeadOrGetMethod || !dc::curlio.is_on() || gCurlIo));
// 'Upload finished' detection failed, generate it now. // 'Upload finished' detection failed, generate it now.
upload_finished(); upload_finished();
} }
@@ -193,9 +199,9 @@ bool HTTPTimeout::data_received(size_t n/*,*/
// queued--><--DNS lookup + connect + send headers-->[<--send body (if any)-->]<--replydelay--><--receive headers + body--><--done // queued--><--DNS lookup + connect + send headers-->[<--send body (if any)-->]<--replydelay--><--receive headers + body--><--done
// ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ // ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
// | | | | | | | | | | | | | | // | | | | | | | | | | | | | |
bool HTTPTimeout::lowspeed(size_t bytes) bool HTTPTimeout::lowspeed(size_t bytes, bool finished)
{ {
//DoutCurlEntering("HTTPTimeout::lowspeed(" << bytes << ")"); commented out... too spammy for normal use. //DoutCurlEntering("HTTPTimeout::lowspeed(" << bytes << ", " << finished << ")"); commented out... too spammy for normal use.
// The algorithm to determine if we timed out if different from how libcurls CURLOPT_LOW_SPEED_TIME works. // The algorithm to determine if we timed out if different from how libcurls CURLOPT_LOW_SPEED_TIME works.
// //
@@ -219,6 +225,9 @@ bool HTTPTimeout::lowspeed(size_t bytes)
// and caused something so evil and hard to find that... NEVER AGAIN! // and caused something so evil and hard to find that... NEVER AGAIN!
llassert(second >= 0); llassert(second >= 0);
// finished should be false until the very last call to this function.
mLastBytesSent = finished;
// If this is the same second as last time, just add the number of bytes to the current bucket. // If this is the same second as last time, just add the number of bytes to the current bucket.
if (second == mLastSecond) if (second == mLastSecond)
{ {
@@ -274,21 +283,39 @@ bool HTTPTimeout::lowspeed(size_t bytes)
mBuckets[mBucket] = bytes; mBuckets[mBucket] = bytes;
// Check if we timed out. // Check if we timed out.
U32 const low_speed_limit = mPolicy->getLowSpeedLimit(); U32 const low_speed_limit = mPolicy->getLowSpeedLimit(); // In bytes/s
U32 mintotalbytes = low_speed_limit * low_speed_time; U32 mintotalbytes = low_speed_limit * low_speed_time; // In bytes.
DoutCurl("Transfered " << mTotalBytes << " bytes in " << llmin(second, (S32)low_speed_time) << " seconds after " << second << " second" << ((second == 1) ? "" : "s") << "."); DoutCurl("Transfered " << mTotalBytes << " bytes in " << llmin(second, (S32)low_speed_time) << " seconds after " << second << " second" << ((second == 1) ? "" : "s") << ".");
if (second >= low_speed_time) if (second >= low_speed_time)
{ {
DoutCurl("Average transfer rate is " << (mTotalBytes / low_speed_time) << " bytes/s (low speed limit is " << low_speed_limit << " bytes/s)"); DoutCurl("Average transfer rate is " << (mTotalBytes / low_speed_time) << " bytes/s (low speed limit is " << low_speed_limit << " bytes/s)");
if (mTotalBytes < mintotalbytes) if (mTotalBytes < mintotalbytes)
{ {
if (finished)
{
llwarns <<
#ifdef CWDEBUG
(void*)get_lockobj() << ": "
#endif
"Transfer rate timeout (average transfer rate below " << low_speed_limit <<
" bytes/s for more than " << low_speed_time << " second" << ((low_speed_time == 1) ? "" : "s") <<
") but we just sent the LAST bytes! Waiting an additional 4 seconds." << llendl;
// Lets hope these last bytes will make it and do not time out on transfer speed anymore.
// Just give these bytes 4 more seconds to be written to the socket (after which we'll
// assume that the 'upload finished' detection failed and we'll wait another ReplyDelay
// seconds before finally, actually timing out.
mStalled = sClockCount + 4 / sClockWidth;
DoutCurl("mStalled set to sClockCount (" << sClockCount << ") + " << (mStalled - sClockCount) << " (4 seconds)");
return false;
}
// The average transfer rate over the passed low_speed_time seconds is too low. Abort the transfer. // The average transfer rate over the passed low_speed_time seconds is too low. Abort the transfer.
llwarns << llwarns <<
#ifdef CWDEBUG #ifdef CWDEBUG
(void*)get_lockobj() << ": " (void*)get_lockobj() << ": "
#endif #endif
"aborting slow connection (average transfer rate below " << low_speed_limit << "aborting slow connection (average transfer rate below " << low_speed_limit <<
" for more than " << low_speed_time << " second" << ((low_speed_time == 1) ? "" : "s") << ")." << llendl; " bytes/s for more than " << low_speed_time << " second" << ((low_speed_time == 1) ? "" : "s") << ")." << llendl;
// This causes curl to exit with CURLE_WRITE_ERROR.
return true; return true;
} }
} }
@@ -327,7 +354,7 @@ bool HTTPTimeout::lowspeed(size_t bytes)
llassert_always(bucket < low_speed_time); llassert_always(bucket < low_speed_time);
total_bytes -= mBuckets[bucket]; // Empty this bucket. total_bytes -= mBuckets[bucket]; // Empty this bucket.
} }
while(total_bytes >= 1); // Use 1 here instead of mintotalbytes, to test that total_bytes indeed always reaches zero. while(total_bytes >= mintotalbytes);
} }
// If this function isn't called again within max_stall_time seconds, we stalled. // If this function isn't called again within max_stall_time seconds, we stalled.
mStalled = sClockCount + max_stall_time / sClockWidth; mStalled = sClockCount + max_stall_time / sClockWidth;
@@ -377,6 +404,19 @@ void HTTPTimeout::done(AICurlEasyRequest_wat const& curlEasyRequest_w, CURLcode
DoutCurl("done: mStalled set to -1"); DoutCurl("done: mStalled set to -1");
} }
bool HTTPTimeout::maybe_upload_finished(void)
{
if (!mUploadFinished && mLastBytesSent)
{
// Assume that 'upload finished' detection failed and the server is slow with a reply.
// Switch to waiting for a reply.
upload_finished();
return true;
}
// The upload certainly finished or certainly did not finish.
return false;
}
// Libcurl uses GetTickCount on windows, with a resolution of 10 to 16 ms. // Libcurl uses GetTickCount on windows, with a resolution of 10 to 16 ms.
// As a result, we can not assume that namelookup_time == 0 has a special meaning. // As a result, we can not assume that namelookup_time == 0 has a special meaning.
#define LOWRESTIMER LL_WINDOWS #define LOWRESTIMER LL_WINDOWS
@@ -493,6 +533,10 @@ void HTTPTimeout::print_diagnostics(CurlEasyRequest const* curl_easy_request, ch
{ {
llinfos << "The request upload finished successfully." << llendl; llinfos << "The request upload finished successfully." << llendl;
} }
else if (mLastBytesSent)
{
llinfos << "All bytes where sent to libcurl for upload." << llendl;
}
if (mLastSecond > 0 && mLowSpeedOn) if (mLastSecond > 0 && mLowSpeedOn)
{ {
llinfos << "The " << (mNothingReceivedYet ? "upload" : "download") << " did last " << mLastSecond << " second" << ((mLastSecond == 1) ? "" : "s") << ", before it timed out." << llendl; llinfos << "The " << (mNothingReceivedYet ? "upload" : "download") << " did last " << mLastSecond << " second" << ((mLastSecond == 1) ? "" : "s") << ", before it timed out." << llendl;

View File

@@ -79,6 +79,7 @@ class HTTPTimeout : public LLRefCount {
U16 mBucket; // The bucket corresponding to mLastSecond. U16 mBucket; // The bucket corresponding to mLastSecond.
bool mNothingReceivedYet; // Set when created, reset when the HTML reply header from the server is received. bool mNothingReceivedYet; // Set when created, reset when the HTML reply header from the server is received.
bool mLowSpeedOn; // Set while uploading or downloading data. bool mLowSpeedOn; // Set while uploading or downloading data.
bool mLastBytesSent; // Set when the last bytes were sent to libcurl to be uploaded.
bool mUploadFinished; // Used to keep track of whether upload_finished was called yet. bool mUploadFinished; // Used to keep track of whether upload_finished was called yet.
S32 mLastSecond; // The time at which lowspeed() was last called, in seconds since mLowSpeedClock. S32 mLastSecond; // The time at which lowspeed() was last called, in seconds since mLowSpeedClock.
S32 mOverwriteSecond; // The second at which the first bucket of this transfer will be overwritten. S32 mOverwriteSecond; // The second at which the first bucket of this transfer will be overwritten.
@@ -94,7 +95,7 @@ class HTTPTimeout : public LLRefCount {
public: public:
HTTPTimeout(AIHTTPTimeoutPolicy const* policy, ThreadSafeBufferedCurlEasyRequest* lock_obj) : HTTPTimeout(AIHTTPTimeoutPolicy const* policy, ThreadSafeBufferedCurlEasyRequest* lock_obj) :
mPolicy(policy), mNothingReceivedYet(true), mLowSpeedOn(false), mUploadFinished(false), mStalled((U64)-1) mPolicy(policy), mNothingReceivedYet(true), mLowSpeedOn(false), mLastBytesSent(false), mUploadFinished(false), mStalled((U64)-1)
#if defined(CWDEBUG) || defined(DEBUG_CURLIO) #if defined(CWDEBUG) || defined(DEBUG_CURLIO)
, mLockObj(lock_obj) , mLockObj(lock_obj)
#endif #endif
@@ -104,7 +105,7 @@ class HTTPTimeout : public LLRefCount {
void upload_finished(void); void upload_finished(void);
// Called when data is sent. Returns true if transfer timed out. // Called when data is sent. Returns true if transfer timed out.
bool data_sent(size_t n); bool data_sent(size_t n, bool finished);
// Called when data is received. Returns true if transfer timed out. // Called when data is received. Returns true if transfer timed out.
bool data_received(size_t n/*,*/ ASSERT_ONLY_COMMA(bool upload_error_status = false)); bool data_received(size_t n/*,*/ ASSERT_ONLY_COMMA(bool upload_error_status = false));
@@ -112,8 +113,8 @@ class HTTPTimeout : public LLRefCount {
// Called immediately before done() after curl finished, with code. // Called immediately before done() after curl finished, with code.
void done(AICurlEasyRequest_wat const& curlEasyRequest_w, CURLcode code); void done(AICurlEasyRequest_wat const& curlEasyRequest_w, CURLcode code);
// Accessor. // Returns true when we REALLY timed out. Might call upload_finished heuristically.
bool has_stalled(void) const { return mStalled < sClockCount; } bool has_stalled(void) { return mStalled < sClockCount && !maybe_upload_finished(); }
// Called from BufferedCurlEasyRequest::processOutput if a timeout occurred. // Called from BufferedCurlEasyRequest::processOutput if a timeout occurred.
void print_diagnostics(CurlEasyRequest const* curl_easy_request, char const* eff_url); void print_diagnostics(CurlEasyRequest const* curl_easy_request, char const* eff_url);
@@ -127,11 +128,19 @@ class HTTPTimeout : public LLRefCount {
void reset_lowspeed(void); void reset_lowspeed(void);
// Common low speed detection, Called from data_sent or data_received. // Common low speed detection, Called from data_sent or data_received.
bool lowspeed(size_t bytes); bool lowspeed(size_t bytes, bool finished = false);
// Return false when we timed out on reply delay, or didn't sent all bytes yet.
// Otherwise calls upload_finished() and return true;
bool maybe_upload_finished(void);
}; };
} // namespace curlthread } // namespace curlthread
} // namespace AICurlPrivate } // namespace AICurlPrivate
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
extern bool gCurlIo;
#endif
#endif #endif

View File

@@ -88,7 +88,7 @@ U16 const AITP_default_DNS_lookup_grace = 60; // Allow for 60 seconds long DNS
U16 const AITP_default_maximum_connect_time = 10; // Allow the SSL/TLS connection through a proxy, including handshakes, to take up to 10 seconds. U16 const AITP_default_maximum_connect_time = 10; // Allow the SSL/TLS connection through a proxy, including handshakes, to take up to 10 seconds.
U16 const AITP_default_maximum_reply_delay = 60; // Allow the server 60 seconds to do whatever it has to do before starting to send data. U16 const AITP_default_maximum_reply_delay = 60; // Allow the server 60 seconds to do whatever it has to do before starting to send data.
U16 const AITP_default_low_speed_time = 30; // If a transfer speed drops below AITP_default_low_speed_limit bytes/s for 30 seconds, terminate the transfer. U16 const AITP_default_low_speed_time = 30; // If a transfer speed drops below AITP_default_low_speed_limit bytes/s for 30 seconds, terminate the transfer.
U32 const AITP_default_low_speed_limit = 56000; // In bytes per second (use for CURLOPT_LOW_SPEED_LIMIT). U32 const AITP_default_low_speed_limit = 7000; // In bytes per second (use for CURLOPT_LOW_SPEED_LIMIT).
U16 const AITP_default_maximum_curl_transaction = 300; // Allow large files to be transfered over slow connections. U16 const AITP_default_maximum_curl_transaction = 300; // Allow large files to be transfered over slow connections.
U16 const AITP_default_maximum_total_delay = 600; // Avoid "leaking" by terminating anything that wasn't completed after 10 minutes. U16 const AITP_default_maximum_total_delay = 600; // Avoid "leaking" by terminating anything that wasn't completed after 10 minutes.
@@ -105,6 +105,7 @@ AIHTTPTimeoutPolicy& AIHTTPTimeoutPolicy::operator=(AIHTTPTimeoutPolicy const& r
mLowSpeedLimit = rhs.mLowSpeedLimit; mLowSpeedLimit = rhs.mLowSpeedLimit;
mMaximumCurlTransaction = rhs.mMaximumCurlTransaction; mMaximumCurlTransaction = rhs.mMaximumCurlTransaction;
mMaximumTotalDelay = rhs.mMaximumTotalDelay; mMaximumTotalDelay = rhs.mMaximumTotalDelay;
changed();
return *this; return *this;
} }
@@ -136,19 +137,27 @@ struct PolicyOp {
class AIHTTPTimeoutPolicyBase : public AIHTTPTimeoutPolicy { class AIHTTPTimeoutPolicyBase : public AIHTTPTimeoutPolicy {
private: private:
std::vector<AIHTTPTimeoutPolicy*> mDerived; // Policies derived from this one. std::vector<AIHTTPTimeoutPolicy*> mDerived; // Policies derived from this one.
PolicyOp const* mOp; // Operator we applied to base to get ourselves.
public: public:
AIHTTPTimeoutPolicyBase(U16 dns_lookup_grace, U16 subsequent_connects, U16 reply_delay, AIHTTPTimeoutPolicyBase(U16 dns_lookup_grace, U16 subsequent_connects, U16 reply_delay,
U16 low_speed_time, U32 low_speed_limit, U16 low_speed_time, U32 low_speed_limit,
U16 curl_transaction, U16 total_delay) : U16 curl_transaction, U16 total_delay) :
AIHTTPTimeoutPolicy(NULL, dns_lookup_grace, subsequent_connects, reply_delay, low_speed_time, low_speed_limit, curl_transaction, total_delay) { } AIHTTPTimeoutPolicy(NULL, dns_lookup_grace, subsequent_connects, reply_delay, low_speed_time, low_speed_limit, curl_transaction, total_delay),
mOp(NULL) { }
// Derive base from base. // Derive base from base.
AIHTTPTimeoutPolicyBase(AIHTTPTimeoutPolicyBase& rhs, PolicyOp const& op) : AIHTTPTimeoutPolicy(rhs) { op.perform(this); } AIHTTPTimeoutPolicyBase(AIHTTPTimeoutPolicyBase& rhs, PolicyOp& op) : AIHTTPTimeoutPolicy(rhs), mOp(&op) { rhs.derived(this); mOp->perform(this); }
// Called for every derived policy. // Called for every derived policy.
void derived(AIHTTPTimeoutPolicy* derived) { mDerived.push_back(derived); } void derived(AIHTTPTimeoutPolicy* derived) { mDerived.push_back(derived); }
// Called when our base changed.
/*virtual*/ void base_changed(void);
// Called when we ourselves changed.
/*virtual*/ void changed(void);
// Provide public acces to sDebugSettingsCurlTimeout for this compilation unit. // Provide public acces to sDebugSettingsCurlTimeout for this compilation unit.
static AIHTTPTimeoutPolicyBase& getDebugSettingsCurlTimeout(void) { return sDebugSettingsCurlTimeout; } static AIHTTPTimeoutPolicyBase& getDebugSettingsCurlTimeout(void) { return sDebugSettingsCurlTimeout; }
@@ -157,6 +166,27 @@ class AIHTTPTimeoutPolicyBase : public AIHTTPTimeoutPolicy {
AIHTTPTimeoutPolicyBase& operator=(AIHTTPTimeoutPolicy const& rhs); AIHTTPTimeoutPolicyBase& operator=(AIHTTPTimeoutPolicy const& rhs);
}; };
void AIHTTPTimeoutPolicyBase::base_changed(void)
{
AIHTTPTimeoutPolicy::base_changed();
if (mOp)
mOp->perform(this);
changed();
}
void AIHTTPTimeoutPolicyBase::changed(void)
{
for (std::vector<AIHTTPTimeoutPolicy*>::iterator iter = mDerived.begin(); iter != mDerived.end(); ++iter)
(*iter)->base_changed();
}
void AIHTTPTimeoutPolicy::changed(void)
{
Dout(dc::notice, "Policy \"" << mName << "\" changed into: DNSLookup: " << mDNSLookupGrace << "; Connect: " << mMaximumConnectTime <<
"; ReplyDelay: " << mMaximumReplyDelay << "; LowSpeedTime: " << mLowSpeedTime << "; LowSpeedLimit: " << mLowSpeedLimit <<
"; MaxTransaction: " << mMaximumCurlTransaction << "; MaxTotalDelay:" << mMaximumTotalDelay);
}
AIHTTPTimeoutPolicy::AIHTTPTimeoutPolicy(AIHTTPTimeoutPolicy& base) : AIHTTPTimeoutPolicy::AIHTTPTimeoutPolicy(AIHTTPTimeoutPolicy& base) :
mName(NULL), mName(NULL),
mBase(static_cast<AIHTTPTimeoutPolicyBase*>(&base)), mBase(static_cast<AIHTTPTimeoutPolicyBase*>(&base)),
@@ -192,6 +222,19 @@ AIHTTPTimeoutPolicy::AIHTTPTimeoutPolicy(char const* name, AIHTTPTimeoutPolicyBa
mBase->derived(this); mBase->derived(this);
} }
void AIHTTPTimeoutPolicy::base_changed(void)
{
// The same as *this = *mBase; but can't use operator= because of an assert that checks that mBase is not set.
mDNSLookupGrace = mBase->mDNSLookupGrace;
mMaximumConnectTime = mBase->mMaximumConnectTime;
mMaximumReplyDelay = mBase->mMaximumReplyDelay;
mLowSpeedTime = mBase->mLowSpeedTime;
mLowSpeedLimit = mBase->mLowSpeedLimit;
mMaximumCurlTransaction = mBase->mMaximumCurlTransaction;
mMaximumTotalDelay = mBase->mMaximumTotalDelay;
changed();
}
//static //static
void AIHTTPTimeoutPolicy::setDefaultCurlTimeout(AIHTTPTimeoutPolicy const& timeout) void AIHTTPTimeoutPolicy::setDefaultCurlTimeout(AIHTTPTimeoutPolicy const& timeout)
{ {
@@ -621,7 +664,7 @@ AIHTTPTimeoutPolicyBase HTTPTimeoutPolicy_default(
AITP_default_maximum_curl_transaction, AITP_default_maximum_curl_transaction,
AITP_default_maximum_total_delay); AITP_default_maximum_total_delay);
//static. Initialized here, but shortly overwritten by Debug Settings. //static. Initialized here, but shortly overwritten by Debug Settings (except for the crash logger, in which case these are the actual values).
AIHTTPTimeoutPolicyBase AIHTTPTimeoutPolicy::sDebugSettingsCurlTimeout( AIHTTPTimeoutPolicyBase AIHTTPTimeoutPolicy::sDebugSettingsCurlTimeout(
AITP_default_DNS_lookup_grace, AITP_default_DNS_lookup_grace,
AITP_default_maximum_connect_time, AITP_default_maximum_connect_time,
@@ -631,8 +674,8 @@ AIHTTPTimeoutPolicyBase AIHTTPTimeoutPolicy::sDebugSettingsCurlTimeout(
AITP_default_maximum_curl_transaction, AITP_default_maximum_curl_transaction,
AITP_default_maximum_total_delay); AITP_default_maximum_total_delay);
// Note: Broken compiler doesn't allow as to use temporaries for the Operator ojects, // Note: All operator objects (Transaction, Connect, etc) must be globals (not temporaries)!
// so they are instantiated separately. // To enforce this they are passes as reference to non-const (but will never be changed).
// This used to be '5 seconds'. // This used to be '5 seconds'.
Transaction transactionOp5s(5); Transaction transactionOp5s(5);

View File

@@ -110,6 +110,12 @@ class AIHTTPTimeoutPolicy {
// Called when a connect to a hostname timed out. // Called when a connect to a hostname timed out.
static bool connect_timed_out(std::string const& hostname); static bool connect_timed_out(std::string const& hostname);
// Called when the base that this policy was based on changed.
virtual void base_changed(void);
// Called when we ourselves changed.
virtual void changed(void);
protected: protected:
// Used by AIHTTPTimeoutPolicyBase::AIHTTPTimeoutPolicyBase(AIHTTPTimeoutPolicyBase&). // Used by AIHTTPTimeoutPolicyBase::AIHTTPTimeoutPolicyBase(AIHTTPTimeoutPolicyBase&).
AIHTTPTimeoutPolicy(AIHTTPTimeoutPolicy&); AIHTTPTimeoutPolicy(AIHTTPTimeoutPolicy&);

View File

@@ -195,18 +195,20 @@ public:
LLAssetType::EType mAssetType; LLAssetType::EType mAssetType;
}; };
static void request( //static
const std::string& url, void LLHTTPClient::request(
std::string const& url,
LLURLRequest::ERequestAction method, LLURLRequest::ERequestAction method,
Injector* body_injector, Injector* body_injector,
LLHTTPClient::ResponderPtr responder, LLHTTPClient::ResponderPtr responder,
AIHTTPHeaders& headers/*,*/ AIHTTPHeaders& headers/*,*/
DEBUG_CURLIO_PARAM(EDebugCurl debug), DEBUG_CURLIO_PARAM(EDebugCurl debug),
EKeepAlive keepalive = keep_alive, EKeepAlive keepalive,
bool is_auth = false, EDoesAuthentication does_auth,
bool no_compression = false, EAllowCompressedReply allow_compression,
AIStateMachine* parent = NULL, AIStateMachine* parent,
AIStateMachine::state_type new_parent_state = 0) AIStateMachine::state_type new_parent_state,
AIEngine* default_engine)
{ {
llassert(responder); llassert(responder);
@@ -219,7 +221,7 @@ static void request(
LLURLRequest* req; LLURLRequest* req;
try try
{ {
req = new LLURLRequest(method, url, body_injector, responder, headers, keepalive, is_auth, no_compression); req = new LLURLRequest(method, url, body_injector, responder, headers, keepalive, does_auth, allow_compression);
#ifdef DEBUG_CURLIO #ifdef DEBUG_CURLIO
req->mCurlEasyRequest.debug(debug); req->mCurlEasyRequest.debug(debug);
#endif #endif
@@ -231,7 +233,7 @@ static void request(
return ; return ;
} }
req->run(parent, new_parent_state, parent != NULL); req->run(parent, new_parent_state, parent != NULL, true, default_engine);
} }
void LLHTTPClient::getByteRange(std::string const& url, S32 offset, S32 bytes, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug)) void LLHTTPClient::getByteRange(std::string const& url, S32 offset, S32 bytes, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
@@ -240,22 +242,22 @@ void LLHTTPClient::getByteRange(std::string const& url, S32 offset, S32 bytes, R
{ {
headers.addHeader("Range", llformat("bytes=%d-%d", offset, offset + bytes - 1)); headers.addHeader("Range", llformat("bytes=%d-%d", offset, offset + bytes - 1));
} }
request(url, LLURLRequest::HTTP_GET, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug)); request(url, HTTP_GET, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
} }
void LLHTTPClient::head(std::string const& url, ResponderHeadersOnly* responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug)) void LLHTTPClient::head(std::string const& url, ResponderHeadersOnly* responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
{ {
request(url, LLURLRequest::HTTP_HEAD, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug)); request(url, HTTP_HEAD, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
} }
void LLHTTPClient::get(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug)) void LLHTTPClient::get(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
{ {
request(url, LLURLRequest::HTTP_GET, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug)); request(url, HTTP_GET, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
} }
void LLHTTPClient::getHeaderOnly(std::string const& url, ResponderHeadersOnly* responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug)) void LLHTTPClient::getHeaderOnly(std::string const& url, ResponderHeadersOnly* responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
{ {
request(url, LLURLRequest::HTTP_HEAD, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug)); request(url, HTTP_HEAD, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
} }
void LLHTTPClient::get(std::string const& url, LLSD const& query, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug)) void LLHTTPClient::get(std::string const& url, LLSD const& query, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
@@ -302,7 +304,7 @@ AIHTTPTimeoutPolicy const& LLHTTPClient::ResponderBase::getHTTPTimeoutPolicy(voi
void LLHTTPClient::ResponderBase::decode_llsd_body(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer, LLSD& content) void LLHTTPClient::ResponderBase::decode_llsd_body(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer, LLSD& content)
{ {
AICurlInterface::Stats::llsd_body_count++; AICurlInterface::Stats::llsd_body_count++;
if (status == HTTP_INTERNAL_ERROR) if (is_internal_http_error(status))
{ {
// In case of an internal error (ie, a curl error), a description of the (curl) error is the best we can do. // In case of an internal error (ie, a curl error), a description of the (curl) error is the best we can do.
// In any case, the body if anything was received at all, can not be relied upon. // In any case, the body if anything was received at all, can not be relied upon.
@@ -353,7 +355,7 @@ void LLHTTPClient::ResponderBase::decode_llsd_body(U32 status, std::string const
void LLHTTPClient::ResponderBase::decode_raw_body(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer, std::string& content) void LLHTTPClient::ResponderBase::decode_raw_body(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer, std::string& content)
{ {
AICurlInterface::Stats::raw_body_count++; AICurlInterface::Stats::raw_body_count++;
if (status == HTTP_INTERNAL_ERROR) if (is_internal_http_error(status))
{ {
// In case of an internal error (ie, a curl error), a description of the (curl) error is the best we can do. // In case of an internal error (ie, a curl error), a description of the (curl) error is the best we can do.
// In any case, the body if anything was received at all, can not be relied upon. // In any case, the body if anything was received at all, can not be relied upon.
@@ -487,7 +489,8 @@ void BlockingResponder::wait(void)
// We're the main thread, so we have to give AIStateMachine CPU cycles. // We're the main thread, so we have to give AIStateMachine CPU cycles.
while (!mFinished) while (!mFinished)
{ {
AIStateMachine::mainloop(); // AIFIXME: this can probably be removed once curl is detached from the main thread.
gMainThreadEngine.mainloop();
ms_sleep(10); ms_sleep(10);
} }
} }
@@ -609,11 +612,10 @@ static LLSD blocking_request(
responder->wait(); responder->wait();
S32 http_status = HTTP_INTERNAL_ERROR;
LLSD response = LLSD::emptyMap(); LLSD response = LLSD::emptyMap();
CURLcode result = responder->result_code(); CURLcode result = responder->result_code();
S32 http_status = responder->http_status();
http_status = responder->http_status();
bool http_success = http_status >= 200 && http_status < 300; bool http_success = http_status >= 200 && http_status < 300;
if (result == CURLE_OK && http_success) if (result == CURLE_OK && http_success)
{ {
@@ -685,17 +687,17 @@ U32 LLHTTPClient::blockingGetRaw(const std::string& url, std::string& body/*,*/
void LLHTTPClient::put(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug)) void LLHTTPClient::put(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
{ {
request(url, LLURLRequest::HTTP_PUT, new LLSDInjector(body), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug)); request(url, HTTP_PUT, new LLSDInjector(body), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), no_keep_alive, no_does_authentication, no_allow_compressed_reply);
} }
void LLHTTPClient::post(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive, AIStateMachine* parent, AIStateMachine::state_type new_parent_state) void LLHTTPClient::post(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive, AIStateMachine* parent, AIStateMachine::state_type new_parent_state)
{ {
request(url, LLURLRequest::HTTP_POST, new LLSDInjector(body), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, false, false, parent, new_parent_state); request(url, HTTP_POST, new LLSDInjector(body), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, no_does_authentication, allow_compressed_reply, parent, new_parent_state);
} }
void LLHTTPClient::postXMLRPC(std::string const& url, XMLRPC_REQUEST xmlrpc_request, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive) void LLHTTPClient::postXMLRPC(std::string const& url, XMLRPC_REQUEST xmlrpc_request, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive)
{ {
request(url, LLURLRequest::HTTP_POST, new XMLRPCInjector(xmlrpc_request), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, true, false); // Does use compression. request(url, HTTP_POST, new XMLRPCInjector(xmlrpc_request), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, does_authentication, allow_compressed_reply);
} }
void LLHTTPClient::postXMLRPC(std::string const& url, char const* method, XMLRPC_VALUE value, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive) void LLHTTPClient::postXMLRPC(std::string const& url, char const* method, XMLRPC_VALUE value, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive)
@@ -706,33 +708,33 @@ void LLHTTPClient::postXMLRPC(std::string const& url, char const* method, XMLRPC
XMLRPC_RequestSetData(xmlrpc_request, value); XMLRPC_RequestSetData(xmlrpc_request, value);
// XMLRPCInjector takes ownership of xmlrpc_request and will free it when done. // XMLRPCInjector takes ownership of xmlrpc_request and will free it when done.
// LLURLRequest takes ownership of the XMLRPCInjector object and will free it when done. // LLURLRequest takes ownership of the XMLRPCInjector object and will free it when done.
request(url, LLURLRequest::HTTP_POST, new XMLRPCInjector(xmlrpc_request), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, true, true); // Does not use compression. request(url, HTTP_POST, new XMLRPCInjector(xmlrpc_request), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, does_authentication, no_allow_compressed_reply);
} }
void LLHTTPClient::postRaw(std::string const& url, char const* data, S32 size, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive) void LLHTTPClient::postRaw(std::string const& url, char const* data, S32 size, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive)
{ {
request(url, LLURLRequest::HTTP_POST, new RawInjector(data, size), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive); request(url, HTTP_POST, new RawInjector(data, size), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive);
} }
void LLHTTPClient::postFile(std::string const& url, std::string const& filename, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive) void LLHTTPClient::postFile(std::string const& url, std::string const& filename, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive)
{ {
request(url, LLURLRequest::HTTP_POST, new FileInjector(filename), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive); request(url, HTTP_POST, new FileInjector(filename), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive);
} }
void LLHTTPClient::postFile(std::string const& url, LLUUID const& uuid, LLAssetType::EType asset_type, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive) void LLHTTPClient::postFile(std::string const& url, LLUUID const& uuid, LLAssetType::EType asset_type, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive)
{ {
request(url, LLURLRequest::HTTP_POST, new VFileInjector(uuid, asset_type), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive); request(url, HTTP_POST, new VFileInjector(uuid, asset_type), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive);
} }
// static // static
void LLHTTPClient::del(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug)) void LLHTTPClient::del(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
{ {
request(url, LLURLRequest::HTTP_DELETE, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug)); request(url, HTTP_DELETE, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
} }
// static // static
void LLHTTPClient::move(std::string const& url, std::string const& destination, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug)) void LLHTTPClient::move(std::string const& url, std::string const& destination, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
{ {
headers.addHeader("Destination", destination); headers.addHeader("Destination", destination);
request(url, LLURLRequest::HTTP_MOVE, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug)); request(url, HTTP_MOVE, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
} }

View File

@@ -46,10 +46,13 @@ class AIHTTPTimeoutPolicy;
class LLBufferArray; class LLBufferArray;
class LLChannelDescriptors; class LLChannelDescriptors;
class AIStateMachine; class AIStateMachine;
class Injector;
class AIEngine;
extern AIHTTPTimeoutPolicy responderIgnore_timeout; extern AIHTTPTimeoutPolicy responderIgnore_timeout;
typedef struct _xmlrpc_request* XMLRPC_REQUEST; typedef struct _xmlrpc_request* XMLRPC_REQUEST;
typedef struct _xmlrpc_value* XMLRPC_VALUE; typedef struct _xmlrpc_value* XMLRPC_VALUE;
extern AIEngine gMainThreadEngine;
// Output parameter of AICurlPrivate::CurlEasyRequest::getResult. // Output parameter of AICurlPrivate::CurlEasyRequest::getResult.
// Used in XMLRPCResponder. // Used in XMLRPCResponder.
@@ -72,6 +75,16 @@ enum EKeepAlive {
keep_alive keep_alive
}; };
enum EDoesAuthentication {
no_does_authentication = 0,
does_authentication
};
enum EAllowCompressedReply {
no_allow_compressed_reply = 0,
allow_compressed_reply
};
#ifdef DEBUG_CURLIO #ifdef DEBUG_CURLIO
enum EDebugCurl { enum EDebugCurl {
debug_off = 0, debug_off = 0,
@@ -84,6 +97,20 @@ enum EDebugCurl {
class LLHTTPClient { class LLHTTPClient {
public: public:
/**
* @brief This enumeration is for specifying the type of request.
*/
enum ERequestAction
{
INVALID,
HTTP_HEAD,
HTTP_GET,
HTTP_PUT,
HTTP_POST,
HTTP_DELETE,
HTTP_MOVE, // Caller will need to set 'Destination' header
REQUEST_ACTION_COUNT
};
/** @name Responder base classes */ /** @name Responder base classes */
//@{ //@{
@@ -363,7 +390,7 @@ public:
} }
public: public:
LegacyPolledResponder(void) : mStatus(HTTP_INTERNAL_ERROR) { } LegacyPolledResponder(void) : mStatus(HTTP_INTERNAL_ERROR_OTHER) { }
// Accessors. // Accessors.
U32 http_status(void) const { return mStatus; } U32 http_status(void) const { return mStatus; }
@@ -393,6 +420,21 @@ public:
//@} //@}
/** General API to request a transfer. */
static void request(
std::string const& url,
ERequestAction method,
Injector* body_injector,
ResponderPtr responder,
AIHTTPHeaders& headers/*,*/
DEBUG_CURLIO_PARAM(EDebugCurl debug),
EKeepAlive keepalive = keep_alive,
EDoesAuthentication does_auth = no_does_authentication,
EAllowCompressedReply allow_compression = allow_compressed_reply,
AIStateMachine* parent = NULL,
/*AIStateMachine::state_type*/ U32 new_parent_state = 0,
AIEngine* default_engine = &gMainThreadEngine);
/** @name non-blocking API */ /** @name non-blocking API */
//@{ //@{
static void head(std::string const& url, ResponderHeadersOnly* responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug = debug_off)); static void head(std::string const& url, ResponderHeadersOnly* responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug = debug_off));

View File

@@ -46,7 +46,6 @@
#include "llscopedvolatileaprpool.h" #include "llscopedvolatileaprpool.h"
#include "llfasttimer.h" #include "llfasttimer.h"
#include "message.h" #include "message.h"
static const U32 HTTP_STATUS_PIPE_ERROR = 499;
/** /**
* String constants * String constants
@@ -60,7 +59,7 @@ const std::string CONTEXT_TRANSFERED_BYTES("transfered_bytes");
// static // static
std::string LLURLRequest::actionAsVerb(LLURLRequest::ERequestAction action) std::string LLURLRequest::actionAsVerb(LLURLRequest::ERequestAction action)
{ {
static int const array_size = HTTP_MOVE + 1; // INVALID == 0 static int const array_size = LLHTTPClient::REQUEST_ACTION_COUNT; // INVALID == 0
static char const* const VERBS[array_size] = static char const* const VERBS[array_size] =
{ {
"(invalid)", "(invalid)",
@@ -71,14 +70,14 @@ std::string LLURLRequest::actionAsVerb(LLURLRequest::ERequestAction action)
"DELETE", "DELETE",
"MOVE" "MOVE"
}; };
return VERBS[action >= array_size ? INVALID : action]; return VERBS[action >= array_size ? LLHTTPClient::INVALID : action];
} }
// This might throw AICurlNoEasyHandle. // This might throw AICurlNoEasyHandle.
LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action, std::string const& url, Injector* body, LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action, std::string const& url, Injector* body,
LLHTTPClient::ResponderPtr responder, AIHTTPHeaders& headers, bool keepalive, bool is_auth, bool no_compression) : LLHTTPClient::ResponderPtr responder, AIHTTPHeaders& headers, bool keepalive, bool is_auth, bool compression) :
mAction(action), mURL(url), mKeepAlive(keepalive), mIsAuth(is_auth), mNoCompression(no_compression), mAction(action), mURL(url), mKeepAlive(keepalive), mIsAuth(is_auth), mNoCompression(!compression),
mBody(body), mResponder(responder), mHeaders(headers) mBody(body), mResponder(responder), mHeaders(headers), mResponderNameCache(responder ? responder->getName() : "<uninitialized>")
{ {
} }
@@ -93,13 +92,13 @@ void LLURLRequest::initialize_impl(void)
useProxy(false); useProxy(false);
} }
if (mAction == HTTP_PUT || mAction == HTTP_POST) if (mAction == LLHTTPClient::HTTP_PUT || mAction == LLHTTPClient::HTTP_POST)
{ {
// If the Content-Type header was passed in we defer to the caller's wisdom, // If the Content-Type header was passed in we defer to the caller's wisdom,
// but if they did not specify a Content-Type, then ask the injector. // but if they did not specify a Content-Type, then ask the injector.
mHeaders.addHeader("Content-Type", mBody->contentType(), AIHTTPHeaders::keep_existing_header); mHeaders.addHeader("Content-Type", mBody->contentType(), AIHTTPHeaders::keep_existing_header);
} }
else if (mAction != HTTP_HEAD) else if (mAction != LLHTTPClient::HTTP_HEAD)
{ {
// Check to see if we have already set Accept or not. If no one // Check to see if we have already set Accept or not. If no one
// set it, set it to application/llsd+xml since that's what we // set it, set it to application/llsd+xml since that's what we
@@ -107,7 +106,7 @@ void LLURLRequest::initialize_impl(void)
mHeaders.addHeader("Accept", "application/llsd+xml", AIHTTPHeaders::keep_existing_header); mHeaders.addHeader("Accept", "application/llsd+xml", AIHTTPHeaders::keep_existing_header);
} }
if (mAction == HTTP_POST && gMessageSystem) if (mAction == LLHTTPClient::HTTP_POST && gMessageSystem)
{ {
mHeaders.addHeader("X-SecondLife-UDP-Listen-Port", llformat("%d", gMessageSystem->mPort)); mHeaders.addHeader("X-SecondLife-UDP-Listen-Port", llformat("%d", gMessageSystem->mPort));
} }
@@ -199,12 +198,12 @@ bool LLURLRequest::configure(AICurlEasyRequest_wat const& curlEasyRequest_w)
{ {
switch(mAction) switch(mAction)
{ {
case HTTP_HEAD: case LLHTTPClient::HTTP_HEAD:
curlEasyRequest_w->setopt(CURLOPT_NOBODY, 1); curlEasyRequest_w->setopt(CURLOPT_NOBODY, 1);
rv = true; rv = true;
break; break;
case HTTP_GET: case LLHTTPClient::HTTP_GET:
curlEasyRequest_w->setopt(CURLOPT_HTTPGET, 1); curlEasyRequest_w->setopt(CURLOPT_HTTPGET, 1);
// Set Accept-Encoding to allow response compression // Set Accept-Encoding to allow response compression
@@ -212,18 +211,18 @@ bool LLURLRequest::configure(AICurlEasyRequest_wat const& curlEasyRequest_w)
rv = true; rv = true;
break; break;
case HTTP_PUT: case LLHTTPClient::HTTP_PUT:
{
// Disable the expect http 1.1 extension. POST and PUT default // Set the handle for an http put
// to using this, causing the broken server to get confused. curlEasyRequest_w->setPut(mBodySize, mKeepAlive);
curlEasyRequest_w->addHeader("Expect:");
curlEasyRequest_w->setopt(CURLOPT_UPLOAD, 1); // Set Accept-Encoding to allow response compression
curlEasyRequest_w->setopt(CURLOPT_INFILESIZE, mBodySize); curlEasyRequest_w->setoptString(CURLOPT_ENCODING, mNoCompression ? "identity" : "");
rv = true; rv = true;
break; break;
}
case HTTP_POST: case LLHTTPClient::HTTP_POST:
{
// Set the handle for an http post // Set the handle for an http post
curlEasyRequest_w->setPost(mBodySize, mKeepAlive); curlEasyRequest_w->setPost(mBodySize, mKeepAlive);
@@ -231,14 +230,14 @@ bool LLURLRequest::configure(AICurlEasyRequest_wat const& curlEasyRequest_w)
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, mNoCompression ? "identity" : ""); curlEasyRequest_w->setoptString(CURLOPT_ENCODING, mNoCompression ? "identity" : "");
rv = true; rv = true;
break; break;
}
case HTTP_DELETE: case LLHTTPClient::HTTP_DELETE:
// Set the handle for an http post // Set the handle for an http post
curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "DELETE"); curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "DELETE");
rv = true; rv = true;
break; break;
case HTTP_MOVE: case LLHTTPClient::HTTP_MOVE:
// Set the handle for an http post // Set the handle for an http post
curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "MOVE"); curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "MOVE");
rv = true; rv = true;
@@ -259,3 +258,33 @@ bool LLURLRequest::configure(AICurlEasyRequest_wat const& curlEasyRequest_w)
} }
return rv; return rv;
} }
// Called from AIStateMachine::mainloop, but put here because we don't want to include llurlrequest.h there of course.
void print_statemachine_diagnostics(U64 total_clocks, U64 max_delta, AIEngine::queued_type::const_reference slowest_element)
{
AIStateMachine const& slowest_state_machine = slowest_element.statemachine();
LLURLRequest const* request = dynamic_cast<LLURLRequest const*>(&slowest_state_machine);
F64 const tfactor = 1000 / calc_clock_frequency();
std::ostringstream msg;
if (total_clocks > max_delta)
{
msg << "AIStateMachine::mainloop did run for " << (total_clocks * tfactor) << " ms. The slowest ";
}
else
{
msg << "AIStateMachine::mainloop: A ";
}
msg << "state machine ";
if (request)
{
msg << "(" << request->getResponderName() << ") ";
}
msg << "ran for " << (max_delta * tfactor) << " ms";
if (slowest_state_machine.getRuntime() > max_delta)
{
msg << " (" << (slowest_state_machine.getRuntime() * tfactor) << " ms in total now)";
}
msg << ".";
llwarns << msg.str() << llendl;
}

View File

@@ -51,20 +51,7 @@ class Injector
class LLURLRequest : public AICurlEasyRequestStateMachine { class LLURLRequest : public AICurlEasyRequestStateMachine {
public: public:
/** typedef LLHTTPClient::ERequestAction ERequestAction;
* @brief This enumeration is for specifying the type of request.
*/
enum ERequestAction
{
INVALID,
HTTP_HEAD,
HTTP_GET,
HTTP_PUT,
HTTP_POST,
HTTP_DELETE,
HTTP_MOVE, // Caller will need to set 'Destination' header
REQUEST_ACTION_COUNT
};
/** /**
* @brief Turn the request action into an http verb. * @brief Turn the request action into an http verb.
@@ -79,6 +66,11 @@ class LLURLRequest : public AICurlEasyRequestStateMachine {
*/ */
LLURLRequest(ERequestAction action, std::string const& url, Injector* body, LLHTTPClient::ResponderPtr responder, AIHTTPHeaders& headers, bool keepalive, bool is_auth, bool no_compression); LLURLRequest(ERequestAction action, std::string const& url, Injector* body, LLHTTPClient::ResponderPtr responder, AIHTTPHeaders& headers, bool keepalive, bool is_auth, bool no_compression);
/**
* @brief Cached value of responder->getName() as passed to the constructor.
*/
char const* getResponderName(void) const { return mResponderNameCache; }
protected: protected:
// Call abort(), not delete. // Call abort(), not delete.
/*virtual*/ ~LLURLRequest() { } /*virtual*/ ~LLURLRequest() { }
@@ -118,6 +110,7 @@ class LLURLRequest : public AICurlEasyRequestStateMachine {
U32 mBodySize; U32 mBodySize;
LLHTTPClient::ResponderPtr mResponder; LLHTTPClient::ResponderPtr mResponder;
AIHTTPHeaders mHeaders; AIHTTPHeaders mHeaders;
char const* mResponderNameCache;
protected: protected:
// Handle initializing the object. // Handle initializing the object.

View File

@@ -4492,17 +4492,6 @@ This should be as low as possible, but too low may break functionality</string>
<key>Value</key> <key>Value</key>
<integer>16</integer> <integer>16</integer>
</map> </map>
<key>CurlMaximumNumberOfHandles</key>
<map>
<key>Comment</key>
<string>Maximum number of handles curl can use (requires restart)</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>S32</string>
<key>Value</key>
<integer>256</integer>
</map>
<key>CurlTimeoutDNSLookup</key> <key>CurlTimeoutDNSLookup</key>
<map> <map>
<key>Comment</key> <key>Comment</key>
@@ -4545,7 +4534,7 @@ This should be as low as possible, but too low may break functionality</string>
<key>Type</key> <key>Type</key>
<string>U32</string> <string>U32</string>
<key>Value</key> <key>Value</key>
<real>56000</real> <real>7000</real>
</map> </map>
<key>CurlTimeoutLowSpeedTime</key> <key>CurlTimeoutLowSpeedTime</key>
<map> <map>

View File

@@ -45,6 +45,18 @@ if [ ! -z "$XBROWSER" ]; then
echo "$0: Trying some others..." echo "$0: Trying some others..."
fi fi
# Launcher for any desktop.
if which xdg-open >/dev/null; then
xdg-open "$URL"
case $? in
0) exit ;;
1) echo "xdg-open: Error in command line syntax." ;;
2) echo "xdg-open: One of the files passed on the command line did not exist." ;;
3) echo "xdg-open: A required tool could not be found." ;;
4) echo "xdg-open: The action failed." ;;
esac
fi
# Launcher the default GNOME browser. # Launcher the default GNOME browser.
if [ ! -z "$GNOME_DESKTOP_SESSION_ID" ] && which gnome-open >/dev/null; then if [ ! -z "$GNOME_DESKTOP_SESSION_ID" ] && which gnome-open >/dev/null; then
gnome-open "$URL" & gnome-open "$URL" &

View File

@@ -234,6 +234,9 @@ extern BOOL gRandomizeFramerate;
extern BOOL gPeriodicSlowFrame; extern BOOL gPeriodicSlowFrame;
extern BOOL gDebugGL; extern BOOL gDebugGL;
extern void startEngineThread(void);
extern void stopEngineThread(void);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// All from the last globals push... // All from the last globals push...
const F32 DEFAULT_AFK_TIMEOUT = 5.f * 60.f; // time with no input before user flagged as Away From Keyboard const F32 DEFAULT_AFK_TIMEOUT = 5.f * 60.f; // time with no input before user flagged as Away From Keyboard
@@ -657,7 +660,7 @@ bool LLAppViewer::init()
mAlloc.setProfilingEnabled(gSavedSettings.getBOOL("MemProfiling")); mAlloc.setProfilingEnabled(gSavedSettings.getBOOL("MemProfiling"));
AIStateMachine::setMaxCount(gSavedSettings.getU32("StateMachineMaxTime")); AIEngine::setMaxCount(gSavedSettings.getU32("StateMachineMaxTime"));
{ {
AIHTTPTimeoutPolicy policy_tmp( AIHTTPTimeoutPolicy policy_tmp(
@@ -1824,6 +1827,7 @@ bool LLAppViewer::cleanup()
llinfos << "Message system deleted." << llendflush; llinfos << "Message system deleted." << llendflush;
LLApp::stopErrorThread(); // The following call is not thread-safe. Have to stop all threads. LLApp::stopErrorThread(); // The following call is not thread-safe. Have to stop all threads.
stopEngineThread();
AICurlInterface::cleanupCurl(); AICurlInterface::cleanupCurl();
// Cleanup settings last in case other classes reference them. // Cleanup settings last in case other classes reference them.
@@ -1901,6 +1905,9 @@ bool LLAppViewer::initThreads()
LLWatchdog::getInstance()->init(watchdog_killer_callback); LLWatchdog::getInstance()->init(watchdog_killer_callback);
} }
// State machine thread.
startEngineThread();
AICurlInterface::startCurlThread(gSavedSettings.getU32("CurlMaxTotalConcurrentConnections"), AICurlInterface::startCurlThread(gSavedSettings.getU32("CurlMaxTotalConcurrentConnections"),
gSavedSettings.getU32("CurlConcurrentConnectionsPerHost"), gSavedSettings.getU32("CurlConcurrentConnectionsPerHost"),
gSavedSettings.getBOOL("NoVerifySSLCert")); gSavedSettings.getBOOL("NoVerifySSLCert"));
@@ -3854,7 +3861,7 @@ void LLAppViewer::idle()
{ {
LLFastTimer t(FTM_STATEMACHINE); LLFastTimer t(FTM_STATEMACHINE);
AIStateMachine::mainloop(); gMainThreadEngine.mainloop();
} }
// Must wait until both have avatar object and mute list, so poll // Must wait until both have avatar object and mute list, so poll

View File

@@ -995,18 +995,8 @@ LLSnapshotLivePreview::EAspectSizeProblem LLSnapshotLivePreview::getAspectSizePr
// llround(window_height * scale_factor) respectively (since we set uncrop = true). // llround(window_height * scale_factor) respectively (since we set uncrop = true).
F32 raw_aspect = (F32)mRawSnapshotWidth / mRawSnapshotHeight; F32 raw_aspect = (F32)mRawSnapshotWidth / mRawSnapshotHeight;
// The smaller dimension might have been rounded up to 0.5 up or down. Calculate upper and lower limits. // The smaller dimension might have been rounded up to 0.5 up or down. Calculate upper and lower limits.
F32 lower_raw_aspect; F32 lower_raw_aspect = (mRawSnapshotWidth - 0.5) / (mRawSnapshotHeight + 0.5);
F32 upper_raw_aspect; F32 upper_raw_aspect = (mRawSnapshotWidth + 0.5) / (mRawSnapshotHeight - 0.5);
if (mRawSnapshotWidth < mRawSnapshotHeight)
{
lower_raw_aspect = (mRawSnapshotWidth - 0.5) / mRawSnapshotHeight;
upper_raw_aspect = (mRawSnapshotWidth + 0.5) / mRawSnapshotHeight;
}
else
{
lower_raw_aspect = mRawSnapshotWidth / (mRawSnapshotHeight + 0.5);
upper_raw_aspect = mRawSnapshotWidth / (mRawSnapshotHeight - 0.5);
}
// Find out if mRawSnapshot was cropped already. // Find out if mRawSnapshot was cropped already.
bool const allow_vertical_crop = window_height * upper_raw_aspect >= window_width; // mRawSnapshot was cropped horizontally. bool const allow_vertical_crop = window_height * upper_raw_aspect >= window_width; // mRawSnapshot was cropped horizontally.
bool const allow_horizontal_crop = window_width / lower_raw_aspect >= window_height; // mRawSnapshot was cropped vertically. bool const allow_horizontal_crop = window_width / lower_raw_aspect >= window_height; // mRawSnapshot was cropped vertically.

View File

@@ -547,7 +547,7 @@ void LLInventoryModelFetchDescendentsResponder::error(U32 status, const std::str
fetcher->incrFetchCount(-1); fetcher->incrFetchCount(-1);
if (status==499) // timed out if (is_internal_http_error_that_warrants_a_retry(status)) // timed out
{ {
for(LLSD::array_const_iterator folder_it = mRequestSD["folders"].beginArray(); for(LLSD::array_const_iterator folder_it = mRequestSD["folders"].beginArray();
folder_it != mRequestSD["folders"].endArray(); folder_it != mRequestSD["folders"].endArray();

View File

@@ -172,6 +172,7 @@ namespace LLMarketplaceImport
if ((status == MarketplaceErrorCodes::IMPORT_REDIRECT) || if ((status == MarketplaceErrorCodes::IMPORT_REDIRECT) ||
(status == MarketplaceErrorCodes::IMPORT_AUTHENTICATION_ERROR) || (status == MarketplaceErrorCodes::IMPORT_AUTHENTICATION_ERROR) ||
(status == MarketplaceErrorCodes::IMPORT_JOB_LOW_SPEED) ||
(status == MarketplaceErrorCodes::IMPORT_JOB_TIMEOUT)) (status == MarketplaceErrorCodes::IMPORT_JOB_TIMEOUT))
{ {
if (gSavedSettings.getBOOL("InventoryOutboxLogging")) if (gSavedSettings.getBOOL("InventoryOutboxLogging"))
@@ -228,6 +229,7 @@ namespace LLMarketplaceImport
} }
if ((status == MarketplaceErrorCodes::IMPORT_AUTHENTICATION_ERROR) || if ((status == MarketplaceErrorCodes::IMPORT_AUTHENTICATION_ERROR) ||
(status == MarketplaceErrorCodes::IMPORT_JOB_LOW_SPEED) ||
(status == MarketplaceErrorCodes::IMPORT_JOB_TIMEOUT)) (status == MarketplaceErrorCodes::IMPORT_JOB_TIMEOUT))
{ {
if (gSavedSettings.getBOOL("InventoryOutboxLogging")) if (gSavedSettings.getBOOL("InventoryOutboxLogging"))

View File

@@ -35,6 +35,7 @@
#include "llsingleton.h" #include "llsingleton.h"
#include "llstring.h" #include "llstring.h"
#include "llhttpstatuscodes.h"
LLSD getMarketplaceStringSubstitutions(); LLSD getMarketplaceStringSubstitutions();
@@ -44,13 +45,14 @@ namespace MarketplaceErrorCodes
{ {
enum eCode enum eCode
{ {
IMPORT_DONE = 200, IMPORT_DONE = HTTP_OK,
IMPORT_PROCESSING = 202, IMPORT_PROCESSING = HTTP_ACCEPTED,
IMPORT_REDIRECT = 302, IMPORT_REDIRECT = HTTP_FOUND,
IMPORT_AUTHENTICATION_ERROR = 401, IMPORT_AUTHENTICATION_ERROR = HTTP_UNAUTHORIZED,
IMPORT_DONE_WITH_ERRORS = 409, IMPORT_DONE_WITH_ERRORS = HTTP_CONFLICT,
IMPORT_JOB_FAILED = 410, IMPORT_JOB_FAILED = HTTP_GONE,
IMPORT_JOB_TIMEOUT = 499, IMPORT_JOB_LOW_SPEED = HTTP_INTERNAL_ERROR_LOW_SPEED,
IMPORT_JOB_TIMEOUT = HTTP_INTERNAL_ERROR_CURL_TIMEOUT
}; };
} }

View File

@@ -1351,17 +1351,17 @@ void AIMeshUpload::initialize_impl()
set_state(AIMeshUpload_start); set_state(AIMeshUpload_start);
} }
void AIMeshUpload::multiplex_impl() void AIMeshUpload::multiplex_impl(state_type run_state)
{ {
switch (mRunState) switch (run_state)
{ {
case AIMeshUpload_start: case AIMeshUpload_start:
mMeshUpload.run(this, AIMeshUpload_threadFinished); mMeshUpload.run(this, AIMeshUpload_threadFinished);
idle(AIMeshUpload_start); // Wait till the thread finished. idle(); // Wait till the thread finished.
break; break;
case AIMeshUpload_threadFinished: case AIMeshUpload_threadFinished:
mMeshUpload->postRequest(mWholeModelUploadURL, this); mMeshUpload->postRequest(mWholeModelUploadURL, this);
idle(AIMeshUpload_threadFinished); // Wait till the responder finished. idle(); // Wait till the responder finished.
break; break;
case AIMeshUpload_responderFinished: case AIMeshUpload_responderFinished:
finish(); finish();
@@ -1402,14 +1402,6 @@ void LLMeshUploadThread::postRequest(std::string& whole_model_upload_url, AIMesh
} }
} }
void AIMeshUpload::abort_impl()
{
}
void AIMeshUpload::finish_impl()
{
}
void dump_llsd_to_file(const LLSD& content, std::string filename) void dump_llsd_to_file(const LLSD& content, std::string filename)
{ {
if (gSavedSettings.getBOOL("MeshUploadLogXML")) if (gSavedSettings.getBOOL("MeshUploadLogXML"))
@@ -1788,7 +1780,7 @@ void LLMeshLODResponder::completedRaw(U32 status, const std::string& reason,
if (data_size < (S32)mRequestedBytes) if (data_size < (S32)mRequestedBytes)
{ {
if (status == 499 || status == 503) if (is_internal_http_error_that_warrants_a_retry(status) || status == HTTP_SERVICE_UNAVAILABLE)
{ //timeout or service unavailable, try again { //timeout or service unavailable, try again
LLMeshRepository::sHTTPRetryCount++; LLMeshRepository::sHTTPRetryCount++;
gMeshRepo.mThread->loadMeshLOD(mMeshParams, mLOD); gMeshRepo.mThread->loadMeshLOD(mMeshParams, mLOD);
@@ -1842,7 +1834,7 @@ void LLMeshSkinInfoResponder::completedRaw(U32 status, const std::string& reason
if (data_size < (S32)mRequestedBytes) if (data_size < (S32)mRequestedBytes)
{ {
if (status == 499 || status == 503) if (is_internal_http_error_that_warrants_a_retry(status) || status == HTTP_SERVICE_UNAVAILABLE)
{ //timeout or service unavailable, try again { //timeout or service unavailable, try again
LLMeshRepository::sHTTPRetryCount++; LLMeshRepository::sHTTPRetryCount++;
gMeshRepo.mThread->loadMeshSkinInfo(mMeshID); gMeshRepo.mThread->loadMeshSkinInfo(mMeshID);
@@ -1896,7 +1888,7 @@ void LLMeshDecompositionResponder::completedRaw(U32 status, const std::string& r
if (data_size < (S32)mRequestedBytes) if (data_size < (S32)mRequestedBytes)
{ {
if (status == 499 || status == 503) if (is_internal_http_error_that_warrants_a_retry(status) || status == HTTP_SERVICE_UNAVAILABLE)
{ //timeout or service unavailable, try again { //timeout or service unavailable, try again
LLMeshRepository::sHTTPRetryCount++; LLMeshRepository::sHTTPRetryCount++;
gMeshRepo.mThread->loadMeshDecomposition(mMeshID); gMeshRepo.mThread->loadMeshDecomposition(mMeshID);
@@ -1950,7 +1942,7 @@ void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& re
if (data_size < (S32)mRequestedBytes) if (data_size < (S32)mRequestedBytes)
{ {
if (status == 499 || status == 503) if (is_internal_http_error_that_warrants_a_retry(status) || status == HTTP_SERVICE_UNAVAILABLE)
{ //timeout or service unavailable, try again { //timeout or service unavailable, try again
LLMeshRepository::sHTTPRetryCount++; LLMeshRepository::sHTTPRetryCount++;
gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID); gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID);
@@ -2001,13 +1993,13 @@ void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason,
// << "Header responder failed with status: " // << "Header responder failed with status: "
// << status << ": " << reason << llendl; // << status << ": " << reason << llendl;
// 503 (service unavailable) or 499 (timeout) // HTTP_SERVICE_UNAVAILABLE (503) or HTTP_INTERNAL_ERROR_*'s.
// can be due to server load and can be retried // can be due to server load and can be retried
// TODO*: Add maximum retry logic, exponential backoff // TODO*: Add maximum retry logic, exponential backoff
// and (somewhat more optional than the others) retries // and (somewhat more optional than the others) retries
// again after some set period of time // again after some set period of time
if (status == 503 || status == 499) if (is_internal_http_error_that_warrants_a_retry(status) || status == HTTP_SERVICE_UNAVAILABLE)
{ //retry { //retry
LLMeshRepository::sHTTPRetryCount++; LLMeshRepository::sHTTPRetryCount++;
LLMeshRepoThread::HeaderRequest req(mMeshParams); LLMeshRepoThread::HeaderRequest req(mMeshParams);

View File

@@ -445,11 +445,9 @@ public:
protected: protected:
// Implement AIStateMachine. // Implement AIStateMachine.
/*virtual*/ const char* state_str_impl(state_type) const; /*virtual*/ const char* state_str_impl(state_type run_state) const;
/*virtual*/ void initialize_impl(); /*virtual*/ void initialize_impl();
/*virtual*/ void multiplex_impl(); /*virtual*/ void multiplex_impl(state_type run_state);
/*virtual*/ void abort_impl();
/*virtual*/ void finish_impl();
}; };
class LLMeshRepository class LLMeshRepository

View File

@@ -203,16 +203,25 @@ void LLPanelPlace::setLandTypeString(const std::string& land_type)
void LLPanelPlace::setErrorStatus(U32 status, const std::string& reason) void LLPanelPlace::setErrorStatus(U32 status, const std::string& reason)
{ {
// We only really handle 404 and 499 errors // We only really handle 404 and timeout errors
std::string error_text; std::string error_text;
if(status == 404) if (status == HTTP_NOT_FOUND)
{ {
error_text = getString("server_error_text"); error_text = getString("server_error_text");
} }
else if(status == 499) else if (status == HTTP_UNAUTHORIZED) // AIFIXME: Is this indeed the error we get when we don't have access rights for this?
{ {
error_text = getString("server_forbidden_text"); error_text = getString("server_forbidden_text");
} }
else if (status == HTTP_INTERNAL_ERROR_LOW_SPEED || status == HTTP_INTERNAL_ERROR_CURL_TIMEOUT)
{
error_text = getString("internal_timeout_text");
}
else
{
llwarns << "Unexpected error (" << status << "): " << reason << llendl;
error_text = llformat("Unexpected Error (%u): %s", status, reason.c_str());
}
mDescEditor->setText(error_text); mDescEditor->setText(error_text);
} }

View File

@@ -261,10 +261,6 @@ extern S32 gStartImageHeight;
// local globals // local globals
// //
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
static bool gCurlIo;
#endif
static LLHost gAgentSimHost; static LLHost gAgentSimHost;
static BOOL gSkipOptionalUpdate = FALSE; static BOOL gSkipOptionalUpdate = FALSE;

View File

@@ -1334,18 +1334,17 @@ bool LLTextureFetchWorker::doWork(S32 param)
mRequestedOffset--; mRequestedOffset--;
} }
try // Will call callbackHttpGet when curl request completes
{ AIHTTPHeaders headers("Accept", "image/x-j2c");
// Will call callbackHttpGet when curl request completes // Call LLHTTPClient::request directly instead of LLHTTPClient::getByteRange, because we want to pass a NULL AIEngine.
AIHTTPHeaders headers("Accept", "image/x-j2c"); if (mRequestedOffset > 0 || mRequestedSize > 0)
LLHTTPClient::getByteRange(mUrl, mRequestedOffset, mRequestedSize, {
new HTTPGetResponder(mFetcher, mID, LLTimer::getTotalTime(), mRequestedSize, mRequestedOffset, true), headers); headers.addHeader("Range", llformat("bytes=%d-%d", mRequestedOffset, mRequestedOffset + mRequestedSize - 1));
res = true; }
} LLHTTPClient::request(mUrl, LLHTTPClient::HTTP_GET, NULL,
catch(AICurlNoEasyHandle const& error) new HTTPGetResponder(mFetcher, mID, LLTimer::getTotalTime(), mRequestedSize, mRequestedOffset, true),
{ headers/*,*/ DEBUG_CURLIO_PARAM(false), keep_alive, no_does_authentication, allow_compressed_reply, NULL, 0, NULL);
llwarns << error.what() << llendl; res = true;
}
} }
if (!res) if (!res)
{ {
@@ -1370,14 +1369,21 @@ bool LLTextureFetchWorker::doWork(S32 param)
if (mRequestedSize < 0) if (mRequestedSize < 0)
{ {
S32 max_attempts; S32 max_attempts;
if (mGetStatus == HTTP_NOT_FOUND || mGetStatus == 499) if (mGetStatus == HTTP_NOT_FOUND || mGetStatus == HTTP_INTERNAL_ERROR_CURL_TIMEOUT || mGetStatus == HTTP_INTERNAL_ERROR_LOW_SPEED)
{ {
mHTTPFailCount = max_attempts = 1; // Don't retry mHTTPFailCount = max_attempts = 1; // Don't retry
if(mGetStatus == HTTP_NOT_FOUND) if(mGetStatus == HTTP_NOT_FOUND)
llwarns << "Texture missing from server (404): " << mUrl << llendl; llwarns << "Texture missing from server (404): " << mUrl << llendl;
else if (mGetStatus == 499) else if (mGetStatus == HTTP_INTERNAL_ERROR_CURL_TIMEOUT || mGetStatus == HTTP_INTERNAL_ERROR_LOW_SPEED)
{ {
llwarns << "No response from server (499): " << mUrl << llendl; if (mGetStatus == HTTP_INTERNAL_ERROR_CURL_TIMEOUT)
{
llwarns << "No response from server (HTTP_INTERNAL_ERROR_CURL_TIMEOUT): " << mUrl << llendl;
}
else
{
llwarns << "Slow response from server (HTTP_INTERNAL_ERROR_LOW_SPEED): " << mUrl << llendl;
}
SGHostBlackList::add(mUrl, 60.0, mGetStatus); SGHostBlackList::add(mUrl, 60.0, mGetStatus);
} }
//roll back to try UDP //roll back to try UDP

View File

@@ -134,7 +134,7 @@ static bool handleTerrainScaleChanged(const LLSD& inputvalue)
bool handleStateMachineMaxTimeChanged(const LLSD& newvalue) bool handleStateMachineMaxTimeChanged(const LLSD& newvalue)
{ {
F32 StateMachineMaxTime = newvalue.asFloat(); F32 StateMachineMaxTime = newvalue.asFloat();
AIStateMachine::setMaxCount(StateMachineMaxTime); AIEngine::setMaxCount(StateMachineMaxTime);
return true; return true;
} }

View File

@@ -97,7 +97,7 @@ public:
} }
// *TODO: 404 = not supported by the grid // *TODO: 404 = not supported by the grid
// *TODO: increase timeout or handle 499 Expired // *TODO: increase timeout or handle HTTP_INTERNAL_ERROR_* time errors.
// Convert config to LLSD. // Convert config to LLSD.
const Json::Value data = root["data"]; const Json::Value data = root["data"];

View File

@@ -35,7 +35,6 @@
#define LLXMLRPCRESPONDER_H #define LLXMLRPCRESPONDER_H
#include <string> #include <string>
#include "llurlrequest.h" // Injector
#include "llcurl.h" #include "llcurl.h"
#include "llhttpstatuscodes.h" #include "llhttpstatuscodes.h"

View File

@@ -52,4 +52,7 @@
<string name="server_forbidden_text"> <string name="server_forbidden_text">
Information about this location is unavailable due to access restrictions. Please check your permissions with the parcel owner. Information about this location is unavailable due to access restrictions. Please check your permissions with the parcel owner.
</string> </string>
<string name="internal_timeout_text">
Information about this location is unavailable due to a network timeout, please try again later.
</string>
</panel> </panel>

View File

@@ -76,7 +76,6 @@ typedef AIAccess<AIRegisteredStateMachines> registered_statemachines_wat;
// static // static
void AIEvent::Register(AIEvents event, AIStateMachine* statemachine, bool one_shot) void AIEvent::Register(AIEvents event, AIStateMachine* statemachine, bool one_shot)
{ {
statemachine->idle();
registered_statemachines_wat registered_statemachines_w(registered_statemachines_list[event]); registered_statemachines_wat registered_statemachines_w(registered_statemachines_list[event]);
registered_statemachines_w->Register(statemachine, one_shot); registered_statemachines_w->Register(statemachine, one_shot);
} }

View File

@@ -49,19 +49,20 @@ class AIInventoryFetchDescendentsObserver : public LLInventoryFetchDescendentsOb
protected: protected:
/*virtual*/ void done() /*virtual*/ void done()
{ {
mStateMachine->set_state(AIFetchInventoryFolder_folderCompleted); mStateMachine->advance_state(AIFetchInventoryFolder_folderCompleted);
delete this; delete this;
} }
private: private:
AIStateMachine* mStateMachine; LLPointer<AIStateMachine> mStateMachine;
}; };
AIInventoryFetchDescendentsObserver::AIInventoryFetchDescendentsObserver(AIStateMachine* statemachine, LLUUID const& folder) : AIInventoryFetchDescendentsObserver::AIInventoryFetchDescendentsObserver(AIStateMachine* statemachine, LLUUID const& folder) :
mStateMachine(statemachine), mStateMachine(statemachine),
LLInventoryFetchDescendentsObserver(folder) LLInventoryFetchDescendentsObserver(folder)
{ {
mStateMachine->idle(); // Call idle() on the parent state machine before passing it.
llassert(mStateMachine->waiting());
startFetch(); startFetch();
if(isFinished()) if(isFinished())
{ {
@@ -97,14 +98,15 @@ void AIFetchInventoryFolder::initialize_impl(void)
set_state(AIFetchInventoryFolder_checkFolderExists); set_state(AIFetchInventoryFolder_checkFolderExists);
if (!gInventory.isInventoryUsable()) if (!gInventory.isInventoryUsable())
{ {
// This immediately calls this->idle(), and then when the event occurs cont(). idle();
// This calls this->cont() when the event occurs.
AIEvent::Register(AIEvent::LLInventoryModel_mIsAgentInvUsable_true, this); AIEvent::Register(AIEvent::LLInventoryModel_mIsAgentInvUsable_true, this);
} }
} }
void AIFetchInventoryFolder::multiplex_impl(void) void AIFetchInventoryFolder::multiplex_impl(state_type run_state)
{ {
switch (mRunState) switch (run_state)
{ {
case AIFetchInventoryFolder_checkFolderExists: case AIFetchInventoryFolder_checkFolderExists:
{ {
@@ -172,6 +174,7 @@ void AIFetchInventoryFolder::multiplex_impl(void)
} }
case AIFetchInventoryFolder_fetchDescendents: case AIFetchInventoryFolder_fetchDescendents:
{ {
idle(); // Wait till the state is set to AIFetchInventoryFolder_folderCompleted.
// This sets the state to AIFetchInventoryFolder_folderCompleted once the folder is complete. // This sets the state to AIFetchInventoryFolder_folderCompleted once the folder is complete.
new AIInventoryFetchDescendentsObserver(this, mFolderUUID); new AIInventoryFetchDescendentsObserver(this, mFolderUUID);
break; break;
@@ -193,10 +196,6 @@ void AIFetchInventoryFolder::multiplex_impl(void)
} }
} }
void AIFetchInventoryFolder::abort_impl(void)
{
}
void AIFetchInventoryFolder::finish_impl(void) void AIFetchInventoryFolder::finish_impl(void)
{ {
if (mNeedNotifyObservers) if (mNeedNotifyObservers)

View File

@@ -138,10 +138,7 @@ class AIFetchInventoryFolder : public AIStateMachine {
/*virtual*/ void initialize_impl(void); /*virtual*/ void initialize_impl(void);
// Handle mRunState. // Handle mRunState.
/*virtual*/ void multiplex_impl(void); /*virtual*/ void multiplex_impl(state_type run_state);
// Handle aborting from current bs_run state.
/*virtual*/ void abort_impl(void);
// Handle cleaning up from initialization (or post abort) state. // Handle cleaning up from initialization (or post abort) state.
/*virtual*/ void finish_impl(void); /*virtual*/ void finish_impl(void);

View File

@@ -66,7 +66,7 @@ char const* AIFilePicker::state_str_impl(state_type run_state) const
return "UNKNOWN STATE"; return "UNKNOWN STATE";
} }
AIFilePicker::AIFilePicker(void) : mPluginManager(NULL), mAutoKill(false), mCanceled(false) AIFilePicker::AIFilePicker(void) : mPluginManager(NULL), mCanceled(false)
{ {
} }
@@ -345,7 +345,7 @@ void AIFilePicker::initialize_impl(void)
set_state(AIFilePicker_initialize_plugin); set_state(AIFilePicker_initialize_plugin);
} }
void AIFilePicker::multiplex_impl(void) void AIFilePicker::multiplex_impl(state_type run_state)
{ {
mPluginManager->update(); // Give the plugin some CPU for it's messages. mPluginManager->update(); // Give the plugin some CPU for it's messages.
LLPluginClassBasic* plugin = mPluginManager->getPlugin(); LLPluginClassBasic* plugin = mPluginManager->getPlugin();
@@ -355,7 +355,7 @@ void AIFilePicker::multiplex_impl(void)
abort(); abort();
return; return;
} }
switch (mRunState) switch (run_state)
{ {
case AIFilePicker_initialize_plugin: case AIFilePicker_initialize_plugin:
{ {
@@ -430,10 +430,6 @@ void AIFilePicker::multiplex_impl(void)
} }
} }
void AIFilePicker::abort_impl(void)
{
}
void AIFilePicker::finish_impl(void) void AIFilePicker::finish_impl(void)
{ {
if (mPluginManager) if (mPluginManager)
@@ -442,12 +438,6 @@ void AIFilePicker::finish_impl(void)
mPluginManager = NULL; mPluginManager = NULL;
} }
mFilter.clear(); // Check that open is called before calling run (again). mFilter.clear(); // Check that open is called before calling run (again).
if (mAutoKill)
{
// The default behavior is to delete the plugin. This can be overridden in
// the callback by calling run() again.
kill();
}
} }
// This function is called when a new message is received from the plugin. // This function is called when a new message is received from the plugin.
@@ -467,7 +457,7 @@ void AIFilePicker::receivePluginMessage(const LLPluginMessage &message)
if (message_name == "canceled") if (message_name == "canceled")
{ {
LL_DEBUGS("Plugin") << "received message \"canceled\"" << LL_ENDL; LL_DEBUGS("Plugin") << "received message \"canceled\"" << LL_ENDL;
set_state(AIFilePicker_canceled); advance_state(AIFilePicker_canceled);
} }
else if (message_name == "done") else if (message_name == "done")
{ {
@@ -478,7 +468,7 @@ void AIFilePicker::receivePluginMessage(const LLPluginMessage &message)
{ {
mFilenames.push_back(*filename); mFilenames.push_back(*filename);
} }
set_state(AIFilePicker_done); advance_state(AIFilePicker_done);
} }
else else
{ {

View File

@@ -136,8 +136,8 @@ new AIFilePicker
which sets the state to AIFilePicker_canceled or AIFilePicker_done which sets the state to AIFilePicker_canceled or AIFilePicker_done
respectively, causing a call to AIStateMachine::finish(), which calls respectively, causing a call to AIStateMachine::finish(), which calls
AIFilePicker::finish_impl which destroys the plugin (mPluginBase), AIFilePicker::finish_impl which destroys the plugin (mPluginBase),
the plugin manager (mPluginManager) and calls AIStateMachine::kill() the plugin manager (mPluginManager) after which the state machine
causing the AIFilePicker to be deleted. calls unref() causing the AIFilePicker to be deleted.
*/ */
@@ -155,7 +155,7 @@ public:
AIFilePicker(void); AIFilePicker(void);
// Create a dynamically created AIFilePicker object. // Create a dynamically created AIFilePicker object.
static AIFilePicker* create(bool auto_kill = true) { AIFilePicker* filepicker = new AIFilePicker; filepicker->mAutoKill = auto_kill; return filepicker; } static AIFilePicker* create(void) { AIFilePicker* filepicker = new AIFilePicker; return filepicker; }
// The starting directory that the user will be in when the file picker opens // The starting directory that the user will be in when the file picker opens
// will be the same as the directory used the last time the file picker was // will be the same as the directory used the last time the file picker was
@@ -191,7 +191,6 @@ private:
typedef std::map<std::string, std::string> context_map_type; //!< Type of mContextMap. typedef std::map<std::string, std::string> context_map_type; //!< Type of mContextMap.
static AIThreadSafeSimpleDC<context_map_type> sContextMap; //!< Map context (ie, "snapshot" or "image") to last used folder. static AIThreadSafeSimpleDC<context_map_type> sContextMap; //!< Map context (ie, "snapshot" or "image") to last used folder.
std::string mContext; //!< Some key to indicate the context (remembers the folder per key). std::string mContext; //!< Some key to indicate the context (remembers the folder per key).
bool mAutoKill; //!< True if the default behavior is to delete itself after being finished.
// Input variables (cache variable between call to open and run). // Input variables (cache variable between call to open and run).
open_type mOpenType; //!< Set to whether opening a filepicker to select for saving one file, for loading one file, or loading multiple files. open_type mOpenType; //!< Set to whether opening a filepicker to select for saving one file, for loading one file, or loading multiple files.
@@ -215,10 +214,7 @@ protected:
/*virtual*/ void initialize_impl(void); /*virtual*/ void initialize_impl(void);
// Handle mRunState. // Handle mRunState.
/*virtual*/ void multiplex_impl(void); /*virtual*/ void multiplex_impl(state_type run_state);
// Handle aborting from current bs_run state.
/*virtual*/ void abort_impl(void);
// Handle cleaning up from initialization (or post abort) state. // Handle cleaning up from initialization (or post abort) state.
/*virtual*/ void finish_impl(void); /*virtual*/ void finish_impl(void);