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