Home | History | Annotate | Download | only in websocket_experiment
      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/profiles/profile.h"
     10 #include "content/browser/browser_thread.h"
     11 #include "net/base/host_resolver.h"
     12 #include "net/base/load_flags.h"
     13 #include "net/base/net_errors.h"
     14 #include "net/url_request/url_request_context_getter.h"
     15 #include "net/websockets/websocket.h"
     16 
     17 using base::Histogram;
     18 using base::LinearHistogram;
     19 
     20 namespace chrome_browser_net_websocket_experiment {
     21 
     22 static 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 
     35 URLFetcher* WebSocketExperimentTask::Context::CreateURLFetcher(
     36     const Config& config, URLFetcher::Delegate* delegate) {
     37   net::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 
     54 net::WebSocket* WebSocketExperimentTask::Context::CreateWebSocket(
     55     const Config& config, net::WebSocketDelegate* delegate) {
     56   net::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.
     75 static const int kUrlFetchDeadlineSec = 10;
     76 static const int kWebSocketConnectDeadlineSec = 10;
     77 // Expects WebSocket live experiment server echoes message back within a few
     78 // seconds.
     79 static 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.
     83 static const int kWebSocketIdleSec = 1;
     84 static const int kWebSocketPushDeadlineSec = 1;
     85 // WebSocket live experiment server sends "bye" message soon.
     86 static const int kWebSocketByeDeadlineSec = 10;
     87 // WebSocket live experiment server closes after it receives "bye" message.
     88 static 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.
     92 static const int kWebSocketTimeSec = 10;
     93 static const int kTimeBucketCount = 50;
     94 
     95 // Holds Histogram objects during experiments run.
     96 static base::hash_map<std::string, Histogram*>* g_histogram_table;
     97 
     98 WebSocketExperimentTask::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 
    117 WebSocketExperimentTask::Config::~Config() {}
    118 
    119 WebSocketExperimentTask::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 
    130 WebSocketExperimentTask::~WebSocketExperimentTask() {
    131   DCHECK(!websocket_);
    132 }
    133 
    134 /* static */
    135 void WebSocketExperimentTask::InitHistogram() {
    136   DCHECK(!g_histogram_table);
    137   g_histogram_table = new base::hash_map<std::string, Histogram*>;
    138 }
    139 
    140 static 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 
    162 static 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   g_histogram_table->insert(std::make_pair(counter_name, counter));
    177   return counter;
    178 }
    179 
    180 static Histogram* GetTimesHistogramForConfig(
    181     const WebSocketExperimentTask::Config& config,
    182     const std::string& name,
    183     base::TimeDelta min,
    184     base::TimeDelta max,
    185     size_t bucket_count) {
    186   DCHECK(g_histogram_table);
    187   std::string counter_name = GetCounterNameForConfig(config, name);
    188   base::hash_map<std::string, Histogram*>::iterator found =
    189       g_histogram_table->find(counter_name);
    190   if (found != g_histogram_table->end()) {
    191     return found->second;
    192   }
    193   Histogram* counter = Histogram::FactoryTimeGet(
    194       counter_name, min, max, bucket_count,
    195       Histogram::kUmaTargetedHistogramFlag);
    196   g_histogram_table->insert(std::make_pair(counter_name, counter));
    197   return counter;
    198 }
    199 
    200 static void UpdateHistogramEnums(
    201     const WebSocketExperimentTask::Config& config,
    202     const std::string& name,
    203     Histogram::Sample sample,
    204     Histogram::Sample boundary_value) {
    205   Histogram* counter = GetEnumsHistogramForConfig(config, name, boundary_value);
    206   counter->Add(sample);
    207 }
    208 
    209 static void UpdateHistogramTimes(
    210     const WebSocketExperimentTask::Config& config,
    211     const std::string& name,
    212     base::TimeDelta sample,
    213     base::TimeDelta min,
    214     base::TimeDelta max,
    215     size_t bucket_count) {
    216   Histogram* counter = GetTimesHistogramForConfig(
    217       config, name, min, max, bucket_count);
    218   counter->AddTime(sample);
    219 }
    220 
    221 /* static */
    222 void WebSocketExperimentTask::ReleaseHistogram() {
    223   DCHECK(g_histogram_table);
    224   delete g_histogram_table;
    225   g_histogram_table = NULL;
    226 }
    227 
    228 void WebSocketExperimentTask::Run() {
    229   DVLOG(1) << "Run WebSocket experiment for " << config_.url << " "
    230            << GetProtocolVersionName(config_.protocol_version);
    231   next_state_ = STATE_URL_FETCH;
    232   DoLoop(net::OK);
    233 }
    234 
    235 void WebSocketExperimentTask::Cancel() {
    236   next_state_ = STATE_NONE;
    237   DoLoop(net::OK);
    238 }
    239 
    240 void WebSocketExperimentTask::SaveResult() const {
    241   DVLOG(1) << "WebSocket experiment save result for " << config_.url
    242            << " last_state=" << result_.last_state;
    243   UpdateHistogramEnums(config_, "LastState", result_.last_state, NUM_STATES);
    244   UpdateHistogramTimes(config_, "UrlFetch", result_.url_fetch,
    245                        base::TimeDelta::FromMilliseconds(1),
    246                        base::TimeDelta::FromSeconds(kUrlFetchDeadlineSec),
    247                        kTimeBucketCount);
    248 
    249   if (result_.last_state < STATE_WEBSOCKET_CONNECT_COMPLETE)
    250     return;
    251 
    252   UpdateHistogramTimes(config_, "WebSocketConnect", result_.websocket_connect,
    253                        base::TimeDelta::FromMilliseconds(1),
    254                        base::TimeDelta::FromSeconds(
    255                            kWebSocketConnectDeadlineSec),
    256                        kTimeBucketCount);
    257 
    258   if (result_.last_state < STATE_WEBSOCKET_RECV_HELLO)
    259     return;
    260 
    261   UpdateHistogramTimes(config_, "WebSocketEcho", result_.websocket_echo,
    262                        base::TimeDelta::FromMilliseconds(1),
    263                        base::TimeDelta::FromSeconds(
    264                            kWebSocketEchoDeadlineSec),
    265                        kTimeBucketCount);
    266 
    267   if (result_.last_state < STATE_WEBSOCKET_KEEP_IDLE)
    268     return;
    269 
    270   UpdateHistogramTimes(config_, "WebSocketIdle", result_.websocket_idle,
    271                        base::TimeDelta::FromMilliseconds(1),
    272                        base::TimeDelta::FromSeconds(
    273                            kWebSocketIdleSec + kWebSocketPushDeadlineSec),
    274                        kTimeBucketCount);
    275 
    276   if (result_.last_state < STATE_WEBSOCKET_CLOSE_COMPLETE)
    277     return;
    278 
    279   UpdateHistogramTimes(config_, "WebSocketTotal", result_.websocket_total,
    280                        base::TimeDelta::FromMilliseconds(1),
    281                        base::TimeDelta::FromSeconds(kWebSocketTimeSec),
    282                        kTimeBucketCount);
    283 }
    284 
    285 // URLFetcher::Delegate method.
    286 void WebSocketExperimentTask::OnURLFetchComplete(
    287     const URLFetcher* source,
    288     const GURL& url,
    289     const net::URLRequestStatus& status,
    290     int response_code,
    291     const ResponseCookies& cookies,
    292     const std::string& data) {
    293   result_.url_fetch = base::TimeTicks::Now() - url_fetch_start_time_;
    294   RevokeTimeoutTimer();
    295   int result = net::ERR_FAILED;
    296   if (next_state_ != STATE_URL_FETCH_COMPLETE) {
    297     DVLOG(1) << "unexpected state=" << next_state_
    298              << " at OnURLFetchComplete for " << config_.http_url;
    299     result = net::ERR_UNEXPECTED;
    300   } else if (response_code == 200 || response_code == 304) {
    301     result = net::OK;
    302   }
    303   DoLoop(result);
    304 }
    305 
    306 // net::WebSocketDelegate
    307 void WebSocketExperimentTask::OnOpen(net::WebSocket* websocket) {
    308   result_.websocket_connect =
    309       base::TimeTicks::Now() - websocket_connect_start_time_;
    310   RevokeTimeoutTimer();
    311   int result = net::ERR_UNEXPECTED;
    312   if (next_state_ == STATE_WEBSOCKET_CONNECT_COMPLETE)
    313     result = net::OK;
    314   else
    315     DVLOG(1) << "unexpected state=" << next_state_
    316              << " at OnOpen for " << config_.url
    317              << " " << GetProtocolVersionName(config_.protocol_version);
    318   DoLoop(result);
    319 }
    320 
    321 void WebSocketExperimentTask::OnMessage(
    322     net::WebSocket* websocket, const std::string& msg) {
    323   if (!result_.websocket_echo.ToInternalValue())
    324     result_.websocket_echo =
    325         base::TimeTicks::Now() - websocket_echo_start_time_;
    326   if (!websocket_idle_start_time_.is_null() &&
    327       !result_.websocket_idle.ToInternalValue())
    328     result_.websocket_idle =
    329         base::TimeTicks::Now() - websocket_idle_start_time_;
    330   RevokeTimeoutTimer();
    331   received_messages_.push_back(msg);
    332   int result = net::ERR_UNEXPECTED;
    333   switch (next_state_) {
    334     case STATE_WEBSOCKET_RECV_HELLO:
    335     case STATE_WEBSOCKET_RECV_PUSH_MESSAGE:
    336     case STATE_WEBSOCKET_RECV_BYE:
    337       result = net::OK;
    338       break;
    339     default:
    340       DVLOG(1) << "unexpected state=" << next_state_
    341                << " at OnMessage for " << config_.url
    342                << " " << GetProtocolVersionName(config_.protocol_version);
    343       break;
    344   }
    345   DoLoop(result);
    346 }
    347 
    348 void WebSocketExperimentTask::OnError(net::WebSocket* websocket) {
    349   // TODO(ukai): record error count?
    350 }
    351 
    352 void WebSocketExperimentTask::OnClose(
    353     net::WebSocket* websocket, bool was_clean) {
    354   RevokeTimeoutTimer();
    355   websocket_ = NULL;
    356   result_.websocket_total =
    357       base::TimeTicks::Now() - websocket_connect_start_time_;
    358   int result = net::ERR_CONNECTION_CLOSED;
    359   if (last_websocket_error_ != net::OK)
    360     result = last_websocket_error_;
    361   DVLOG(1) << "WebSocket onclose was_clean=" << was_clean
    362            << " next_state=" << next_state_
    363            << " last_error=" << net::ErrorToString(result);
    364   if (config_.protocol_version == net::WebSocket::DEFAULT_VERSION) {
    365     if (next_state_ == STATE_WEBSOCKET_CLOSE_COMPLETE && was_clean)
    366       result = net::OK;
    367   } else {
    368     // DRAFT75 doesn't report was_clean correctly.
    369     if (next_state_ == STATE_WEBSOCKET_CLOSE_COMPLETE)
    370       result = net::OK;
    371   }
    372   DoLoop(result);
    373 }
    374 
    375 void WebSocketExperimentTask::OnSocketError(
    376     const net::WebSocket* websocket, int error) {
    377   DVLOG(1) << "WebSocket socket level error=" << net::ErrorToString(error)
    378            << " next_state=" << next_state_
    379            << " for " << config_.url
    380            << " " << GetProtocolVersionName(config_.protocol_version);
    381   last_websocket_error_ = error;
    382 }
    383 
    384 void WebSocketExperimentTask::SetContext(Context* context) {
    385   context_.reset(context);
    386 }
    387 
    388 void WebSocketExperimentTask::OnTimedOut() {
    389   DVLOG(1) << "OnTimedOut next_state=" << next_state_
    390            << " for " << config_.url
    391            << " " << GetProtocolVersionName(config_.protocol_version);
    392   RevokeTimeoutTimer();
    393   DoLoop(net::ERR_TIMED_OUT);
    394 }
    395 
    396 void WebSocketExperimentTask::DoLoop(int result) {
    397   if (next_state_ == STATE_NONE) {
    398     Finish(net::ERR_ABORTED);
    399     return;
    400   }
    401   do {
    402     State state = next_state_;
    403     next_state_ = STATE_NONE;
    404     switch (state) {
    405       case STATE_URL_FETCH:
    406         result = DoURLFetch();
    407         break;
    408       case STATE_URL_FETCH_COMPLETE:
    409         result = DoURLFetchComplete(result);
    410         break;
    411       case STATE_WEBSOCKET_CONNECT:
    412         result = DoWebSocketConnect();
    413         break;
    414       case STATE_WEBSOCKET_CONNECT_COMPLETE:
    415         result = DoWebSocketConnectComplete(result);
    416         break;
    417       case STATE_WEBSOCKET_SEND_HELLO:
    418         result = DoWebSocketSendHello();
    419         break;
    420       case STATE_WEBSOCKET_RECV_HELLO:
    421         result = DoWebSocketReceiveHello(result);
    422         break;
    423       case STATE_WEBSOCKET_KEEP_IDLE:
    424         result = DoWebSocketKeepIdle();
    425         break;
    426       case STATE_WEBSOCKET_KEEP_IDLE_COMPLETE:
    427         result = DoWebSocketKeepIdleComplete(result);
    428         break;
    429       case STATE_WEBSOCKET_RECV_PUSH_MESSAGE:
    430         result = DoWebSocketReceivePushMessage(result);
    431         break;
    432       case STATE_WEBSOCKET_ECHO_BACK_MESSAGE:
    433         result = DoWebSocketEchoBackMessage();
    434         break;
    435       case STATE_WEBSOCKET_RECV_BYE:
    436         result = DoWebSocketReceiveBye(result);
    437         break;
    438       case STATE_WEBSOCKET_CLOSE:
    439         result = DoWebSocketClose();
    440         break;
    441       case STATE_WEBSOCKET_CLOSE_COMPLETE:
    442         result = DoWebSocketCloseComplete(result);
    443         break;
    444       default:
    445         NOTREACHED();
    446         break;
    447     }
    448     result_.last_state = state;
    449   } while (result != net::ERR_IO_PENDING && next_state_ != STATE_NONE);
    450 
    451   if (result != net::ERR_IO_PENDING)
    452     Finish(result);
    453 }
    454 
    455 int WebSocketExperimentTask::DoURLFetch() {
    456   DCHECK(!url_fetcher_.get());
    457 
    458   url_fetcher_.reset(context_->CreateURLFetcher(config_, this));
    459   if (!url_fetcher_.get()) {
    460     // Request context is not ready.
    461     next_state_ = STATE_NONE;
    462     return net::ERR_UNEXPECTED;
    463   }
    464 
    465   next_state_ = STATE_URL_FETCH_COMPLETE;
    466   SetTimeout(config_.url_fetch_deadline_ms);
    467   url_fetch_start_time_ = base::TimeTicks::Now();
    468   url_fetcher_->Start();
    469   return net::ERR_IO_PENDING;
    470 }
    471 
    472 int WebSocketExperimentTask::DoURLFetchComplete(int result) {
    473   url_fetcher_.reset();
    474 
    475   if (result < 0)
    476     return result;
    477 
    478   next_state_ = STATE_WEBSOCKET_CONNECT;
    479   return net::OK;
    480 }
    481 
    482 int WebSocketExperimentTask::DoWebSocketConnect() {
    483   DCHECK(!websocket_);
    484 
    485   websocket_ = context_->CreateWebSocket(config_, this);
    486   if (!websocket_) {
    487     // Request context is not ready.
    488     next_state_ = STATE_NONE;
    489     return net::ERR_UNEXPECTED;
    490   }
    491   next_state_ = STATE_WEBSOCKET_CONNECT_COMPLETE;
    492   websocket_connect_start_time_ = base::TimeTicks::Now();
    493   websocket_->Connect();
    494 
    495   SetTimeout(config_.websocket_onopen_deadline_ms);
    496   return net::ERR_IO_PENDING;
    497 }
    498 
    499 int WebSocketExperimentTask::DoWebSocketConnectComplete(int result) {
    500   if (result < 0)
    501     return result;
    502   DCHECK(websocket_);
    503 
    504   next_state_ = STATE_WEBSOCKET_SEND_HELLO;
    505   return net::OK;
    506 }
    507 
    508 int WebSocketExperimentTask::DoWebSocketSendHello() {
    509   DCHECK(websocket_);
    510 
    511   next_state_ = STATE_WEBSOCKET_RECV_HELLO;
    512 
    513   websocket_echo_start_time_ = base::TimeTicks::Now();
    514   websocket_->Send(config_.websocket_hello_message);
    515   SetTimeout(config_.websocket_hello_echoback_deadline_ms);
    516   return net::ERR_IO_PENDING;
    517 }
    518 
    519 int WebSocketExperimentTask::DoWebSocketReceiveHello(int result) {
    520   if (result < 0)
    521     return result;
    522 
    523   DCHECK(websocket_);
    524 
    525   if (received_messages_.size() != 1)
    526     return net::ERR_INVALID_RESPONSE;
    527 
    528   std::string msg = received_messages_.front();
    529   received_messages_.pop_front();
    530   if (msg != config_.websocket_hello_message)
    531     return net::ERR_INVALID_RESPONSE;
    532 
    533   next_state_ = STATE_WEBSOCKET_KEEP_IDLE;
    534   return net::OK;
    535 }
    536 
    537 int WebSocketExperimentTask::DoWebSocketKeepIdle() {
    538   DCHECK(websocket_);
    539 
    540   next_state_ = STATE_WEBSOCKET_KEEP_IDLE_COMPLETE;
    541   websocket_idle_start_time_ = base::TimeTicks::Now();
    542   SetTimeout(config_.websocket_idle_ms);
    543   return net::ERR_IO_PENDING;
    544 }
    545 
    546 int WebSocketExperimentTask::DoWebSocketKeepIdleComplete(int result) {
    547   if (result != net::ERR_TIMED_OUT) {
    548     // Server sends back too early, or unexpected close?
    549     if (result == net::OK)
    550       result = net::ERR_UNEXPECTED;
    551     return result;
    552   }
    553 
    554   DCHECK(websocket_);
    555 
    556   next_state_ = STATE_WEBSOCKET_RECV_PUSH_MESSAGE;
    557   SetTimeout(config_.websocket_receive_push_message_deadline_ms);
    558   return net::ERR_IO_PENDING;
    559 }
    560 
    561 int WebSocketExperimentTask::DoWebSocketReceivePushMessage(int result) {
    562   if (result < 0)
    563     return result;
    564 
    565   DCHECK(websocket_);
    566   if (received_messages_.size() != 1)
    567     return net::ERR_INVALID_RESPONSE;
    568 
    569   push_message_ = received_messages_.front();
    570   received_messages_.pop_front();
    571 
    572   next_state_ = STATE_WEBSOCKET_ECHO_BACK_MESSAGE;
    573   return net::OK;
    574 }
    575 
    576 int WebSocketExperimentTask::DoWebSocketEchoBackMessage() {
    577   DCHECK(websocket_);
    578   DCHECK(!push_message_.empty());
    579 
    580   next_state_ = STATE_WEBSOCKET_RECV_BYE;
    581   websocket_->Send(push_message_);
    582   SetTimeout(config_.websocket_bye_deadline_ms);
    583   return net::ERR_IO_PENDING;
    584 }
    585 
    586 int WebSocketExperimentTask::DoWebSocketReceiveBye(int result) {
    587   if (result < 0)
    588     return result;
    589 
    590   DCHECK(websocket_);
    591 
    592   if (received_messages_.size() != 1)
    593     return net::ERR_INVALID_RESPONSE;
    594 
    595   std::string bye = received_messages_.front();
    596   received_messages_.pop_front();
    597 
    598   if (bye != config_.websocket_bye_message)
    599     return net::ERR_INVALID_RESPONSE;
    600 
    601   next_state_ = STATE_WEBSOCKET_CLOSE;
    602   return net::OK;
    603 }
    604 
    605 int WebSocketExperimentTask::DoWebSocketClose() {
    606   DCHECK(websocket_);
    607 
    608   next_state_ = STATE_WEBSOCKET_CLOSE_COMPLETE;
    609   websocket_->Close();
    610   SetTimeout(config_.websocket_close_deadline_ms);
    611   return net::ERR_IO_PENDING;
    612 }
    613 
    614 int WebSocketExperimentTask::DoWebSocketCloseComplete(int result) {
    615   websocket_ = NULL;
    616   return result;
    617 }
    618 
    619 void WebSocketExperimentTask::SetTimeout(int64 deadline_ms) {
    620   bool r = BrowserThread::PostDelayedTask(
    621       BrowserThread::IO,
    622       FROM_HERE,
    623       method_factory_.NewRunnableMethod(&WebSocketExperimentTask::OnTimedOut),
    624       deadline_ms);
    625   DCHECK(r) << "No IO thread running?";
    626 }
    627 
    628 void WebSocketExperimentTask::RevokeTimeoutTimer() {
    629   method_factory_.RevokeAll();
    630 }
    631 
    632 void WebSocketExperimentTask::Finish(int result) {
    633   url_fetcher_.reset();
    634   scoped_refptr<net::WebSocket> websocket = websocket_;
    635   websocket_ = NULL;
    636   if (websocket)
    637     websocket->DetachDelegate();
    638   DVLOG(1) << "Finish WebSocket experiment for " << config_.url
    639            << " " << GetProtocolVersionName(config_.protocol_version)
    640            << " next_state=" << next_state_
    641            << " result=" << net::ErrorToString(result);
    642   callback_->Run(result);  // will release this.
    643 }
    644 
    645 }  // namespace chrome_browser_net
    646