Index: net/url_request/url_request_unittest.cc |
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc |
index db31eb519d958267d3f8d4bbed519c63ce68b44a..f3845d136c9b55c7fa4a01dc88ac4cd51f95f098 100644 |
--- a/net/url_request/url_request_unittest.cc |
+++ b/net/url_request/url_request_unittest.cc |
@@ -385,8 +385,18 @@ class BlockingNetworkDelegate : public TestNetworkDelegate { |
USER_CALLBACK, // User takes care of doing a callback. |retval_| and |
// |auth_retval_| are ignored. In every blocking stage the |
// message loop is quit. |
+ USER_NOTIFY, // User is notified by a provided callback of the |
+ // blocking, and synchronously returns instructions |
+ // for handling it. |
}; |
+ using NotificationCallback = |
+ base::Callback<Error(const CompletionCallback&, const URLRequest*)>; |
+ |
+ using NotificationAuthCallback = |
+ base::Callback<NetworkDelegate::AuthRequiredResponse(const AuthCallback&, |
+ const URLRequest*)>; |
+ |
// Creates a delegate which does not block at all. |
explicit BlockingNetworkDelegate(BlockMode block_mode); |
@@ -423,6 +433,17 @@ class BlockingNetworkDelegate : public TestNetworkDelegate { |
block_on_ = block_on; |
} |
+ // Only valid if |block_mode_| == USER_NOTIFY |
+ void set_notification_callback( |
+ const NotificationCallback& notification_callback) { |
+ notification_callback_ = notification_callback; |
+ } |
+ |
+ void set_notification_auth_callback( |
+ const NotificationAuthCallback& notification_auth_callback) { |
+ notification_auth_callback_ = notification_auth_callback; |
+ } |
+ |
// Allows the user to check in which state did we block. |
Stage stage_blocked_for_callback() const { |
EXPECT_EQ(USER_CALLBACK, block_mode_); |
@@ -461,7 +482,9 @@ class BlockingNetworkDelegate : public TestNetworkDelegate { |
// Checks whether we should block in |stage|. If yes, returns an error code |
// and optionally sets up callback based on |block_mode_|. If no, returns OK. |
- int MaybeBlockStage(Stage stage, const CompletionCallback& callback); |
+ int MaybeBlockStage(Stage stage, |
+ const URLRequest* request, |
+ const CompletionCallback& callback); |
// Configuration parameters, can be adjusted by public methods: |
const BlockMode block_mode_; |
@@ -488,6 +511,10 @@ class BlockingNetworkDelegate : public TestNetworkDelegate { |
CompletionCallback callback_; |
AuthCallback auth_callback_; |
+ // Callback to request user instructions for blocking. |
+ NotificationCallback notification_callback_; |
+ NotificationAuthCallback notification_auth_callback_; |
+ |
base::WeakPtrFactory<BlockingNetworkDelegate> weak_factory_; |
DISALLOW_COPY_AND_ASSIGN(BlockingNetworkDelegate); |
@@ -547,7 +574,7 @@ int BlockingNetworkDelegate::OnBeforeURLRequest( |
if (!redirect_url_.is_empty()) |
*new_url = redirect_url_; |
- return MaybeBlockStage(ON_BEFORE_URL_REQUEST, callback); |
+ return MaybeBlockStage(ON_BEFORE_URL_REQUEST, request, callback); |
} |
int BlockingNetworkDelegate::OnBeforeStartTransaction( |
@@ -556,7 +583,7 @@ int BlockingNetworkDelegate::OnBeforeStartTransaction( |
HttpRequestHeaders* headers) { |
TestNetworkDelegate::OnBeforeStartTransaction(request, callback, headers); |
- return MaybeBlockStage(ON_BEFORE_SEND_HEADERS, callback); |
+ return MaybeBlockStage(ON_BEFORE_SEND_HEADERS, request, callback); |
} |
int BlockingNetworkDelegate::OnHeadersReceived( |
@@ -571,7 +598,7 @@ int BlockingNetworkDelegate::OnHeadersReceived( |
override_response_headers, |
allowed_unsafe_redirect_url); |
- return MaybeBlockStage(ON_HEADERS_RECEIVED, callback); |
+ return MaybeBlockStage(ON_HEADERS_RECEIVED, request, callback); |
} |
NetworkDelegate::AuthRequiredResponse BlockingNetworkDelegate::OnAuthRequired( |
@@ -609,6 +636,11 @@ NetworkDelegate::AuthRequiredResponse BlockingNetworkDelegate::OnAuthRequired( |
base::ThreadTaskRunnerHandle::Get()->PostTask( |
FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); |
return AUTH_REQUIRED_RESPONSE_IO_PENDING; |
+ |
+ case USER_NOTIFY: |
+ // If the callback returns ERR_IO_PENDING, the user has accepted |
+ // responsibility for running the callback in the future. |
+ return notification_auth_callback_.Run(callback, request); |
} |
NOTREACHED(); |
return AUTH_REQUIRED_RESPONSE_NO_ACTION; // Dummy value. |
@@ -623,6 +655,7 @@ void BlockingNetworkDelegate::Reset() { |
int BlockingNetworkDelegate::MaybeBlockStage( |
BlockingNetworkDelegate::Stage stage, |
+ const URLRequest* request, |
const CompletionCallback& callback) { |
// Check that the user has provided callback for the previous blocked stage. |
EXPECT_EQ(NOT_BLOCKED, stage_blocked_for_callback_); |
@@ -648,6 +681,11 @@ int BlockingNetworkDelegate::MaybeBlockStage( |
base::ThreadTaskRunnerHandle::Get()->PostTask( |
FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); |
return ERR_IO_PENDING; |
+ |
+ case USER_NOTIFY: |
+ // If the callback returns ERR_IO_PENDING, the user has accepted |
+ // responsibility for running the callback in the future. |
+ return notification_callback_.Run(callback, request); |
} |
NOTREACHED(); |
return 0; |
@@ -7843,7 +7881,7 @@ TEST_F(URLRequestTestHTTP, NetworkAccessedClearOnLoadOnlyFromCache) { |
EXPECT_FALSE(req->response_info().network_accessed); |
} |
-// Test that a single job with a throttled priority completes |
+// Test that a single job with a THROTTLED priority completes |
// correctly in the absence of contention. |
TEST_F(URLRequestTestHTTP, ThrottledPriority) { |
ASSERT_TRUE(http_test_server()->Start()); |
@@ -7858,6 +7896,100 @@ TEST_F(URLRequestTestHTTP, ThrottledPriority) { |
EXPECT_TRUE(req->status().is_success()); |
} |
+// A class to hold state for responding to USER_NOTIFY callbacks from |
+// BlockingNetworkDelegate. |
+class NotificationCallbackHandler { |
+ public: |
+ // Default constructed object doesn't block anything. |
+ NotificationCallbackHandler() {} |
+ |
+ void AddURLRequestToBlockList(const URLRequest* request) { |
+ requests_to_block_.insert(request); |
+ } |
+ |
+ Error ShouldBlockRequest(const CompletionCallback& callback, |
+ const URLRequest* request) { |
+ if (requests_to_block_.find(request) == requests_to_block_.end()) { |
+ return OK; |
+ } |
+ |
+ DCHECK(blocked_callbacks_.find(request) == blocked_callbacks_.end()); |
+ blocked_callbacks_[request] = callback; |
+ return ERR_IO_PENDING; |
+ } |
+ |
+ // Erases object's memory of blocked callbacks as a side effect. |
+ void GetBlockedCallbacks( |
+ std::map<const URLRequest*, CompletionCallback>* blocked_callbacks) { |
+ blocked_callbacks_.swap(*blocked_callbacks); |
+ } |
+ |
+ private: |
+ std::set<const URLRequest*> requests_to_block_; |
+ std::map<const URLRequest*, CompletionCallback> blocked_callbacks_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(NotificationCallbackHandler); |
+}; |
+ |
+TEST_F(URLRequestTestHTTP, MultiThrottledPriority) { |
+ ASSERT_TRUE(http_test_server()->Start()); |
+ |
+ NotificationCallbackHandler notification_handler; |
+ TestDelegate d; |
+ BlockingNetworkDelegate network_delegate( |
+ BlockingNetworkDelegate::USER_NOTIFY); |
+ network_delegate.set_block_on( |
+ BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS); |
+ network_delegate.set_notification_callback( |
+ base::Bind(&NotificationCallbackHandler::ShouldBlockRequest, |
+ // Both objects are owned by this function, and |
+ // |*network_delegate| will be destroyed first, so |
+ // it's safe to pass it an unretained pointer. |
+ base::Unretained(¬ification_handler))); |
+ |
+ TestURLRequestContext context(true); |
+ context.set_network_delegate(&network_delegate); |
+ context.Init(); |
+ |
+ GURL test_url(http_test_server()->GetURL("/")); |
+ std::unique_ptr<URLRequest> req1( |
+ context.CreateRequest(test_url, THROTTLED, &d)); |
+ notification_handler.AddURLRequestToBlockList(req1.get()); |
+ |
+ std::unique_ptr<URLRequest> req2( |
+ context.CreateRequest(test_url, THROTTLED, &d)); |
+ notification_handler.AddURLRequestToBlockList(req2.get()); |
+ |
+ std::unique_ptr<URLRequest> req3( |
+ context.CreateRequest(test_url, THROTTLED, &d)); |
+ req1->Start(); |
+ req2->Start(); |
+ req3->Start(); |
+ base::RunLoop().RunUntilIdle(); |
+ |
+ // The first two requests should be blocked based on the notification |
+ // callback, and their status should have blocked the third request. |
+ EXPECT_TRUE(req1->status().is_io_pending()); |
+ EXPECT_TRUE(req2->status().is_io_pending()); |
+ EXPECT_TRUE(req3->status().is_io_pending()); |
+ |
+ std::map<const URLRequest*, CompletionCallback> blocked_callbacks; |
+ notification_handler.GetBlockedCallbacks(&blocked_callbacks); |
+ ASSERT_EQ(2u, blocked_callbacks.size()); |
+ ASSERT_TRUE(blocked_callbacks.find(req1.get()) != blocked_callbacks.end()); |
+ ASSERT_TRUE(blocked_callbacks.find(req2.get()) != blocked_callbacks.end()); |
+ |
+ // Unblocking req2 should allow it to run until end *and* then allow req3 |
+ // to run until it ends. |
+ blocked_callbacks[req2.get()].Run(OK); |
+ base::RunLoop().RunUntilIdle(); |
+ notification_handler.GetBlockedCallbacks(&blocked_callbacks); |
+ EXPECT_EQ(0u, blocked_callbacks.size()); |
+ EXPECT_TRUE(req1->status().is_io_pending()); |
+ EXPECT_EQ(URLRequestStatus::SUCCESS, req2->status().status()); |
+ EXPECT_EQ(URLRequestStatus::SUCCESS, req3->status().status()); |
+} |
+ |
TEST_F(URLRequestTestHTTP, RawBodyBytesNoContentEncoding) { |
ASSERT_TRUE(http_test_server()->Start()); |