summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_service.cc94
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_service.h24
2 files changed, 74 insertions, 44 deletions
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.cc b/chrome/browser/safe_browsing/safe_browsing_service.cc
index c829c50..a119e09 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_service.cc
@@ -108,6 +108,9 @@ bool SafeBrowsingService::CheckUrl(const GURL& url, Client* client) {
void SafeBrowsingService::CancelCheck(Client* client) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
for (CurrentChecks::iterator i = checks_.begin(); i != checks_.end(); ++i) {
+ // We can't delete matching checks here because the db thread has a copy of
+ // the pointer. Instead, we simply NULL out the client, and when the db
+ // thread calls us back, we'll clean up the check.
if ((*i)->client == client)
(*i)->client = NULL;
}
@@ -115,9 +118,13 @@ void SafeBrowsingService::CancelCheck(Client* client) {
// Scan the queued clients store. Clients may be here if they requested a URL
// check before the database has finished loading.
for (std::deque<QueuedCheck>::iterator it(queued_checks_.begin());
- it != queued_checks_.end(); ++it) {
+ it != queued_checks_.end(); ) {
+ // In this case it's safe to delete matches entirely since nothing has a
+ // pointer to them.
if (it->client == client)
- it->client = NULL;
+ it = queued_checks_.erase(it);
+ else
+ ++it;
}
}
@@ -264,12 +271,25 @@ void SafeBrowsingService::RegisterPrefs(PrefService* prefs) {
void SafeBrowsingService::CloseDatabase() {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
- // It's important not to queue up a second request; if we did, then
- // |closing_database_| would be reset after handling the first request, and if
- // any functions on the db thread recreated the database, we could start using
- // it on the IO thread and then have the second request handler delete it out
- // from under us.
- if (closing_database_ || !database_)
+ // Cases to avoid:
+ // * If |closing_database_| is true, continuing will queue up a second
+ // request, |closing_database_| will be reset after handling the first
+ // request, and if any functions on the db thread recreate the database, we
+ // could start using it on the IO thread and then have the second request
+ // handler delete it out from under us.
+ // * If |database_| is NULL, then either no creation request is in flight, in
+ // which case we don't need to do anything, or one is in flight, in which
+ // case the database will be recreated before our deletion request is
+ // handled, and could be used on the IO thread in that time period, leading
+ // to the same problem as above.
+ // * If |queued_checks_| is non-empty and |database_| is non-NULL, we're
+ // about to be called back (in DatabaseLoadComplete()). This will call
+ // CheckUrl(), which will want the database. Closing the database here
+ // would lead to an infinite loop in DatabaseLoadComplete(), and even if it
+ // didn't, it would be pointless since we'd just want to recreate.
+ //
+ // The first two cases above are handled by checking database_available().
+ if (!database_available() || !queued_checks_.empty())
return;
closing_database_ = true;
@@ -341,6 +361,15 @@ void SafeBrowsingService::OnIOShutdown() {
delete protocol_manager_;
protocol_manager_ = NULL;
+ // Delete queued checks, calling back any clients with 'URL_SAFE'.
+ // If we don't do this here we may fail to close the database below.
+ while (!queued_checks_.empty()) {
+ QueuedCheck check = queued_checks_.front();
+ if (check.client)
+ check.client->OnUrlCheckResult(check.url, URL_SAFE);
+ queued_checks_.pop_front();
+ }
+
// Close the database. We don't simply DeleteSoon() because if a close is
// already pending, we'll double-free, and we don't set |database_| to NULL
// because if there is still anything running on the db thread, it could
@@ -355,15 +384,9 @@ void SafeBrowsingService::OnIOShutdown() {
// See comments on the declaration of |safe_browsing_thread_|.
safe_browsing_thread_.reset();
- // Delete queued and pending checks once the database thread is done, calling
- // back any clients with 'URL_SAFE'.
- while (!queued_checks_.empty()) {
- QueuedCheck check = queued_checks_.front();
- if (check.client)
- check.client->OnUrlCheckResult(check.url, URL_SAFE);
- queued_checks_.pop_front();
- }
-
+ // Delete pending checks, calling back any clients with 'URL_SAFE'. We have
+ // to do this after the db thread returns because methods on it can have
+ // copies of these pointers, so deleting them might lead to accessing garbage.
for (CurrentChecks::iterator it = checks_.begin();
it != checks_.end(); ++it) {
if ((*it)->client)
@@ -383,7 +406,7 @@ bool SafeBrowsingService::MakeDatabaseAvailable() {
safe_browsing_thread_->message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(this, &SafeBrowsingService::GetDatabase));
}
- return !closing_database_ && (database_ != NULL);
+ return database_available();
}
SafeBrowsingDatabase* SafeBrowsingService::GetDatabase() {
@@ -497,8 +520,23 @@ void SafeBrowsingService::DatabaseLoadComplete() {
if (!enabled_)
return;
- // If we have any queued requests, we can now check them.
- RunQueuedClients();
+ HISTOGRAM_COUNTS("SB.QueueDepth", queued_checks_.size());
+ if (queued_checks_.empty())
+ return;
+
+ // If the database isn't already available, calling CheckUrl() in the loop
+ // below will add the check back to the queue, and we'll infinite-loop.
+ DCHECK(database_available());
+ while (!queued_checks_.empty()) {
+ QueuedCheck check = queued_checks_.front();
+ HISTOGRAM_TIMES("SB.QueueDelay", Time::Now() - check.start);
+ // If CheckUrl() determines the URL is safe immediately, it doesn't call the
+ // client's handler function (because normally it's being directly called by
+ // the client). Since we're not the client, we have to convey this result.
+ if (check.client && CheckUrl(check.url, check.client))
+ check.client->OnUrlCheckResult(check.url, URL_SAFE);
+ queued_checks_.pop_front();
+ }
}
void SafeBrowsingService::HandleChunkForDatabase(
@@ -568,7 +606,7 @@ void SafeBrowsingService::Start() {
void SafeBrowsingService::OnCloseDatabase() {
DCHECK(MessageLoop::current() == safe_browsing_thread_->message_loop());
- DCHECK(closing_database_);
+ DCHECK(database_available());
// Because |closing_database_| is true, nothing on the IO thread will be
// accessing the database, so it's safe to delete and then NULL the pointer.
@@ -580,9 +618,6 @@ void SafeBrowsingService::OnCloseDatabase() {
void SafeBrowsingService::OnResetDatabase() {
DCHECK(MessageLoop::current() == safe_browsing_thread_->message_loop());
GetDatabase()->ResetDatabase();
- ChromeThread::PostTask(
- ChromeThread::IO, FROM_HERE,
- NewRunnableMethod(this, &SafeBrowsingService::DatabaseLoadComplete));
}
void SafeBrowsingService::CacheHashResults(
@@ -704,14 +739,3 @@ void SafeBrowsingService::ReportMalware(const GURL& malware_url,
protocol_manager_->ReportMalware(malware_url, page_url, referrer_url);
}
-
-void SafeBrowsingService::RunQueuedClients() {
- DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
- HISTOGRAM_COUNTS("SB.QueueDepth", queued_checks_.size());
- while (!queued_checks_.empty()) {
- QueuedCheck check = queued_checks_.front();
- HISTOGRAM_TIMES("SB.QueueDelay", Time::Now() - check.start);
- CheckUrl(check.url, check.client);
- queued_checks_.pop_front();
- }
-}
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.h b/chrome/browser/safe_browsing/safe_browsing_service.h
index 9269083..f9c3ba9 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.h
+++ b/chrome/browser/safe_browsing/safe_browsing_service.h
@@ -139,8 +139,12 @@ class SafeBrowsingService
// Preference handling.
static void RegisterPrefs(PrefService* prefs);
- // Called on the IO thread to close the database, freeing the memory
+ // Called on the IO thread to try to close the database, freeing the memory
// associated with it. The database will be automatically reopened as needed.
+ //
+ // NOTE: Actual database closure is asynchronous, and until it happens, the IO
+ // thread is not allowed to access it; may not actually trigger a close if one
+ // is already pending or doing so would cause problems.
void CloseDatabase();
// Called on the IO thread to reset the database.
@@ -184,8 +188,13 @@ class SafeBrowsingService
// Called to shutdown operations on the io_thread.
void OnIOShutdown();
- // Called on the IO thread. Returns whether |database_| exists. If it does
- // not exist, queues up a call on the db thread to create it.
+ // Returns whether |database_| exists and is accessible.
+ bool database_available() const {
+ return !closing_database_ && (database_ != NULL);
+ }
+
+ // Called on the IO thread. If the database does not exist, queues up a call
+ // on the db thread to create it. Returns whether the database is available.
//
// Note that this is only needed outside the db thread, since functions on the
// db thread can call GetDatabase() directly.
@@ -211,7 +220,9 @@ class SafeBrowsingService
// Called on the IO thread after the database reports that it added a chunk.
void OnChunkInserted();
- // Notification that the database is done loading its bloom filter.
+ // Notification that the database is done loading its bloom filter. We may
+ // have had to queue checks until the database is ready, and if so, this
+ // checks them.
void DatabaseLoadComplete();
// Called on the database thread to add/remove chunks and host keys.
@@ -259,11 +270,6 @@ class SafeBrowsingService
const GURL& page_url,
const GURL& referrer_url);
- // During a reset or the initial load we may have to queue checks until the
- // database is ready. This method is run once the database has loaded (or if
- // we shut down SafeBrowsing before the database has finished loading).
- void RunQueuedClients();
-
CurrentChecks checks_;
// Used for issuing only one GetHash request for a given prefix.