Commit 864cf45b authored by jb's avatar jb Committed by Commit bot

Deal with canceled requests when flushing deferred messages.

Flushing deferred messages might lead to a request being canceled
(e.g. when an ImageResource loads a corrupt image). The code didn't
fully take this into account which would cause crashes (and resource
leaks if it would have survived).

BUG=

Review-Url: https://codereview.chromium.org/2425173003
Cr-Commit-Position: refs/heads/master@{#427298}
parent 680d822c
...@@ -173,16 +173,13 @@ bool ResourceDispatcher::OnMessageReceived(const IPC::Message& message) { ...@@ -173,16 +173,13 @@ bool ResourceDispatcher::OnMessageReceived(const IPC::Message& message) {
request_info->deferred_message_queue.push_back(new IPC::Message(message)); request_info->deferred_message_queue.push_back(new IPC::Message(message));
return true; return true;
} }
// Make sure any deferred messages are dispatched before we dispatch more. // Make sure any deferred messages are dispatched before we dispatch more.
if (!request_info->deferred_message_queue.empty()) { if (!request_info->deferred_message_queue.empty()) {
FlushDeferredMessages(request_id);
request_info = GetPendingRequestInfo(request_id);
DCHECK(request_info);
if (request_info->is_deferred) {
request_info->deferred_message_queue.push_back(new IPC::Message(message)); request_info->deferred_message_queue.push_back(new IPC::Message(message));
FlushDeferredMessages(request_id);
return true; return true;
} }
}
DispatchMessage(message); DispatchMessage(message);
return true; return true;
...@@ -580,11 +577,8 @@ void ResourceDispatcher::DispatchMessage(const IPC::Message& message) { ...@@ -580,11 +577,8 @@ void ResourceDispatcher::DispatchMessage(const IPC::Message& message) {
} }
void ResourceDispatcher::FlushDeferredMessages(int request_id) { void ResourceDispatcher::FlushDeferredMessages(int request_id) {
PendingRequestMap::iterator it = pending_requests_.find(request_id); PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
if (it == pending_requests_.end()) // The request could have become invalid. if (!request_info || request_info->is_deferred)
return;
PendingRequestInfo* request_info = it->second.get();
if (request_info->is_deferred)
return; return;
// Because message handlers could result in request_info being destroyed, // Because message handlers could result in request_info being destroyed,
// we need to work with a stack reference to the deferred queue. // we need to work with a stack reference to the deferred queue.
...@@ -595,17 +589,21 @@ void ResourceDispatcher::FlushDeferredMessages(int request_id) { ...@@ -595,17 +589,21 @@ void ResourceDispatcher::FlushDeferredMessages(int request_id) {
q.pop_front(); q.pop_front();
DispatchMessage(*m); DispatchMessage(*m);
delete m; delete m;
// If this request is deferred in the context of the above message, then
// we should honor the same and stop dispatching further messages.
// We need to find the request again in the list as it may have completed // We need to find the request again in the list as it may have completed
// by now and the request_info instance above may be invalid. // by now and the request_info instance above may be invalid.
PendingRequestMap::iterator index = pending_requests_.find(request_id); request_info = GetPendingRequestInfo(request_id);
if (index != pending_requests_.end()) { if (!request_info) {
PendingRequestInfo* pending_request = index->second.get(); // The recipient is gone, the messages won't be handled and
if (pending_request->is_deferred) { // resources they might hold won't be released. Explicitly release
pending_request->deferred_message_queue.swap(q); // them from here so that they won't leak.
ReleaseResourcesInMessageQueue(&q);
return; return;
} }
// If this request is deferred in the context of the above message, then
// we should honor the same and stop dispatching further messages.
if (request_info->is_deferred) {
request_info->deferred_message_queue.swap(q);
return;
} }
} }
} }
......
...@@ -93,6 +93,11 @@ class TestRequestPeer : public RequestPeer { ...@@ -93,6 +93,11 @@ class TestRequestPeer : public RequestPeer {
EXPECT_FALSE(context_->complete); EXPECT_FALSE(context_->complete);
context_->data.append(data->payload(), data->length()); context_->data.append(data->payload(), data->length());
context_->total_encoded_data_length += data->encoded_data_length(); context_->total_encoded_data_length += data->encoded_data_length();
if (context_->cancel_on_receive_data) {
dispatcher_->Cancel(context_->request_id);
context_->cancelled = true;
}
} }
void OnCompletedRequest(int error_code, void OnCompletedRequest(int error_code,
...@@ -117,6 +122,7 @@ class TestRequestPeer : public RequestPeer { ...@@ -117,6 +122,7 @@ class TestRequestPeer : public RequestPeer {
int seen_redirects = 0; int seen_redirects = 0;
bool cancel_on_receive_response = false; bool cancel_on_receive_response = false;
bool cancel_on_receive_data = false;
bool received_response = false; bool received_response = false;
// Data received. If downloading to file, remains empty. // Data received. If downloading to file, remains empty.
...@@ -810,6 +816,113 @@ TEST_F(ResourceDispatcherTest, CancelDeferredRequest) { ...@@ -810,6 +816,113 @@ TEST_F(ResourceDispatcherTest, CancelDeferredRequest) {
EXPECT_EQ(0, peer_context.seen_redirects); EXPECT_EQ(0, peer_context.seen_redirects);
} }
// Checks cancelling a request while flushing deferred requests from
// the FlushDeferredMessages() task.
TEST_F(ResourceDispatcherTest, CancelWhileFlushingDeferredRequests) {
std::unique_ptr<ResourceRequest> request(CreateResourceRequest(false));
TestRequestPeer::Context peer_context;
int request_id = StartAsync(std::move(request), NULL, &peer_context);
// Cancel the request when the data message is handled.
peer_context.cancel_on_receive_data = true;
int id = ConsumeRequestResource();
EXPECT_EQ(0u, queued_messages());
dispatcher()->SetDefersLoading(request_id, true);
NotifyReceivedResponse(id);
NotifySetDataBuffer(id, strlen(kTestPageContents));
NotifyDataReceived(id, kTestPageContents);
// None of the messages should have been processed yet.
EXPECT_EQ("", peer_context.data);
EXPECT_FALSE(peer_context.complete);
EXPECT_EQ(0u, queued_messages());
dispatcher()->SetDefersLoading(request_id, false);
// Make sure that the FlushDeferredMessages() task posted from
// SetDefersLoading() is run. It should dispatch all the deferred
// messages.
base::RunLoop().RunUntilIdle();
// When the deferred DataReceived is dispatched, the handler will
// cancel the request, but the ACK is sent after the handler
// returns, so the cancel request ends up before the ACK in the
// message queue.
ConsumeCancelRequest(id);
ConsumeDataReceived_ACK(id);
// The data was consumed before the handler canceled
// the request, so the data should have been received.
EXPECT_EQ(kTestPageContents, peer_context.data);
EXPECT_FALSE(peer_context.complete);
EXPECT_EQ(0u, queued_messages());
}
// Checks cancelling a request while flushing deferred requests from
// OnMessageReceived().
TEST_F(ResourceDispatcherTest,
CancelWhileFlushingDeferredRequestsFromOnMessageReceived) {
std::unique_ptr<ResourceRequest> request(CreateResourceRequest(false));
TestRequestPeer::Context peer_context;
int request_id = StartAsync(std::move(request), NULL, &peer_context);
// Cancel the request when the data message is handled.
peer_context.cancel_on_receive_data = true;
int id = ConsumeRequestResource();
EXPECT_EQ(0u, queued_messages());
dispatcher()->SetDefersLoading(request_id, true);
NotifyReceivedResponse(id);
NotifySetDataBuffer(id, strlen(kTestPageContents));
NotifyDataReceived(id, kTestPageContents);
// None of the messages should have been processed yet.
EXPECT_EQ("", peer_context.data);
EXPECT_FALSE(peer_context.complete);
EXPECT_EQ(0u, queued_messages());
dispatcher()->SetDefersLoading(request_id, false);
// SetDefersLoading() posts a task to run FlushDeferredMessages() to dispatch
// the deferred messages. Since the message loop hasn't been run yet the
// task hasn't been run either and no IPC-messages should have been
// dispatched.
EXPECT_EQ("", peer_context.data);
EXPECT_FALSE(peer_context.complete);
EXPECT_EQ(0u, queued_messages());
// Calling NotifyRequestComplete() here, before the task from
// SetDefersLoading() has been run, triggers the flush in
// OnMessageReceived().
NotifyRequestComplete(id, strlen(kTestPageContents));
// When the deferred DataReceived is dispatched, the handler will
// cancel the request, but the ACK is sent after the handler
// returns, so the cancel request ends up before the ACK in the
// message queue.
ConsumeCancelRequest(id);
ConsumeDataReceived_ACK(id);
// The data was consumed before the handler canceled
// the request, so the data should have been received.
EXPECT_EQ(kTestPageContents, peer_context.data);
EXPECT_FALSE(peer_context.complete);
EXPECT_EQ(0u, queued_messages());
// Make sure that the FlushDeferredMessages() task posted from
// SetDefersLoading() is run. The messages should already have been
// flushed above, so it should be a NOOP.
base::RunLoop().RunUntilIdle();
// Check that the task didn't change anything.
EXPECT_EQ(kTestPageContents, peer_context.data);
EXPECT_FALSE(peer_context.complete);
EXPECT_EQ(0u, queued_messages());
}
TEST_F(ResourceDispatcherTest, DownloadToFile) { TEST_F(ResourceDispatcherTest, DownloadToFile) {
std::unique_ptr<ResourceRequest> request(CreateResourceRequest(true)); std::unique_ptr<ResourceRequest> request(CreateResourceRequest(true));
TestRequestPeer::Context peer_context; TestRequestPeer::Context peer_context;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment