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 179909 ea78684e87160d80dc77776f1ee492d74626ea7d
parent 179908 6d3edd61d26723caa991bef00caca14607b2822f
child 179910 4672ae9dd0c8d06f410c20eabd0c0bc50c597e72
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersEhsan, mayhemer
bugs880864
milestone31.0a1
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