This fixes at least one case (crash report 8407), which comes down to not cleanly informing a responder of failure when the request url is empty (or so badly formed that it isn't a valid url). As a result, the statemachine would abort() without informing the responder - which is bad, sort of. The previous cases where the responder needed to be informed of a failure, namely "statemachine timed_out()" and "bad_socket()" when a socket suddenly becomes bad for unknown reason, have been replaced with the more general 'aborted()' function, which must be called before the statemachine calls abort(). Clearly this has been done for all cases of abort() now, so that if the llerrs fires again in the future then that would have to be after the statemachine calls finish(), which is still as "impossible" as it was - hence the llerrs is still there to make sure. The reason that this seldom happened on SL, and more often on opensim, even more often on home-brew test grids, seems plausible: malformed urls happen more in those cases. I also took the opportunity to improve the robustness of cases where the curl error code is checked: it makes no sense to check what curl gives as error code when an internal error occurred.
273 lines
9.3 KiB
C++
273 lines
9.3 KiB
C++
/**
|
|
* @file aicurleasyrequeststatemachine.cpp
|
|
* @brief Implementation of AICurlEasyRequestStateMachine
|
|
*
|
|
* Copyright (c) 2012, Aleric Inglewood.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution.
|
|
*
|
|
* CHANGELOG
|
|
* and additional copyright holders.
|
|
*
|
|
* 06/05/2012
|
|
* Initial version, written by Aleric Inglewood @ SL
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
#include "aicurleasyrequeststatemachine.h"
|
|
#include "aihttptimeoutpolicy.h"
|
|
#include "llcontrol.h"
|
|
|
|
enum curleasyrequeststatemachine_state_type {
|
|
AICurlEasyRequestStateMachine_addRequest = AIStateMachine::max_state,
|
|
AICurlEasyRequestStateMachine_waitAdded,
|
|
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_removed_after_finished,
|
|
AICurlEasyRequestStateMachine_bad_file_descriptor
|
|
};
|
|
|
|
char const* AICurlEasyRequestStateMachine::state_str_impl(state_type run_state) const
|
|
{
|
|
switch(run_state)
|
|
{
|
|
AI_CASE_RETURN(AICurlEasyRequestStateMachine_addRequest);
|
|
AI_CASE_RETURN(AICurlEasyRequestStateMachine_waitAdded);
|
|
AI_CASE_RETURN(AICurlEasyRequestStateMachine_timedOut);
|
|
AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed);
|
|
AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed_after_finished);
|
|
AI_CASE_RETURN(AICurlEasyRequestStateMachine_bad_file_descriptor);
|
|
}
|
|
return "UNKNOWN STATE";
|
|
}
|
|
|
|
void AICurlEasyRequestStateMachine::initialize_impl(void)
|
|
{
|
|
{
|
|
AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest);
|
|
llassert(curlEasyRequest_w->is_finalized()); // Call finalizeRequest() before calling run().
|
|
curlEasyRequest_w->send_handle_events_to(this);
|
|
}
|
|
mAdded = false;
|
|
mTimedOut = false;
|
|
mFinished = false;
|
|
mHandled = false;
|
|
set_state(AICurlEasyRequestStateMachine_addRequest);
|
|
}
|
|
|
|
// CURL-THREAD
|
|
void AICurlEasyRequestStateMachine::added_to_multi_handle(AICurlEasyRequest_wat&)
|
|
{
|
|
}
|
|
|
|
// CURL-THREAD
|
|
void AICurlEasyRequestStateMachine::finished(AICurlEasyRequest_wat&)
|
|
{
|
|
mFinished = true;
|
|
}
|
|
|
|
// CURL-THREAD
|
|
void AICurlEasyRequestStateMachine::removed_from_multi_handle(AICurlEasyRequest_wat&)
|
|
{
|
|
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
|
|
// a BufferedCurlEasyRequest with a still active Responder.
|
|
advance_state(mFinished ? AICurlEasyRequestStateMachine_removed_after_finished : AICurlEasyRequestStateMachine_removed);
|
|
}
|
|
|
|
// CURL-THREAD
|
|
void AICurlEasyRequestStateMachine::bad_file_descriptor(AICurlEasyRequest_wat&)
|
|
{
|
|
if (!mFinished)
|
|
{
|
|
mFinished = true;
|
|
advance_state(AICurlEasyRequestStateMachine_bad_file_descriptor);
|
|
}
|
|
}
|
|
|
|
#ifdef SHOW_ASSERT
|
|
// CURL-THREAD
|
|
void AICurlEasyRequestStateMachine::queued_for_removal(AICurlEasyRequest_wat&)
|
|
{
|
|
llassert(mFinished || mTimedOut); // See AICurlEasyRequestStateMachine::removed_from_multi_handle
|
|
}
|
|
#endif
|
|
|
|
void AICurlEasyRequestStateMachine::multiplex_impl(state_type run_state)
|
|
{
|
|
switch (run_state)
|
|
{
|
|
case AICurlEasyRequestStateMachine_addRequest:
|
|
{
|
|
set_state(AICurlEasyRequestStateMachine_waitAdded);
|
|
idle(); // Wait till AICurlEasyRequestStateMachine::added_to_multi_handle() is called.
|
|
|
|
// This is a work around for the case that this request had a bad url, in order to avoid a crash later on.
|
|
bool empty_url = AICurlEasyRequest_rat(*mCurlEasyRequest)->getLowercaseServicename().empty();
|
|
if (empty_url)
|
|
{
|
|
AICurlEasyRequest_wat(*mCurlEasyRequest)->aborted(HTTP_INTERNAL_ERROR_OTHER, "Not a valid URL.");
|
|
abort();
|
|
break;
|
|
}
|
|
|
|
// 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
|
|
// immediately after this call.
|
|
mAdded = true;
|
|
mCurlEasyRequest.addRequest(); // This causes the state to be changed, now or later, to
|
|
// AICurlEasyRequestStateMachine_removed_after_finished.
|
|
|
|
// The state at this point is thus one of
|
|
// 1) AICurlEasyRequestStateMachine_waitAdded (idle)
|
|
// 2) AICurlEasyRequestStateMachine_removed_after_finished (running)
|
|
|
|
if (mTotalDelayTimeout > 0.f)
|
|
{
|
|
// Set an inactivity timer.
|
|
// 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.
|
|
mTimer = new AITimer;
|
|
mTimer->setInterval(mTotalDelayTimeout);
|
|
mTimer->run(this, AICurlEasyRequestStateMachine_timedOut, false, false);
|
|
}
|
|
break;
|
|
}
|
|
case AICurlEasyRequestStateMachine_waitAdded:
|
|
{
|
|
// Nothing to do.
|
|
idle();
|
|
break;
|
|
}
|
|
case AICurlEasyRequestStateMachine_timedOut:
|
|
{
|
|
// It is possible that exactly at this point the state changes into
|
|
// AICurlEasyRequestStateMachine_removed_after_finished, with as result that mTimedOut
|
|
// is set while we will continue with that state. Hence that mTimedOut
|
|
// is explicitly reset in that state.
|
|
|
|
// Libcurl failed to deliver within a reasonable time... Abort operation in order
|
|
// to free this curl easy handle and to notify the application that it didn't work.
|
|
mTimedOut = true;
|
|
llassert(mAdded);
|
|
mAdded = false;
|
|
mCurlEasyRequest.removeRequest();
|
|
idle(); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
|
|
break;
|
|
}
|
|
case AICurlEasyRequestStateMachine_removed_after_finished:
|
|
{
|
|
if (!mHandled)
|
|
{
|
|
// Only do this once.
|
|
mHandled = true;
|
|
|
|
if (mTimer)
|
|
{
|
|
// Stop the timer. Note that it's the main thread that generates timer events,
|
|
// so we're certain that there will be no time out anymore if we reach this point.
|
|
mTimer->abort();
|
|
}
|
|
|
|
// The request finished and either data or an error code is available.
|
|
AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest);
|
|
easy_request_w->processOutput();
|
|
}
|
|
|
|
// See above.
|
|
mTimedOut = false;
|
|
/* Fall-Through */
|
|
}
|
|
case AICurlEasyRequestStateMachine_removed:
|
|
{
|
|
// The request was removed from the multi handle.
|
|
|
|
// We're done. If we timed out, abort -- or else the application will
|
|
// think that getResult() will return a valid error code from libcurl.
|
|
if (mTimedOut)
|
|
{
|
|
AICurlEasyRequest_wat(*mCurlEasyRequest)->aborted(HTTP_INTERNAL_ERROR_CURL_LOCKUP, "Request timeout, aborted.");
|
|
abort();
|
|
}
|
|
else
|
|
finish();
|
|
|
|
break;
|
|
}
|
|
case AICurlEasyRequestStateMachine_bad_file_descriptor:
|
|
{
|
|
AICurlEasyRequest_wat(*mCurlEasyRequest)->aborted(HTTP_INTERNAL_ERROR_CURL_BADSOCKET, "File descriptor went bad! Aborted.");
|
|
abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AICurlEasyRequestStateMachine::abort_impl(void)
|
|
{
|
|
DoutEntering(dc::curl, "AICurlEasyRequestStateMachine::abort_impl() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
|
// Revert call to addRequest() if that was already called (and the request wasn't removed again already).
|
|
if (mAdded)
|
|
{
|
|
// Note that it's safe to call this even if the curl thread already removed it, or will remove it
|
|
// after we called this, before processing the remove command; only the curl thread calls
|
|
// MultiHandle::remove_easy_request, which is a no-op when called twice for the same easy request.
|
|
mAdded = false;
|
|
mCurlEasyRequest.removeRequest();
|
|
}
|
|
}
|
|
|
|
void AICurlEasyRequestStateMachine::finish_impl(void)
|
|
{
|
|
DoutEntering(dc::curl, "AICurlEasyRequestStateMachine::finish_impl() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
|
// Revoke callbacks.
|
|
{
|
|
AICurlEasyRequest_wat curl_easy_request_w(*mCurlEasyRequest);
|
|
curl_easy_request_w->send_buffer_events_to(NULL);
|
|
curl_easy_request_w->send_handle_events_to(NULL);
|
|
curl_easy_request_w->revokeCallbacks();
|
|
}
|
|
if (mTimer)
|
|
{
|
|
// Stop the timer, if it's still running.
|
|
if (!mHandled)
|
|
mTimer->abort();
|
|
}
|
|
}
|
|
|
|
AICurlEasyRequestStateMachine::AICurlEasyRequestStateMachine(CWD_ONLY(bool debug)) :
|
|
#ifdef CWDEBUG
|
|
AIStateMachine(debug),
|
|
#endif
|
|
mTotalDelayTimeout(AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout().getTotalDelay())
|
|
{
|
|
Dout(dc::statemachine(mSMDebug), "Calling AICurlEasyRequestStateMachine(void) [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
|
AICurlInterface::Stats::AICurlEasyRequestStateMachine_count++;
|
|
}
|
|
|
|
void AICurlEasyRequestStateMachine::setTotalDelayTimeout(F32 totalDelayTimeout)
|
|
{
|
|
mTotalDelayTimeout = totalDelayTimeout;
|
|
}
|
|
|
|
AICurlEasyRequestStateMachine::~AICurlEasyRequestStateMachine()
|
|
{
|
|
Dout(dc::statemachine(mSMDebug), "Calling ~AICurlEasyRequestStateMachine() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
|
--AICurlInterface::Stats::AICurlEasyRequestStateMachine_count;
|
|
}
|
|
|