Commit 14655855 authored by Dan Elphick's avatar Dan Elphick Committed by Commit Bot

Add global scheduling to Background Fetch

The scheduler is now responsible for choosing which
BackgroundFetchJobController runs next. It round-robins through the
list of job controllers launching at most one download from each at a
time. Previously each JobController would get a new request from the
DataManager as soon it completed one, meaning that there would be as
many active downloads as there are JobControllers. The new scheduler
limits downloads globally and so will prevent too many downloads from
happening concurrently.

BackgroundFetchScheduler gets requests from a RequestManager
(implemented by BackgroundFetchDataManager) and delivers them to a
Controller (implemented by BackgroundFetchJobController).

Also eliminates MarkRequestAsStarted and related functions as we don't
do anything with the info.

Bug: 757760
Change-Id: I59f68347e93d278285c83351cbe4e1dd22b91b1b
Reviewed-on: https://chromium-review.googlesource.com/776666
Commit-Queue: Dan Elphick <delphick@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#526950}
parent 0197ba7a
......@@ -411,6 +411,8 @@ jumbo_source_set("browser") {
"background_fetch/background_fetch_request_info.cc",
"background_fetch/background_fetch_request_info.h",
"background_fetch/background_fetch_request_manager.h",
"background_fetch/background_fetch_scheduler.cc",
"background_fetch/background_fetch_scheduler.h",
"background_fetch/background_fetch_service_impl.cc",
"background_fetch/background_fetch_service_impl.h",
"background_fetch/storage/cleanup_task.cc",
......
......@@ -10,6 +10,7 @@
#include "content/browser/background_fetch/background_fetch_job_controller.h"
#include "content/browser/background_fetch/background_fetch_registration_id.h"
#include "content/browser/background_fetch/background_fetch_registration_notifier.h"
#include "content/browser/background_fetch/background_fetch_scheduler.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/public/browser/background_fetch_delegate.h"
#include "content/public/browser/browser_context.h"
......@@ -47,6 +48,7 @@ BackgroundFetchContext::BackgroundFetchContext(
registration_notifier_(
std::make_unique<BackgroundFetchRegistrationNotifier>()),
delegate_proxy_(browser_context_->GetBackgroundFetchDelegate()),
scheduler_(std::make_unique<BackgroundFetchScheduler>(&data_manager_)),
weak_factory_(this) {
// Although this lives only on the IO thread, it is constructed on UI thread.
DCHECK_CURRENTLY_ON(BrowserThread::UI);
......@@ -182,7 +184,8 @@ void BackgroundFetchContext::CreateController(
DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto controller = std::make_unique<BackgroundFetchJobController>(
&delegate_proxy_, registration_id, options, registration, &data_manager_,
&delegate_proxy_, registration_id, options, registration,
scheduler_.get(),
// Safe because JobControllers are destroyed before RegistrationNotifier.
base::BindRepeating(&BackgroundFetchRegistrationNotifier::Notify,
base::Unretained(registration_notifier_.get())),
......@@ -197,9 +200,7 @@ void BackgroundFetchContext::CreateController(
data_manager_.GetTotalNumberOfRequests(registration_id),
std::vector<std::string>() /* outstanding download GUIDs */);
// Start fetching the first few requests immediately. At some point in the
// future we may want a more elaborate scheduling mechanism here.
controller->Start();
scheduler_->AddJobController(controller.get());
job_controllers_.insert(
std::make_pair(registration_id.unique_id(), std::move(controller)));
......
......@@ -30,6 +30,7 @@ class BackgroundFetchJobController;
struct BackgroundFetchOptions;
class BackgroundFetchRegistrationId;
class BackgroundFetchRegistrationNotifier;
class BackgroundFetchScheduler;
class BrowserContext;
class ServiceWorkerContextWrapper;
struct ServiceWorkerFetchRequest;
......@@ -173,6 +174,7 @@ class CONTENT_EXPORT BackgroundFetchContext
BackgroundFetchEventDispatcher event_dispatcher_;
std::unique_ptr<BackgroundFetchRegistrationNotifier> registration_notifier_;
BackgroundFetchDelegateProxy delegate_proxy_;
std::unique_ptr<BackgroundFetchScheduler> scheduler_;
// Map from background fetch registration |unique_id|s to active job
// controllers. Must be destroyed before |data_manager_| and
......
......@@ -82,24 +82,9 @@ class BackgroundFetchDataManager::RegistrationData {
return request;
}
// Marks the |request| as having started with the given |download_guid|.
// Persistent storage needs to store the association so we can resume fetches
// after a browser restart, here we just verify that the |request| is active.
void MarkRequestAsStarted(BackgroundFetchRequestInfo* request,
const std::string& download_guid) {
const auto iter = std::find_if(
active_requests_.begin(), active_requests_.end(),
[&request](scoped_refptr<BackgroundFetchRequestInfo> active_request) {
return active_request->request_index() == request->request_index();
});
// The |request| must have been consumed from this RegistrationData.
DCHECK(iter != active_requests_.end());
}
// Marks the |request| as having completed. Verifies that the |request| is
// currently active and moves it to the |completed_requests_| vector.
bool MarkRequestAsComplete(BackgroundFetchRequestInfo* request) {
void MarkRequestAsComplete(BackgroundFetchRequestInfo* request) {
const auto iter = std::find_if(
active_requests_.begin(), active_requests_.end(),
[&request](scoped_refptr<BackgroundFetchRequestInfo> active_request) {
......@@ -113,10 +98,6 @@ class BackgroundFetchDataManager::RegistrationData {
active_requests_.erase(iter);
complete_requests_downloaded_bytes_ += request->GetFileSize();
bool has_pending_or_active_requests =
!pending_requests_.empty() || !active_requests_.empty();
return has_pending_or_active_requests;
}
// Returns the vector with all completed requests part of this registration.
......@@ -325,33 +306,19 @@ void BackgroundFetchDataManager::PopNextRequest(
std::move(callback).Run(std::move(next_request));
}
void BackgroundFetchDataManager::MarkRequestAsStarted(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchRequestInfo* request,
const std::string& download_guid) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto iter = registrations_.find(registration_id.unique_id());
DCHECK(iter != registrations_.end());
RegistrationData* registration_data = iter->second.get();
registration_data->MarkRequestAsStarted(request, download_guid);
}
void BackgroundFetchDataManager::MarkRequestAsComplete(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchRequestInfo* request,
MarkedCompleteCallback callback) {
BackgroundFetchScheduler::MarkedCompleteCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto iter = registrations_.find(registration_id.unique_id());
DCHECK(iter != registrations_.end());
RegistrationData* registration_data = iter->second.get();
bool has_pending_or_active_requests =
registration_data->MarkRequestAsComplete(request);
registration_data->MarkRequestAsComplete(request);
std::move(callback).Run(has_pending_or_active_requests);
std::move(callback).Run();
}
void BackgroundFetchDataManager::GetSettledFetchesForRegistration(
......
......@@ -17,7 +17,7 @@
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "content/browser/background_fetch/background_fetch_registration_id.h"
#include "content/browser/background_fetch/background_fetch_request_manager.h"
#include "content/browser/background_fetch/background_fetch_scheduler.h"
#include "content/browser/background_fetch/storage/database_task.h"
#include "content/common/content_export.h"
#include "third_party/WebKit/public/platform/modules/background_fetch/background_fetch.mojom.h"
......@@ -47,7 +47,7 @@ class ServiceWorkerContextWrapper;
//
// Storage schema is documented in storage/README.md
class CONTENT_EXPORT BackgroundFetchDataManager
: public BackgroundFetchRequestManager {
: public BackgroundFetchScheduler::RequestProvider {
public:
using SettledFetchesCallback = base::OnceCallback<void(
blink::mojom::BackgroundFetchError,
......@@ -57,10 +57,13 @@ class CONTENT_EXPORT BackgroundFetchDataManager
using GetRegistrationCallback =
base::OnceCallback<void(blink::mojom::BackgroundFetchError,
std::unique_ptr<BackgroundFetchRegistration>)>;
using NextRequestCallback =
base::OnceCallback<void(scoped_refptr<BackgroundFetchRequestInfo>)>;
BackgroundFetchDataManager(
BrowserContext* browser_context,
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context);
~BackgroundFetchDataManager() override;
// Creates and stores a new registration with the given properties. Will
......@@ -122,17 +125,13 @@ class CONTENT_EXPORT BackgroundFetchDataManager
int GetTotalNumberOfRequests(
const BackgroundFetchRegistrationId& registration_id) const;
// BackgroundFetchRequestManager implementation:
// BackgroundFetchScheduler::RequestProvider implementation:
void PopNextRequest(const BackgroundFetchRegistrationId& registration_id,
NextRequestCallback callback) override;
void MarkRequestAsStarted(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchRequestInfo* request,
const std::string& download_guid) override;
void MarkRequestAsComplete(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchRequestInfo* request,
MarkedCompleteCallback callback) override;
BackgroundFetchScheduler::MarkedCompleteCallback callback) override;
private:
FRIEND_TEST_ALL_PREFIXES(BackgroundFetchDataManagerTest, Cleanup);
......
......@@ -19,14 +19,14 @@ BackgroundFetchJobController::BackgroundFetchJobController(
const BackgroundFetchRegistration& registration,
BackgroundFetchRequestManager* request_manager,
ProgressCallback progress_callback,
FinishedCallback finished_callback)
: registration_id_(registration_id),
BackgroundFetchScheduler::FinishedCallback finished_callback)
: BackgroundFetchScheduler::Controller(registration_id,
std::move(finished_callback)),
options_(options),
complete_requests_downloaded_bytes_cache_(registration.downloaded),
request_manager_(request_manager),
delegate_proxy_(delegate_proxy),
progress_callback_(std::move(progress_callback)),
finished_callback_(std::move(finished_callback)),
weak_ptr_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
}
......@@ -37,8 +37,15 @@ void BackgroundFetchJobController::InitializeRequestStatus(
const std::vector<std::string>& outstanding_guids) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Don't allow double initialization.
DCHECK_GT(total_downloads, 0);
DCHECK_EQ(total_downloads_, 0);
completed_downloads_ = completed_downloads;
total_downloads_ = total_downloads;
delegate_proxy_->CreateDownloadJob(
registration_id_.unique_id(), options_.title, registration_id_.origin(),
registration_id().unique_id(), options_.title, registration_id().origin(),
GetWeakPtr(), completed_downloads, total_downloads, outstanding_guids);
}
......@@ -46,39 +53,29 @@ BackgroundFetchJobController::~BackgroundFetchJobController() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
}
void BackgroundFetchJobController::Start() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// TODO(crbug.com/741609): Enforce kMaximumBackgroundFetchParallelRequests
// globally and/or per origin rather than per fetch.
for (size_t i = 0; i < kMaximumBackgroundFetchParallelRequests; i++) {
request_manager_->PopNextRequest(
registration_id_,
base::BindOnce(&BackgroundFetchJobController::StartRequest,
weak_ptr_factory_.GetWeakPtr()));
}
bool BackgroundFetchJobController::HasMoreRequests() {
return completed_downloads_ < total_downloads_;
}
void BackgroundFetchJobController::StartRequest(
scoped_refptr<BackgroundFetchRequestInfo> request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!request) {
// This can happen when |Start| tries to start multiple initial requests,
// but the fetch does not contain that many pending requests; or when
// |DidMarkRequestCompleted| tries to start the next request but there are
// none left, perhaps because the registration was aborted.
return;
}
DCHECK_LT(completed_downloads_, total_downloads_);
DCHECK(request);
active_request_download_bytes_[request->download_guid()] = 0;
delegate_proxy_->StartRequest(registration_id_.unique_id(),
registration_id_.origin(), request);
delegate_proxy_->StartRequest(registration_id().unique_id(),
registration_id().origin(), request);
}
void BackgroundFetchJobController::DidStartRequest(
const scoped_refptr<BackgroundFetchRequestInfo>& request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
request_manager_->MarkRequestAsStarted(registration_id_, request.get(),
request->download_guid());
// TODO(delphick): Either add CORS check here or remove this function and do
// the CORS check in BackgroundFetchDelegateImpl (since
// download::Client::OnDownloadStarted returns a value that can abort the
// download).
}
void BackgroundFetchJobController::DidUpdateRequest(
......@@ -92,7 +89,7 @@ void BackgroundFetchJobController::DidUpdateRequest(
active_request_download_bytes_[download_guid] = bytes_downloaded;
progress_callback_.Run(registration_id_.unique_id(), options_.download_total,
progress_callback_.Run(registration_id().unique_id(), options_.download_total,
complete_requests_downloaded_bytes_cache_ +
GetInProgressDownloadedBytes());
}
......@@ -103,38 +100,9 @@ void BackgroundFetchJobController::DidCompleteRequest(
active_request_download_bytes_.erase(request->download_guid());
complete_requests_downloaded_bytes_cache_ += request->GetFileSize();
++completed_downloads_;
// The RequestManager must acknowledge that it stored the data and that there
// are no more pending requests to avoid marking this job as completed too
// early.
request_manager_->MarkRequestAsComplete(
registration_id_, request.get(),
base::BindOnce(&BackgroundFetchJobController::DidMarkRequestCompleted,
weak_ptr_factory_.GetWeakPtr()));
}
void BackgroundFetchJobController::DidMarkRequestCompleted(
bool has_pending_or_active_requests) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// If not all requests have completed, start a pending request if there are
// any left, and bail.
if (has_pending_or_active_requests) {
request_manager_->PopNextRequest(
registration_id_,
base::BindOnce(&BackgroundFetchJobController::StartRequest,
weak_ptr_factory_.GetWeakPtr()));
return;
}
// Otherwise the job this controller is responsible for has completed.
if (finished_callback_) {
// Copy registration_id_ onto stack as finished_callback_ may delete |this|.
BackgroundFetchRegistrationId registration_id(registration_id_);
// This call will be ignored if the job has already been aborted.
std::move(finished_callback_).Run(registration_id, false /* aborted */);
}
request_manager_->MarkRequestAsComplete(registration_id(), request.get());
}
void BackgroundFetchJobController::AbortFromUser() {
......@@ -146,7 +114,7 @@ void BackgroundFetchJobController::AbortFromUser() {
void BackgroundFetchJobController::UpdateUI(const std::string& title) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
delegate_proxy_->UpdateUI(registration_id_.unique_id(), title);
delegate_proxy_->UpdateUI(registration_id().unique_id(), title);
}
uint64_t BackgroundFetchJobController::GetInProgressDownloadedBytes() {
......@@ -164,16 +132,13 @@ void BackgroundFetchJobController::Abort() {
void BackgroundFetchJobController::Abort(bool cancel_download) {
if (cancel_download)
delegate_proxy_->Abort(registration_id_.unique_id());
if (!finished_callback_)
return;
// Copy registration_id_ onto stack as finished_callback_ may delete |this|.
BackgroundFetchRegistrationId registration_id(registration_id_);
delegate_proxy_->Abort(registration_id().unique_id());
// This call will be ignored if job has already completed/failed/aborted.
std::move(finished_callback_).Run(registration_id, true /* aborted */);
std::vector<std::string> aborted_guids;
for (const auto& pair : active_request_download_bytes_)
aborted_guids.push_back(pair.first);
request_manager_->OnJobAborted(registration_id(), std::move(aborted_guids));
Finish(true /* aborted */);
}
} // namespace content
......@@ -16,6 +16,7 @@
#include "content/browser/background_fetch/background_fetch_delegate_proxy.h"
#include "content/browser/background_fetch/background_fetch_registration_id.h"
#include "content/browser/background_fetch/background_fetch_request_info.h"
#include "content/browser/background_fetch/background_fetch_scheduler.h"
#include "content/common/background_fetch/background_fetch_types.h"
#include "content/common/content_export.h"
#include "content/public/browser/browser_thread.h"
......@@ -34,7 +35,8 @@ class BackgroundFetchRequestManager;
// registration has been aborted, or once it has completed/failed and the
// waitUntil promise has been resolved so UpdateUI can no longer be called).
class CONTENT_EXPORT BackgroundFetchJobController final
: public BackgroundFetchDelegateProxy::Controller {
: public BackgroundFetchDelegateProxy::Controller,
public BackgroundFetchScheduler::Controller {
public:
using FinishedCallback =
base::OnceCallback<void(const BackgroundFetchRegistrationId&,
......@@ -50,13 +52,9 @@ class CONTENT_EXPORT BackgroundFetchJobController final
const BackgroundFetchRegistration& registration,
BackgroundFetchRequestManager* request_manager,
ProgressCallback progress_callback,
FinishedCallback finished_callback);
BackgroundFetchScheduler::FinishedCallback finished_callback);
~BackgroundFetchJobController() override;
// Starts fetching the first few requests. The controller will continue to
// fetch new content until all requests have been handled.
void Start();
// Initializes the job controller with the status of the active and completed
// downloads. Only called when this has been loaded from the database.
void InitializeRequestStatus(
......@@ -74,11 +72,6 @@ class CONTENT_EXPORT BackgroundFetchJobController final
// Aborts the job including cancelling any ongoing downloads.
void Abort();
// Returns the registration id for which this job is fetching data.
const BackgroundFetchRegistrationId& registration_id() const {
return registration_id_;
}
// Returns the options with which this job is fetching data.
const BackgroundFetchOptions& options() const { return options_; }
......@@ -96,21 +89,16 @@ class CONTENT_EXPORT BackgroundFetchJobController final
const scoped_refptr<BackgroundFetchRequestInfo>& request) override;
void AbortFromUser() override;
// BackgroundFetchScheduler::Controller implementation:
bool HasMoreRequests() override;
void StartRequest(scoped_refptr<BackgroundFetchRequestInfo> request) override;
private:
// Aborts a job updating the registration with the new state. If
// |cancel_download| is true, the ongoing download is also cancelled
// (otherwise it assumes that has already happened).
void Abort(bool cancel_download);
// Requests the download manager to start fetching |request|.
void StartRequest(scoped_refptr<BackgroundFetchRequestInfo> request);
// Called when a completed download has been marked as such in DataManager.
void DidMarkRequestCompleted(bool has_pending_or_active_requests);
// The registration id on behalf of which this controller is fetching data.
BackgroundFetchRegistrationId registration_id_;
// Options for the represented background fetch registration.
BackgroundFetchOptions options_;
......@@ -132,8 +120,11 @@ class CONTENT_EXPORT BackgroundFetchJobController final
// Callback run each time download progress updates.
ProgressCallback progress_callback_;
// Callback for when all fetches have completed/failed/aborted.
FinishedCallback finished_callback_;
// Number of requests that comprise the whole job.
int total_downloads_ = 0;
// Number of the requests that have been completed so far.
int completed_downloads_ = 0;
base::WeakPtrFactory<BackgroundFetchJobController> weak_ptr_factory_;
......
......@@ -6,6 +6,7 @@
#define CONTENT_BROWSER_BACKGROUND_FETCH_BACKGROUND_FETCH_REQUEST_MANAGER_H_
#include <string>
#include <vector>
#include "base/callback_forward.h"
#include "base/memory/scoped_refptr.h"
......@@ -20,32 +21,19 @@ class BackgroundFetchRequestInfo;
// |BackgroundFetchRegistrationId| that may be backed by a database.
class BackgroundFetchRequestManager {
public:
using NextRequestCallback =
base::OnceCallback<void(scoped_refptr<BackgroundFetchRequestInfo>)>;
using MarkedCompleteCallback =
base::OnceCallback<void(bool /* has_pending_or_active_requests */)>;
virtual ~BackgroundFetchRequestManager() {}
// Removes the next request, if any, from the pending requests queue, and
// invokes the |callback| with that request, else a null request.
virtual void PopNextRequest(
const BackgroundFetchRegistrationId& registration_id,
NextRequestCallback callback) = 0;
// Marks that the |request|, part of the Background Fetch identified by
// |registration_id|, has been started as |download_guid|.
virtual void MarkRequestAsStarted(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchRequestInfo* request,
const std::string& download_guid) = 0;
// Marks that the |request|, part of the Background Fetch identified by
// |registration_id|, has completed.
virtual void MarkRequestAsComplete(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchRequestInfo* request,
MarkedCompleteCallback callback) = 0;
scoped_refptr<BackgroundFetchRequestInfo> request) = 0;
// Called when the job identified by |registration_id| has been aborted along
// with the GUIDs of any associated downloads that were still active.
virtual void OnJobAborted(
const BackgroundFetchRegistrationId& registration_id,
std::vector<std::string> aborted_guids) = 0;
};
} // namespace content
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/background_fetch/background_fetch_scheduler.h"
#include "base/guid.h"
#include "content/browser/background_fetch/background_fetch_job_controller.h"
namespace content {
BackgroundFetchScheduler::Controller::Controller(
const BackgroundFetchRegistrationId& registration_id,
FinishedCallback finished_callback)
: registration_id_(registration_id),
finished_callback_(std::move(finished_callback)) {
DCHECK(finished_callback_);
}
BackgroundFetchScheduler::Controller::~Controller() = default;
void BackgroundFetchScheduler::Controller::Finish(bool abort) {
DCHECK(abort || !HasMoreRequests());
std::move(finished_callback_).Run(registration_id_, abort);
}
BackgroundFetchScheduler::BackgroundFetchScheduler(
BackgroundFetchScheduler::RequestProvider* request_provider)
: request_provider_(request_provider) {}
BackgroundFetchScheduler::~BackgroundFetchScheduler() = default;
void BackgroundFetchScheduler::AddJobController(
BackgroundFetchScheduler::Controller* controller) {
controller_queue_.push_back(controller);
while (!controller_queue_.empty() &&
download_controller_map_.size() < max_concurrent_downloads_) {
ScheduleDownload();
}
}
void BackgroundFetchScheduler::ScheduleDownload() {
DCHECK(download_controller_map_.size() < max_concurrent_downloads_);
if (controller_queue_.empty())
return;
auto* controller = controller_queue_.front();
controller_queue_.pop_front();
request_provider_->PopNextRequest(
controller->registration_id(),
base::BindOnce(&BackgroundFetchScheduler::DidPopNextRequest,
base::Unretained(this), controller));
}
void BackgroundFetchScheduler::DidPopNextRequest(
BackgroundFetchScheduler::Controller* controller,
scoped_refptr<BackgroundFetchRequestInfo> request_info) {
download_controller_map_[request_info->download_guid()] = controller;
controller->StartRequest(request_info);
}
void BackgroundFetchScheduler::MarkRequestAsComplete(
const BackgroundFetchRegistrationId& registration_id,
scoped_refptr<BackgroundFetchRequestInfo> request) {
DCHECK(download_controller_map_.count(request->download_guid()));
auto* controller = download_controller_map_[request->download_guid()];
download_controller_map_.erase(request->download_guid());
request_provider_->MarkRequestAsComplete(
controller->registration_id(), request.get(),
base::BindOnce(&BackgroundFetchScheduler::DidMarkRequestAsComplete,
base::Unretained(this), controller));
}
void BackgroundFetchScheduler::DidMarkRequestAsComplete(
BackgroundFetchScheduler::Controller* controller) {
if (controller->HasMoreRequests())
controller_queue_.push_back(controller);
else
controller->Finish(false);
ScheduleDownload();
}
void BackgroundFetchScheduler::OnJobAborted(
const BackgroundFetchRegistrationId& registration_id,
std::vector<std::string> aborted_guids) {
// For every active download that was aborted, remove it and schedule a new
// download.
for (const auto& guid : aborted_guids) {
download_controller_map_.erase(guid);
ScheduleDownload();
}
}
} // namespace content
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_BACKGROUND_FETCH_BACKGROUND_FETCH_SCHEDULER_H_
#define CONTENT_BROWSER_BACKGROUND_FETCH_BACKGROUND_FETCH_SCHEDULER_H_
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/containers/circular_deque.h"
#include "base/macros.h"
#include "content/browser/background_fetch/background_fetch_registration_id.h"
#include "content/browser/background_fetch/background_fetch_request_manager.h"
#include "content/common/content_export.h"
namespace content {
class BackgroundFetchRegistrationId;
class BackgroundFetchRequestInfo;
// Maintains a list of Controllers and chooses which ones should launch new
// downloads.
class CONTENT_EXPORT BackgroundFetchScheduler
: public BackgroundFetchRequestManager {
public:
using FinishedCallback =
base::OnceCallback<void(const BackgroundFetchRegistrationId&,
bool /* aborted */)>;
using MarkedCompleteCallback = base::OnceCallback<void()>;
// Interface for download job controllers.
class CONTENT_EXPORT Controller {
public:
virtual ~Controller();
// Returns whether the Controller has any pending download requests.
virtual bool HasMoreRequests() = 0;
// Requests the download manager to start fetching |request|.
virtual void StartRequest(
scoped_refptr<BackgroundFetchRequestInfo> request) = 0;
void Finish(bool abort);
const BackgroundFetchRegistrationId& registration_id() const {
return registration_id_;
}
protected:
Controller(const BackgroundFetchRegistrationId& registration_id,
FinishedCallback finished_callback);
private:
BackgroundFetchRegistrationId registration_id_;
FinishedCallback finished_callback_;
};
using NextRequestCallback =
base::OnceCallback<void(scoped_refptr<BackgroundFetchRequestInfo>)>;
class CONTENT_EXPORT RequestProvider {
public:
virtual ~RequestProvider() {}
// Retrieves the next pending request for |registration_id| and invoke
// |callback| with it.
virtual void PopNextRequest(
const BackgroundFetchRegistrationId& registration_id,
NextRequestCallback callback) = 0;
// Marks |request| as complete and calls |callback| when done.
virtual void MarkRequestAsComplete(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchRequestInfo* request,
MarkedCompleteCallback callback) = 0;
};
explicit BackgroundFetchScheduler(RequestProvider* request_provider);
~BackgroundFetchScheduler() override;
// Adds a new job controller to the scheduler. May immediately start to
// schedule jobs for |controller|.
void AddJobController(Controller* controller);
void set_max_concurrent_downloads(size_t new_max) {
max_concurrent_downloads_ = new_max;
}
// BackgroundFetchRequestManager implementation:
void MarkRequestAsComplete(
const BackgroundFetchRegistrationId& registration_id,
scoped_refptr<BackgroundFetchRequestInfo> request) override;
void OnJobAborted(const BackgroundFetchRegistrationId& registration_id,
std::vector<std::string> aborted_guids) override;
private:
void ScheduleDownload();
void DidPopNextRequest(BackgroundFetchScheduler::Controller* controller,
scoped_refptr<BackgroundFetchRequestInfo>);
void DidMarkRequestAsComplete(
BackgroundFetchScheduler::Controller* controller);
RequestProvider* request_provider_;
// The scheduler owns all the job controllers, holding them either in the
// controller queue or the guid to controller map.
base::circular_deque<Controller*> controller_queue_;
std::map<std::string, Controller*> download_controller_map_;
size_t max_concurrent_downloads_ = 1;
DISALLOW_COPY_AND_ASSIGN(BackgroundFetchScheduler);
};
} // namespace content
#endif // CONTENT_BROWSER_BACKGROUND_FETCH_BACKGROUND_FETCH_SCHEDULER_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/background_fetch/background_fetch_scheduler.h"
#include <vector>
#include "base/guid.h"
#include "base/strings/string_number_conversions.h"
#include "content/browser/background_fetch/background_fetch_request_info.h"
#include "content/browser/background_fetch/background_fetch_test_base.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::ElementsAre;
namespace content {
namespace {
const int kExampleServiceWorkerRegistrationId = 1;
const char kExampleDeveloperId1[] = "my-example-id";
const char kExampleDeveloperId2[] = "my-other-id";
class FakeController : public BackgroundFetchScheduler::Controller {
public:
FakeController(const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchScheduler* scheduler,
const std::string& name,
std::vector<std::string>* controller_sequence_list,
int total_jobs)
: BackgroundFetchScheduler::Controller(
registration_id,
base::BindOnce(&FakeController::OnJobFinished)),
scheduler_(scheduler),
controller_sequence_list_(controller_sequence_list),
name_(name),
total_jobs_(total_jobs) {}
~FakeController() override {}
// BackgroundFetchScheduler::Controller implementation:
bool HasMoreRequests() override { return jobs_started_ < total_jobs_; }
void StartRequest(
scoped_refptr<BackgroundFetchRequestInfo> request) override {
DCHECK_LT(jobs_started_, total_jobs_);
++jobs_started_;
controller_sequence_list_->push_back(name_ +
base::IntToString(jobs_started_));
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&FakeController::OnDownloadCompleted,
base::Unretained(this), std::move(request)));
}
private:
void OnDownloadCompleted(scoped_refptr<BackgroundFetchRequestInfo> request) {
scheduler_->MarkRequestAsComplete(registration_id(), std::move(request));
}
static void OnJobFinished(const BackgroundFetchRegistrationId&,
bool aborted) {}
int jobs_started_ = 0;
BackgroundFetchScheduler* scheduler_;
std::vector<std::string>* controller_sequence_list_;
std::string name_;
int total_jobs_;
};
class BackgroundFetchSchedulerTest
: public BackgroundFetchTestBase,
public BackgroundFetchScheduler::RequestProvider {
public:
BackgroundFetchSchedulerTest() : scheduler_(this) {}
// Posts itself as a task |number_of_barriers| times and on the last iteration
// invokes the quit_closure.
void PostQuitAfterRepeatingBarriers(base::Closure quit_closure,
int number_of_barriers) {
if (--number_of_barriers == 0) {
quit_closure.Run();
return;
}
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(
&BackgroundFetchSchedulerTest::PostQuitAfterRepeatingBarriers,
base::Unretained(this), quit_closure, number_of_barriers));
}
void PopNextRequest(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchScheduler::NextRequestCallback callback) override {
ServiceWorkerFetchRequest fetch_request(GURL(), "GET",
ServiceWorkerHeaderMap(),
Referrer(), false /* is_reload */);
auto request = base::MakeRefCounted<BackgroundFetchRequestInfo>(
0 /* request_count */, fetch_request);
request->InitializeDownloadGuid();
std::move(callback).Run(std::move(request));
}
void MarkRequestAsComplete(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchRequestInfo* request,
BackgroundFetchScheduler::MarkedCompleteCallback callback) override {
std::move(callback).Run();
}
protected:
BackgroundFetchScheduler scheduler_;
std::vector<std::string> controller_sequence_list_;
};
} // namespace
TEST_F(BackgroundFetchSchedulerTest, SingleController) {
BackgroundFetchRegistrationId registration_id1(
kExampleServiceWorkerRegistrationId, origin(), kExampleDeveloperId1,
base::GenerateGUID());
FakeController controller(registration_id1, &scheduler_, "A",
&controller_sequence_list_, 4);
scheduler_.AddJobController(&controller);
base::RunLoop().RunUntilIdle();
EXPECT_THAT(controller_sequence_list_, ElementsAre("A1", "A2", "A3", "A4"));
}
TEST_F(BackgroundFetchSchedulerTest, TwoControllers) {
BackgroundFetchRegistrationId registration_id1(
kExampleServiceWorkerRegistrationId, origin(), kExampleDeveloperId1,
base::GenerateGUID());
BackgroundFetchRegistrationId registration_id2(
kExampleServiceWorkerRegistrationId, origin(), kExampleDeveloperId2,
base::GenerateGUID());
FakeController controller1(registration_id1, &scheduler_, "A",
&controller_sequence_list_, 4);
FakeController controller2(registration_id2, &scheduler_, "B",
&controller_sequence_list_, 4);
scheduler_.AddJobController(&controller1);
scheduler_.AddJobController(&controller2);
{
base::RunLoop run_loop;
PostQuitAfterRepeatingBarriers(run_loop.QuitClosure(), 3);
run_loop.Run();
// Only one task is run at a time so after 3 barrier iterations, 3 tasks
// should have been have run.
EXPECT_THAT(controller_sequence_list_, ElementsAre("A1", "B1", "A2"));
}
base::RunLoop().RunUntilIdle();
EXPECT_THAT(controller_sequence_list_,
ElementsAre("A1", "B1", "A2", "B2", "A3", "B3", "A4", "B4"));
}
TEST_F(BackgroundFetchSchedulerTest, TwoControllers_TwoConcurrent) {
BackgroundFetchRegistrationId registration_id1(
kExampleServiceWorkerRegistrationId, origin(), kExampleDeveloperId1,
base::GenerateGUID());
BackgroundFetchRegistrationId registration_id2(
kExampleServiceWorkerRegistrationId, origin(), kExampleDeveloperId2,
base::GenerateGUID());
FakeController controller1(registration_id1, &scheduler_, "A",
&controller_sequence_list_, 4);
FakeController controller2(registration_id2, &scheduler_, "B",
&controller_sequence_list_, 4);
scheduler_.set_max_concurrent_downloads(2);
scheduler_.AddJobController(&controller1);
scheduler_.AddJobController(&controller2);
{
base::RunLoop run_loop;
PostQuitAfterRepeatingBarriers(run_loop.QuitClosure(), 3);
run_loop.Run();
// Two tasks are run at a time so after 3 barrier iterations, 6 tasks should
// have run.
EXPECT_THAT(controller_sequence_list_,
ElementsAre("A1", "B1", "A2", "B2", "A3", "B3"));
}
base::RunLoop().RunUntilIdle();
EXPECT_THAT(controller_sequence_list_,
ElementsAre("A1", "B1", "A2", "B2", "A3", "B3", "A4", "B4"));
}
} // namespace content
......@@ -1186,6 +1186,7 @@ test("content_unittests") {
"../browser/background_fetch/background_fetch_event_dispatcher_unittest.cc",
"../browser/background_fetch/background_fetch_job_controller_unittest.cc",
"../browser/background_fetch/background_fetch_registration_notifier_unittest.cc",
"../browser/background_fetch/background_fetch_scheduler_unittest.cc",
"../browser/background_fetch/background_fetch_service_unittest.cc",
"../browser/background_sync/background_sync_manager_unittest.cc",
"../browser/background_sync/background_sync_network_observer_unittest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment