зеркало из https://github.com/mozilla/pjs.git
Bug 572223 - too much cookies.sqlite io. Part 3: sync read per domain. r=sdwilsh
This commit is contained in:
Родитель
e47e106e5e
Коммит
345ce33388
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче