Bug 880864 - User dictionary (persdict.dat) read on main thread. r=Ehsan,r=mayhemer
☠☠ backed out by 3aee83b292ca ☠ ☠
authorRoberto A. Vitillo <rvitillo@mozilla.com>
Tue, 22 Apr 2014 04:02:00 +0200
changeset 197999 ea78684e87160d80dc77776f1ee492d74626ea7d
parent 197998 6d3edd61d26723caa991bef00caca14607b2822f
child 198000 4672ae9dd0c8d06f410c20eabd0c0bc50c597e72
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersEhsan, mayhemer
bugs880864
milestone31.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 880864 - User dictionary (persdict.dat) read on main thread. r=Ehsan,r=mayhemer
extensions/spellcheck/src/mozPersonalDictionary.cpp
extensions/spellcheck/src/mozPersonalDictionary.h
--- a/extensions/spellcheck/src/mozPersonalDictionary.cpp
+++ b/extensions/spellcheck/src/mozPersonalDictionary.cpp
@@ -12,16 +12,18 @@
 #include "nsIObserverService.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsIWeakReference.h"
 #include "nsCRT.h"
 #include "nsNetUtil.h"
 #include "nsStringEnumerator.h"
 #include "nsUnicharInputStream.h"
+#include "nsIRunnable.h"
+#include "nsThreadUtils.h"
 
 #define MOZ_PERSONAL_DICT_NAME "persdict.dat"
 
 const int kMaxWordLen=256;
 
 /**
  * This is the most braindead implementation of a personal dictionary possible.
  * There is not much complexity needed, though.  It could be made much faster,
@@ -42,18 +44,43 @@ NS_INTERFACE_MAP_BEGIN(mozPersonalDictio
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIPersonalDictionary)
   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozPersonalDictionary)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_1(mozPersonalDictionary, mEncoder)
 
+class mozPersonalDictionaryLoader MOZ_FINAL : public nsRunnable
+{
+public:
+  mozPersonalDictionaryLoader(mozPersonalDictionary *dict) : mDict(dict)
+  {
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (!NS_IsMainThread()) {
+      mDict->SyncLoad();
+
+      // Release refptr on the mainthread
+      NS_DispatchToMainThread(this);
+    }
+
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<mozPersonalDictionary> mDict;
+};
+
 mozPersonalDictionary::mozPersonalDictionary()
- : mDirty(false)
+ : mDirty(false),
+   mIsLoaded(false),
+   mMonitor("mozPersonalDictionary::mMonitor")
 {
 }
 
 mozPersonalDictionary::~mozPersonalDictionary()
 {
 }
 
 nsresult mozPersonalDictionary::Init()
@@ -64,49 +91,109 @@ nsresult mozPersonalDictionary::Init()
   NS_ENSURE_STATE(svc);
   // we want to reload the dictionary if the profile switches
   nsresult rv = svc->AddObserver(this, "profile-do-change", true);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = svc->AddObserver(this, "profile-before-change", true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   Load();
-  
+
   return NS_OK;
 }
 
-/* void Load (); */
+void mozPersonalDictionary::WaitForLoad()
+{
+  if (mIsLoaded) {
+    return;
+  }
+
+  mozilla::MonitorAutoLock mon(mMonitor);
+
+  if (!mIsLoaded) {
+    mon.Wait();
+  }
+}
+
 NS_IMETHODIMP mozPersonalDictionary::Load()
 {
+  nsresult rv;
+  mozilla::MonitorAutoLock mon(mMonitor);
+
+  if (mIsLoaded) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIRunnable> runnable = new mozPersonalDictionaryLoader(this);
+
+  rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
+  return rv;
+}
+
+void mozPersonalDictionary::SyncLoad()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  mozilla::MonitorAutoLock mon(mMonitor);
+
+  if (mIsLoaded) {
+    return;
+  }
+
+  SyncLoadInternal();
+  mIsLoaded = true;
+  mon.Notify();
+}
+
+void mozPersonalDictionary::SyncLoadInternal()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
   //FIXME Deinst  -- get dictionary name from prefs;
-  nsresult res;
+  nsresult rv;
   nsCOMPtr<nsIFile> theFile;
   bool dictExists;
 
-  res = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(theFile));
-  if(NS_FAILED(res)) return res;
-  if(!theFile)return NS_ERROR_FAILURE;
-  res = theFile->Append(NS_LITERAL_STRING(MOZ_PERSONAL_DICT_NAME));
-  if(NS_FAILED(res)) return res;
-  res = theFile->Exists(&dictExists);
-  if(NS_FAILED(res)) return res;
+  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(theFile));
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  if (!theFile) {
+    return;
+  }
+
+  rv = theFile->Append(NS_LITERAL_STRING(MOZ_PERSONAL_DICT_NAME));
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  rv = theFile->Exists(&dictExists);
+  if (NS_FAILED(rv)) {
+    return;
+  }
 
   if (!dictExists) {
     // Nothing is really wrong...
-    return NS_OK;
+    return;
   }
-  
+
   nsCOMPtr<nsIInputStream> inStream;
   NS_NewLocalFileInputStream(getter_AddRefs(inStream), theFile);
 
   nsCOMPtr<nsIUnicharInputStream> convStream;
-  res = nsSimpleUnicharStreamFactory::GetInstance()->
+  rv = nsSimpleUnicharStreamFactory::GetInstance()->
     CreateInstanceFromUTF8Stream(inStream, getter_AddRefs(convStream));
-  if(NS_FAILED(res)) return res;
-  
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
   // we're rereading to get rid of the old data  -- we shouldn't have any, but...
   mDictionaryTable.Clear();
 
   char16_t c;
   uint32_t nRead;
   bool done = false;
   do{  // read each line of text into the string array.
     if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) break;
@@ -118,18 +205,16 @@ NS_IMETHODIMP mozPersonalDictionary::Loa
       while((c != '\n') && (c != '\r') && !done){
         word.Append(c);
         if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) done = true;
       }
       mDictionaryTable.PutEntry(word.get());
     }
   } while(!done);
   mDirty = false;
-  
-  return res;
 }
 
 // A little helper function to add the key to the list.
 // This is not threadsafe, and only safe if the consumer does not 
 // modify the list.
 static PLDHashOperator
 AddHostToStringArray(nsUnicharPtrHashKey *aEntry, void *aArg)
 {
@@ -138,16 +223,17 @@ AddHostToStringArray(nsUnicharPtrHashKey
 }
 
 /* void Save (); */
 NS_IMETHODIMP mozPersonalDictionary::Save()
 {
   nsCOMPtr<nsIFile> theFile;
   nsresult res;
 
+  WaitForLoad();
   if(!mDirty) return NS_OK;
 
   //FIXME Deinst  -- get dictionary name from prefs;
   res = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(theFile));
   if(NS_FAILED(res)) return res;
   if(!theFile)return NS_ERROR_FAILURE;
   res = theFile->Append(NS_LITERAL_STRING(MOZ_PERSONAL_DICT_NAME));
   if(NS_FAILED(res)) return res;
@@ -183,16 +269,18 @@ NS_IMETHODIMP mozPersonalDictionary::Sav
 }
 
 /* readonly attribute nsIStringEnumerator GetWordList() */
 NS_IMETHODIMP mozPersonalDictionary::GetWordList(nsIStringEnumerator **aWords)
 {
   NS_ENSURE_ARG_POINTER(aWords);
   *aWords = nullptr;
 
+  WaitForLoad();
+
   nsTArray<nsString> *array = new nsTArray<nsString>(mDictionaryTable.Count());
   if (!array)
     return NS_ERROR_OUT_OF_MEMORY;
 
   mDictionaryTable.EnumerateEntries(AddHostToStringArray, array);
 
   array->Sort();
 
@@ -200,31 +288,37 @@ NS_IMETHODIMP mozPersonalDictionary::Get
 }
 
 /* boolean Check (in wstring word, in wstring language); */
 NS_IMETHODIMP mozPersonalDictionary::Check(const char16_t *aWord, const char16_t *aLanguage, bool *aResult)
 {
   NS_ENSURE_ARG_POINTER(aWord);
   NS_ENSURE_ARG_POINTER(aResult);
 
+  WaitForLoad();
+
   *aResult = (mDictionaryTable.GetEntry(aWord) || mIgnoreTable.GetEntry(aWord));
   return NS_OK;
 }
 
 /* void AddWord (in wstring word); */
 NS_IMETHODIMP mozPersonalDictionary::AddWord(const char16_t *aWord, const char16_t *aLang)
 {
+  WaitForLoad();
+
   mDictionaryTable.PutEntry(aWord);
   mDirty = true;
   return NS_OK;
 }
 
 /* void RemoveWord (in wstring word); */
 NS_IMETHODIMP mozPersonalDictionary::RemoveWord(const char16_t *aWord, const char16_t *aLang)
 {
+  WaitForLoad();
+
   mDictionaryTable.RemoveEntry(aWord);
   mDirty = true;
   return NS_OK;
 }
 
 /* void IgnoreWord (in wstring word); */
 NS_IMETHODIMP mozPersonalDictionary::IgnoreWord(const char16_t *aWord)
 {
@@ -232,16 +326,18 @@ NS_IMETHODIMP mozPersonalDictionary::Ign
   if (aWord && !mIgnoreTable.GetEntry(aWord)) 
     mIgnoreTable.PutEntry(aWord);
   return NS_OK;
 }
 
 /* void EndSession (); */
 NS_IMETHODIMP mozPersonalDictionary::EndSession()
 {
+  WaitForLoad();
+
   Save(); // save any custom words at the end of a spell check session
   mIgnoreTable.Clear();
   return NS_OK;
 }
 
 /* void AddCorrection (in wstring word, in wstring correction); */
 NS_IMETHODIMP mozPersonalDictionary::AddCorrection(const char16_t *word, const char16_t *correction, const char16_t *lang)
 {
@@ -259,16 +355,18 @@ NS_IMETHODIMP mozPersonalDictionary::Get
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 /* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */
 NS_IMETHODIMP mozPersonalDictionary::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
 {
   if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
-    Load();  // load automatically clears out the existing dictionary table
+    WaitForLoad();
+    mIsLoaded = false;
+    Load(); // load automatically clears out the existing dictionary table
   } else if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
     Save();
   }
 
   return NS_OK;
 }
 
--- a/extensions/spellcheck/src/mozPersonalDictionary.h
+++ b/extensions/spellcheck/src/mozPersonalDictionary.h
@@ -12,38 +12,61 @@
 #include "mozIPersonalDictionary.h"
 #include "nsIUnicodeEncoder.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 #include "nsTHashtable.h"
 #include "nsCRT.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsHashKeys.h"
+#include <mozilla/Monitor.h>
 
 #define MOZ_PERSONALDICTIONARY_CONTRACTID "@mozilla.org/spellchecker/personaldictionary;1"
 #define MOZ_PERSONALDICTIONARY_CID         \
 { /* 7EF52EAF-B7E1-462B-87E2-5D1DBACA9048 */  \
 0X7EF52EAF, 0XB7E1, 0X462B, \
   { 0X87, 0XE2, 0X5D, 0X1D, 0XBA, 0XCA, 0X90, 0X48 } }
 
+class mozPersonalDictionaryLoader;
+
 class mozPersonalDictionary : public mozIPersonalDictionary,
                               public nsIObserver,
                               public nsSupportsWeakReference
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_MOZIPERSONALDICTIONARY
   NS_DECL_NSIOBSERVER
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(mozPersonalDictionary, mozIPersonalDictionary)
 
   mozPersonalDictionary();
   virtual ~mozPersonalDictionary();
 
   nsresult Init();
 
 protected:
-  bool           mDirty;       /* has the dictionary been modified */
+  /* has the dictionary been modified */
+  bool mDirty;
+
+  /* true if the dictionary has been loaded from disk */
+  bool mIsLoaded;
+
+  mozilla::Monitor mMonitor;
   nsTHashtable<nsUnicharPtrHashKey> mDictionaryTable;
   nsTHashtable<nsUnicharPtrHashKey> mIgnoreTable;
-  nsCOMPtr<nsIUnicodeEncoder>  mEncoder; /*Encoder to use to compare with spellchecker word */
+
+  /*Encoder to use to compare with spellchecker word */
+  nsCOMPtr<nsIUnicodeEncoder>  mEncoder;
+
+private:
+  /* wait for the asynchronous load of the dictionary to be completed */
+  void WaitForLoad();
+
+  /* enter the monitor before starting a synchronous load off the main-thread */
+  void SyncLoad();
+
+  /* perform a synchronous load of the dictionary from disk */
+  void SyncLoadInternal();
+
+  friend class mozPersonalDictionaryLoader;
 };
 
 #endif