Back to index

lightning-sunbird  0.9+nobinonly
nsIOThreadPool.cpp
Go to the documentation of this file.
00001 /* ***** BEGIN LICENSE BLOCK *****
00002  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003  *
00004  * The contents of this file are subject to the Mozilla Public License Version
00005  * 1.1 (the "License"); you may not use this file except in compliance with
00006  * the License. You may obtain a copy of the License at
00007  * http://www.mozilla.org/MPL/
00008  *
00009  * Software distributed under the License is distributed on an "AS IS" basis,
00010  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011  * for the specific language governing rights and limitations under the
00012  * License.
00013  *
00014  * The Original Code is Mozilla.
00015  *
00016  * The Initial Developer of the Original Code is IBM Corporation.
00017  * Portions created by IBM Corporation are Copyright (C) 2003
00018  * IBM Corporation. All Rights Reserved.
00019  *
00020  * Contributor(s):
00021  *   IBM Corp.
00022  *
00023  * Alternatively, the contents of this file may be used under the terms of
00024  * either the GNU General Public License Version 2 or later (the "GPL"), or
00025  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00026  * in which case the provisions of the GPL or the LGPL are applicable instead
00027  * of those above. If you wish to allow use of your version of this file only
00028  * under the terms of either the GPL or the LGPL, and not to allow others to
00029  * use your version of this file under the terms of the MPL, indicate your
00030  * decision by deleting the provisions above and replace them with the notice
00031  * and other provisions required by the GPL or the LGPL. If you do not delete
00032  * the provisions above, a recipient may use your version of this file under
00033  * the terms of any one of the MPL, the GPL or the LGPL.
00034  *
00035  * ***** END LICENSE BLOCK ***** */
00036 
00037 #include "nsIEventTarget.h"
00038 #include "nsIServiceManager.h"
00039 #include "nsIObserverService.h"
00040 #include "nsIObserver.h"
00041 #include "nsAutoLock.h"
00042 #include "nsCOMPtr.h"
00043 #include "prclist.h"
00044 #include "prlog.h"
00045 
00046 #if defined(PR_LOGGING)
00047 //
00048 // NSPR_LOG_MODULES=nsIOThreadPool:5
00049 //
00050 static PRLogModuleInfo *gIOThreadPoolLog = nsnull;
00051 #endif
00052 #define LOG(args) PR_LOG(gIOThreadPoolLog, PR_LOG_DEBUG, args)
00053 
00054 // this number specifies the maximum number of threads.
00055 #define MAX_THREADS 4
00056 
00057 // this number specifies how long to wait before killing an idle thread.  it's
00058 // important to pick a large enough value here to minimize thread churn.
00059 #define IDLE_TIMEOUT PR_SecondsToInterval(60)
00060 
00061 #define PLEVENT_FROM_LINK(_link) \
00062     ((PLEvent*) ((char*) (_link) - offsetof(PLEvent, link)))
00063 
00064 //-----------------------------------------------------------------------------
00065 // pool of joinable threads used for general purpose i/o tasks
00066 //
00067 // the main entry point to this class is nsIEventTarget.  events posted to
00068 // the thread pool are dispatched on one of the threads.  a variable number
00069 // of threads are maintained.  the threads die off if they remain idle for
00070 // more than THREAD_IDLE_TIMEOUT.  the thread pool shuts down when it receives
00071 // the "xpcom-shutdown" event.
00072 //-----------------------------------------------------------------------------
00073 
00074 class nsIOThreadPool : public nsIEventTarget
00075                      , public nsIObserver
00076 {
00077 public:
00078     NS_DECL_ISUPPORTS
00079     NS_DECL_NSIEVENTTARGET
00080     NS_DECL_NSIOBSERVER
00081 
00082     nsresult Init();
00083     void     Shutdown();
00084 
00085 private:
00086     virtual ~nsIOThreadPool();
00087 
00088     PR_STATIC_CALLBACK(void) ThreadFunc(void *);
00089 
00090     // mLock protects all (exceptions during Init and Shutdown)
00091     PRLock    *mLock;
00092     PRCondVar *mIdleThreadCV;   // notified to wake up an idle thread
00093     PRCondVar *mExitThreadCV;   // notified when a thread exits
00094     PRUint32   mNumThreads;     // number of active + idle threads
00095     PRUint32   mNumIdleThreads; // number of idle threads
00096     PRCList    mEventQ;         // queue of PLEvent structs
00097     PRBool     mShutdown;       // set to true if shutting down
00098 };
00099 
00100 NS_IMPL_THREADSAFE_ISUPPORTS2(nsIOThreadPool, nsIEventTarget, nsIObserver)
00101 
00102 nsresult
00103 nsIOThreadPool::Init()
00104 {
00105 #if defined(PR_LOGGING)
00106     if (!gIOThreadPoolLog)
00107         gIOThreadPoolLog = PR_NewLogModule("nsIOThreadPool");
00108 #endif
00109 
00110     mNumThreads = 0;
00111     mNumIdleThreads = 0;
00112     mShutdown = PR_FALSE;
00113 
00114     mLock = PR_NewLock();
00115     if (!mLock)
00116         return NS_ERROR_OUT_OF_MEMORY;
00117 
00118     mIdleThreadCV = PR_NewCondVar(mLock);
00119     if (!mIdleThreadCV)
00120         return NS_ERROR_OUT_OF_MEMORY;
00121 
00122     mExitThreadCV = PR_NewCondVar(mLock);
00123     if (!mExitThreadCV)
00124         return NS_ERROR_OUT_OF_MEMORY;
00125 
00126     PR_INIT_CLIST(&mEventQ);
00127 
00128     // we want to shutdown the i/o thread pool at xpcom-shutdown time...
00129     nsCOMPtr<nsIObserverService> os = do_GetService("@mozilla.org/observer-service;1");
00130     if (os)
00131         os->AddObserver(this, "xpcom-shutdown", PR_FALSE);
00132     return NS_OK;
00133 }
00134 
00135 nsIOThreadPool::~nsIOThreadPool()
00136 {
00137     LOG(("Destroying nsIOThreadPool @%p\n", this));
00138 
00139 #ifdef DEBUG
00140     NS_ASSERTION(PR_CLIST_IS_EMPTY(&mEventQ), "leaking events");
00141     NS_ASSERTION(mNumThreads == 0, "leaking thread(s)");
00142 #endif
00143 
00144     if (mIdleThreadCV)
00145         PR_DestroyCondVar(mIdleThreadCV);
00146     if (mExitThreadCV)
00147         PR_DestroyCondVar(mExitThreadCV);
00148     if (mLock)
00149         PR_DestroyLock(mLock);
00150 }
00151 
00152 void
00153 nsIOThreadPool::Shutdown()
00154 {
00155     LOG(("nsIOThreadPool::Shutdown\n"));
00156 
00157     // synchronize with background threads...
00158     {
00159         nsAutoLock lock(mLock);
00160         mShutdown = PR_TRUE;
00161 
00162         PR_NotifyAllCondVar(mIdleThreadCV);
00163 
00164         while (mNumThreads != 0)
00165             PR_WaitCondVar(mExitThreadCV, PR_INTERVAL_NO_TIMEOUT);
00166     }
00167 }
00168 
00169 NS_IMETHODIMP
00170 nsIOThreadPool::PostEvent(PLEvent *event)
00171 {
00172     LOG(("nsIOThreadPool::PostEvent [event=%p]\n", event));
00173 
00174     nsAutoLock lock(mLock);
00175 
00176     // if we are shutting down, then prevent additional events from being
00177     // added to the queue...
00178     if (mShutdown)
00179         return NS_ERROR_UNEXPECTED;
00180     
00181     nsresult rv = NS_OK;
00182 
00183     PR_APPEND_LINK(&event->link, &mEventQ);
00184 
00185     // now, look for an available idle thread...
00186     if (mNumIdleThreads)
00187         PR_NotifyCondVar(mIdleThreadCV); // wake up an idle thread
00188 
00189     // or, try to create a new thread unless we have reached our maximum...
00190     else if (mNumThreads < MAX_THREADS) {
00191         NS_ADDREF_THIS(); // the thread owns a reference to us
00192         mNumThreads++;
00193         PRThread *thread = PR_CreateThread(PR_USER_THREAD,
00194                                            ThreadFunc,
00195                                            this,
00196                                            PR_PRIORITY_NORMAL,
00197                                            PR_GLOBAL_THREAD,
00198                                            PR_UNJOINABLE_THREAD,
00199                                            0);
00200         if (!thread) {
00201             NS_RELEASE_THIS();
00202             mNumThreads--;
00203             rv = NS_ERROR_OUT_OF_MEMORY;
00204         }
00205     }
00206     // else, we expect one of the active threads to process the event queue.
00207 
00208     return rv;
00209 }
00210 
00211 NS_IMETHODIMP
00212 nsIOThreadPool::IsOnCurrentThread(PRBool *result)
00213 {
00214     // no one should be calling this method.  if this assertion gets hit,
00215     // then we need to think carefully about what this method should be
00216     // returning.
00217     NS_NOTREACHED("nsIOThreadPool::IsOnCurrentThread");
00218 
00219     // fudging this a bit since we actually cover several threads...
00220     *result = PR_FALSE;
00221     return NS_OK;
00222 }
00223 
00224 NS_IMETHODIMP
00225 nsIOThreadPool::Observe(nsISupports *, const char *topic, const PRUnichar *)
00226 {
00227     NS_ASSERTION(strcmp(topic, "xpcom-shutdown") == 0, "unexpected topic");
00228     Shutdown();
00229     return NS_OK;
00230 }
00231 
00232 void
00233 nsIOThreadPool::ThreadFunc(void *arg)
00234 {
00235     nsIOThreadPool *pool = (nsIOThreadPool *) arg;
00236 
00237     LOG(("entering ThreadFunc\n"));
00238 
00239     {
00240         nsAutoLock lock(pool->mLock);
00241 
00242         for (;;) {
00243             PRIntervalTime start = PR_IntervalNow(), timeout = IDLE_TIMEOUT;
00244             //
00245             // wait for one or more of the following to occur:
00246             //  (1) the event queue has an event to process
00247             //  (2) the shutdown flag has been set
00248             //  (3) the thread has been idle for too long
00249             //
00250             // PR_WaitCondVar will return when any of these conditions is true.
00251             //
00252             while (PR_CLIST_IS_EMPTY(&pool->mEventQ) && !pool->mShutdown) {
00253                 pool->mNumIdleThreads++;
00254                 PR_WaitCondVar(pool->mIdleThreadCV, timeout);
00255                 pool->mNumIdleThreads--;
00256 
00257                 PRIntervalTime delta = PR_IntervalNow() - start;
00258                 if (delta >= timeout)
00259                     break;
00260                 timeout -= delta;
00261                 start += delta;
00262             }
00263 
00264             // if the queue is still empty, then kill this thread (either we
00265             // are shutting down or the thread exceeded the idle timeout)...
00266             if (PR_CLIST_IS_EMPTY(&pool->mEventQ))
00267                 break;
00268 
00269             // handle one event at a time: we don't want this one thread to hog
00270             // all the events while other threads may be able to help out ;-)
00271             do {
00272                 PLEvent *event = PLEVENT_FROM_LINK(PR_LIST_HEAD(&pool->mEventQ));
00273                 PR_REMOVE_AND_INIT_LINK(&event->link);
00274 
00275                 LOG(("event:%p\n", event));
00276 
00277                 // release lock!
00278                 lock.unlock();
00279                 PL_HandleEvent(event);
00280                 lock.lock();
00281             }
00282             while (!PR_CLIST_IS_EMPTY(&pool->mEventQ));
00283         }
00284 
00285         // thread is going away...
00286         pool->mNumThreads--;
00287         PR_NotifyCondVar(pool->mExitThreadCV);
00288     }
00289 
00290     // release our reference to the pool
00291     NS_RELEASE(pool);
00292 
00293     LOG(("leaving ThreadFunc\n"));
00294 }
00295 
00296 //-----------------------------------------------------------------------------
00297 
00298 NS_METHOD
00299 net_NewIOThreadPool(nsISupports *outer, REFNSIID iid, void **result)
00300 {
00301     nsIOThreadPool *pool = new nsIOThreadPool();
00302     if (!pool)
00303         return NS_ERROR_OUT_OF_MEMORY;
00304     NS_ADDREF(pool);
00305     nsresult rv = pool->Init();
00306     if (NS_SUCCEEDED(rv))
00307         rv = pool->QueryInterface(iid, result);
00308     NS_RELEASE(pool);
00309     return rv;
00310 }