Part of bug 440794 - Leverage Offline capabilities to make sending email appear faster - protect against shutting down whilst sending email when quit-application-requested has not been received. r/sr=bienvenu
authorMark Banner <bugzilla@standard8.plus.com>
Wed, 13 May 2009 09:12:27 +0100
changeset 2617 f2ad961aefcb72a823613fd371249968cbb5060c
parent 2616 6a6386c16e9833c9c441481e3eda0a5dedf90154
child 2618 8ca8d974b7099853a48fbc78cd75adbff5f183ff
push id2128
push userbugzilla@standard8.plus.com
push dateWed, 13 May 2009 08:58:29 +0000
treeherdercomm-central@8ca8d974b709 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs440794
Part of bug 440794 - Leverage Offline capabilities to make sending email appear faster - protect against shutting down whilst sending email when quit-application-requested has not been received. r/sr=bienvenu
mailnews/base/src/nsMsgMailSession.cpp
mailnews/base/src/nsMsgMailSession.h
mailnews/compose/public/nsIMsgSendLater.idl
mailnews/compose/src/nsMsgSendLater.cpp
--- a/mailnews/base/src/nsMsgMailSession.cpp
+++ b/mailnews/base/src/nsMsgMailSession.cpp
@@ -50,16 +50,18 @@
 #include "nsIAppStartup.h"
 #include "nsXPFEComponentsCID.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIAppShellService.h"
 #include "nsAppShellCID.h"
 #include "nsIWindowMediator.h"
 #include "nsIWindowWatcher.h"
 #include "nsIMsgMailNewsUrl.h"
+#include "prcmon.h"
+#include "nsThreadUtils.h"
 
 NS_IMPL_THREADSAFE_ADDREF(nsMsgMailSession)
 NS_IMPL_THREADSAFE_RELEASE(nsMsgMailSession)
 NS_INTERFACE_MAP_BEGIN(nsMsgMailSession)
   NS_INTERFACE_MAP_ENTRY(nsIMsgMailSession)
   NS_INTERFACE_MAP_ENTRY(nsIFolderListener)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgMailSession)
 NS_INTERFACE_MAP_END_THREADSAFE
@@ -520,27 +522,38 @@ nsMsgMailSession::GetDataFilesDir(const 
   return rv;
 }
 
 /********************************************************************************/
 
 NS_IMPL_ISUPPORTS3(nsMsgShutdownService, nsIMsgShutdownService, nsIUrlListener, nsIObserver)
 
 nsMsgShutdownService::nsMsgShutdownService()
+: mProcessedShutdown(PR_FALSE),
+  mQuitForced(PR_FALSE),
+  mReadyToQuit(PR_FALSE)
 {
   nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
   if (observerService)
+  {
     observerService->AddObserver(this, "quit-application-requested", PR_FALSE);
+    observerService->AddObserver(this, "quit-application-granted", PR_FALSE);
+    observerService->AddObserver(this, "quit-application", PR_FALSE);
+  }
 }
 
 nsMsgShutdownService::~nsMsgShutdownService()
 {
   nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
   if (observerService)
+  {  
     observerService->RemoveObserver(this, "quit-application-requested");
+    observerService->RemoveObserver(this, "quit-application-granted");
+    observerService->RemoveObserver(this, "quit-application");
+  }
 }
 
 nsresult nsMsgShutdownService::ProcessNextTask()
 {
   PRBool shutdownTasksDone = PR_TRUE;
   
   PRInt32 count = mShutdownTasks.Count();
   if (mTaskIndex < count)
@@ -573,34 +586,66 @@ nsresult nsMsgShutdownService::ProcessNe
   {
     mMsgProgress->OnStateChange(nsnull, nsnull, nsIWebProgressListener::STATE_STOP, NS_OK);
     AttemptShutdown();
   }
   
   return NS_OK;
 }
 
-nsresult nsMsgShutdownService::AttemptShutdown()
+void nsMsgShutdownService::AttemptShutdown()
 {
-  nsCOMPtr<nsIAppStartup> appStartup = do_GetService(NS_APPSTARTUP_CONTRACTID);
-  NS_ENSURE_TRUE(appStartup, NS_ERROR_FAILURE);
-  return appStartup->Quit(nsIAppStartup::eAttemptQuit);
+  if (mQuitForced)
+  {
+    PR_CEnterMonitor(this);
+    mReadyToQuit = PR_TRUE;
+    PR_CNotifyAll(this);
+    PR_CExitMonitor(this);
+  }
+  else
+  {
+    nsCOMPtr<nsIAppStartup> appStartup =
+      do_GetService(NS_APPSTARTUP_CONTRACTID);
+    NS_ENSURE_TRUE(appStartup, );
+    NS_ENSURE_SUCCESS(appStartup->Quit(nsIAppStartup::eAttemptQuit), );
+  }
 }
 
 NS_IMETHODIMP nsMsgShutdownService::SetShutdownListener(nsIWebProgressListener *inListener)
 {
   NS_ENSURE_TRUE(mMsgProgress, NS_ERROR_FAILURE);
   mMsgProgress->RegisterListener(inListener);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgShutdownService::Observe(nsISupports *aSubject,
                                             const char *aTopic,
                                             const PRUnichar *aData)
 {
+  // Due to bug 459376 we don't always get quit-application-requested and
+  // quit-application-granted. quit-application-requested is preferred, but if
+  // we don't then we have to hook onto quit-application, but we don't want
+  // to do the checking twice so we set some flags to prevent that.
+  if (!strcmp(aTopic, "quit-application-granted"))
+  {
+    // Quit application has been requested and granted, therefore we will shut
+    // down. 
+    mProcessedShutdown = PR_TRUE;
+    return NS_OK;
+  }
+
+  // If we've already processed a shutdown notification, no need to do it again.
+  if (!strcmp(aTopic, "quit-application"))
+  {
+    if (mProcessedShutdown)
+      return NS_OK;
+    else
+      mQuitForced = PR_TRUE;
+  }
+
   nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
   NS_ENSURE_STATE(observerService);
   
   nsCOMPtr<nsISimpleEnumerator> listenerEnum;
   nsresult rv = observerService->EnumerateObservers("msg-shutdown", getter_AddRefs(listenerEnum));
   if (NS_SUCCEEDED(rv) && listenerEnum)
   {
     PRBool hasMore;
@@ -619,17 +664,17 @@ NS_IMETHODIMP nsMsgShutdownService::Obse
         PRBool shouldRunTask;
         curTask->GetNeedsToRunTask(&shouldRunTask);
         if (shouldRunTask)
           mShutdownTasks.AppendObject(curTask);
       }
       
       listenerEnum->HasMoreElements(&hasMore);
     }
-    
+
     if (mShutdownTasks.Count() < 1)
       return NS_ERROR_FAILURE;
     
     mTaskIndex = 0;
     
     mMsgProgress = do_CreateInstance(NS_MSGPROGRESS_CONTRACTID);
     NS_ENSURE_TRUE(mMsgProgress, NS_ERROR_FAILURE);
     
@@ -652,23 +697,41 @@ NS_IMETHODIMP nsMsgShutdownService::Obse
       //If not use the hidden window.
       if (!internalDomWin)
       {
         nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
         appShell->GetHiddenDOMWindow(getter_AddRefs(internalDomWin));
         NS_ENSURE_TRUE(internalDomWin, NS_ERROR_FAILURE);  // bail if we don't get a window.
       }
     }
-    
-    nsCOMPtr<nsISupportsPRBool> stopShutdown = do_QueryInterface(aSubject);
-    stopShutdown->SetData(PR_TRUE);
-    
+
+    if (!mQuitForced)
+    {
+      nsCOMPtr<nsISupportsPRBool> stopShutdown = do_QueryInterface(aSubject);
+      stopShutdown->SetData(PR_TRUE);
+    }
+
     mMsgProgress->OpenProgressDialog(internalDomWin, topMsgWindow, 
                                      "chrome://messenger/content/shutdownWindow.xul", 
                                      PR_FALSE, nsnull);
+
+    if (mQuitForced)
+    {
+      nsIThread *thread = NS_GetCurrentThread();
+
+      mReadyToQuit = PR_FALSE;
+      while (!mReadyToQuit)
+      {
+        PR_CEnterMonitor(this);
+        // Waiting for 50 milliseconds
+        PR_CWait(this, PR_MicrosecondsToInterval(50000UL));
+        PR_CExitMonitor(this);
+        NS_ProcessPendingEvents(thread);
+      }
+    }
   }
   
   return NS_OK;
 }
 
 // nsIUrlListener
 NS_IMETHODIMP nsMsgShutdownService::OnStartRunningUrl(nsIURI *url)
 {
@@ -695,17 +758,18 @@ NS_IMETHODIMP nsMsgShutdownService::GetN
 NS_IMETHODIMP nsMsgShutdownService::StartShutdownTasks()
 {
   ProcessNextTask();
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgShutdownService::CancelShutdownTasks()
 {
-  return AttemptShutdown();
+  AttemptShutdown();
+  return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgShutdownService::SetStatusText(const nsAString & inStatusString)
 {
   nsString statusString(inStatusString);
   mMsgProgress->OnStatusChange(nsnull, nsnull, NS_OK, statusString.get());
   return NS_OK;
 }
--- a/mailnews/base/src/nsMsgMailSession.h
+++ b/mailnews/base/src/nsMsgMailSession.h
@@ -117,17 +117,20 @@ public:
   
   NS_DECL_ISUPPORTS
   NS_DECL_NSIMSGSHUTDOWNSERVICE
   NS_DECL_NSIURLLISTENER
   NS_DECL_NSIOBSERVER
     
 protected:
   nsresult ProcessNextTask();
-  nsresult AttemptShutdown();
+  void AttemptShutdown();
   
 private:
   nsCOMArray<nsIMsgShutdownTask> mShutdownTasks;
   nsCOMPtr<nsIMsgProgress>       mMsgProgress;
   PRUint32                       mTaskIndex;
+  PRPackedBool mProcessedShutdown;
+  PRPackedBool mQuitForced;
+  PRPackedBool mReadyToQuit;
 };
 
 #endif /* nsMsgMailSession_h__ */
--- a/mailnews/compose/public/nsIMsgSendLater.idl
+++ b/mailnews/compose/public/nsIMsgSendLater.idl
@@ -45,18 +45,18 @@ interface nsIMsgSendLaterListener;
 interface nsIMsgFolder;
 
 /**
  * nsIMsgSendLater is a service used for sending messages in the background.
  * Messages should be saved to an identity's unsent messages folder, and then
  * can be sent by calling sendUnsentMessages.
  *
  * Although the service supports passing identities as parameters, until bug
- * 317803 is fixed, all identities use the same folder, and hence currently it
- * has no effect.
+ * 317803 is fixed, all identities use the same folder, and hence the option
+ * currently doesn't work.
  */
 [scriptable, uuid(fa324a4b-4b87-4e9a-a3c0-af9071a358df)]
 interface nsIMsgSendLater : nsIStreamListener 
 {
   /// Used to obtain status feedback for when messages are sent.
   attribute nsIMsgStatusFeedback statusFeedback;
 
   /**
--- a/mailnews/compose/src/nsMsgSendLater.cpp
+++ b/mailnews/compose/src/nsMsgSendLater.cpp
@@ -128,28 +128,32 @@ nsMsgSendLater::Init()
   if (NS_FAILED(rv) || !sendInBackground)
     return NS_OK;
 
   // We need to know when we're shutting down.
   nsCOMPtr<nsIObserverService> observerService =
     do_GetService("@mozilla.org/observer-service;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = observerService->AddObserver(this, "xpcom-shutdown", PR_FALSE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   rv = observerService->AddObserver(this, "quit-application", PR_FALSE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = observerService->AddObserver(this, "msg-shutdown", PR_FALSE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Subscribe to the unsent messages folder
-  nsCOMPtr<nsIMsgFolder> unsentFolder;
-  rv = GetUnsentMessagesFolder(nsnull, getter_AddRefs(unsentFolder));
+  // XXX This code should be set up for multiple unsent folders, however we
+  // don't support that at the moment, so for now just assume one folder.
+  rv = GetUnsentMessagesFolder(nsnull, getter_AddRefs(mMessageFolder));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = unsentFolder->AddFolderListener(this);
+  rv = mMessageFolder->AddFolderListener(this);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // XXX may want to send messages X seconds after startup if there are any.
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -166,31 +170,43 @@ nsMsgSendLater::Observe(nsISupports *aSu
     mTimerSet = PR_FALSE;
     // If we've already started a send since the timer fired, don't start
     // another
     if (!mSendingMessages)
       InternalSendMessages(PR_FALSE, nsnull);
   }
   else if (!strcmp(aTopic, "quit-application"))
   {
+    // If the timer is set, cancel it - we're quitting, the shutdown service
+    // interfaces will sort out sending etc.
+    if (mTimer)
+      mTimer->Cancel();
+
+    mTimerSet = PR_FALSE;
+  }
+  else if (!strcmp(aTopic, "xpcom-shutdown"))
+  {
     // We're shutting down. Unsubscribe from the unsentFolder notifications
     // they aren't any use to us now, we don't want to start sending more
     // messages.
-    nsCOMPtr<nsIMsgFolder> unsentFolder;
-    nsresult rv = GetUnsentMessagesFolder(nsnull, getter_AddRefs(unsentFolder));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = unsentFolder->RemoveFolderListener(this);
-    NS_ENSURE_SUCCESS(rv, rv);
+    nsresult rv;
+    if (mMessageFolder)
+    {
+      rv = mMessageFolder->RemoveFolderListener(this);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
 
     // Now remove ourselves from the observer service as well.
     nsCOMPtr<nsIObserverService> observerService =
       do_GetService("@mozilla.org/observer-service;1", &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    rv = observerService->RemoveObserver(this, "xpcom-shutdown");
+    NS_ENSURE_SUCCESS(rv, rv);
+
     rv = observerService->RemoveObserver(this, "quit-application");
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = observerService->RemoveObserver(this, "msg-shutdown");
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
@@ -654,16 +670,19 @@ nsMsgSendLater::StartNextMailFileSend()
   nsCOMPtr<nsISupports> currentItem;
   rv = mEnumerator->GetNext(getter_AddRefs(currentItem));
   NS_ENSURE_SUCCESS(rv, rv);
 
   mMessage = do_QueryInterface(currentItem); 
   if (!mMessage)
     return NS_ERROR_NOT_AVAILABLE;
 
+  if (!mMessageFolder)
+    return NS_ERROR_UNEXPECTED;
+
   mMessageFolder->GetUriForMsg(mMessage, messageURI);
 
   rv = nsMsgCreateTempFile("nsqmail.tmp", getter_AddRefs(mTempFile)); 
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIMsgMessageService> messageService;
   rv = GetMessageServiceFromURI(messageURI, getter_AddRefs(messageService));
   if (NS_FAILED(rv) && !messageService)
@@ -702,23 +721,29 @@ nsMsgSendLater::GetUnsentMessagesFolder(
   return LocateMessageFolder(aIdentity, nsIMsgSend::nsMsgQueueForLater,
                              uri.get(), folder);
 }
 
 NS_IMETHODIMP
 nsMsgSendLater::HasUnsentMessages(nsIMsgIdentity *aIdentity, PRBool *aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
+  nsresult rv;
 
-  nsCOMPtr<nsIMsgFolder> msgFolder;
-  nsresult rv = GetUnsentMessagesFolder(aIdentity, getter_AddRefs(msgFolder));
-  NS_ENSURE_SUCCESS(rv, rv);
+  // XXX This code should be set up for multiple unsent folders, however we
+  // don't support that at the moment, so for now just assume one folder.
+  if (!mMessageFolder)
+  {
+    rv = GetUnsentMessagesFolder(nsnull,
+                                 getter_AddRefs(mMessageFolder));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   PRInt32 totalMessages;
-  rv = msgFolder->GetTotalMessages(PR_FALSE, &totalMessages);
+  rv = mMessageFolder->GetTotalMessages(PR_FALSE, &totalMessages);
   NS_ENSURE_SUCCESS(rv, rv);
 
   *aResult = totalMessages > 0;
   return NS_OK;
 }
 
 //
 // To really finalize this capability, we need to have the ability to get
@@ -755,19 +780,26 @@ nsMsgSendLater::InternalSendMessages(PRB
 {
   // Protect against being called whilst we're already sending.
   if (mSendingMessages)
   {
     NS_ERROR("nsMsgSendLater is already sending messages\n");
     return NS_ERROR_FAILURE;
   }
 
-  nsresult rv = GetUnsentMessagesFolder(aIdentity,
-                                        getter_AddRefs(mMessageFolder));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv;
+
+  // XXX This code should be set up for multiple unsent folders, however we
+  // don't support that at the moment, so for now just assume one folder.
+  if (!mMessageFolder)
+  {
+    rv = GetUnsentMessagesFolder(nsnull,
+                                 getter_AddRefs(mMessageFolder));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   // ### fix me - if we need to reparse the folder, this will be asynchronous
   nsCOMPtr<nsISimpleEnumerator> enumerator;
   rv = mMessageFolder->GetMessages(getter_AddRefs(enumerator));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // copy all the elements in the enumerator into our isupports array....
 
@@ -862,17 +894,21 @@ nsMsgSendLater::DeleteCurrentMessage()
     return NS_OK;
   }
 
   // Get the composition fields interface
   nsCOMPtr<nsIMutableArray> msgArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
   if (!msgArray)
     return NS_ERROR_FACTORY_NOT_LOADED;
 
+  if (!mMessageFolder)
+    return NS_ERROR_UNEXPECTED;
+
   msgArray->InsertElementAt(mMessage, 0, PR_FALSE);
+
   nsresult res = mMessageFolder->DeleteMessages(msgArray, nsnull, PR_TRUE, PR_FALSE, nsnull, PR_FALSE /*allowUndo*/);
   if (NS_FAILED(res))
     return NS_ERROR_FAILURE;
 
   // Null out the message so we don't try and delete it again.
   mMessage = nsnull;
 
   return NS_OK;
@@ -1278,16 +1314,24 @@ nsMsgSendLater::EndSendMessages(nsresult
   // StartNextMailFileSend to fully finish the sending. Therefore set
   // mSendingMessages to false here so that we don't think we're still trying
   // to send messages
   mSendingMessages = PR_FALSE;
 
   // Clear out our array of messages.
   mMessagesToSend.Clear();
 
+  // We don't need to keep hold of the database now we've finished sending.
+  (void)mMessageFolder->SetMsgDatabase(nsnull);
+
+  // or the enumerator, temp file or output stream
+  mEnumerator = nsnull;
+  mTempFile = nsnull;
+  mOutFile = nsnull;  
+
   NOTIFY_LISTENERS(OnStopSending, (aStatus, aMsg, aTotalTried, aSuccessful));
 
   // If we've got a shutdown listener, notify it that we've finished.
   if (mShutdownListener)
   {
     mShutdownListener->OnStopRunningUrl(nsnull, NS_OK);
     mShutdownListener = nsnull;
   }