Bug 663479: Move all IDB structured clone operations to the main thread. r=bent
authorKyle Huey <khuey@kylehuey.com>
Fri, 24 Jun 2011 07:21:21 -0700
changeset 71757 139cc50ebb22128ad62ff0e34bed6ce1cb42c54b
parent 71756 d004850a8358c85e2fdfb0e6dea290fc1909e5ee
child 71758 5e2bd89639b9c445a3a69fc0a4f18e157b825eb2
push id20605
push userkhuey@mozilla.com
push dateSat, 25 Jun 2011 17:13:33 +0000
treeherdermozilla-central@82b9558a9eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs663479
milestone7.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 663479: Move all IDB structured clone operations to the main thread. r=bent
configure.in
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/IDBObjectStore.h
xpcom/ds/nsCRT.cpp
xpcom/ds/nsCRT.h
--- a/configure.in
+++ b/configure.in
@@ -3304,17 +3304,17 @@ case $target in
     AC_SEARCH_LIBS(dlopen, dl, 
         MOZ_CHECK_HEADER(dlfcn.h, 
         AC_DEFINE(HAVE_DLOPEN)))
     ;;
 esac
 
 _SAVE_CFLAGS="$CFLAGS"
 CFLAGS="$CFLAGS -D_GNU_SOURCE"
-AC_CHECK_FUNCS(dladdr)
+AC_CHECK_FUNCS(dladdr memmem)
 CFLAGS="$_SAVE_CFLAGS"
 
 if test ! "$GNU_CXX"; then
 
     case $target in
     *-aix*)
 	AC_CHECK_LIB(C_r, demangle)
 	;;
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -58,16 +58,19 @@
 #include "IDBKeyRange.h"
 #include "IDBTransaction.h"
 #include "DatabaseInfo.h"
 
 USING_INDEXEDDB_NAMESPACE
 
 namespace {
 
+// This is just to give us some random marker in the byte stream
+static const PRUint64 kTotallyRandomNumber = LL_INIT(0x286F258B, 0x177D47A9);
+
 class AddHelper : public AsyncConnectionHelper
 {
 public:
   AddHelper(IDBTransaction* aTransaction,
             IDBRequest* aRequest,
             IDBObjectStore* aObjectStore,
             JSAutoStructuredCloneBuffer& aCloneBuffer,
             const Key& aKey,
@@ -91,17 +94,16 @@ public:
 
   void ReleaseMainThreadObjects()
   {
     mObjectStore = nsnull;
     IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
     AsyncConnectionHelper::ReleaseMainThreadObjects();
   }
 
-  nsresult ModifyValueForNewKey();
   nsresult UpdateIndexes(mozIStorageConnection* aConnection,
                          PRInt64 aObjectDataId);
 
 private:
   // In-params.
   nsRefPtr<IDBObjectStore> mObjectStore;
 
   // These may change in the autoincrement case.
@@ -835,19 +837,18 @@ IDBObjectStore::ClearStructuredCloneBuff
 }
 
 // static
 bool
 IDBObjectStore::DeserializeValue(JSContext* aCx,
                                  JSAutoStructuredCloneBuffer& aBuffer,
                                  jsval* aValue)
 {
-  /*
-   *  This function can be called on multiple threads!  Be careful!
-   */
+  NS_ASSERTION(NS_IsMainThread(),
+               "Should only be deserializing on the main thread!");
   NS_ASSERTION(aCx, "A JSContext is required!");
 
   if (!aBuffer.data()) {
     *aValue = JSVAL_VOID;
     return true;
   }
 
   JSAutoRequest ar(aCx);
@@ -856,29 +857,81 @@ IDBObjectStore::DeserializeValue(JSConte
 }
 
 // static
 bool
 IDBObjectStore::SerializeValue(JSContext* aCx,
                                JSAutoStructuredCloneBuffer& aBuffer,
                                jsval aValue)
 {
-  /*
-   *  This function can be called on multiple threads!  Be careful!
-   */
+  NS_ASSERTION(NS_IsMainThread(),
+               "Should only be serializing on the main thread!");
   NS_ASSERTION(aCx, "A JSContext is required!");
 
   JSAutoRequest ar(aCx);
 
   return aBuffer.write(aCx, aValue, nsnull);
 }
 
+static inline jsdouble
+SwapBytes(PRUint64 u)
+{
+#ifdef IS_BIG_ENDIAN
+    return ((u & 0x00000000000000ffLLU) << 56) |
+           ((u & 0x000000000000ff00LLU) << 40) |
+           ((u & 0x0000000000ff0000LLU) << 24) |
+            ((u & 0x00000000ff000000LLU) << 8) |
+            ((u & 0x000000ff00000000LLU) >> 8) |
+           ((u & 0x0000ff0000000000LLU) >> 24) |
+           ((u & 0x00ff000000000000LLU) >> 40) |
+           ((u & 0xff00000000000000LLU) >> 56);
+#else
+     return u;
+#endif
+}
+
+nsresult
+IDBObjectStore::ModifyValueForNewKey(JSAutoStructuredCloneBuffer& aBuffer,
+                                     Key& aKey)
+{
+  NS_ASSERTION(IsAutoIncrement() && KeyPath().IsEmpty() && aKey.IsInt(),
+               "Don't call me!");
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread");
+  NS_ASSERTION(mKeyPathSerializationOffset, "How did this happen?");
+
+  // The minus 8 dangling off the end here is to account for the null entry
+  // that terminates the buffer
+  const PRUint32 keyPropLen = mKeyPathSerialization.nbytes() -
+                              mKeyPathSerializationOffset - sizeof(PRUint64);
+
+  const char* location = nsCRT::memmem((char*)aBuffer.data(),
+                                       aBuffer.nbytes(),
+                                       (char*)mKeyPathSerialization.data() +
+                                       mKeyPathSerializationOffset,
+                                       keyPropLen);
+  NS_ASSERTION(location, "How did this happen?");
+
+  // This is a duplicate of the js engine's byte munging here
+  union {
+    jsdouble d;
+    PRUint64 u;
+  } pun;
+
+  pun.d = SwapBytes(aKey.IntValue());
+
+  memcpy(const_cast<char*>(location) + keyPropLen -
+         sizeof(pun.u), // We're overwriting the last 8 bytes
+         &pun.u, sizeof(PRUint64));
+  return NS_OK;
+}
+
 IDBObjectStore::IDBObjectStore()
 : mId(LL_MININT),
-  mAutoIncrement(PR_FALSE)
+  mAutoIncrement(PR_FALSE),
+  mKeyPathSerializationOffset(0)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 }
 
 IDBObjectStore::~IDBObjectStore()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 }
@@ -927,21 +980,57 @@ IDBObjectStore::GetAddInfo(JSContext* aC
   ObjectStoreInfo* info;
   if (!ObjectStoreInfo::Get(mTransaction->Database()->Id(), mName, &info)) {
     NS_ERROR("This should never fail!");
   }
 
   rv = GetIndexUpdateInfo(info, aCx, aValue, aUpdateInfoArray);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-  if (!IDBObjectStore::SerializeValue(aCx, aCloneBuffer, aValue)) {
-    return NS_ERROR_DOM_DATA_CLONE_ERR;
+  const jschar* keyPathChars =
+    reinterpret_cast<const jschar*>(mKeyPath.get());
+  const size_t keyPathLen = mKeyPath.Length();
+  JSBool ok = JS_FALSE;
+
+  if (!mKeyPath.IsEmpty() && aKey.IsUnset()) {
+    NS_ASSERTION(mAutoIncrement, "Should have bailed earlier!");
+
+    jsval key;
+    ok = JS_NewNumberValue(aCx, kTotallyRandomNumber, &key);
+    NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+ 
+    ok = JS_DefineUCProperty(aCx, JSVAL_TO_OBJECT(aValue), keyPathChars,
+                             keyPathLen, key, nsnull, nsnull,
+                             JSPROP_ENUMERATE);
+    NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    // From this point on we have to try to remove the property.
+    rv = EnsureKeyPathSerializationData(aCx);
   }
 
-  return NS_OK;
+  // We guard on rv being a success because we need to run the property
+  // deletion code below even if we should not be serializing the value
+  if (NS_SUCCEEDED(rv) && 
+      !IDBObjectStore::SerializeValue(aCx, aCloneBuffer, aValue)) {
+    rv = NS_ERROR_DOM_DATA_CLONE_ERR;
+  }
+
+  if (ok) {
+    // If this fails, we lose, and the web page sees a magical property
+    // appear on the object :-(
+    jsval succeeded;
+    ok = JS_DeleteUCProperty2(aCx, JSVAL_TO_OBJECT(aValue), keyPathChars,
+                              keyPathLen, &succeeded);
+    NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+    NS_ASSERTION(JSVAL_IS_BOOLEAN(succeeded), "Wtf?");
+    NS_ENSURE_TRUE(JSVAL_TO_BOOLEAN(succeeded),
+                   NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  }
+
+  return rv;
 }
 
 nsresult
 IDBObjectStore::AddOrPut(const jsval& aValue,
                          const jsval& aKey,
                          JSContext* aCx,
                          PRUint8 aOptionalArgCount,
                          nsIIDBRequest** _retval,
@@ -982,16 +1071,56 @@ IDBObjectStore::AddOrPut(const jsval& aV
 
   rv = helper->DispatchToTransactionPool();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   request.forget(_retval);
   return NS_OK;
 }
 
+nsresult
+IDBObjectStore::EnsureKeyPathSerializationData(JSContext* aCx)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread");
+
+  if (!mKeyPathSerializationOffset) {
+    JSBool ok;
+
+    JSAutoStructuredCloneBuffer emptyObjectBuffer;
+    JSAutoStructuredCloneBuffer fakeObjectBuffer;
+
+    const jschar* keyPathChars =
+      reinterpret_cast<const jschar*>(mKeyPath.get());
+    const size_t keyPathLen = mKeyPath.Length();
+
+    JSObject* object = JS_NewObject(aCx, nsnull, nsnull, nsnull);
+    NS_ENSURE_TRUE(object, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    ok = emptyObjectBuffer.write(aCx, OBJECT_TO_JSVAL(object));
+    NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    jsval key;
+    // This is just to give us some random marker in the byte stream
+    ok = JS_NewNumberValue(aCx, kTotallyRandomNumber, &key);
+    NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    ok = JS_DefineUCProperty(aCx, object, keyPathChars, keyPathLen,
+                             key, nsnull, nsnull, JSPROP_ENUMERATE);
+    NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    ok = fakeObjectBuffer.write(aCx, OBJECT_TO_JSVAL(object));
+    NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    mKeyPathSerialization.swap(fakeObjectBuffer);
+    mKeyPathSerializationOffset = emptyObjectBuffer.nbytes();
+  }
+
+  return NS_OK;
+}
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBObjectStore)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBObjectStore)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mTransaction,
                                                        nsIDOMEventTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOwner)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptContext)
 
@@ -1716,17 +1845,17 @@ AddHelper::DoDatabaseWork(mozIStorageCon
       NS_ASSERTION(mKey.IntValue() == oldKey, "Something went haywire!");
     }
 #endif
 
     if (!keyPath.IsEmpty() && unsetKey) {
       // Special case where someone put an object into an autoIncrement'ing
       // objectStore with no key in its keyPath set. We needed to figure out
       // which row id we would get above before we could set that properly.
-      rv = ModifyValueForNewKey();
+      rv = mObjectStore->ModifyValueForNewKey(mCloneBuffer, mKey);
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
       scoper.Abandon();
       rv = stmt->Reset();
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
       stmt = mTransaction->AddStatement(false, true, true);
       NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
@@ -1773,68 +1902,16 @@ AddHelper::GetSuccessResult(JSContext* a
   NS_ASSERTION(!mKey.IsUnset(), "Badness!");
 
   mCloneBuffer.clear(aCx);
 
   return IDBObjectStore::GetJSValFromKey(mKey, aCx, aVal);
 }
 
 nsresult
-AddHelper::ModifyValueForNewKey()
-{
-  NS_ASSERTION(mObjectStore->IsAutoIncrement() &&
-               !mObjectStore->KeyPath().IsEmpty() &&
-               mKey.IsInt(),
-               "Don't call me!");
-
-  const nsString& keyPath = mObjectStore->KeyPath();
-
-  JSContext* cx = nsnull;
-  nsresult rv = nsContentUtils::ThreadJSContextStack()->GetSafeJSContext(&cx);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  JSAutoRequest ar(cx);
-
-  jsval clone;
-  if (!IDBObjectStore::DeserializeValue(cx, mCloneBuffer, &clone)) {
-    return NS_ERROR_DOM_DATA_CLONE_ERR;
-  }
-
-  NS_ASSERTION(!JSVAL_IS_PRIMITIVE(clone), "We should have an object!");
-
-  JSObject* obj = JSVAL_TO_OBJECT(clone);
-  JSBool ok;
-
-  const jschar* keyPathChars = reinterpret_cast<const jschar*>(keyPath.get());
-  const size_t keyPathLen = keyPath.Length();
-
-#ifdef DEBUG
-  {
-    jsval prop;
-    ok = JS_GetUCProperty(cx, obj, keyPathChars, keyPathLen, &prop);
-    NS_ASSERTION(ok && JSVAL_IS_VOID(prop), "Already has a key prop!");
-  }
-#endif
-
-  jsval key;
-  ok = JS_NewNumberValue(cx, mKey.IntValue(), &key);
-  NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
-
-  ok = JS_DefineUCProperty(cx, obj, keyPathChars, keyPathLen, key, nsnull,
-                           nsnull, JSPROP_ENUMERATE);
-  NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
-
-  if (!IDBObjectStore::SerializeValue(cx, mCloneBuffer, OBJECT_TO_JSVAL(obj))) {
-    return NS_ERROR_DOM_DATA_CLONE_ERR;
-  }
-
-  return NS_OK;
-}
-
-nsresult
 GetHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_PRECONDITION(aConnection, "Passed a null connection!");
 
   nsCOMPtr<mozIStorageStatement> stmt =
     mTransaction->GetStatement(mObjectStore->IsAutoIncrement());
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -152,16 +152,19 @@ public:
     return mKeyPath;
   }
 
   IDBTransaction* Transaction()
   {
     return mTransaction;
   }
 
+  nsresult ModifyValueForNewKey(JSAutoStructuredCloneBuffer& aBuffer,
+                                Key& aKey);
+
 protected:
   IDBObjectStore();
   ~IDBObjectStore();
 
   nsresult GetAddInfo(JSContext* aCx,
                       jsval aValue,
                       jsval aKeyVal,
                       JSAutoStructuredCloneBuffer& aCloneBuffer,
@@ -170,28 +173,35 @@ protected:
 
   nsresult AddOrPut(const jsval& aValue,
                     const jsval& aKey,
                     JSContext* aCx,
                     PRUint8 aOptionalArgCount,
                     nsIIDBRequest** _retval,
                     bool aOverwrite);
 
+  nsresult EnsureKeyPathSerializationData(JSContext* aCx);
+
 private:
   nsRefPtr<IDBTransaction> mTransaction;
 
   nsCOMPtr<nsIScriptContext> mScriptContext;
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
   PRInt64 mId;
   nsString mName;
   nsString mKeyPath;
   PRBool mAutoIncrement;
   PRUint32 mDatabaseId;
   PRUint32 mStructuredCloneVersion;
 
+  // Used to store a serialized representation of the fake property
+  // entry used to handle autoincrement with keypaths.
+  JSAutoStructuredCloneBuffer mKeyPathSerialization;
+  PRUint32 mKeyPathSerializationOffset;
+
   nsTArray<nsRefPtr<IDBIndex> > mCreatedIndexes;
 
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_idbobjectstore_h__
--- a/xpcom/ds/nsCRT.cpp
+++ b/xpcom/ds/nsCRT.cpp
@@ -158,16 +158,38 @@ PRInt32 nsCRT::strncmp(const PRUnichar* 
           return 1;
         }
       } while (--n != 0);
     }
   }
   return 0;
 }
 
+const char* nsCRT::memmem(const char* haystack, PRUint32 haystackLen,
+                          const char* needle, PRUint32 needleLen)
+{
+  // Sanity checking
+  if (!(haystack && needle && haystackLen && needleLen &&
+        needleLen <= haystackLen))
+    return NULL;
+
+#ifdef HAVE_MEMMEM
+  return (const char*)::memmem(haystack, haystackLen, needle, needleLen);
+#else
+  // No memmem means we need to roll our own.  This isn't really optimized
+  // for performance ... if that becomes an issue we can take some inspiration
+  // from the js string compare code in jsstr.cpp
+  for (PRInt32 i = 0; i < haystackLen - needleLen; i++) {
+    if (!memcmp(haystack + i, needle, needleLen))
+      return haystack + i;
+  }
+#endif
+  return NULL;
+}
+
 PRUnichar* nsCRT::strdup(const PRUnichar* str)
 {
   PRUint32 len = nsCRT::strlen(str);
   return strndup(str, len);
 }
 
 PRUnichar* nsCRT::strndup(const PRUnichar* str, PRUint32 len)
 {
--- a/xpcom/ds/nsCRT.h
+++ b/xpcom/ds/nsCRT.h
@@ -205,16 +205,22 @@ public:
   }
 
   /// Like strcmp except for ucs2 strings
   static PRInt32 strcmp(const PRUnichar* s1, const PRUnichar* s2);
   /// Like strcmp except for ucs2 strings
   static PRInt32 strncmp(const PRUnichar* s1, const PRUnichar* s2,
                          PRUint32 aMaxLen);
 
+  // The GNU libc has memmem, which is strstr except for binary data
+  // This is our own implementation that uses memmem on platforms
+  // where it's available.
+  static const char* memmem(const char* haystack, PRUint32 haystackLen,
+                            const char* needle, PRUint32 needleLen);
+
   // You must use nsCRT::free(PRUnichar*) to free memory allocated
   // by nsCRT::strdup(PRUnichar*).
   static PRUnichar* strdup(const PRUnichar* str);
 
   // You must use nsCRT::free(PRUnichar*) to free memory allocated
   // by strndup(PRUnichar*, PRUint32).
   static PRUnichar* strndup(const PRUnichar* str, PRUint32 len);