OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/policy/device_management_service.h" | 5 #include "chrome/browser/policy/device_management_service.h" |
6 | 6 |
7 #include <utility> | 7 #include <utility> |
8 | 8 |
9 #include "base/bind.h" | 9 #include "base/bind.h" |
10 #include "base/compiler_specific.h" | 10 #include "base/compiler_specific.h" |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
46 namespace { | 46 namespace { |
47 | 47 |
48 const char kValueAgent[] = "%s %s(%s)"; | 48 const char kValueAgent[] = "%s %s(%s)"; |
49 const char kValuePlatform[] = "%s|%s|%s"; | 49 const char kValuePlatform[] = "%s|%s|%s"; |
50 | 50 |
51 const char kPostContentType[] = "application/protobuf"; | 51 const char kPostContentType[] = "application/protobuf"; |
52 | 52 |
53 const char kServiceTokenAuthHeader[] = "Authorization: GoogleLogin auth="; | 53 const char kServiceTokenAuthHeader[] = "Authorization: GoogleLogin auth="; |
54 const char kDMTokenAuthHeader[] = "Authorization: GoogleDMToken token="; | 54 const char kDMTokenAuthHeader[] = "Authorization: GoogleDMToken token="; |
55 | 55 |
| 56 // Number of times to retry on ERR_NETWORK_CHANGED errors. |
| 57 const int kMaxNetworkChangedRetries = 3; |
| 58 |
56 // HTTP Error Codes of the DM Server with their concrete meanings in the context | 59 // HTTP Error Codes of the DM Server with their concrete meanings in the context |
57 // of the DM Server communication. | 60 // of the DM Server communication. |
58 const int kSuccess = 200; | 61 const int kSuccess = 200; |
59 const int kInvalidArgument = 400; | 62 const int kInvalidArgument = 400; |
60 const int kInvalidAuthCookieOrDMToken = 401; | 63 const int kInvalidAuthCookieOrDMToken = 401; |
61 const int kMissingLicenses = 402; | 64 const int kMissingLicenses = 402; |
62 const int kDeviceManagementNotAllowed = 403; | 65 const int kDeviceManagementNotAllowed = 403; |
63 const int kInvalidURL = 404; // This error is not coming from the GFE. | 66 const int kInvalidURL = 404; // This error is not coming from the GFE. |
64 const int kInvalidSerialNumber = 405; | 67 const int kInvalidSerialNumber = 405; |
65 const int kDeviceIdConflict = 409; | 68 const int kDeviceIdConflict = 409; |
(...skipping 17 matching lines...) Expand all Loading... |
83 case net::ERR_HTTPS_PROXY_TUNNEL_RESPONSE: | 86 case net::ERR_HTTPS_PROXY_TUNNEL_RESPONSE: |
84 case net::ERR_MANDATORY_PROXY_CONFIGURATION_FAILED: | 87 case net::ERR_MANDATORY_PROXY_CONFIGURATION_FAILED: |
85 case net::ERR_PROXY_CERTIFICATE_INVALID: | 88 case net::ERR_PROXY_CERTIFICATE_INVALID: |
86 case net::ERR_SOCKS_CONNECTION_FAILED: | 89 case net::ERR_SOCKS_CONNECTION_FAILED: |
87 case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE: | 90 case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE: |
88 return true; | 91 return true; |
89 } | 92 } |
90 return false; | 93 return false; |
91 } | 94 } |
92 | 95 |
93 bool IsProtobufMimeType(const net::URLFetcher* source) { | 96 bool IsProtobufMimeType(const net::URLFetcher* fetcher) { |
94 return source->GetResponseHeaders()->HasHeaderValue( | 97 return fetcher->GetResponseHeaders()->HasHeaderValue( |
95 "content-type", "application/x-protobuffer"); | 98 "content-type", "application/x-protobuffer"); |
96 } | 99 } |
97 | 100 |
| 101 bool FailedWithProxy(const net::URLFetcher* fetcher) { |
| 102 if ((fetcher->GetLoadFlags() & net::LOAD_BYPASS_PROXY) != 0) { |
| 103 // The request didn't use a proxy. |
| 104 return false; |
| 105 } |
| 106 |
| 107 if (!fetcher->GetStatus().is_success() && |
| 108 IsProxyError(fetcher->GetStatus())) { |
| 109 LOG(WARNING) << "Proxy failed while contacting dmserver."; |
| 110 return true; |
| 111 } |
| 112 |
| 113 if (fetcher->GetStatus().is_success() && |
| 114 fetcher->GetResponseCode() == kSuccess && |
| 115 fetcher->WasFetchedViaProxy() && |
| 116 !IsProtobufMimeType(fetcher)) { |
| 117 // The proxy server can be misconfigured but pointing to an existing |
| 118 // server that replies to requests. Try to recover if a successful |
| 119 // request that went through a proxy returns an unexpected mime type. |
| 120 LOG(WARNING) << "Got bad mime-type in response from dmserver that was " |
| 121 << "fetched via a proxy."; |
| 122 return true; |
| 123 } |
| 124 |
| 125 return false; |
| 126 } |
| 127 |
98 const char* UserAffiliationToString(UserAffiliation affiliation) { | 128 const char* UserAffiliationToString(UserAffiliation affiliation) { |
99 switch (affiliation) { | 129 switch (affiliation) { |
100 case USER_AFFILIATION_MANAGED: | 130 case USER_AFFILIATION_MANAGED: |
101 return dm_protocol::kValueUserAffiliationManaged; | 131 return dm_protocol::kValueUserAffiliationManaged; |
102 case USER_AFFILIATION_NONE: | 132 case USER_AFFILIATION_NONE: |
103 return dm_protocol::kValueUserAffiliationNone; | 133 return dm_protocol::kValueUserAffiliationNone; |
104 } | 134 } |
105 NOTREACHED() << "Invalid user affiliation " << affiliation; | 135 NOTREACHED() << "Invalid user affiliation " << affiliation; |
106 return dm_protocol::kValueUserAffiliationNone; | 136 return dm_protocol::kValueUserAffiliationNone; |
107 } | 137 } |
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
268 int response_code, | 298 int response_code, |
269 const net::ResponseCookies& cookies, | 299 const net::ResponseCookies& cookies, |
270 const std::string& data); | 300 const std::string& data); |
271 | 301 |
272 // Gets the URL to contact. | 302 // Gets the URL to contact. |
273 GURL GetURL(const std::string& server_url); | 303 GURL GetURL(const std::string& server_url); |
274 | 304 |
275 // Configures the fetcher, setting up payload and headers. | 305 // Configures the fetcher, setting up payload and headers. |
276 void ConfigureRequest(net::URLFetcher* fetcher); | 306 void ConfigureRequest(net::URLFetcher* fetcher); |
277 | 307 |
| 308 // Returns true if this job should be retried. |fetcher| has just completed, |
| 309 // and can be inspected to determine if the request failed and should be |
| 310 // retried. |
| 311 bool ShouldRetry(const net::URLFetcher* fetcher); |
| 312 |
| 313 // Invoked right before retrying this job. |
| 314 void PrepareRetry(); |
| 315 |
278 protected: | 316 protected: |
279 // DeviceManagementRequestJob: | 317 // DeviceManagementRequestJob: |
280 virtual void Run() OVERRIDE; | 318 virtual void Run() OVERRIDE; |
281 | 319 |
282 private: | 320 private: |
283 // Invokes the callback with the given error code. | 321 // Invokes the callback with the given error code. |
284 void ReportError(DeviceManagementStatus code); | 322 void ReportError(DeviceManagementStatus code); |
285 | 323 |
286 // Pointer to the service this job is associated with. | 324 // Pointer to the service this job is associated with. |
287 DeviceManagementService* service_; | 325 DeviceManagementService* service_; |
288 | 326 |
| 327 // Whether the BYPASS_PROXY flag should be set by ConfigureRequest(). |
| 328 bool bypass_proxy_; |
| 329 |
| 330 // Number of times that this job has been retried due to ERR_NETWORK_CHANGED. |
| 331 int retries_count_; |
| 332 |
289 DISALLOW_COPY_AND_ASSIGN(DeviceManagementRequestJobImpl); | 333 DISALLOW_COPY_AND_ASSIGN(DeviceManagementRequestJobImpl); |
290 }; | 334 }; |
291 | 335 |
292 DeviceManagementRequestJobImpl::DeviceManagementRequestJobImpl( | 336 DeviceManagementRequestJobImpl::DeviceManagementRequestJobImpl( |
293 JobType type, | 337 JobType type, |
294 DeviceManagementService* service) | 338 DeviceManagementService* service) |
295 : DeviceManagementRequestJob(type), | 339 : DeviceManagementRequestJob(type), |
296 service_(service) {} | 340 service_(service), |
| 341 bypass_proxy_(false), |
| 342 retries_count_(0) {} |
297 | 343 |
298 DeviceManagementRequestJobImpl::~DeviceManagementRequestJobImpl() { | 344 DeviceManagementRequestJobImpl::~DeviceManagementRequestJobImpl() { |
299 service_->RemoveJob(this); | 345 service_->RemoveJob(this); |
300 } | 346 } |
301 | 347 |
302 void DeviceManagementRequestJobImpl::Run() { | 348 void DeviceManagementRequestJobImpl::Run() { |
303 service_->AddJob(this); | 349 service_->AddJob(this); |
304 } | 350 } |
305 | 351 |
306 void DeviceManagementRequestJobImpl::HandleResponse( | 352 void DeviceManagementRequestJobImpl::HandleResponse( |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
382 result += '&'; | 428 result += '&'; |
383 result += net::EscapeQueryParamValue(entry->first, true); | 429 result += net::EscapeQueryParamValue(entry->first, true); |
384 result += '='; | 430 result += '='; |
385 result += net::EscapeQueryParamValue(entry->second, true); | 431 result += net::EscapeQueryParamValue(entry->second, true); |
386 } | 432 } |
387 return GURL(result); | 433 return GURL(result); |
388 } | 434 } |
389 | 435 |
390 void DeviceManagementRequestJobImpl::ConfigureRequest( | 436 void DeviceManagementRequestJobImpl::ConfigureRequest( |
391 net::URLFetcher* fetcher) { | 437 net::URLFetcher* fetcher) { |
| 438 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | |
| 439 net::LOAD_DO_NOT_SAVE_COOKIES | |
| 440 net::LOAD_DISABLE_CACHE | |
| 441 (bypass_proxy_ ? net::LOAD_BYPASS_PROXY : 0)); |
392 std::string payload; | 442 std::string payload; |
393 CHECK(request_.SerializeToString(&payload)); | 443 CHECK(request_.SerializeToString(&payload)); |
394 fetcher->SetUploadData(kPostContentType, payload); | 444 fetcher->SetUploadData(kPostContentType, payload); |
395 std::string extra_headers; | 445 std::string extra_headers; |
396 if (!gaia_token_.empty()) | 446 if (!gaia_token_.empty()) |
397 extra_headers += kServiceTokenAuthHeader + gaia_token_ + "\n"; | 447 extra_headers += kServiceTokenAuthHeader + gaia_token_ + "\n"; |
398 if (!dm_token_.empty()) | 448 if (!dm_token_.empty()) |
399 extra_headers += kDMTokenAuthHeader + dm_token_ + "\n"; | 449 extra_headers += kDMTokenAuthHeader + dm_token_ + "\n"; |
400 fetcher->SetExtraRequestHeaders(extra_headers); | 450 fetcher->SetExtraRequestHeaders(extra_headers); |
401 } | 451 } |
402 | 452 |
| 453 bool DeviceManagementRequestJobImpl::ShouldRetry( |
| 454 const net::URLFetcher* fetcher) { |
| 455 if (FailedWithProxy(fetcher) && !bypass_proxy_) { |
| 456 // Retry the job if it failed due to a broken proxy, by bypassing the |
| 457 // proxy on the next try. |
| 458 bypass_proxy_ = true; |
| 459 return true; |
| 460 } |
| 461 |
| 462 // Early device policy fetches on ChromeOS and Auto-Enrollment checks are |
| 463 // often interrupted during ChromeOS startup when network change notifications |
| 464 // are sent. Allowing the fetcher to retry once after that is enough to |
| 465 // recover; allow it to retry up to 3 times just in case. |
| 466 if (fetcher->GetStatus().error() == net::ERR_NETWORK_CHANGED && |
| 467 retries_count_ < kMaxNetworkChangedRetries) { |
| 468 ++retries_count_; |
| 469 return true; |
| 470 } |
| 471 |
| 472 // The request didn't fail, or the limit of retry attempts has been reached; |
| 473 // forward the result to the job owner. |
| 474 return false; |
| 475 } |
| 476 |
| 477 void DeviceManagementRequestJobImpl::PrepareRetry() { |
| 478 if (!retry_callback_.is_null()) |
| 479 retry_callback_.Run(this); |
| 480 } |
| 481 |
403 void DeviceManagementRequestJobImpl::ReportError(DeviceManagementStatus code) { | 482 void DeviceManagementRequestJobImpl::ReportError(DeviceManagementStatus code) { |
404 em::DeviceManagementResponse dummy_response; | 483 em::DeviceManagementResponse dummy_response; |
405 callback_.Run(code, dummy_response); | 484 callback_.Run(code, dummy_response); |
406 } | 485 } |
407 | 486 |
408 DeviceManagementRequestJob::~DeviceManagementRequestJob() {} | 487 DeviceManagementRequestJob::~DeviceManagementRequestJob() {} |
409 | 488 |
410 void DeviceManagementRequestJob::SetGaiaToken(const std::string& gaia_token) { | 489 void DeviceManagementRequestJob::SetGaiaToken(const std::string& gaia_token) { |
411 gaia_token_ = gaia_token; | 490 gaia_token_ = gaia_token; |
412 } | 491 } |
(...skipping 21 matching lines...) Expand all Loading... |
434 } | 513 } |
435 | 514 |
436 DeviceManagementRequestJob::DeviceManagementRequestJob(JobType type) { | 515 DeviceManagementRequestJob::DeviceManagementRequestJob(JobType type) { |
437 AddParameter(dm_protocol::kParamRequest, JobTypeToRequestType(type)); | 516 AddParameter(dm_protocol::kParamRequest, JobTypeToRequestType(type)); |
438 AddParameter(dm_protocol::kParamDeviceType, dm_protocol::kValueDeviceType); | 517 AddParameter(dm_protocol::kParamDeviceType, dm_protocol::kValueDeviceType); |
439 AddParameter(dm_protocol::kParamAppType, dm_protocol::kValueAppType); | 518 AddParameter(dm_protocol::kParamAppType, dm_protocol::kValueAppType); |
440 AddParameter(dm_protocol::kParamAgent, GetAgentString()); | 519 AddParameter(dm_protocol::kParamAgent, GetAgentString()); |
441 AddParameter(dm_protocol::kParamPlatform, GetPlatformString()); | 520 AddParameter(dm_protocol::kParamPlatform, GetPlatformString()); |
442 } | 521 } |
443 | 522 |
| 523 void DeviceManagementRequestJob::SetRetryCallback( |
| 524 const RetryCallback& retry_callback) { |
| 525 retry_callback_ = retry_callback; |
| 526 } |
| 527 |
444 void DeviceManagementRequestJob::Start(const Callback& callback) { | 528 void DeviceManagementRequestJob::Start(const Callback& callback) { |
445 callback_ = callback; | 529 callback_ = callback; |
446 Run(); | 530 Run(); |
447 } | 531 } |
448 | 532 |
449 void DeviceManagementRequestJob::AddParameter(const std::string& name, | 533 void DeviceManagementRequestJob::AddParameter(const std::string& name, |
450 const std::string& value) { | 534 const std::string& value) { |
451 query_params_.push_back(std::make_pair(name, value)); | 535 query_params_.push_back(std::make_pair(name, value)); |
452 } | 536 } |
453 | 537 |
(...skipping 20 matching lines...) Expand all Loading... |
474 | 558 |
475 void DeviceManagementService::Initialize() { | 559 void DeviceManagementService::Initialize() { |
476 if (initialized_) | 560 if (initialized_) |
477 return; | 561 return; |
478 DCHECK(!request_context_getter_); | 562 DCHECK(!request_context_getter_); |
479 request_context_getter_ = new DeviceManagementRequestContextGetter( | 563 request_context_getter_ = new DeviceManagementRequestContextGetter( |
480 g_browser_process->system_request_context()); | 564 g_browser_process->system_request_context()); |
481 initialized_ = true; | 565 initialized_ = true; |
482 | 566 |
483 while (!queued_jobs_.empty()) { | 567 while (!queued_jobs_.empty()) { |
484 StartJob(queued_jobs_.front(), false); | 568 StartJob(queued_jobs_.front()); |
485 queued_jobs_.pop_front(); | 569 queued_jobs_.pop_front(); |
486 } | 570 } |
487 } | 571 } |
488 | 572 |
489 void DeviceManagementService::Shutdown() { | 573 void DeviceManagementService::Shutdown() { |
490 for (JobFetcherMap::iterator job(pending_jobs_.begin()); | 574 for (JobFetcherMap::iterator job(pending_jobs_.begin()); |
491 job != pending_jobs_.end(); | 575 job != pending_jobs_.end(); |
492 ++job) { | 576 ++job) { |
493 delete job->first; | 577 delete job->first; |
494 queued_jobs_.push_back(job->second); | 578 queued_jobs_.push_back(job->second); |
495 } | 579 } |
496 pending_jobs_.clear(); | 580 pending_jobs_.clear(); |
497 } | 581 } |
498 | 582 |
499 DeviceManagementService::DeviceManagementService( | 583 DeviceManagementService::DeviceManagementService( |
500 const std::string& server_url) | 584 const std::string& server_url) |
501 : server_url_(server_url), | 585 : server_url_(server_url), |
502 initialized_(false), | 586 initialized_(false), |
503 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { | 587 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { |
504 } | 588 } |
505 | 589 |
506 void DeviceManagementService::StartJob(DeviceManagementRequestJobImpl* job, | 590 void DeviceManagementService::StartJob(DeviceManagementRequestJobImpl* job) { |
507 bool bypass_proxy) { | |
508 net::URLFetcher* fetcher = net::URLFetcher::Create( | 591 net::URLFetcher* fetcher = net::URLFetcher::Create( |
509 0, job->GetURL(server_url_), net::URLFetcher::POST, this); | 592 0, job->GetURL(server_url_), net::URLFetcher::POST, this); |
510 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | | |
511 net::LOAD_DO_NOT_SAVE_COOKIES | | |
512 net::LOAD_DISABLE_CACHE | | |
513 (bypass_proxy ? net::LOAD_BYPASS_PROXY : 0)); | |
514 fetcher->SetRequestContext(request_context_getter_.get()); | 593 fetcher->SetRequestContext(request_context_getter_.get()); |
515 // Early device policy fetches on ChromeOS and Auto-Enrollment checks are | |
516 // often interrupted during ChromeOS startup when network change notifications | |
517 // are sent. Allowing the fetcher to retry once after that is enough to | |
518 // recover; allow it to retry up to 3 times just in case. | |
519 // http://crosbug.com/16114 | |
520 fetcher->SetAutomaticallyRetryOnNetworkChanges(3); | |
521 job->ConfigureRequest(fetcher); | 594 job->ConfigureRequest(fetcher); |
522 pending_jobs_[fetcher] = job; | 595 pending_jobs_[fetcher] = job; |
523 fetcher->Start(); | 596 fetcher->Start(); |
524 } | 597 } |
525 | 598 |
526 void DeviceManagementService::OnURLFetchComplete( | 599 void DeviceManagementService::OnURLFetchComplete( |
527 const net::URLFetcher* source) { | 600 const net::URLFetcher* source) { |
528 JobFetcherMap::iterator entry(pending_jobs_.find(source)); | 601 JobFetcherMap::iterator entry(pending_jobs_.find(source)); |
529 if (entry == pending_jobs_.end()) { | 602 if (entry == pending_jobs_.end()) { |
530 NOTREACHED() << "Callback from foreign URL fetcher"; | 603 NOTREACHED() << "Callback from foreign URL fetcher"; |
531 return; | 604 return; |
532 } | 605 } |
533 | 606 |
534 DeviceManagementRequestJobImpl* job = entry->second; | 607 DeviceManagementRequestJobImpl* job = entry->second; |
535 pending_jobs_.erase(entry); | 608 pending_jobs_.erase(entry); |
536 | 609 |
537 // Retry the job if it failed due to a broken proxy, by bypassing the | 610 if (job->ShouldRetry(source)) { |
538 // proxy on the next try. Don't retry if this URLFetcher already bypassed | 611 VLOG(1) << "Retrying dmserver request."; |
539 // the proxy. | 612 job->PrepareRetry(); |
540 bool retry = false; | 613 StartJob(job); |
541 if ((source->GetLoadFlags() & net::LOAD_BYPASS_PROXY) == 0) { | |
542 if (!source->GetStatus().is_success() && | |
543 IsProxyError(source->GetStatus())) { | |
544 LOG(WARNING) << "Proxy failed while contacting dmserver."; | |
545 retry = true; | |
546 } else if (source->GetStatus().is_success() && | |
547 source->GetResponseCode() == kSuccess && | |
548 source->WasFetchedViaProxy() && | |
549 !IsProtobufMimeType(source)) { | |
550 // The proxy server can be misconfigured but pointing to an existing | |
551 // server that replies to requests. Try to recover if a successful | |
552 // request that went through a proxy returns an unexpected mime type. | |
553 LOG(WARNING) << "Got bad mime-type in response from dmserver that was " | |
554 << "fetched via a proxy."; | |
555 retry = true; | |
556 } | |
557 } | |
558 | |
559 if (retry) { | |
560 LOG(WARNING) << "Retrying dmserver request without using a proxy."; | |
561 StartJob(job, true); | |
562 } else { | 614 } else { |
563 std::string data; | 615 std::string data; |
564 source->GetResponseAsString(&data); | 616 source->GetResponseAsString(&data); |
565 job->HandleResponse(source->GetStatus(), source->GetResponseCode(), | 617 job->HandleResponse(source->GetStatus(), source->GetResponseCode(), |
566 source->GetCookies(), data); | 618 source->GetCookies(), data); |
567 } | 619 } |
568 delete source; | 620 delete source; |
569 } | 621 } |
570 | 622 |
571 void DeviceManagementService::AddJob(DeviceManagementRequestJobImpl* job) { | 623 void DeviceManagementService::AddJob(DeviceManagementRequestJobImpl* job) { |
572 if (initialized_) | 624 if (initialized_) |
573 StartJob(job, false); | 625 StartJob(job); |
574 else | 626 else |
575 queued_jobs_.push_back(job); | 627 queued_jobs_.push_back(job); |
576 } | 628 } |
577 | 629 |
578 void DeviceManagementService::RemoveJob(DeviceManagementRequestJobImpl* job) { | 630 void DeviceManagementService::RemoveJob(DeviceManagementRequestJobImpl* job) { |
579 for (JobFetcherMap::iterator entry(pending_jobs_.begin()); | 631 for (JobFetcherMap::iterator entry(pending_jobs_.begin()); |
580 entry != pending_jobs_.end(); | 632 entry != pending_jobs_.end(); |
581 ++entry) { | 633 ++entry) { |
582 if (entry->second == job) { | 634 if (entry->second == job) { |
583 delete entry->first; | 635 delete entry->first; |
584 pending_jobs_.erase(entry); | 636 pending_jobs_.erase(entry); |
585 return; | 637 return; |
586 } | 638 } |
587 } | 639 } |
588 | 640 |
589 const JobQueue::iterator elem = | 641 const JobQueue::iterator elem = |
590 std::find(queued_jobs_.begin(), queued_jobs_.end(), job); | 642 std::find(queued_jobs_.begin(), queued_jobs_.end(), job); |
591 if (elem != queued_jobs_.end()) | 643 if (elem != queued_jobs_.end()) |
592 queued_jobs_.erase(elem); | 644 queued_jobs_.erase(elem); |
593 } | 645 } |
594 | 646 |
595 } // namespace policy | 647 } // namespace policy |
OLD | NEW |