Fix Cancel() semantics under MSW and other improvements to it

Under MSW, don't set the state to State_Cancelled as soon as Cancel()
was called, as the request was still used from the other threads
afterwards, resulting in race conditions and crashes.

Fix this by just removing the SetState(State_Cancelled) call from the
main thread, as it was redundant anyhow. This also makes the behaviour
correspond to the documentation, which indicates that Cancel() works
asynchronously.

Also ensure, for all backends, that we actually cancel the request only
once, even if public Cancel() is called multiple times. This required
renaming the existing wxWebRequestImpl::Cancel() to DoCancel().
This commit is contained in:
Vadim Zeitlin
2021-01-16 13:49:22 +01:00
parent b428e531ee
commit 508a4f6ca8
9 changed files with 49 additions and 21 deletions

View File

@@ -76,8 +76,6 @@ public:
void Start() wxOVERRIDE; void Start() wxOVERRIDE;
void Cancel() wxOVERRIDE;
wxWebResponseImplPtr GetResponse() const wxOVERRIDE wxWebResponseImplPtr GetResponse() const wxOVERRIDE
{ return m_response; } { return m_response; }
@@ -99,6 +97,8 @@ public:
} }
private: private:
void DoCancel() wxOVERRIDE;
wxWebSessionWinHTTP& m_sessionImpl; wxWebSessionWinHTTP& m_sessionImpl;
wxString m_url; wxString m_url;
HINTERNET m_connect; HINTERNET m_connect;

View File

@@ -86,8 +86,6 @@ public:
void Start() wxOVERRIDE; void Start() wxOVERRIDE;
void Cancel() wxOVERRIDE;
wxWebResponseImplPtr GetResponse() const wxOVERRIDE wxWebResponseImplPtr GetResponse() const wxOVERRIDE
{ return m_response; } { return m_response; }
@@ -120,6 +118,8 @@ public:
{ return m_authChallenge.get(); } { return m_authChallenge.get(); }
private: private:
void DoCancel() wxOVERRIDE;
wxWebSessionURLSession& m_sessionImpl; wxWebSessionURLSession& m_sessionImpl;
wxString m_url; wxString m_url;
WX_NSURLSessionTask m_task; WX_NSURLSessionTask m_task;

View File

@@ -72,7 +72,9 @@ public:
// Precondition for this method checked by caller: current state is idle. // Precondition for this method checked by caller: current state is idle.
virtual void Start() = 0; virtual void Start() = 0;
virtual void Cancel() = 0; // Precondition for this method checked by caller: not idle and not already
// cancelled.
void Cancel();
virtual wxWebResponseImplPtr GetResponse() const = 0; virtual wxWebResponseImplPtr GetResponse() const = 0;
@@ -114,6 +116,8 @@ protected:
wxEvtHandler* handler, wxEvtHandler* handler,
int id); int id);
bool WasCancelled() const { return m_cancelled; }
// Call SetState() with either State_Failed or State_Completed appropriate // Call SetState() with either State_Failed or State_Completed appropriate
// for the response status. // for the response status.
void SetFinalStateFromStatus(); void SetFinalStateFromStatus();
@@ -121,6 +125,9 @@ protected:
static bool IsActiveState(wxWebRequest::State state); static bool IsActiveState(wxWebRequest::State state);
private: private:
// Called from public Cancel() at most once per object.
virtual void DoCancel() = 0;
wxWebSession& m_session; wxWebSession& m_session;
wxEvtHandler* const m_handler; wxEvtHandler* const m_handler;
const int m_id; const int m_id;
@@ -128,6 +135,9 @@ private:
wxFileOffset m_bytesReceived; wxFileOffset m_bytesReceived;
wxCharBuffer m_dataText; wxCharBuffer m_dataText;
// Initially false, set to true after the first call to Cancel().
bool m_cancelled;
wxDECLARE_NO_COPY_CLASS(wxWebRequestImpl); wxDECLARE_NO_COPY_CLASS(wxWebRequestImpl);
}; };

View File

@@ -50,8 +50,6 @@ public:
void Start() wxOVERRIDE; void Start() wxOVERRIDE;
void Cancel() wxOVERRIDE;
wxWebResponseImplPtr GetResponse() const wxOVERRIDE wxWebResponseImplPtr GetResponse() const wxOVERRIDE
{ return m_response; } { return m_response; }
@@ -79,6 +77,8 @@ public:
size_t CURLOnRead(char* buffer, size_t size); size_t CURLOnRead(char* buffer, size_t size);
private: private:
void DoCancel() wxOVERRIDE;
wxWebSessionCURL& m_sessionImpl; wxWebSessionCURL& m_sessionImpl;
CURL* m_handle; CURL* m_handle;

View File

@@ -244,6 +244,11 @@ public:
Note that cancelling is asynchronous, so the application needs to wait Note that cancelling is asynchronous, so the application needs to wait
until the request state becomes @c State_Cancelled to know when the until the request state becomes @c State_Cancelled to know when the
request was really cancelled. request was really cancelled.
Request must be active when Cancel() is called, i.e. the current state
can't be @c State_Idle. However, because it can be difficult to avoid
doing it in some circumstances, Cancel() may be called multiple times
and only a single wxWebRequestEvent will be sent even in this case.
*/ */
void Cancel(); void Cancel();

View File

@@ -66,10 +66,26 @@ wxWebRequestImpl::wxWebRequestImpl(wxWebSession& session,
m_handler(handler), m_handler(handler),
m_id(id), m_id(id),
m_state(wxWebRequest::State_Idle), m_state(wxWebRequest::State_Idle),
m_bytesReceived(0) m_bytesReceived(0),
m_cancelled(false)
{ {
} }
void wxWebRequestImpl::Cancel()
{
if ( m_cancelled )
{
// Nothing to do, don't even assert -- it's ok to call Cancel()
// multiple times, but calling DoCancel() once is enough.
return;
}
wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: cancelling", this);
m_cancelled = true;
DoCancel();
}
void wxWebRequestImpl::SetFinalStateFromStatus() void wxWebRequestImpl::SetFinalStateFromStatus()
{ {
const wxWebResponseImplPtr& resp = GetResponse(); const wxWebResponseImplPtr& resp = GetResponse();
@@ -397,6 +413,9 @@ void wxWebRequest::Cancel()
{ {
wxCHECK_IMPL_VOID(); wxCHECK_IMPL_VOID();
wxCHECK_RET( m_impl->GetState() != wxWebRequest::State_Idle,
"Not yet started requests can't be cancelled" );
m_impl->Cancel(); m_impl->Cancel();
} }

View File

@@ -263,7 +263,7 @@ bool wxWebRequestCURL::StartRequest()
return true; return true;
} }
void wxWebRequestCURL::Cancel() void wxWebRequestCURL::DoCancel()
{ {
m_sessionImpl.CancelRequest(this); m_sessionImpl.CancelRequest(this);
} }

View File

@@ -174,7 +174,7 @@ wxWebRequestWinHTTP::HandleCallback(DWORD dwInternetStatus,
if ( dwStatusInformationLength > 0 ) if ( dwStatusInformationLength > 0 )
{ {
if ( !m_response->ReportAvailableData(dwStatusInformationLength) if ( !m_response->ReportAvailableData(dwStatusInformationLength)
&& GetState() != wxWebRequest::State_Cancelled ) && !WasCancelled() )
SetFailedWithLastError(); SetFailedWithLastError();
} }
else else
@@ -195,7 +195,7 @@ wxWebRequestWinHTTP::HandleCallback(DWORD dwInternetStatus,
// "Failing" with "cancelled" error is not actually an error if // "Failing" with "cancelled" error is not actually an error if
// we're expecting it, i.e. if our Cancel() had been called. // we're expecting it, i.e. if our Cancel() had been called.
if ( asyncResult->dwError == ERROR_WINHTTP_OPERATION_CANCELLED && if ( asyncResult->dwError == ERROR_WINHTTP_OPERATION_CANCELLED &&
GetState() == wxWebRequest::State_Cancelled ) WasCancelled() )
SetState(wxWebRequest::State_Cancelled); SetState(wxWebRequest::State_Cancelled);
else else
SetFailed(asyncResult->dwError); SetFailed(asyncResult->dwError);
@@ -398,17 +398,11 @@ void wxWebRequestWinHTTP::SendRequest()
SetState(wxWebRequest::State_Active); SetState(wxWebRequest::State_Active);
} }
void wxWebRequestWinHTTP::Cancel() void wxWebRequestWinHTTP::DoCancel()
{
wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: cancelling", this);
SetState(wxWebRequest::State_Cancelled);
if ( m_request != NULL )
{ {
wxWinHTTPCloseHandle(m_request); wxWinHTTPCloseHandle(m_request);
m_request = NULL; m_request = NULL;
} }
}
// //
// wxWebResponseWinHTTP // wxWebResponseWinHTTP

View File

@@ -220,7 +220,7 @@ void wxWebRequestURLSession::Start()
[m_task resume]; [m_task resume];
} }
void wxWebRequestURLSession::Cancel() void wxWebRequestURLSession::DoCancel()
{ {
[m_task cancel]; [m_task cancel];
} }