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 72272 139cc50ebb22128ad62ff0e34bed6ce1cb42c54b
parent 72271 d004850a8358c85e2fdfb0e6dea290fc1909e5ee
child 72273 5e2bd89639b9c445a3a69fc0a4f18e157b825eb2
push idunknown
push userunknown
push dateunknown
reviewersbent
bugs663479
milestone7.0a1
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);