Don't allow writers to starve

This commit is contained in:
Ben Turner 2010-05-28 22:45:24 -07:00
parent d2fb9aee66
commit 438b20ac44
8 changed files with 202 additions and 10 deletions

View File

@ -384,6 +384,8 @@ IDBObjectStoreRequest::GetName(nsAString& aName)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
aName.Assign(mName); aName.Assign(mName);
return NS_OK; return NS_OK;
} }
@ -393,6 +395,8 @@ IDBObjectStoreRequest::GetKeyPath(nsAString& aKeyPath)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
aKeyPath.Assign(mKeyPath); aKeyPath.Assign(mKeyPath);
return NS_OK; return NS_OK;
} }
@ -402,6 +406,8 @@ IDBObjectStoreRequest::GetIndexNames(nsIDOMDOMStringList** aIndexNames)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
nsRefPtr<nsDOMStringList> list(new nsDOMStringList()); nsRefPtr<nsDOMStringList> list(new nsDOMStringList());
#if 0 #if 0
PRUint32 count = mIndexes.Length(); PRUint32 count = mIndexes.Length();
@ -419,6 +425,8 @@ IDBObjectStoreRequest::Get(nsIVariant* aKey,
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
Key key; Key key;
nsresult rv = GetKeyFromVariant(aKey, key); nsresult rv = GetKeyFromVariant(aKey, key);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
@ -446,6 +454,9 @@ IDBObjectStoreRequest::GetAll(nsIIDBKeyRange* aKeyRange,
nsIIDBRequest** _retval) nsIIDBRequest** _retval)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
NS_NOTYETIMPLEMENTED("Implement me!"); NS_NOTYETIMPLEMENTED("Implement me!");
return NS_ERROR_NOT_IMPLEMENTED; return NS_ERROR_NOT_IMPLEMENTED;
} }
@ -457,6 +468,8 @@ IDBObjectStoreRequest::Add(nsIVariant* /* aValue */,
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
if (mMode != nsIIDBTransaction::READ_WRITE) { if (mMode != nsIIDBTransaction::READ_WRITE) {
return NS_ERROR_OBJECT_IS_IMMUTABLE; return NS_ERROR_OBJECT_IS_IMMUTABLE;
} }
@ -493,6 +506,8 @@ IDBObjectStoreRequest::Modify(nsIVariant* /* aValue */,
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
if (mMode != nsIIDBTransaction::READ_WRITE) { if (mMode != nsIIDBTransaction::READ_WRITE) {
return NS_ERROR_OBJECT_IS_IMMUTABLE; return NS_ERROR_OBJECT_IS_IMMUTABLE;
} }
@ -529,6 +544,8 @@ IDBObjectStoreRequest::AddOrModify(nsIVariant* /* aValue */,
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
if (mMode != nsIIDBTransaction::READ_WRITE) { if (mMode != nsIIDBTransaction::READ_WRITE) {
return NS_ERROR_OBJECT_IS_IMMUTABLE; return NS_ERROR_OBJECT_IS_IMMUTABLE;
} }
@ -564,6 +581,8 @@ IDBObjectStoreRequest::Remove(nsIVariant* aKey,
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
if (mMode != nsIIDBTransaction::READ_WRITE) { if (mMode != nsIIDBTransaction::READ_WRITE) {
return NS_ERROR_OBJECT_IS_IMMUTABLE; return NS_ERROR_OBJECT_IS_IMMUTABLE;
} }
@ -598,6 +617,10 @@ IDBObjectStoreRequest::OpenCursor(nsIIDBKeyRange* aRange,
nsIIDBRequest** _retval) nsIIDBRequest** _retval)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
NS_NOTYETIMPLEMENTED("Implement me!");
return NS_ERROR_NOT_IMPLEMENTED; return NS_ERROR_NOT_IMPLEMENTED;
} }
@ -608,6 +631,9 @@ IDBObjectStoreRequest::CreateIndex(const nsAString& aName,
nsIIDBRequest** _retval) nsIIDBRequest** _retval)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
NS_NOTYETIMPLEMENTED("Implement me!"); NS_NOTYETIMPLEMENTED("Implement me!");
return NS_ERROR_NOT_IMPLEMENTED; return NS_ERROR_NOT_IMPLEMENTED;
} }
@ -617,6 +643,9 @@ IDBObjectStoreRequest::Index(const nsAString& aName,
nsIIDBIndexRequest** _retval) nsIIDBIndexRequest** _retval)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
NS_NOTYETIMPLEMENTED("Implement me!"); NS_NOTYETIMPLEMENTED("Implement me!");
return NS_ERROR_NOT_IMPLEMENTED; return NS_ERROR_NOT_IMPLEMENTED;
} }
@ -626,6 +655,9 @@ IDBObjectStoreRequest::RemoveIndex(const nsAString& aName,
nsIIDBRequest** _retval) nsIIDBRequest** _retval)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(mTransaction->TransactionIsOpen());
NS_NOTYETIMPLEMENTED("Implement me!"); NS_NOTYETIMPLEMENTED("Implement me!");
return NS_ERROR_NOT_IMPLEMENTED; return NS_ERROR_NOT_IMPLEMENTED;
} }
@ -638,14 +670,19 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
nsresult rv; nsresult rv;
bool mayOverwrite = mOverwrite; bool mayOverwrite = mOverwrite;
bool unsetKey = mKey.IsUnset();
if (mKey.IsUnset()) { if (unsetKey) {
NS_ASSERTION(mAutoIncrement, "Must have a key for non-autoIncrement!"); NS_ASSERTION(mAutoIncrement, "Must have a key for non-autoIncrement!");
// Will need to add first and then set the key later. // Will need to add first and then set the key later.
mayOverwrite = false; mayOverwrite = false;
} }
if (mAutoIncrement && !unsetKey) {
mayOverwrite = true;
}
nsCOMPtr<mozIStorageStatement> stmt = nsCOMPtr<mozIStorageStatement> stmt =
mTransaction->AddStatement(mCreate, mayOverwrite, mAutoIncrement); mTransaction->AddStatement(mCreate, mayOverwrite, mAutoIncrement);
NS_ENSURE_TRUE(stmt, nsIIDBDatabaseException::UNKNOWN_ERR); NS_ENSURE_TRUE(stmt, nsIIDBDatabaseException::UNKNOWN_ERR);
@ -678,8 +715,6 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
} }
if (mAutoIncrement && mCreate && !mOverwrite) { if (mAutoIncrement && mCreate && !mOverwrite) {
bool unsetKey = mKey.IsUnset();
#ifdef DEBUG #ifdef DEBUG
PRInt64 oldKey = unsetKey ? 0 : mKey.IntValue(); PRInt64 oldKey = unsetKey ? 0 : mKey.IntValue();
#endif #endif

View File

@ -441,6 +441,9 @@ NS_IMETHODIMP
IDBTransactionRequest::GetDb(nsIIDBDatabase** aDB) IDBTransactionRequest::GetDb(nsIIDBDatabase** aDB)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(TransactionIsOpen());
NS_ADDREF(*aDB = mDatabase); NS_ADDREF(*aDB = mDatabase);
return NS_OK; return NS_OK;
} }
@ -449,6 +452,9 @@ NS_IMETHODIMP
IDBTransactionRequest::GetReadyState(PRUint16* aReadyState) IDBTransactionRequest::GetReadyState(PRUint16* aReadyState)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(TransactionIsOpen());
*aReadyState = mReadyState; *aReadyState = mReadyState;
return NS_OK; return NS_OK;
} }
@ -457,6 +463,9 @@ NS_IMETHODIMP
IDBTransactionRequest::GetMode(PRUint16* aMode) IDBTransactionRequest::GetMode(PRUint16* aMode)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(TransactionIsOpen());
*aMode = mMode; *aMode = mMode;
return NS_OK; return NS_OK;
} }
@ -466,6 +475,8 @@ IDBTransactionRequest::GetObjectStoreNames(nsIDOMDOMStringList** aObjectStores)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(TransactionIsOpen());
nsRefPtr<nsDOMStringList> list(new nsDOMStringList()); nsRefPtr<nsDOMStringList> list(new nsDOMStringList());
PRUint32 count = mObjectStoreNames.Length(); PRUint32 count = mObjectStoreNames.Length();
for (PRUint32 index = 0; index < count; index++) { for (PRUint32 index = 0; index < count; index++) {
@ -481,6 +492,8 @@ IDBTransactionRequest::ObjectStore(const nsAString& aName,
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(TransactionIsOpen());
ObjectStoreInfo* info = nsnull; ObjectStoreInfo* info = nsnull;
PRUint32 count = mObjectStoreNames.Length(); PRUint32 count = mObjectStoreNames.Length();
@ -512,6 +525,8 @@ IDBTransactionRequest::Abort()
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_WARNING("Abort doesn't actually do anything yet! Fix me now!"); NS_WARNING("Abort doesn't actually do anything yet! Fix me now!");
NS_ENSURE_STATE(TransactionIsOpen());
nsCOMPtr<nsIRunnable> runnable = nsCOMPtr<nsIRunnable> runnable =
IDBEvent::CreateGenericEventRunnable(NS_LITERAL_STRING(ABORT_EVT_STR), IDBEvent::CreateGenericEventRunnable(NS_LITERAL_STRING(ABORT_EVT_STR),
this); this);
@ -527,6 +542,9 @@ NS_IMETHODIMP
IDBTransactionRequest::GetOncomplete(nsIDOMEventListener** aOncomplete) IDBTransactionRequest::GetOncomplete(nsIDOMEventListener** aOncomplete)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(TransactionIsOpen());
return GetInnerEventListener(mOnCompleteListener, aOncomplete); return GetInnerEventListener(mOnCompleteListener, aOncomplete);
} }
@ -534,6 +552,9 @@ NS_IMETHODIMP
IDBTransactionRequest::SetOncomplete(nsIDOMEventListener* aOncomplete) IDBTransactionRequest::SetOncomplete(nsIDOMEventListener* aOncomplete)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(TransactionIsOpen());
return RemoveAddEventListener(NS_LITERAL_STRING(COMPLETE_EVT_STR), return RemoveAddEventListener(NS_LITERAL_STRING(COMPLETE_EVT_STR),
mOnCompleteListener, aOncomplete); mOnCompleteListener, aOncomplete);
} }
@ -542,6 +563,9 @@ NS_IMETHODIMP
IDBTransactionRequest::GetOnabort(nsIDOMEventListener** aOnabort) IDBTransactionRequest::GetOnabort(nsIDOMEventListener** aOnabort)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(TransactionIsOpen());
return GetInnerEventListener(mOnAbortListener, aOnabort); return GetInnerEventListener(mOnAbortListener, aOnabort);
} }
@ -549,6 +573,9 @@ NS_IMETHODIMP
IDBTransactionRequest::SetOnabort(nsIDOMEventListener* aOnabort) IDBTransactionRequest::SetOnabort(nsIDOMEventListener* aOnabort)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(TransactionIsOpen());
return RemoveAddEventListener(NS_LITERAL_STRING(ABORT_EVT_STR), return RemoveAddEventListener(NS_LITERAL_STRING(ABORT_EVT_STR),
mOnAbortListener, aOnabort); mOnAbortListener, aOnabort);
} }
@ -557,6 +584,9 @@ NS_IMETHODIMP
IDBTransactionRequest::GetOntimeout(nsIDOMEventListener** aOntimeout) IDBTransactionRequest::GetOntimeout(nsIDOMEventListener** aOntimeout)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(TransactionIsOpen());
return GetInnerEventListener(mOnTimeoutListener, aOntimeout); return GetInnerEventListener(mOnTimeoutListener, aOntimeout);
} }
@ -564,6 +594,9 @@ NS_IMETHODIMP
IDBTransactionRequest::SetOntimeout(nsIDOMEventListener* aOntimeout) IDBTransactionRequest::SetOntimeout(nsIDOMEventListener* aOntimeout)
{ {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_STATE(TransactionIsOpen());
return RemoveAddEventListener(NS_LITERAL_STRING(TIMEOUT_EVT_STR), return RemoveAddEventListener(NS_LITERAL_STRING(TIMEOUT_EVT_STR),
mOnTimeoutListener, aOntimeout); mOnTimeoutListener, aOntimeout);
} }

View File

@ -103,6 +103,11 @@ public:
void CloseConnection(); void CloseConnection();
bool TransactionIsOpen() {
return mReadyState == nsIIDBTransaction::INITIAL ||
mReadyState == nsIIDBTransaction::LOADING;
}
private: private:
IDBTransactionRequest(); IDBTransactionRequest();
~IDBTransactionRequest(); ~IDBTransactionRequest();

View File

@ -151,9 +151,10 @@ CreateTables(mozIStorageConnection* aDBConn)
// Table `ai_object_data` // Table `ai_object_data`
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE ai_object_data (" "CREATE TABLE ai_object_data ("
"id INTEGER PRIMARY KEY, " "id INTEGER, "
"object_store_id INTEGER NOT NULL, " "object_store_id INTEGER NOT NULL, "
"data TEXT NOT NULL, " "data TEXT NOT NULL, "
"PRIMARY KEY (id), "
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
"CASCADE" "CASCADE"
");" ");"

View File

@ -67,10 +67,11 @@ bool gShutdown = false;
struct TransactionObjectStoreInfo struct TransactionObjectStoreInfo
{ {
TransactionObjectStoreInfo() : writing(false) { } TransactionObjectStoreInfo() : writing(false), writerWaiting(false) { }
nsString objectStoreName; nsString objectStoreName;
bool writing; bool writing;
bool writerWaiting;
}; };
} // anonymous namespace } // anonymous namespace
@ -311,7 +312,7 @@ TransactionThreadPool::TransactionCanRun(IDBTransactionRequest* aTransaction,
for (PRUint32 transactionIndex = 0; for (PRUint32 transactionIndex = 0;
transactionIndex < transactionCount; transactionIndex < transactionCount;
transactionIndex++) { transactionIndex++) {
const TransactionInfo& transactionInfo = TransactionInfo& transactionInfo =
transactionsInProgress->ElementAt(transactionIndex); transactionsInProgress->ElementAt(transactionIndex);
if (transactionInfo.transaction == aTransaction) { if (transactionInfo.transaction == aTransaction) {
@ -321,14 +322,14 @@ TransactionThreadPool::TransactionCanRun(IDBTransactionRequest* aTransaction,
} }
// Not our transaction, see if the objectStores overlap. // Not our transaction, see if the objectStores overlap.
const nsTArray<TransactionObjectStoreInfo>& objectStoreInfoArray = nsTArray<TransactionObjectStoreInfo>& objectStoreInfoArray =
transactionInfo.objectStoreInfo; transactionInfo.objectStoreInfo;
PRUint32 objectStoreCount = objectStoreInfoArray.Length(); PRUint32 objectStoreCount = objectStoreInfoArray.Length();
for (PRUint32 objectStoreIndex = 0; for (PRUint32 objectStoreIndex = 0;
objectStoreIndex < objectStoreCount; objectStoreIndex < objectStoreCount;
objectStoreIndex++) { objectStoreIndex++) {
const TransactionObjectStoreInfo& objectStoreInfo = TransactionObjectStoreInfo& objectStoreInfo =
objectStoreInfoArray[objectStoreIndex]; objectStoreInfoArray[objectStoreIndex];
if (objectStoreNames.Contains(objectStoreInfo.objectStoreName)) { if (objectStoreNames.Contains(objectStoreInfo.objectStoreName)) {
@ -336,12 +337,13 @@ TransactionThreadPool::TransactionCanRun(IDBTransactionRequest* aTransaction,
switch (mode) { switch (mode) {
case nsIIDBTransaction::READ_WRITE: { case nsIIDBTransaction::READ_WRITE: {
// Someone else is reading or writing to this table, we can't // Someone else is reading or writing to this table, we can't
// run now. // run now. Mark that we're waiting for it though.
objectStoreInfo.writerWaiting = true;
return false; return false;
} }
case nsIIDBTransaction::READ_ONLY: { case nsIIDBTransaction::READ_ONLY: {
if (objectStoreInfo.writing) { if (objectStoreInfo.writing || objectStoreInfo.writerWaiting) {
// Someone else is writing to this table, we can't run now. // Someone else is writing to this table, we can't run now.
return false; return false;
} }

View File

@ -62,6 +62,7 @@ _TEST_FILES = \
test_readonly_transactions.html \ test_readonly_transactions.html \
test_remove_objectStore.html \ test_remove_objectStore.html \
test_setVersion.html \ test_setVersion.html \
test_writer_starvation.html \
$(NULL) $(NULL)
libs:: $(_TEST_FILES) libs:: $(_TEST_FILES)

View File

@ -53,6 +53,15 @@
is(event.result, key2, "modify gave the same key back"); is(event.result, key2, "modify gave the same key back");
key2 = 100;
request = objectStore.add({}, key2);
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield;
is(event.result, key2, "modify gave the same key back");
try { try {
objectStore.addOrModify({}); objectStore.addOrModify({});
ok(false, "addOrModify with no key should throw!"); ok(false, "addOrModify with no key should throw!");

View File

@ -0,0 +1,106 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
<title>Indexed Database Property Test</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="text/javascript;version=1.7">
function testSteps()
{
const READ_ONLY = Components.interfaces.nsIIDBTransaction.READ_ONLY;
const READ_WRITE = Components.interfaces.nsIIDBTransaction.READ_WRITE;
const name = window.location.pathname;
const description = "My Test Database";
let request = indexedDB.open(name, description);
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
let db = event.result;
request = db.createObjectStore("foo", "", true);
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
is(event.transaction.mode, READ_WRITE, "Correct mode");
request = event.result.add({});
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield;
let key = event.result;
ok(key, "Got a key");
SimpleTest.executeSoon(function() { testGenerator.next(); });
yield;
let objectStore = db.objectStore("foo");
let continueReading = true;
let readerCount = 0;
let callbackCount = 0;
let finalCallbackCount = 0;
// Generate a bunch of reads right away without returning to the event
// loop.
for (let i = 0; i < 20; i++) {
readerCount++;
request = objectStore.get(key);
request.onerror = errorHandler;
request.onsuccess = function(event) {
callbackCount++;
};
}
while (continueReading) {
readerCount++;
request = db.objectStore("foo").get(key);
request.onerror = errorHandler;
request.onsuccess = function(event) {
is(event.transaction.mode, READ_ONLY, "Correct mode");
callbackCount++;
if (callbackCount == 100) {
request = db.objectStore("foo", READ_WRITE).add({}, readerCount);
request.onerror = errorHandler;
request.onsuccess = function(event) {
continueReading = false;
finalCallbackCount = callbackCount;
ok(event.result == callbackCount,
"write callback came before later reads");
}
}
};
SimpleTest.executeSoon(function() { testGenerator.next(); });
yield;
}
while (callbackCount < readerCount) {
SimpleTest.executeSoon(function() { testGenerator.next(); });
yield;
}
is(callbackCount, readerCount, "All requests accounted for");
ok(callbackCount > finalCallbackCount, "More readers after writer");
finishTest();
yield;
}
</script>
<script type="text/javascript;version=1.7" src="helpers.js"></script>
</head>
<body onload="runTest();"></body>
</html>