| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/proxy/multi_threaded_proxy_resolver.h" |
| |
| #include "base/message_loop.h" |
| #include "base/stl_util.h" |
| #include "base/string_util.h" |
| #include "base/stringprintf.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "googleurl/src/gurl.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/net_log.h" |
| #include "net/base/net_log_unittest.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/proxy/proxy_info.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| // A synchronous mock ProxyResolver implementation, which can be used in |
| // conjunction with MultiThreadedProxyResolver. |
| // - returns a single-item proxy list with the query's host. |
| class MockProxyResolver : public ProxyResolver { |
| public: |
| MockProxyResolver() |
| : ProxyResolver(true /*expects_pac_bytes*/), |
| wrong_loop_(MessageLoop::current()), |
| request_count_(0), |
| purge_count_(0) {} |
| |
| // ProxyResolver implementation. |
| virtual int GetProxyForURL(const GURL& query_url, |
| ProxyInfo* results, |
| const CompletionCallback& callback, |
| RequestHandle* request, |
| const BoundNetLog& net_log) OVERRIDE { |
| if (resolve_latency_ != base::TimeDelta()) |
| base::PlatformThread::Sleep(resolve_latency_); |
| |
| CheckIsOnWorkerThread(); |
| |
| EXPECT_TRUE(callback.is_null()); |
| EXPECT_TRUE(request == NULL); |
| |
| // Write something into |net_log| (doesn't really have any meaning.) |
| net_log.BeginEvent(NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE); |
| |
| results->UseNamedProxy(query_url.host()); |
| |
| // Return a success code which represents the request's order. |
| return request_count_++; |
| } |
| |
| virtual void CancelRequest(RequestHandle request) OVERRIDE { |
| NOTREACHED(); |
| } |
| |
| virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE { |
| NOTREACHED(); |
| return LOAD_STATE_IDLE; |
| } |
| |
| virtual LoadState GetLoadStateThreadSafe( |
| RequestHandle request) const OVERRIDE { |
| NOTREACHED(); |
| return LOAD_STATE_IDLE; |
| } |
| |
| virtual void CancelSetPacScript() OVERRIDE { |
| NOTREACHED(); |
| } |
| |
| virtual int SetPacScript( |
| const scoped_refptr<ProxyResolverScriptData>& script_data, |
| const CompletionCallback& callback) OVERRIDE { |
| CheckIsOnWorkerThread(); |
| last_script_data_ = script_data; |
| return OK; |
| } |
| |
| virtual void PurgeMemory() OVERRIDE { |
| CheckIsOnWorkerThread(); |
| ++purge_count_; |
| } |
| |
| int purge_count() const { return purge_count_; } |
| int request_count() const { return request_count_; } |
| |
| const ProxyResolverScriptData* last_script_data() const { |
| return last_script_data_; |
| } |
| |
| void SetResolveLatency(base::TimeDelta latency) { |
| resolve_latency_ = latency; |
| } |
| |
| private: |
| void CheckIsOnWorkerThread() { |
| // We should be running on the worker thread -- while we don't know the |
| // message loop of MultiThreadedProxyResolver's worker thread, we do |
| // know that it is going to be distinct from the loop running the |
| // test, so at least make sure it isn't the main loop. |
| EXPECT_NE(MessageLoop::current(), wrong_loop_); |
| } |
| |
| MessageLoop* wrong_loop_; |
| int request_count_; |
| int purge_count_; |
| scoped_refptr<ProxyResolverScriptData> last_script_data_; |
| base::TimeDelta resolve_latency_; |
| }; |
| |
| |
| // A mock synchronous ProxyResolver which can be set to block upon reaching |
| // GetProxyForURL(). |
| // TODO(eroman): WaitUntilBlocked() *must* be called before calling Unblock(), |
| // otherwise there will be a race on |should_block_| since it is |
| // read without any synchronization. |
| class BlockableProxyResolver : public MockProxyResolver { |
| public: |
| BlockableProxyResolver() |
| : should_block_(false), |
| unblocked_(true, true), |
| blocked_(true, false) { |
| } |
| |
| void Block() { |
| should_block_ = true; |
| unblocked_.Reset(); |
| } |
| |
| void Unblock() { |
| should_block_ = false; |
| blocked_.Reset(); |
| unblocked_.Signal(); |
| } |
| |
| void WaitUntilBlocked() { |
| blocked_.Wait(); |
| } |
| |
| virtual int GetProxyForURL(const GURL& query_url, |
| ProxyInfo* results, |
| const CompletionCallback& callback, |
| RequestHandle* request, |
| const BoundNetLog& net_log) OVERRIDE { |
| if (should_block_) { |
| blocked_.Signal(); |
| unblocked_.Wait(); |
| } |
| |
| return MockProxyResolver::GetProxyForURL( |
| query_url, results, callback, request, net_log); |
| } |
| |
| private: |
| bool should_block_; |
| base::WaitableEvent unblocked_; |
| base::WaitableEvent blocked_; |
| }; |
| |
| // ForwardingProxyResolver forwards all requests to |impl|. |
| class ForwardingProxyResolver : public ProxyResolver { |
| public: |
| explicit ForwardingProxyResolver(ProxyResolver* impl) |
| : ProxyResolver(impl->expects_pac_bytes()), |
| impl_(impl) {} |
| |
| virtual int GetProxyForURL(const GURL& query_url, |
| ProxyInfo* results, |
| const CompletionCallback& callback, |
| RequestHandle* request, |
| const BoundNetLog& net_log) OVERRIDE { |
| return impl_->GetProxyForURL( |
| query_url, results, callback, request, net_log); |
| } |
| |
| virtual void CancelRequest(RequestHandle request) OVERRIDE { |
| impl_->CancelRequest(request); |
| } |
| |
| virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE { |
| NOTREACHED(); |
| return LOAD_STATE_IDLE; |
| } |
| |
| virtual LoadState GetLoadStateThreadSafe( |
| RequestHandle request) const OVERRIDE { |
| NOTREACHED(); |
| return LOAD_STATE_IDLE; |
| } |
| |
| virtual void CancelSetPacScript() OVERRIDE { |
| impl_->CancelSetPacScript(); |
| } |
| |
| virtual int SetPacScript( |
| const scoped_refptr<ProxyResolverScriptData>& script_data, |
| const CompletionCallback& callback) OVERRIDE { |
| return impl_->SetPacScript(script_data, callback); |
| } |
| |
| virtual void PurgeMemory() OVERRIDE { |
| impl_->PurgeMemory(); |
| } |
| |
| private: |
| ProxyResolver* impl_; |
| }; |
| |
| // This factory returns ProxyResolvers that forward all requests to |
| // |resolver|. |
| class ForwardingProxyResolverFactory : public ProxyResolverFactory { |
| public: |
| explicit ForwardingProxyResolverFactory(ProxyResolver* resolver) |
| : ProxyResolverFactory(resolver->expects_pac_bytes()), |
| resolver_(resolver) {} |
| |
| virtual ProxyResolver* CreateProxyResolver() OVERRIDE { |
| return new ForwardingProxyResolver(resolver_); |
| } |
| |
| private: |
| ProxyResolver* resolver_; |
| }; |
| |
| // This factory returns new instances of BlockableProxyResolver. |
| class BlockableProxyResolverFactory : public ProxyResolverFactory { |
| public: |
| BlockableProxyResolverFactory() : ProxyResolverFactory(true) {} |
| |
| ~BlockableProxyResolverFactory() { |
| STLDeleteElements(&resolvers_); |
| } |
| |
| virtual ProxyResolver* CreateProxyResolver() OVERRIDE { |
| BlockableProxyResolver* resolver = new BlockableProxyResolver; |
| resolvers_.push_back(resolver); |
| return new ForwardingProxyResolver(resolver); |
| } |
| |
| std::vector<BlockableProxyResolver*> resolvers() { |
| return resolvers_; |
| } |
| |
| private: |
| std::vector<BlockableProxyResolver*> resolvers_; |
| }; |
| |
| TEST(MultiThreadedProxyResolverTest, SingleThread_Basic) { |
| const size_t kNumThreads = 1u; |
| scoped_ptr<MockProxyResolver> mock(new MockProxyResolver); |
| MultiThreadedProxyResolver resolver( |
| new ForwardingProxyResolverFactory(mock.get()), kNumThreads); |
| |
| int rv; |
| |
| EXPECT_TRUE(resolver.expects_pac_bytes()); |
| |
| // Call SetPacScriptByData() -- verify that it reaches the synchronous |
| // resolver. |
| TestCompletionCallback set_script_callback; |
| rv = resolver.SetPacScript( |
| ProxyResolverScriptData::FromUTF8("pac script bytes"), |
| set_script_callback.callback()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| EXPECT_EQ(OK, set_script_callback.WaitForResult()); |
| EXPECT_EQ(ASCIIToUTF16("pac script bytes"), |
| mock->last_script_data()->utf16()); |
| |
| // Start request 0. |
| TestCompletionCallback callback0; |
| CapturingBoundNetLog log0; |
| ProxyInfo results0; |
| rv = resolver.GetProxyForURL(GURL("http://request0"), &results0, |
| callback0.callback(), NULL, log0.bound()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Wait for request 0 to finish. |
| rv = callback0.WaitForResult(); |
| EXPECT_EQ(0, rv); |
| EXPECT_EQ("PROXY request0:80", results0.ToPacString()); |
| |
| // The mock proxy resolver should have written 1 log entry. And |
| // on completion, this should have been copied into |log0|. |
| // We also have 1 log entry that was emitted by the |
| // MultiThreadedProxyResolver. |
| CapturingNetLog::CapturedEntryList entries0; |
| log0.GetEntries(&entries0); |
| |
| ASSERT_EQ(2u, entries0.size()); |
| EXPECT_EQ(NetLog::TYPE_SUBMITTED_TO_RESOLVER_THREAD, entries0[0].type); |
| |
| // Start 3 more requests (request1 to request3). |
| |
| TestCompletionCallback callback1; |
| ProxyInfo results1; |
| rv = resolver.GetProxyForURL(GURL("http://request1"), &results1, |
| callback1.callback(), NULL, BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| TestCompletionCallback callback2; |
| ProxyInfo results2; |
| rv = resolver.GetProxyForURL(GURL("http://request2"), &results2, |
| callback2.callback(), NULL, BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| TestCompletionCallback callback3; |
| ProxyInfo results3; |
| rv = resolver.GetProxyForURL(GURL("http://request3"), &results3, |
| callback3.callback(), NULL, BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Wait for the requests to finish (they must finish in the order they were |
| // started, which is what we check for from their magic return value) |
| |
| rv = callback1.WaitForResult(); |
| EXPECT_EQ(1, rv); |
| EXPECT_EQ("PROXY request1:80", results1.ToPacString()); |
| |
| rv = callback2.WaitForResult(); |
| EXPECT_EQ(2, rv); |
| EXPECT_EQ("PROXY request2:80", results2.ToPacString()); |
| |
| rv = callback3.WaitForResult(); |
| EXPECT_EQ(3, rv); |
| EXPECT_EQ("PROXY request3:80", results3.ToPacString()); |
| |
| // Ensure that PurgeMemory() reaches the wrapped resolver and happens on the |
| // right thread. |
| EXPECT_EQ(0, mock->purge_count()); |
| resolver.PurgeMemory(); |
| // There is no way to get a callback directly when PurgeMemory() completes, so |
| // we queue up a dummy request after the PurgeMemory() call and wait until it |
| // finishes to ensure PurgeMemory() has had a chance to run. |
| TestCompletionCallback dummy_callback; |
| rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("dummy"), |
| dummy_callback.callback()); |
| EXPECT_EQ(OK, dummy_callback.WaitForResult()); |
| EXPECT_EQ(1, mock->purge_count()); |
| } |
| |
| // Tests that the NetLog is updated to include the time the request was waiting |
| // to be scheduled to a thread. |
| TEST(MultiThreadedProxyResolverTest, |
| SingleThread_UpdatesNetLogWithThreadWait) { |
| const size_t kNumThreads = 1u; |
| scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver); |
| MultiThreadedProxyResolver resolver( |
| new ForwardingProxyResolverFactory(mock.get()), kNumThreads); |
| |
| int rv; |
| |
| // Initialize the resolver. |
| TestCompletionCallback init_callback; |
| rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("foo"), |
| init_callback.callback()); |
| EXPECT_EQ(OK, init_callback.WaitForResult()); |
| |
| // Block the proxy resolver, so no request can complete. |
| mock->Block(); |
| |
| // Start request 0. |
| ProxyResolver::RequestHandle request0; |
| TestCompletionCallback callback0; |
| ProxyInfo results0; |
| CapturingBoundNetLog log0; |
| rv = resolver.GetProxyForURL(GURL("http://request0"), &results0, |
| callback0.callback(), &request0, log0.bound()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Start 2 more requests (request1 and request2). |
| |
| TestCompletionCallback callback1; |
| ProxyInfo results1; |
| CapturingBoundNetLog log1; |
| rv = resolver.GetProxyForURL(GURL("http://request1"), &results1, |
| callback1.callback(), NULL, log1.bound()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| ProxyResolver::RequestHandle request2; |
| TestCompletionCallback callback2; |
| ProxyInfo results2; |
| CapturingBoundNetLog log2; |
| rv = resolver.GetProxyForURL(GURL("http://request2"), &results2, |
| callback2.callback(), &request2, log2.bound()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Unblock the worker thread so the requests can continue running. |
| mock->WaitUntilBlocked(); |
| mock->Unblock(); |
| |
| // Check that request 0 completed as expected. |
| // The NetLog has 1 entry that came from the MultiThreadedProxyResolver, and |
| // 1 entry from the mock proxy resolver. |
| EXPECT_EQ(0, callback0.WaitForResult()); |
| EXPECT_EQ("PROXY request0:80", results0.ToPacString()); |
| |
| CapturingNetLog::CapturedEntryList entries0; |
| log0.GetEntries(&entries0); |
| |
| ASSERT_EQ(2u, entries0.size()); |
| EXPECT_EQ(NetLog::TYPE_SUBMITTED_TO_RESOLVER_THREAD, |
| entries0[0].type); |
| |
| // Check that request 1 completed as expected. |
| EXPECT_EQ(1, callback1.WaitForResult()); |
| EXPECT_EQ("PROXY request1:80", results1.ToPacString()); |
| |
| CapturingNetLog::CapturedEntryList entries1; |
| log1.GetEntries(&entries1); |
| |
| ASSERT_EQ(4u, entries1.size()); |
| EXPECT_TRUE(LogContainsBeginEvent( |
| entries1, 0, |
| NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD)); |
| EXPECT_TRUE(LogContainsEndEvent( |
| entries1, 1, |
| NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD)); |
| |
| // Check that request 2 completed as expected. |
| EXPECT_EQ(2, callback2.WaitForResult()); |
| EXPECT_EQ("PROXY request2:80", results2.ToPacString()); |
| |
| CapturingNetLog::CapturedEntryList entries2; |
| log2.GetEntries(&entries2); |
| |
| ASSERT_EQ(4u, entries2.size()); |
| EXPECT_TRUE(LogContainsBeginEvent( |
| entries2, 0, |
| NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD)); |
| EXPECT_TRUE(LogContainsEndEvent( |
| entries2, 1, |
| NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD)); |
| } |
| |
| // Cancel a request which is in progress, and then cancel a request which |
| // is pending. |
| TEST(MultiThreadedProxyResolverTest, SingleThread_CancelRequest) { |
| const size_t kNumThreads = 1u; |
| scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver); |
| MultiThreadedProxyResolver resolver( |
| new ForwardingProxyResolverFactory(mock.get()), |
| kNumThreads); |
| |
| int rv; |
| |
| // Initialize the resolver. |
| TestCompletionCallback init_callback; |
| rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("foo"), |
| init_callback.callback()); |
| EXPECT_EQ(OK, init_callback.WaitForResult()); |
| |
| // Block the proxy resolver, so no request can complete. |
| mock->Block(); |
| |
| // Start request 0. |
| ProxyResolver::RequestHandle request0; |
| TestCompletionCallback callback0; |
| ProxyInfo results0; |
| rv = resolver.GetProxyForURL(GURL("http://request0"), &results0, |
| callback0.callback(), &request0, BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Wait until requests 0 reaches the worker thread. |
| mock->WaitUntilBlocked(); |
| |
| // Start 3 more requests (request1 : request3). |
| |
| TestCompletionCallback callback1; |
| ProxyInfo results1; |
| rv = resolver.GetProxyForURL(GURL("http://request1"), &results1, |
| callback1.callback(), NULL, BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| ProxyResolver::RequestHandle request2; |
| TestCompletionCallback callback2; |
| ProxyInfo results2; |
| rv = resolver.GetProxyForURL(GURL("http://request2"), &results2, |
| callback2.callback(), &request2, BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| TestCompletionCallback callback3; |
| ProxyInfo results3; |
| rv = resolver.GetProxyForURL(GURL("http://request3"), &results3, |
| callback3.callback(), NULL, BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Cancel request0 (inprogress) and request2 (pending). |
| resolver.CancelRequest(request0); |
| resolver.CancelRequest(request2); |
| |
| // Unblock the worker thread so the requests can continue running. |
| mock->Unblock(); |
| |
| // Wait for requests 1 and 3 to finish. |
| |
| rv = callback1.WaitForResult(); |
| EXPECT_EQ(1, rv); |
| EXPECT_EQ("PROXY request1:80", results1.ToPacString()); |
| |
| rv = callback3.WaitForResult(); |
| // Note that since request2 was cancelled before reaching the resolver, |
| // the request count is 2 and not 3 here. |
| EXPECT_EQ(2, rv); |
| EXPECT_EQ("PROXY request3:80", results3.ToPacString()); |
| |
| // Requests 0 and 2 which were cancelled, hence their completion callbacks |
| // were never summoned. |
| EXPECT_FALSE(callback0.have_result()); |
| EXPECT_FALSE(callback2.have_result()); |
| } |
| |
| // Test that deleting MultiThreadedProxyResolver while requests are |
| // outstanding cancels them (and doesn't leak anything). |
| TEST(MultiThreadedProxyResolverTest, SingleThread_CancelRequestByDeleting) { |
| const size_t kNumThreads = 1u; |
| scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver); |
| scoped_ptr<MultiThreadedProxyResolver> resolver( |
| new MultiThreadedProxyResolver( |
| new ForwardingProxyResolverFactory(mock.get()), kNumThreads)); |
| |
| int rv; |
| |
| // Initialize the resolver. |
| TestCompletionCallback init_callback; |
| rv = resolver->SetPacScript(ProxyResolverScriptData::FromUTF8("foo"), |
| init_callback.callback()); |
| EXPECT_EQ(OK, init_callback.WaitForResult()); |
| |
| // Block the proxy resolver, so no request can complete. |
| mock->Block(); |
| |
| // Start 3 requests. |
| |
| TestCompletionCallback callback0; |
| ProxyInfo results0; |
| rv = resolver->GetProxyForURL(GURL("http://request0"), &results0, |
| callback0.callback(), NULL, BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| TestCompletionCallback callback1; |
| ProxyInfo results1; |
| rv = resolver->GetProxyForURL(GURL("http://request1"), &results1, |
| callback1.callback(), NULL, BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| TestCompletionCallback callback2; |
| ProxyInfo results2; |
| rv = resolver->GetProxyForURL(GURL("http://request2"), &results2, |
| callback2.callback(), NULL, BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Wait until request 0 reaches the worker thread. |
| mock->WaitUntilBlocked(); |
| |
| // Add some latency, to improve the chance that when |
| // MultiThreadedProxyResolver is deleted below we are still running inside |
| // of the worker thread. The test will pass regardless, so this race doesn't |
| // cause flakiness. However the destruction during execution is a more |
| // interesting case to test. |
| mock->SetResolveLatency(base::TimeDelta::FromMilliseconds(100)); |
| |
| // Unblock the worker thread and delete the underlying |
| // MultiThreadedProxyResolver immediately. |
| mock->Unblock(); |
| resolver.reset(); |
| |
| // Give any posted tasks a chance to run (in case there is badness). |
| MessageLoop::current()->RunUntilIdle(); |
| |
| // Check that none of the outstanding requests were completed. |
| EXPECT_FALSE(callback0.have_result()); |
| EXPECT_FALSE(callback1.have_result()); |
| EXPECT_FALSE(callback2.have_result()); |
| } |
| |
| // Cancel an outstanding call to SetPacScriptByData(). |
| TEST(MultiThreadedProxyResolverTest, SingleThread_CancelSetPacScript) { |
| const size_t kNumThreads = 1u; |
| scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver); |
| MultiThreadedProxyResolver resolver( |
| new ForwardingProxyResolverFactory(mock.get()), kNumThreads); |
| |
| int rv; |
| |
| TestCompletionCallback set_pac_script_callback; |
| rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("data"), |
| set_pac_script_callback.callback()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Cancel the SetPacScriptByData request. |
| resolver.CancelSetPacScript(); |
| |
| // Start another SetPacScript request |
| TestCompletionCallback set_pac_script_callback2; |
| rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("data2"), |
| set_pac_script_callback2.callback()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Wait for the initialization to complete. |
| |
| rv = set_pac_script_callback2.WaitForResult(); |
| EXPECT_EQ(0, rv); |
| EXPECT_EQ(ASCIIToUTF16("data2"), mock->last_script_data()->utf16()); |
| |
| // The first SetPacScript callback should never have been completed. |
| EXPECT_FALSE(set_pac_script_callback.have_result()); |
| } |
| |
| // Tests setting the PAC script once, lazily creating new threads, and |
| // cancelling requests. |
| TEST(MultiThreadedProxyResolverTest, ThreeThreads_Basic) { |
| const size_t kNumThreads = 3u; |
| BlockableProxyResolverFactory* factory = new BlockableProxyResolverFactory; |
| MultiThreadedProxyResolver resolver(factory, kNumThreads); |
| |
| int rv; |
| |
| EXPECT_TRUE(resolver.expects_pac_bytes()); |
| |
| // Call SetPacScriptByData() -- verify that it reaches the synchronous |
| // resolver. |
| TestCompletionCallback set_script_callback; |
| rv = resolver.SetPacScript( |
| ProxyResolverScriptData::FromUTF8("pac script bytes"), |
| set_script_callback.callback()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| EXPECT_EQ(OK, set_script_callback.WaitForResult()); |
| // One thread has been provisioned (i.e. one ProxyResolver was created). |
| ASSERT_EQ(1u, factory->resolvers().size()); |
| EXPECT_EQ(ASCIIToUTF16("pac script bytes"), |
| factory->resolvers()[0]->last_script_data()->utf16()); |
| |
| const int kNumRequests = 9; |
| TestCompletionCallback callback[kNumRequests]; |
| ProxyInfo results[kNumRequests]; |
| ProxyResolver::RequestHandle request[kNumRequests]; |
| |
| // Start request 0 -- this should run on thread 0 as there is nothing else |
| // going on right now. |
| rv = resolver.GetProxyForURL( |
| GURL("http://request0"), &results[0], callback[0].callback(), &request[0], |
| BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Wait for request 0 to finish. |
| rv = callback[0].WaitForResult(); |
| EXPECT_EQ(0, rv); |
| EXPECT_EQ("PROXY request0:80", results[0].ToPacString()); |
| ASSERT_EQ(1u, factory->resolvers().size()); |
| EXPECT_EQ(1, factory->resolvers()[0]->request_count()); |
| |
| MessageLoop::current()->RunUntilIdle(); |
| |
| // We now start 8 requests in parallel -- this will cause the maximum of |
| // three threads to be provisioned (an additional two from what we already |
| // have). |
| |
| for (int i = 1; i < kNumRequests; ++i) { |
| rv = resolver.GetProxyForURL( |
| GURL(base::StringPrintf("http://request%d", i)), &results[i], |
| callback[i].callback(), &request[i], BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| } |
| |
| // We should now have a total of 3 threads, each with its own ProxyResolver |
| // that will get initialized with the same data. (We check this later since |
| // the assignment happens on the worker threads and may not have occurred |
| // yet.) |
| ASSERT_EQ(3u, factory->resolvers().size()); |
| |
| // Cancel 3 of the 8 oustanding requests. |
| resolver.CancelRequest(request[1]); |
| resolver.CancelRequest(request[3]); |
| resolver.CancelRequest(request[6]); |
| |
| // Wait for the remaining requests to complete. |
| int kNonCancelledRequests[] = {2, 4, 5, 7, 8}; |
| for (size_t i = 0; i < arraysize(kNonCancelledRequests); ++i) { |
| int request_index = kNonCancelledRequests[i]; |
| EXPECT_GE(callback[request_index].WaitForResult(), 0); |
| } |
| |
| // Check that the cancelled requests never invoked their callback. |
| EXPECT_FALSE(callback[1].have_result()); |
| EXPECT_FALSE(callback[3].have_result()); |
| EXPECT_FALSE(callback[6].have_result()); |
| |
| // We call SetPacScript again, solely to stop the current worker threads. |
| // (That way we can test to see the values observed by the synchronous |
| // resolvers in a non-racy manner). |
| TestCompletionCallback set_script_callback2; |
| rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("xyz"), |
| set_script_callback2.callback()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| EXPECT_EQ(OK, set_script_callback2.WaitForResult()); |
| ASSERT_EQ(4u, factory->resolvers().size()); |
| |
| for (int i = 0; i < 3; ++i) { |
| EXPECT_EQ( |
| ASCIIToUTF16("pac script bytes"), |
| factory->resolvers()[i]->last_script_data()->utf16()) << "i=" << i; |
| } |
| |
| EXPECT_EQ(ASCIIToUTF16("xyz"), |
| factory->resolvers()[3]->last_script_data()->utf16()); |
| |
| // We don't know the exact ordering that requests ran on threads with, |
| // but we do know the total count that should have reached the threads. |
| // 8 total were submitted, and three were cancelled. Of the three that |
| // were cancelled, one of them (request 1) was cancelled after it had |
| // already been posted to the worker thread. So the resolvers will |
| // have seen 6 total (and 1 from the run prior). |
| ASSERT_EQ(4u, factory->resolvers().size()); |
| int total_count = 0; |
| for (int i = 0; i < 3; ++i) { |
| total_count += factory->resolvers()[i]->request_count(); |
| } |
| EXPECT_EQ(7, total_count); |
| } |
| |
| // Tests using two threads. The first request hangs the first thread. Checks |
| // that other requests are able to complete while this first request remains |
| // stalled. |
| TEST(MultiThreadedProxyResolverTest, OneThreadBlocked) { |
| const size_t kNumThreads = 2u; |
| BlockableProxyResolverFactory* factory = new BlockableProxyResolverFactory; |
| MultiThreadedProxyResolver resolver(factory, kNumThreads); |
| |
| int rv; |
| |
| EXPECT_TRUE(resolver.expects_pac_bytes()); |
| |
| // Initialize the resolver. |
| TestCompletionCallback set_script_callback; |
| rv = resolver.SetPacScript( |
| ProxyResolverScriptData::FromUTF8("pac script bytes"), |
| set_script_callback.callback()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| EXPECT_EQ(OK, set_script_callback.WaitForResult()); |
| // One thread has been provisioned (i.e. one ProxyResolver was created). |
| ASSERT_EQ(1u, factory->resolvers().size()); |
| EXPECT_EQ(ASCIIToUTF16("pac script bytes"), |
| factory->resolvers()[0]->last_script_data()->utf16()); |
| |
| const int kNumRequests = 4; |
| TestCompletionCallback callback[kNumRequests]; |
| ProxyInfo results[kNumRequests]; |
| ProxyResolver::RequestHandle request[kNumRequests]; |
| |
| // Start a request that will block the first thread. |
| |
| factory->resolvers()[0]->Block(); |
| |
| rv = resolver.GetProxyForURL( |
| GURL("http://request0"), &results[0], callback[0].callback(), &request[0], |
| BoundNetLog()); |
| |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| factory->resolvers()[0]->WaitUntilBlocked(); |
| |
| // Start 3 more requests -- they should all be serviced by thread #2 |
| // since thread #1 is blocked. |
| |
| for (int i = 1; i < kNumRequests; ++i) { |
| rv = resolver.GetProxyForURL( |
| GURL(base::StringPrintf("http://request%d", i)), |
| &results[i], callback[i].callback(), &request[i], BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| } |
| |
| // Wait for the three requests to complete (they should complete in FIFO |
| // order). |
| for (int i = 1; i < kNumRequests; ++i) { |
| EXPECT_EQ(i - 1, callback[i].WaitForResult()); |
| } |
| |
| // Unblock the first thread. |
| factory->resolvers()[0]->Unblock(); |
| EXPECT_EQ(0, callback[0].WaitForResult()); |
| |
| // All in all, the first thread should have seen just 1 request. And the |
| // second thread 3 requests. |
| ASSERT_EQ(2u, factory->resolvers().size()); |
| EXPECT_EQ(1, factory->resolvers()[0]->request_count()); |
| EXPECT_EQ(3, factory->resolvers()[1]->request_count()); |
| } |
| |
| } // namespace |
| |
| } // namespace net |