summaryrefslogtreecommitdiffstats
path: root/net/proxy/sync_host_resolver_bridge_unittest.cc
blob: 712234dd5d2eed83f3dab23c44edc3534f286786 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
// Copyright (c) 2010 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/sync_host_resolver_bridge.h"

#include "base/threading/thread.h"
#include "base/waitable_event.h"
#include "net/base/address_list.h"
#include "net/base/net_errors.h"
#include "net/base/net_log.h"
#include "net/proxy/multi_threaded_proxy_resolver.h"
#include "net/base/test_completion_callback.h"
#include "net/proxy/proxy_info.h"
#include "testing/gtest/include/gtest/gtest.h"

// TODO(eroman): This test should be moved into
//               multi_threaded_proxy_resolver_unittest.cc.

namespace net {

namespace {

// This implementation of HostResolver allows blocking until a resolve request
// has been received. The resolve requests it receives will never be completed.
class BlockableHostResolver : public HostResolver {
 public:
  BlockableHostResolver()
      : event_(true, false),
        was_request_cancelled_(false) {
  }

  virtual int Resolve(const RequestInfo& info,
                      AddressList* addresses,
                      CompletionCallback* callback,
                      RequestHandle* out_req,
                      const BoundNetLog& net_log) {
    EXPECT_TRUE(callback);
    EXPECT_TRUE(out_req);
    *out_req = reinterpret_cast<RequestHandle*>(1);  // Magic value.

    // Indicate to the caller that a request was received.
    event_.Signal();

    // We return ERR_IO_PENDING, as this request will NEVER be completed.
    // Expectation is for the caller to later cancel the request.
    return ERR_IO_PENDING;
  }

  virtual void CancelRequest(RequestHandle req) {
    EXPECT_EQ(reinterpret_cast<RequestHandle*>(1), req);
    was_request_cancelled_ = true;
  }

  virtual void AddObserver(Observer* observer) {
    NOTREACHED();
  }

  virtual void RemoveObserver(Observer* observer) {
    NOTREACHED();
  }

  // Waits until Resolve() has been called.
  void WaitUntilRequestIsReceived() {
    event_.Wait();
  }

  bool was_request_cancelled() const {
    return was_request_cancelled_;
  }

 private:
  // Event to notify when a resolve request was received.
  base::WaitableEvent event_;
  bool was_request_cancelled_;
};

// This implementation of ProxyResolver simply does a synchronous resolve
// on |host_resolver| in response to GetProxyForURL().
class SyncProxyResolver : public ProxyResolver {
 public:
  explicit SyncProxyResolver(SyncHostResolverBridge* host_resolver)
      : ProxyResolver(false), host_resolver_(host_resolver) {}

  virtual int GetProxyForURL(const GURL& url,
                             ProxyInfo* results,
                             CompletionCallback* callback,
                             RequestHandle* request,
                             const BoundNetLog& net_log) {
    EXPECT_FALSE(callback);
    EXPECT_FALSE(request);

    // Do a synchronous host resolve.
    HostResolver::RequestInfo info(HostPortPair::FromURL(url));
    AddressList addresses;
    int rv =
        host_resolver_->Resolve(info, &addresses, NULL, NULL, BoundNetLog());

    EXPECT_EQ(ERR_ABORTED, rv);

    return rv;
  }

  virtual void CancelRequest(RequestHandle request) {
    NOTREACHED();
  }

  virtual void Shutdown() {
    host_resolver_->Shutdown();
  }

  virtual void CancelSetPacScript() {
    NOTREACHED();
  }

  virtual int SetPacScript(
      const scoped_refptr<ProxyResolverScriptData>& script_data,
      CompletionCallback* callback) {
    return OK;
  }

 private:
  SyncHostResolverBridge* const host_resolver_;
};

class SyncProxyResolverFactory : public ProxyResolverFactory {
 public:
  // Takes ownership of |sync_host_resolver|.
  explicit SyncProxyResolverFactory(SyncHostResolverBridge* sync_host_resolver)
      : ProxyResolverFactory(false),
        sync_host_resolver_(sync_host_resolver) {
  }

  virtual ProxyResolver* CreateProxyResolver() {
    return new SyncProxyResolver(sync_host_resolver_.get());
  }

 private:
  const scoped_ptr<SyncHostResolverBridge> sync_host_resolver_;
};

// This helper thread is used to create the circumstances for the deadlock.
// It is analagous to the "IO thread" which would be main thread running the
// network stack.
class IOThread : public base::Thread {
 public:
  IOThread() : base::Thread("IO-thread") {}

  virtual ~IOThread() {
    Stop();
  }

  BlockableHostResolver* async_resolver() {
    return async_resolver_.get();
  }

 protected:
  virtual void Init() {
    async_resolver_.reset(new BlockableHostResolver());

    // Create a synchronous host resolver that operates the async host
    // resolver on THIS thread.
    SyncHostResolverBridge* sync_resolver =
        new SyncHostResolverBridge(async_resolver_.get(), message_loop());

    proxy_resolver_.reset(
        new MultiThreadedProxyResolver(
            new SyncProxyResolverFactory(sync_resolver),
            1u));

    // Initialize the resolver.
    TestCompletionCallback callback;
    proxy_resolver_->SetPacScript(ProxyResolverScriptData::FromURL(GURL()),
                                  &callback);
    EXPECT_EQ(OK, callback.WaitForResult());

    // Start an asynchronous request to the proxy resolver
    // (note that it will never complete).
    proxy_resolver_->GetProxyForURL(GURL("http://test/"), &results_,
                                    &callback_, &request_, BoundNetLog());
  }

  virtual void CleanUp() {
    // Cancel the outstanding request (note however that this will not
    // unblock the PAC thread though).
    proxy_resolver_->CancelRequest(request_);

    // Delete the single threaded proxy resolver.
    proxy_resolver_.reset();

    // (There may have been a completion posted back to origin thread, avoid
    // leaking it by running).
    MessageLoop::current()->RunAllPending();

    // During the teardown sequence of the single threaded proxy resolver,
    // the outstanding host resolve should have been cancelled.
    EXPECT_TRUE(async_resolver_->was_request_cancelled());
  }

 private:
  // This (async) host resolver will outlive the thread that is operating it
  // synchronously.
  scoped_ptr<BlockableHostResolver> async_resolver_;

  scoped_ptr<ProxyResolver> proxy_resolver_;

  // Data for the outstanding request to the single threaded proxy resolver.
  TestCompletionCallback callback_;
  ProxyInfo results_;
  ProxyResolver::RequestHandle request_;
};

// Test that a deadlock does not happen during shutdown when a host resolve
// is outstanding on the SyncHostResolverBridge.
// This is a regression test for http://crbug.com/41244.
TEST(MultiThreadedProxyResolverTest, ShutdownIsCalledBeforeThreadJoin) {
  IOThread io_thread;
  base::Thread::Options options;
  options.message_loop_type = MessageLoop::TYPE_IO;
  ASSERT_TRUE(io_thread.StartWithOptions(options));

  io_thread.async_resolver()->WaitUntilRequestIsReceived();

  // Now upon exitting this scope, the IOThread is destroyed -- this will
  // stop the IOThread, which will in turn delete the
  // SingleThreadedProxyResolver, which in turn will stop its internal
  // PAC thread (which is currently blocked waiting on the host resolve which
  // is running on IOThread).  The IOThread::Cleanup() will verify that after
  // the PAC thread is stopped, it cancels the request on the HostResolver.
}

}  // namespace

}  // namespace net