websocket_experiment_task.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/net/websocket_experiment/websocket_experiment_task.h"
6
7#include "base/hash_tables.h"
8#include "base/metrics/histogram.h"
9#include "chrome/browser/browser_thread.h"
10#include "chrome/browser/profiles/profile.h"
11#include "chrome/common/net/url_request_context_getter.h"
12#include "net/base/host_resolver.h"
13#include "net/base/load_flags.h"
14#include "net/base/net_errors.h"
15#include "net/websockets/websocket.h"
16
17using base::Histogram;
18using base::LinearHistogram;
19
20namespace chrome_browser_net_websocket_experiment {
21
22static std::string GetProtocolVersionName(
23    net::WebSocket::ProtocolVersion protocol_version) {
24  switch (protocol_version) {
25    case net::WebSocket::DEFAULT_VERSION:
26      return "default protocol";
27    case net::WebSocket::DRAFT75:
28      return "draft 75 protocol";
29    default:
30      NOTREACHED();
31  }
32  return "";
33}
34
35URLFetcher* WebSocketExperimentTask::Context::CreateURLFetcher(
36    const Config& config, URLFetcher::Delegate* delegate) {
37  URLRequestContextGetter* getter =
38      Profile::GetDefaultRequestContext();
39  // Profile::GetDefaultRequestContext() is initialized lazily, on the UI
40  // thread. So here, where we access it from the IO thread, if the task runs
41  // before it has gotten lazily initialized yet.
42  if (!getter)
43    return NULL;
44  URLFetcher* fetcher =
45      new URLFetcher(config.http_url, URLFetcher::GET, delegate);
46  fetcher->set_request_context(getter);
47  fetcher->set_load_flags(
48      net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
49      net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SEND_AUTH_DATA |
50      net::LOAD_IGNORE_CERT_AUTHORITY_INVALID);
51  return fetcher;
52}
53
54net::WebSocket* WebSocketExperimentTask::Context::CreateWebSocket(
55    const Config& config, net::WebSocketDelegate* delegate) {
56  URLRequestContextGetter* getter =
57      Profile::GetDefaultRequestContext();
58  // Profile::GetDefaultRequestContext() is initialized lazily, on the UI
59  // thread. So here, where we access it from the IO thread, if the task runs
60  // before it has gotten lazily initialized yet.
61  if (!getter)
62    return NULL;
63  net::WebSocket::Request* request(
64      new net::WebSocket::Request(config.url,
65                                  config.ws_protocol,
66                                  config.ws_origin,
67                                  config.ws_location,
68                                  config.protocol_version,
69                                  getter->GetURLRequestContext()));
70  return new net::WebSocket(request, delegate);
71}
72
73// Expects URL Fetch and WebSocket connection handshake will finish in
74// a few seconds.
75static const int kUrlFetchDeadlineSec = 10;
76static const int kWebSocketConnectDeadlineSec = 10;
77// Expects WebSocket live experiment server echoes message back within a few
78// seconds.
79static const int kWebSocketEchoDeadlineSec = 5;
80// WebSocket live experiment server keeps idle for 1.5 seconds and sends
81// a message.  So, expects idle for at least 1 second and expects message
82// arrives within 1 second after that.
83static const int kWebSocketIdleSec = 1;
84static const int kWebSocketPushDeadlineSec = 1;
85// WebSocket live experiment server sends "bye" message soon.
86static const int kWebSocketByeDeadlineSec = 10;
87// WebSocket live experiment server closes after it receives "bye" message.
88static const int kWebSocketCloseDeadlineSec = 5;
89
90// All of above are expected within a few seconds.  We'd like to see time
91// distribution between 0 to 10 seconds.
92static const int kWebSocketTimeSec = 10;
93static const int kTimeBucketCount = 50;
94
95// Holds Histogram objects during experiments run.
96static base::hash_map<std::string, Histogram*>* g_histogram_table;
97
98WebSocketExperimentTask::Config::Config()
99    : ws_protocol("google-websocket-liveexperiment"),
100      ws_origin("http://dev.chromium.org/"),
101      protocol_version(net::WebSocket::DEFAULT_VERSION),
102      url_fetch_deadline_ms(kUrlFetchDeadlineSec * 1000),
103      websocket_onopen_deadline_ms(kWebSocketConnectDeadlineSec * 1000),
104      websocket_hello_message("Hello"),
105      websocket_hello_echoback_deadline_ms(kWebSocketEchoDeadlineSec * 1000),
106      // Note: websocket live experiment server is configured to wait 1.5 sec
107      // in websocket_experiment_def.txt on server.  So, client expects idle
108      // at least 1 sec and expects a message arrival within next 1 sec.
109      websocket_idle_ms(kWebSocketIdleSec * 1000),
110      websocket_receive_push_message_deadline_ms(
111          kWebSocketPushDeadlineSec * 1000),
112      websocket_bye_message("Bye"),
113      websocket_bye_deadline_ms(kWebSocketByeDeadlineSec * 1000),
114      websocket_close_deadline_ms(kWebSocketCloseDeadlineSec * 1000) {
115}
116
117WebSocketExperimentTask::Config::~Config() {}
118
119WebSocketExperimentTask::WebSocketExperimentTask(
120    const Config& config,
121    net::CompletionCallback* callback)
122    : config_(config),
123      context_(ALLOW_THIS_IN_INITIALIZER_LIST(new Context())),
124      method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
125      callback_(callback),
126      next_state_(STATE_NONE),
127      last_websocket_error_(net::OK) {
128}
129
130WebSocketExperimentTask::~WebSocketExperimentTask() {
131  DCHECK(!websocket_);
132}
133
134/* static */
135void WebSocketExperimentTask::InitHistogram() {
136  DCHECK(!g_histogram_table);
137  g_histogram_table = new base::hash_map<std::string, Histogram*>;
138}
139
140static std::string GetCounterNameForConfig(
141    const WebSocketExperimentTask::Config& config, const std::string& name) {
142  std::string protocol_version = "";
143  switch (config.protocol_version) {
144    case net::WebSocket::DEFAULT_VERSION:
145      protocol_version = "Draft76";
146      break;
147    case net::WebSocket::DRAFT75:
148      protocol_version = "";
149      break;
150    default:
151      NOTREACHED();
152  }
153  if (config.url.SchemeIs("wss")) {
154    return "WebSocketExperiment.Secure" + protocol_version + "." + name;
155  } else if (config.url.has_port() && config.url.IntPort() != 80) {
156    return "WebSocketExperiment.NoDefaultPort" + protocol_version + "." + name;
157  } else {
158    return "WebSocketExperiment.Basic" + protocol_version + "." + name;
159  }
160}
161
162static Histogram* GetEnumsHistogramForConfig(
163    const WebSocketExperimentTask::Config& config,
164    const std::string& name,
165    Histogram::Sample boundary_value) {
166  DCHECK(g_histogram_table);
167  std::string counter_name = GetCounterNameForConfig(config, name);
168  base::hash_map<std::string, Histogram*>::iterator found =
169      g_histogram_table->find(counter_name);
170  if (found != g_histogram_table->end()) {
171    return found->second;
172  }
173  Histogram* counter = LinearHistogram::FactoryGet(
174      counter_name, 1, boundary_value, boundary_value + 1,
175      Histogram::kUmaTargetedHistogramFlag);
176  counter->AddRef();  // Released in ReleaseHistogram().
177  g_histogram_table->insert(std::make_pair(counter_name, counter));
178  return counter;
179}
180
181static Histogram* GetTimesHistogramForConfig(
182    const WebSocketExperimentTask::Config& config,
183    const std::string& name,
184    base::TimeDelta min,
185    base::TimeDelta max,
186    size_t bucket_count) {
187  DCHECK(g_histogram_table);
188  std::string counter_name = GetCounterNameForConfig(config, name);
189  base::hash_map<std::string, Histogram*>::iterator found =
190      g_histogram_table->find(counter_name);
191  if (found != g_histogram_table->end()) {
192    return found->second;
193  }
194  Histogram* counter = Histogram::FactoryTimeGet(
195      counter_name, min, max, bucket_count,
196      Histogram::kUmaTargetedHistogramFlag);
197  counter->AddRef();  // Released in ReleaseHistogram().
198  g_histogram_table->insert(std::make_pair(counter_name, counter));
199  return counter;
200}
201
202static void UpdateHistogramEnums(
203    const WebSocketExperimentTask::Config& config,
204    const std::string& name,
205    Histogram::Sample sample,
206    Histogram::Sample boundary_value) {
207  Histogram* counter = GetEnumsHistogramForConfig(config, name, boundary_value);
208  counter->Add(sample);
209}
210
211static void UpdateHistogramTimes(
212    const WebSocketExperimentTask::Config& config,
213    const std::string& name,
214    base::TimeDelta sample,
215    base::TimeDelta min,
216    base::TimeDelta max,
217    size_t bucket_count) {
218  Histogram* counter = GetTimesHistogramForConfig(
219      config, name, min, max, bucket_count);
220  counter->AddTime(sample);
221}
222
223/* static */
224void WebSocketExperimentTask::ReleaseHistogram() {
225  DCHECK(g_histogram_table);
226  for (base::hash_map<std::string, Histogram*>::iterator iter =
227           g_histogram_table->begin();
228       iter != g_histogram_table->end();
229       ++iter) {
230    Histogram* counter = iter->second;
231    if (counter != NULL)
232      counter->Release();
233    iter->second = NULL;
234  }
235  delete g_histogram_table;
236  g_histogram_table = NULL;
237}
238
239void WebSocketExperimentTask::Run() {
240  DVLOG(1) << "Run WebSocket experiment for " << config_.url << " "
241           << GetProtocolVersionName(config_.protocol_version);
242  next_state_ = STATE_URL_FETCH;
243  DoLoop(net::OK);
244}
245
246void WebSocketExperimentTask::Cancel() {
247  next_state_ = STATE_NONE;
248  DoLoop(net::OK);
249}
250
251void WebSocketExperimentTask::SaveResult() const {
252  DVLOG(1) << "WebSocket experiment save result for " << config_.url
253           << " last_state=" << result_.last_state;
254  UpdateHistogramEnums(config_, "LastState", result_.last_state, NUM_STATES);
255  UpdateHistogramTimes(config_, "UrlFetch", result_.url_fetch,
256                       base::TimeDelta::FromMilliseconds(1),
257                       base::TimeDelta::FromSeconds(kUrlFetchDeadlineSec),
258                       kTimeBucketCount);
259
260  if (result_.last_state < STATE_WEBSOCKET_CONNECT_COMPLETE)
261    return;
262
263  UpdateHistogramTimes(config_, "WebSocketConnect", result_.websocket_connect,
264                       base::TimeDelta::FromMilliseconds(1),
265                       base::TimeDelta::FromSeconds(
266                           kWebSocketConnectDeadlineSec),
267                       kTimeBucketCount);
268
269  if (result_.last_state < STATE_WEBSOCKET_RECV_HELLO)
270    return;
271
272  UpdateHistogramTimes(config_, "WebSocketEcho", result_.websocket_echo,
273                       base::TimeDelta::FromMilliseconds(1),
274                       base::TimeDelta::FromSeconds(
275                           kWebSocketEchoDeadlineSec),
276                       kTimeBucketCount);
277
278  if (result_.last_state < STATE_WEBSOCKET_KEEP_IDLE)
279    return;
280
281  UpdateHistogramTimes(config_, "WebSocketIdle", result_.websocket_idle,
282                       base::TimeDelta::FromMilliseconds(1),
283                       base::TimeDelta::FromSeconds(
284                           kWebSocketIdleSec + kWebSocketPushDeadlineSec),
285                       kTimeBucketCount);
286
287  if (result_.last_state < STATE_WEBSOCKET_CLOSE_COMPLETE)
288    return;
289
290  UpdateHistogramTimes(config_, "WebSocketTotal", result_.websocket_total,
291                       base::TimeDelta::FromMilliseconds(1),
292                       base::TimeDelta::FromSeconds(kWebSocketTimeSec),
293                       kTimeBucketCount);
294}
295
296// URLFetcher::Delegate method.
297void WebSocketExperimentTask::OnURLFetchComplete(
298    const URLFetcher* source,
299    const GURL& url,
300    const net::URLRequestStatus& status,
301    int response_code,
302    const ResponseCookies& cookies,
303    const std::string& data) {
304  result_.url_fetch = base::TimeTicks::Now() - url_fetch_start_time_;
305  RevokeTimeoutTimer();
306  int result = net::ERR_FAILED;
307  if (next_state_ != STATE_URL_FETCH_COMPLETE) {
308    DVLOG(1) << "unexpected state=" << next_state_
309             << " at OnURLFetchComplete for " << config_.http_url;
310    result = net::ERR_UNEXPECTED;
311  } else if (response_code == 200 || response_code == 304) {
312    result = net::OK;
313  }
314  DoLoop(result);
315}
316
317// net::WebSocketDelegate
318void WebSocketExperimentTask::OnOpen(net::WebSocket* websocket) {
319  result_.websocket_connect =
320      base::TimeTicks::Now() - websocket_connect_start_time_;
321  RevokeTimeoutTimer();
322  int result = net::ERR_UNEXPECTED;
323  if (next_state_ == STATE_WEBSOCKET_CONNECT_COMPLETE)
324    result = net::OK;
325  else
326    DVLOG(1) << "unexpected state=" << next_state_
327             << " at OnOpen for " << config_.url
328             << " " << GetProtocolVersionName(config_.protocol_version);
329  DoLoop(result);
330}
331
332void WebSocketExperimentTask::OnMessage(
333    net::WebSocket* websocket, const std::string& msg) {
334  if (!result_.websocket_echo.ToInternalValue())
335    result_.websocket_echo =
336        base::TimeTicks::Now() - websocket_echo_start_time_;
337  if (!websocket_idle_start_time_.is_null() &&
338      !result_.websocket_idle.ToInternalValue())
339    result_.websocket_idle =
340        base::TimeTicks::Now() - websocket_idle_start_time_;
341  RevokeTimeoutTimer();
342  received_messages_.push_back(msg);
343  int result = net::ERR_UNEXPECTED;
344  switch (next_state_) {
345    case STATE_WEBSOCKET_RECV_HELLO:
346    case STATE_WEBSOCKET_RECV_PUSH_MESSAGE:
347    case STATE_WEBSOCKET_RECV_BYE:
348      result = net::OK;
349      break;
350    default:
351      DVLOG(1) << "unexpected state=" << next_state_
352               << " at OnMessage for " << config_.url
353               << " " << GetProtocolVersionName(config_.protocol_version);
354      break;
355  }
356  DoLoop(result);
357}
358
359void WebSocketExperimentTask::OnError(net::WebSocket* websocket) {
360  // TODO(ukai): record error count?
361}
362
363void WebSocketExperimentTask::OnClose(
364    net::WebSocket* websocket, bool was_clean) {
365  RevokeTimeoutTimer();
366  websocket_ = NULL;
367  result_.websocket_total =
368      base::TimeTicks::Now() - websocket_connect_start_time_;
369  int result = net::ERR_CONNECTION_CLOSED;
370  if (last_websocket_error_ != net::OK)
371    result = last_websocket_error_;
372  DVLOG(1) << "WebSocket onclose was_clean=" << was_clean
373           << " next_state=" << next_state_
374           << " last_error=" << net::ErrorToString(result);
375  if (config_.protocol_version == net::WebSocket::DEFAULT_VERSION) {
376    if (next_state_ == STATE_WEBSOCKET_CLOSE_COMPLETE && was_clean)
377      result = net::OK;
378  } else {
379    // DRAFT75 doesn't report was_clean correctly.
380    if (next_state_ == STATE_WEBSOCKET_CLOSE_COMPLETE)
381      result = net::OK;
382  }
383  DoLoop(result);
384}
385
386void WebSocketExperimentTask::OnSocketError(
387    const net::WebSocket* websocket, int error) {
388  DVLOG(1) << "WebSocket socket level error=" << net::ErrorToString(error)
389           << " next_state=" << next_state_
390           << " for " << config_.url
391           << " " << GetProtocolVersionName(config_.protocol_version);
392  last_websocket_error_ = error;
393}
394
395void WebSocketExperimentTask::SetContext(Context* context) {
396  context_.reset(context);
397}
398
399void WebSocketExperimentTask::OnTimedOut() {
400  DVLOG(1) << "OnTimedOut next_state=" << next_state_
401           << " for " << config_.url
402           << " " << GetProtocolVersionName(config_.protocol_version);
403  RevokeTimeoutTimer();
404  DoLoop(net::ERR_TIMED_OUT);
405}
406
407void WebSocketExperimentTask::DoLoop(int result) {
408  if (next_state_ == STATE_NONE) {
409    Finish(net::ERR_ABORTED);
410    return;
411  }
412  do {
413    State state = next_state_;
414    next_state_ = STATE_NONE;
415    switch (state) {
416      case STATE_URL_FETCH:
417        result = DoURLFetch();
418        break;
419      case STATE_URL_FETCH_COMPLETE:
420        result = DoURLFetchComplete(result);
421        break;
422      case STATE_WEBSOCKET_CONNECT:
423        result = DoWebSocketConnect();
424        break;
425      case STATE_WEBSOCKET_CONNECT_COMPLETE:
426        result = DoWebSocketConnectComplete(result);
427        break;
428      case STATE_WEBSOCKET_SEND_HELLO:
429        result = DoWebSocketSendHello();
430        break;
431      case STATE_WEBSOCKET_RECV_HELLO:
432        result = DoWebSocketReceiveHello(result);
433        break;
434      case STATE_WEBSOCKET_KEEP_IDLE:
435        result = DoWebSocketKeepIdle();
436        break;
437      case STATE_WEBSOCKET_KEEP_IDLE_COMPLETE:
438        result = DoWebSocketKeepIdleComplete(result);
439        break;
440      case STATE_WEBSOCKET_RECV_PUSH_MESSAGE:
441        result = DoWebSocketReceivePushMessage(result);
442        break;
443      case STATE_WEBSOCKET_ECHO_BACK_MESSAGE:
444        result = DoWebSocketEchoBackMessage();
445        break;
446      case STATE_WEBSOCKET_RECV_BYE:
447        result = DoWebSocketReceiveBye(result);
448        break;
449      case STATE_WEBSOCKET_CLOSE:
450        result = DoWebSocketClose();
451        break;
452      case STATE_WEBSOCKET_CLOSE_COMPLETE:
453        result = DoWebSocketCloseComplete(result);
454        break;
455      default:
456        NOTREACHED();
457        break;
458    }
459    result_.last_state = state;
460  } while (result != net::ERR_IO_PENDING && next_state_ != STATE_NONE);
461
462  if (result != net::ERR_IO_PENDING)
463    Finish(result);
464}
465
466int WebSocketExperimentTask::DoURLFetch() {
467  DCHECK(!url_fetcher_.get());
468
469  url_fetcher_.reset(context_->CreateURLFetcher(config_, this));
470  if (!url_fetcher_.get()) {
471    // Request context is not ready.
472    next_state_ = STATE_NONE;
473    return net::ERR_UNEXPECTED;
474  }
475
476  next_state_ = STATE_URL_FETCH_COMPLETE;
477  SetTimeout(config_.url_fetch_deadline_ms);
478  url_fetch_start_time_ = base::TimeTicks::Now();
479  url_fetcher_->Start();
480  return net::ERR_IO_PENDING;
481}
482
483int WebSocketExperimentTask::DoURLFetchComplete(int result) {
484  url_fetcher_.reset();
485
486  if (result < 0)
487    return result;
488
489  next_state_ = STATE_WEBSOCKET_CONNECT;
490  return net::OK;
491}
492
493int WebSocketExperimentTask::DoWebSocketConnect() {
494  DCHECK(!websocket_);
495
496  websocket_ = context_->CreateWebSocket(config_, this);
497  if (!websocket_) {
498    // Request context is not ready.
499    next_state_ = STATE_NONE;
500    return net::ERR_UNEXPECTED;
501  }
502  next_state_ = STATE_WEBSOCKET_CONNECT_COMPLETE;
503  websocket_connect_start_time_ = base::TimeTicks::Now();
504  websocket_->Connect();
505
506  SetTimeout(config_.websocket_onopen_deadline_ms);
507  return net::ERR_IO_PENDING;
508}
509
510int WebSocketExperimentTask::DoWebSocketConnectComplete(int result) {
511  if (result < 0)
512    return result;
513  DCHECK(websocket_);
514
515  next_state_ = STATE_WEBSOCKET_SEND_HELLO;
516  return net::OK;
517}
518
519int WebSocketExperimentTask::DoWebSocketSendHello() {
520  DCHECK(websocket_);
521
522  next_state_ = STATE_WEBSOCKET_RECV_HELLO;
523
524  websocket_echo_start_time_ = base::TimeTicks::Now();
525  websocket_->Send(config_.websocket_hello_message);
526  SetTimeout(config_.websocket_hello_echoback_deadline_ms);
527  return net::ERR_IO_PENDING;
528}
529
530int WebSocketExperimentTask::DoWebSocketReceiveHello(int result) {
531  if (result < 0)
532    return result;
533
534  DCHECK(websocket_);
535
536  if (received_messages_.size() != 1)
537    return net::ERR_INVALID_RESPONSE;
538
539  std::string msg = received_messages_.front();
540  received_messages_.pop_front();
541  if (msg != config_.websocket_hello_message)
542    return net::ERR_INVALID_RESPONSE;
543
544  next_state_ = STATE_WEBSOCKET_KEEP_IDLE;
545  return net::OK;
546}
547
548int WebSocketExperimentTask::DoWebSocketKeepIdle() {
549  DCHECK(websocket_);
550
551  next_state_ = STATE_WEBSOCKET_KEEP_IDLE_COMPLETE;
552  websocket_idle_start_time_ = base::TimeTicks::Now();
553  SetTimeout(config_.websocket_idle_ms);
554  return net::ERR_IO_PENDING;
555}
556
557int WebSocketExperimentTask::DoWebSocketKeepIdleComplete(int result) {
558  if (result != net::ERR_TIMED_OUT) {
559    // Server sends back too early, or unexpected close?
560    if (result == net::OK)
561      result = net::ERR_UNEXPECTED;
562    return result;
563  }
564
565  DCHECK(websocket_);
566
567  next_state_ = STATE_WEBSOCKET_RECV_PUSH_MESSAGE;
568  SetTimeout(config_.websocket_receive_push_message_deadline_ms);
569  return net::ERR_IO_PENDING;
570}
571
572int WebSocketExperimentTask::DoWebSocketReceivePushMessage(int result) {
573  if (result < 0)
574    return result;
575
576  DCHECK(websocket_);
577  if (received_messages_.size() != 1)
578    return net::ERR_INVALID_RESPONSE;
579
580  push_message_ = received_messages_.front();
581  received_messages_.pop_front();
582
583  next_state_ = STATE_WEBSOCKET_ECHO_BACK_MESSAGE;
584  return net::OK;
585}
586
587int WebSocketExperimentTask::DoWebSocketEchoBackMessage() {
588  DCHECK(websocket_);
589  DCHECK(!push_message_.empty());
590
591  next_state_ = STATE_WEBSOCKET_RECV_BYE;
592  websocket_->Send(push_message_);
593  SetTimeout(config_.websocket_bye_deadline_ms);
594  return net::ERR_IO_PENDING;
595}
596
597int WebSocketExperimentTask::DoWebSocketReceiveBye(int result) {
598  if (result < 0)
599    return result;
600
601  DCHECK(websocket_);
602
603  if (received_messages_.size() != 1)
604    return net::ERR_INVALID_RESPONSE;
605
606  std::string bye = received_messages_.front();
607  received_messages_.pop_front();
608
609  if (bye != config_.websocket_bye_message)
610    return net::ERR_INVALID_RESPONSE;
611
612  next_state_ = STATE_WEBSOCKET_CLOSE;
613  return net::OK;
614}
615
616int WebSocketExperimentTask::DoWebSocketClose() {
617  DCHECK(websocket_);
618
619  next_state_ = STATE_WEBSOCKET_CLOSE_COMPLETE;
620  websocket_->Close();
621  SetTimeout(config_.websocket_close_deadline_ms);
622  return net::ERR_IO_PENDING;
623}
624
625int WebSocketExperimentTask::DoWebSocketCloseComplete(int result) {
626  websocket_ = NULL;
627  return result;
628}
629
630void WebSocketExperimentTask::SetTimeout(int64 deadline_ms) {
631  bool r = BrowserThread::PostDelayedTask(
632      BrowserThread::IO,
633      FROM_HERE,
634      method_factory_.NewRunnableMethod(&WebSocketExperimentTask::OnTimedOut),
635      deadline_ms);
636  DCHECK(r) << "No IO thread running?";
637}
638
639void WebSocketExperimentTask::RevokeTimeoutTimer() {
640  method_factory_.RevokeAll();
641}
642
643void WebSocketExperimentTask::Finish(int result) {
644  url_fetcher_.reset();
645  scoped_refptr<net::WebSocket> websocket = websocket_;
646  websocket_ = NULL;
647  if (websocket)
648    websocket->DetachDelegate();
649  DVLOG(1) << "Finish WebSocket experiment for " << config_.url
650           << " " << GetProtocolVersionName(config_.protocol_version)
651           << " next_state=" << next_state_
652           << " result=" << net::ErrorToString(result);
653  callback_->Run(result);  // will release this.
654}
655
656}  // namespace chrome_browser_net
657