Fix crash when libcurl calls read callback before calling socket callback.
In days of usage this has never happened before, but apparently it's possible. The solution chosen is to create the AIHTTPTimeout object on the fly when it doesn't exist and let it be picked up later when the CurlSocketInfo for the transfer is created.
This commit is contained in:
@@ -1202,6 +1202,25 @@ void CurlEasyRequest::set_timeout_opts(void)
|
|||||||
setopt(CURLOPT_TIMEOUT, mTimeoutPolicy->getCurlTransaction());
|
setopt(CURLOPT_TIMEOUT, mTimeoutPolicy->getCurlTransaction());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CurlEasyRequest::create_timeout_object(ThreadSafeCurlEasyRequest* lockobj)
|
||||||
|
{
|
||||||
|
mTimeout = new curlthread::HTTPTimeout(mTimeoutPolicy, lockobj);
|
||||||
|
}
|
||||||
|
|
||||||
|
LLPointer<curlthread::HTTPTimeout>& CurlEasyRequest::get_timeout_object(ThreadSafeCurlEasyRequest* lockobj)
|
||||||
|
{
|
||||||
|
if (mTimeoutIsOrphan)
|
||||||
|
{
|
||||||
|
mTimeoutIsOrphan = false;
|
||||||
|
llassert_always(mTimeout);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
create_timeout_object(lockobj);
|
||||||
|
}
|
||||||
|
return mTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
void CurlEasyRequest::print_curl_timings(void) const
|
void CurlEasyRequest::print_curl_timings(void) const
|
||||||
{
|
{
|
||||||
double t;
|
double t;
|
||||||
|
|||||||
@@ -325,6 +325,9 @@ class CurlEasyRequest : public CurlEasyHandle {
|
|||||||
// Used in applyDefaultOptions.
|
// Used in applyDefaultOptions.
|
||||||
static CURLcode curlCtxCallback(CURL* curl, void* sslctx, void* parm);
|
static CURLcode curlCtxCallback(CURL* curl, void* sslctx, void* parm);
|
||||||
|
|
||||||
|
// Called from get_timeout_object and httptimeout.
|
||||||
|
void create_timeout_object(ThreadSafeCurlEasyRequest* lockobj);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Set default options that we want applied to all curl easy handles.
|
// Set default options that we want applied to all curl easy handles.
|
||||||
void applyDefaultOptions(void);
|
void applyDefaultOptions(void);
|
||||||
@@ -370,7 +373,8 @@ class CurlEasyRequest : public CurlEasyHandle {
|
|||||||
|
|
||||||
AIHTTPTimeoutPolicy const* mTimeoutPolicy;
|
AIHTTPTimeoutPolicy const* mTimeoutPolicy;
|
||||||
std::string mLowercaseHostname; // Lowercase hostname (canonicalized) extracted from the url.
|
std::string mLowercaseHostname; // Lowercase hostname (canonicalized) extracted from the url.
|
||||||
LLPointer<curlthread::HTTPTimeout> mTimeout;
|
LLPointer<curlthread::HTTPTimeout> mTimeout;// Timeout administration object associated with last created CurlSocketInfo.
|
||||||
|
bool mTimeoutIsOrphan; // Set to true when mTimeout is not (yet) associated with a CurlSocketInfo.
|
||||||
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
|
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
|
||||||
public:
|
public:
|
||||||
bool mDebugIsGetMethod;
|
bool mDebugIsGetMethod;
|
||||||
@@ -381,9 +385,10 @@ class CurlEasyRequest : public CurlEasyHandle {
|
|||||||
AIHTTPTimeoutPolicy const* getTimeoutPolicy(void) const { return mTimeoutPolicy; }
|
AIHTTPTimeoutPolicy const* getTimeoutPolicy(void) const { return mTimeoutPolicy; }
|
||||||
std::string const& getLowercaseHostname(void) const { return mLowercaseHostname; }
|
std::string const& getLowercaseHostname(void) const { return mLowercaseHostname; }
|
||||||
// Called by CurlSocketInfo to allow access to the last (after a redirect) HTTPTimeout object related to this request.
|
// Called by CurlSocketInfo to allow access to the last (after a redirect) HTTPTimeout object related to this request.
|
||||||
void set_timeout_object(LLPointer<curlthread::HTTPTimeout>& timeout) { mTimeout = timeout; }
|
// This creates mTimeout (unless mTimeoutIsOrphan is set in which case it adopts the orphan).
|
||||||
// And the accessor for it.
|
LLPointer<curlthread::HTTPTimeout>& get_timeout_object(ThreadSafeCurlEasyRequest* lockobj);
|
||||||
LLPointer<curlthread::HTTPTimeout>& httptimeout(void) { return mTimeout; }
|
// Accessor for mTimeout with optional creation of orphaned object (if lockobj != NULL).
|
||||||
|
LLPointer<curlthread::HTTPTimeout>& httptimeout(ThreadSafeCurlEasyRequest* lockobj = NULL) { if (lockobj && !mTimeout) create_timeout_object(lockobj); 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) const { return mTimeout && mTimeout->has_stalled(); }
|
||||||
|
|
||||||
@@ -391,7 +396,7 @@ class CurlEasyRequest : public CurlEasyHandle {
|
|||||||
// This class may only be created by constructing a ThreadSafeCurlEasyRequest.
|
// This class may only be created by constructing a ThreadSafeCurlEasyRequest.
|
||||||
friend class ThreadSafeCurlEasyRequest;
|
friend class ThreadSafeCurlEasyRequest;
|
||||||
// Throws AICurlNoEasyHandle.
|
// Throws AICurlNoEasyHandle.
|
||||||
CurlEasyRequest(void) : mHeaders(NULL), mEventsTarget(NULL), mResult(CURLE_FAILED_INIT), mTimeoutPolicy(NULL)
|
CurlEasyRequest(void) : mHeaders(NULL), mEventsTarget(NULL), mResult(CURLE_FAILED_INIT), mTimeoutPolicy(NULL), mTimeoutIsOrphan(false)
|
||||||
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
|
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
|
||||||
, mDebugIsGetMethod(false)
|
, mDebugIsGetMethod(false)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -773,8 +773,7 @@ CurlSocketInfo::CurlSocketInfo(MultiHandle& multi_handle, CURL* easy, curl_socke
|
|||||||
// CurlSocketInfo objects for a request and we need upload_finished() to be called on the HTTPTimeout
|
// CurlSocketInfo objects for a request and we need upload_finished() to be called on the HTTPTimeout
|
||||||
// object related to THIS CurlSocketInfo.
|
// object related to THIS CurlSocketInfo.
|
||||||
AICurlEasyRequest_wat easy_request_w(*lockobj);
|
AICurlEasyRequest_wat easy_request_w(*lockobj);
|
||||||
mTimeout = new HTTPTimeout(easy_request_w->getTimeoutPolicy(), lockobj);
|
mTimeout = easy_request_w->get_timeout_object(lockobj);
|
||||||
easy_request_w->set_timeout_object(mTimeout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CurlSocketInfo::~CurlSocketInfo()
|
CurlSocketInfo::~CurlSocketInfo()
|
||||||
@@ -2171,7 +2170,11 @@ size_t CurlResponderBuffer::curlReadCallback(char* data, size_t size, size_t nme
|
|||||||
AICurlResponderBuffer_wat buffer_w(*lockobj);
|
AICurlResponderBuffer_wat buffer_w(*lockobj);
|
||||||
buffer_w->mLastRead = buffer_w->getInput()->readAfter(sChannels.out(), buffer_w->mLastRead, (U8*)data, bytes);
|
buffer_w->mLastRead = buffer_w->getInput()->readAfter(sChannels.out(), buffer_w->mLastRead, (U8*)data, bytes);
|
||||||
buffer_w->mRequestTransferedBytes += bytes; // Accumulate data sent to the server.
|
buffer_w->mRequestTransferedBytes += bytes; // Accumulate data sent to the server.
|
||||||
if (buffered_easy_request_w->httptimeout()->data_sent(bytes)) // Timeout administration.
|
// Timeout administration. Note that it can happen that we get here
|
||||||
|
// before the socket callback has been called, because the silly libcurl
|
||||||
|
// writes headers without informing us. In that case it's OK to create
|
||||||
|
// the Timeout object on the fly, so pass lockobj.
|
||||||
|
if (buffered_easy_request_w->httptimeout(lockobj)->data_sent(bytes))
|
||||||
{
|
{
|
||||||
// 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;
|
||||||
|
|||||||
Reference in New Issue
Block a user