Bug 572223 - too much cookies.sqlite io. Part 3: sync read per domain. r=sdwilsh

This commit is contained in:
Dan Witte 2010-07-29 12:17:42 -07:00
Родитель fc1c9b37ef
Коммит 794bd4fe32
3 изменённых файлов: 242 добавлений и 43 удалений

Просмотреть файл

@ -87,6 +87,8 @@ using namespace mozilla::net;
* useful types & constants
******************************************************************************/
static nsCookieService *gCookieService;
// XXX_hack. See bug 178993.
// This is a hack to hide HttpOnly cookies from older browsers
static const char kHttpOnlyPrefix[] = "#HttpOnly_";
@ -372,6 +374,7 @@ public:
NS_IMETHOD HandleError(mozIStorageError* aError)
{
// XXX Ignore corruption handling for now. See bug 547031.
#ifdef PR_LOGGING
PRInt32 result = -1;
aError->GetResult(&result);
@ -461,7 +464,7 @@ protected:
bool mCanceled;
public:
ReadCookieDBListener() : mCanceled(PR_FALSE) { }
ReadCookieDBListener() : mCanceled(false) { }
void Cancel() { mCanceled = true; }
@ -470,7 +473,7 @@ public:
nsresult rv;
nsCOMPtr<mozIStorageRow> row;
nsTArray<CookieDomainTuple> &cookieArray =
nsCookieService::gCookieService->mDefaultDBState.hostArray;
gCookieService->mDefaultDBState.hostArray;
while (1) {
rv = aResult->GetNextRow(getter_AddRefs(row));
@ -480,7 +483,8 @@ public:
break;
CookieDomainTuple *tuple = cookieArray.AppendElement();
nsCookieService::gCookieService->ReadRow(row, *tuple);
row->GetUTF8String(9, tuple->baseDomain);
tuple->cookie = gCookieService->GetCookieFromRow(row);
}
return NS_OK;
@ -502,7 +506,7 @@ public:
switch (aReason) {
case mozIStorageStatementCallback::REASON_FINISHED:
nsCookieService::gCookieService->ReadComplete();
gCookieService->AsyncReadComplete();
break;
case mozIStorageStatementCallback::REASON_CANCELED:
// Nothing more to do here. The partially read data has already been
@ -550,8 +554,6 @@ NS_IMPL_ISUPPORTS1(CloseCookieDBListener, mozIStorageCompletionCallback)
* singleton instance ctor/dtor methods
******************************************************************************/
static nsCookieService *gCookieService;
nsICookieService*
nsCookieService::GetXPCOMSingleton()
{
@ -724,8 +726,9 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
return NS_ERROR_UNEXPECTED;
// open a connection to the cookie database, and only cache our connection
// and statements upon success.
rv = storage->OpenUnsharedDatabase(cookieFile, getter_AddRefs(mDBState->dbConn));
// and statements upon success. The connection is opened shared such that
// the main and background threads can operate on the db concurrently.
rv = storage->OpenDatabase(cookieFile, getter_AddRefs(mDBState->dbConn));
NS_ENSURE_SUCCESS(rv, rv);
PRBool tableExists = PR_FALSE;
@ -968,17 +971,8 @@ nsCookieService::CloseDB()
if (mDefaultDBState.dbConn) {
// Cancel any pending read. No further results will be received by our
// read listener.
if (mDefaultDBState.pendingRead) {
NS_ASSERTION(mDefaultDBState.readListener, "no read listener");
mDefaultDBState.readListener->Cancel();
nsresult rv = mDefaultDBState.pendingRead->Cancel();
NS_ASSERT_SUCCESS(rv);
mDefaultDBState.pendingRead = nsnull;
mDefaultDBState.readListener = nsnull;
mDefaultDBState.hostArray.Clear();
}
if (mDefaultDBState.pendingRead)
CancelAsyncRead(PR_TRUE);
mDefaultDBState.dbConn->AsyncClose(mCloseListener);
mDefaultDBState.dbConn = nsnull;
@ -1311,6 +1305,8 @@ nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
nsGetEnumeratorData data(&cookieList, PR_Now() / PR_USEC_PER_SEC);
EnsureReadComplete();
mDBState->hostTable.EnumerateEntries(COMArrayCallback, &data);
return NS_NewArrayEnumerator(aEnumerator, cookieList);
@ -1431,54 +1427,230 @@ nsCookieService::Read()
NS_ASSERT_SUCCESS(rv);
mDefaultDBState.readListener = readListener;
if (!mDefaultDBState.readSet.IsInitialized())
mDefaultDBState.readSet.Init();
return NS_OK;
}
void
nsCookieService::ReadRow(mozIStorageRow *aRow, CookieDomainTuple &aTuple)
// Extract data from a single result row and create an nsCookie.
// This is templated since 'T' is different for sync vs async results.
template<class T> nsCookie*
nsCookieService::GetCookieFromRow(T &aRow)
{
// Skip reading 'baseDomain' -- up to the caller.
PRInt64 creationID = aRow->AsInt64(0);
nsCString name, value, host, path;
aRow->GetUTF8String(1, name);
aRow->GetUTF8String(2, value);
aRow->GetUTF8String(3, host);
aRow->GetUTF8String(4, path);
nsresult rv = aRow->GetUTF8String(1, name);
NS_ASSERT_SUCCESS(rv);
rv = aRow->GetUTF8String(2, value);
NS_ASSERT_SUCCESS(rv);
rv = aRow->GetUTF8String(3, host);
NS_ASSERT_SUCCESS(rv);
rv = aRow->GetUTF8String(4, path);
NS_ASSERT_SUCCESS(rv);
PRInt64 expiry = aRow->AsInt64(5);
PRInt64 lastAccessed = aRow->AsInt64(6);
PRBool isSecure = 0 != aRow->AsInt32(7);
PRBool isHttpOnly = 0 != aRow->AsInt32(8);
aRow->GetUTF8String(9, aTuple.baseDomain);
// Create a new nsCookie and assign the data.
aTuple.cookie =
nsCookie::Create(name, value, host, path,
expiry,
lastAccessed,
creationID,
PR_FALSE,
isSecure,
isHttpOnly);
// create a new nsCookie and assign the data.
return nsCookie::Create(name, value, host, path,
expiry,
lastAccessed,
creationID,
PR_FALSE,
isSecure,
isHttpOnly);
}
void
nsCookieService::ReadComplete()
nsCookieService::AsyncReadComplete()
{
NS_ASSERTION(mDBState == &mDefaultDBState, "not in default db state");
NS_ASSERTION(mDBState->pendingRead, "no pending read");
NS_ASSERTION(mDBState->readListener, "no read listener");
// Merge the data read on the background thread with the data synchronously
// read on the main thread. Note that transactions on the cookie table may
// have occurred on the main thread since, making the background data stale.
for (PRUint32 i = 0; i < mDefaultDBState.hostArray.Length(); ++i) {
const CookieDomainTuple &tuple = mDefaultDBState.hostArray[i];
// Tiebreak: if the given base domain has already been read in, ignore
// the background data. Note that readSet may contain domains that were
// queried but found not to be in the db -- that's harmless.
if (mDefaultDBState.readSet.GetEntry(tuple.baseDomain))
continue;
AddCookieToList(tuple.baseDomain, tuple.cookie, NULL, PR_FALSE);
}
mDefaultDBState.hostArray.Clear();
mDefaultDBState.stmtReadDomain = nsnull;
mDefaultDBState.pendingRead = nsnull;
mDefaultDBState.readListener = nsnull;
mDefaultDBState.hostArray.Clear();
mDefaultDBState.readSet.Clear();
mObserverService->NotifyObservers(nsnull, "cookie-db-read", nsnull);
COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read(): %ld cookies read",
mDefaultDBState.cookieCount));
}
void
nsCookieService::CancelAsyncRead(PRBool aPurgeReadSet)
{
NS_ASSERTION(mDBState == &mDefaultDBState, "not in default db state");
NS_ASSERTION(mDBState->pendingRead, "no pending read");
NS_ASSERTION(mDBState->readListener, "no read listener");
// Cancel the pending read, kill the read listener, and empty the array
// of data already read in on the background thread.
mDefaultDBState.readListener->Cancel();
nsresult rv = mDefaultDBState.pendingRead->Cancel();
NS_ASSERT_SUCCESS(rv);
mDefaultDBState.stmtReadDomain = nsnull;
mDefaultDBState.pendingRead = nsnull;
mDefaultDBState.readListener = nsnull;
mDefaultDBState.hostArray.Clear();
// Only clear the 'readSet' table if we no longer need to know what set of
// data is already accounted for.
if (aPurgeReadSet)
mDefaultDBState.readSet.Clear();
}
void
nsCookieService::EnsureReadDomain(const nsCString &aBaseDomain)
{
NS_ASSERTION(!mDBState->dbConn || mDBState == &mDefaultDBState,
"not in default db state");
// Fast path 1: nothing to read, or we've already finished reading.
if (NS_LIKELY(!mDBState->dbConn || !mDefaultDBState.pendingRead))
return;
// Fast path 2: already read in this particular domain.
if (NS_LIKELY(mDefaultDBState.readSet.GetEntry(aBaseDomain)))
return;
// Read in the data synchronously.
nsresult rv;
if (!mDefaultDBState.stmtReadDomain) {
// Cache the statement, since it's likely to be used again.
rv = mDefaultDBState.dbConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT "
"id, "
"name, "
"value, "
"host, "
"path, "
"expiry, "
"lastAccessed, "
"isSecure, "
"isHttpOnly "
"FROM moz_cookies "
"WHERE baseDomain = ?1"),
getter_AddRefs(mDefaultDBState.stmtReadDomain));
// XXX Ignore corruption for now. See bug 547031.
if (NS_FAILED(rv)) return;
}
mozStorageStatementScoper scoper(mDefaultDBState.stmtReadDomain);
rv = mDefaultDBState.stmtReadDomain->BindUTF8StringByIndex(0, aBaseDomain);
NS_ASSERT_SUCCESS(rv);
PRBool hasResult;
PRUint32 readCount = 0;
nsCString name, value, host, path;
while (1) {
rv = mDefaultDBState.stmtReadDomain->ExecuteStep(&hasResult);
// XXX Ignore corruption for now. See bug 547031.
if (NS_FAILED(rv)) return;
if (!hasResult)
break;
nsCookie* newCookie = GetCookieFromRow(mDefaultDBState.stmtReadDomain);
AddCookieToList(aBaseDomain, newCookie, NULL, PR_FALSE);
++readCount;
}
// Add it to the hashset of read entries, so we don't read it again.
mDefaultDBState.readSet.PutEntry(aBaseDomain);
COOKIE_LOGSTRING(PR_LOG_DEBUG,
("EnsureReadDomain(): %ld cookies read for base domain %s",
readCount, aBaseDomain.get()));
}
void
nsCookieService::EnsureReadComplete()
{
NS_ASSERTION(!mDBState->dbConn || mDBState == &mDefaultDBState,
"not in default db state");
// Fast path 1: nothing to read, or we've already finished reading.
if (NS_LIKELY(!mDBState->dbConn || !mDefaultDBState.pendingRead))
return;
// Cancel the pending read, so we don't get any more results.
CancelAsyncRead(PR_FALSE);
// Read in the data synchronously.
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mDefaultDBState.dbConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT "
"id, "
"name, "
"value, "
"host, "
"path, "
"expiry, "
"lastAccessed, "
"isSecure, "
"isHttpOnly, "
"baseDomain "
"FROM moz_cookies"), getter_AddRefs(stmt));
// XXX Ignore corruption for now. See bug 547031.
if (NS_FAILED(rv)) return;
nsCString baseDomain, name, value, host, path;
PRBool hasResult;
PRUint32 readCount = 0;
while (1) {
rv = stmt->ExecuteStep(&hasResult);
// XXX Ignore corruption for now. See bug 547031.
if (NS_FAILED(rv)) return;
if (!hasResult)
break;
// Make sure we haven't already read the data.
stmt->GetUTF8String(9, baseDomain);
if (mDefaultDBState.readSet.GetEntry(baseDomain))
continue;
nsCookie* newCookie = GetCookieFromRow(stmt);
AddCookieToList(baseDomain, newCookie, NULL, PR_FALSE);
++readCount;
}
mDefaultDBState.readSet.Clear();
mObserverService->NotifyObservers(nsnull, "cookie-db-read", nsnull);
COOKIE_LOGSTRING(PR_LOG_DEBUG,
("EnsureReadComplete(): %ld cookies read", readCount));
}
NS_IMETHODIMP
nsCookieService::ImportCookies(nsIFile *aCookieFile)
{
@ -1530,6 +1702,9 @@ nsCookieService::ImportCookies(nsIFile *aCookieFile)
*
*/
// First, ensure we've read in everything from the database, if we have one.
EnsureReadComplete();
// We will likely be adding a bunch of cookies to the DB, so we use async
// batching with storage to make this super fast.
nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
@ -1726,6 +1901,8 @@ nsCookieService::GetCookieInternal(nsIURI *aHostURI,
PRInt64 currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
PRBool stale = PR_FALSE;
EnsureReadDomain(baseDomain);
// perform the hash lookup
nsCookieEntry *entry = mDBState->hostTable.GetEntry(baseDomain);
if (!entry)
@ -2785,6 +2962,8 @@ nsCookieService::PurgeCookies(PRInt64 aCurrentTimeInUsec)
stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
}
EnsureReadComplete();
nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC,
aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList, paramsArray);
mDBState->hostTable.EnumerateEntries(purgeCookiesCallback, &data);
@ -2877,6 +3056,8 @@ PRUint32
nsCookieService::CountCookiesFromHostInternal(const nsCString &aBaseDomain,
nsEnumerationData &aData)
{
EnsureReadDomain(aBaseDomain);
nsCookieEntry *entry = mDBState->hostTable.GetEntry(aBaseDomain);
if (!entry)
return 0;
@ -2940,6 +3121,8 @@ nsCookieService::GetCookiesFromHost(const nsACString &aHost,
nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
PRInt64 currentTime = PR_Now() / PR_USEC_PER_SEC;
EnsureReadDomain(baseDomain);
nsCookieEntry *entry = mDBState->hostTable.GetEntry(baseDomain);
if (!entry)
return NS_NewEmptyEnumerator(aEnumerator);
@ -2965,6 +3148,8 @@ nsCookieService::FindCookie(const nsCString &aBaseDomain,
nsListIter &aIter,
PRInt64 aCurrentTime)
{
EnsureReadDomain(aBaseDomain);
nsCookieEntry *entry = mDBState->hostTable.GetEntry(aBaseDomain);
if (!entry)
return PR_FALSE;

Просмотреть файл

@ -158,9 +158,20 @@ struct DBState
nsCOMPtr<mozIStorageStatement> stmtDelete;
nsCOMPtr<mozIStorageStatement> stmtUpdate;
// Various parts representing asynchronous read state. These are useful
// while the background read is taking place.
nsCOMPtr<mozIStorageStatement> stmtReadDomain;
nsCOMPtr<mozIStoragePendingStatement> pendingRead;
ReadCookieDBListener* readListener; // weak ref
// The asynchronous read listener. This is a weak ref (storage has ownership)
// since it may need to outlive the DBState's database connection.
ReadCookieDBListener* readListener;
// An array of (baseDomain, cookie) tuples representing data read in
// asynchronously. This is merged into hostTable once read is complete.
nsTArray<CookieDomainTuple> hostArray;
// A hashset of baseDomains read in synchronously, while the async read is
// in flight. This is used to keep track of which data in hostArray is stale
// when the time comes to merge.
nsTHashtable<nsCStringHashKey> readSet;
};
// these constants represent a decision about a cookie based on user prefs.
@ -206,8 +217,11 @@ class nsCookieService : public nsICookieService
nsresult CreateTable();
void CloseDB();
nsresult Read();
void ReadRow(mozIStorageRow *aRow, CookieDomainTuple &aCookeTuple);
void ReadComplete();
template<class T> nsCookie* GetCookieFromRow(T &aRow);
void AsyncReadComplete();
void CancelAsyncRead(PRBool aPurgeReadSet);
void EnsureReadDomain(const nsCString &aBaseDomain);
void EnsureReadComplete();
nsresult NormalizeHost(nsCString &aHost);
nsresult GetBaseDomain(nsIURI *aHostURI, nsCString &aBaseDomain, PRBool &aRequireHostMatch);
nsresult GetBaseDomainFromHost(const nsACString &aHost, nsCString &aBaseDomain);

Просмотреть файл

@ -783,7 +783,7 @@ Statement::GetUTF8String(PRUint32 aIndex,
nsresult rv = GetTypeOfIndex(aIndex, &type);
NS_ENSURE_SUCCESS(rv, rv);
if (type == mozIStorageStatement::VALUE_TYPE_NULL) {
// NULL columns should have IsVod set to distinguis them from an empty
// NULL columns should have IsVoid set to distinguish them from the empty
// string.
_value.Truncate(0);
_value.SetIsVoid(PR_TRUE);
@ -806,7 +806,7 @@ Statement::GetString(PRUint32 aIndex,
nsresult rv = GetTypeOfIndex(aIndex, &type);
NS_ENSURE_SUCCESS(rv, rv);
if (type == mozIStorageStatement::VALUE_TYPE_NULL) {
// NULL columns should have IsVod set to distinguis them from an empty
// NULL columns should have IsVoid set to distinguish them from the empty
// string.
_value.Truncate(0);
_value.SetIsVoid(PR_TRUE);