// vim:set sw=4 sts=4 et cin: /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2002 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Darin Fisher * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifdef MOZ_LOGGING #define FORCE_PR_LOG #endif #include "nsSocketTransportService2.h" #include "nsSocketTransport2.h" #include "nsReadableUtils.h" #include "nsAutoLock.h" #include "nsNetError.h" #include "prnetdb.h" #include "prlock.h" #include "prerror.h" #include "plstr.h" #if defined(PR_LOGGING) PRLogModuleInfo *gSocketTransportLog = nsnull; #endif nsSocketTransportService *gSocketTransportService = nsnull; PRThread *gSocketThread = nsnull; //----------------------------------------------------------------------------- // ctor/dtor (called on the main/UI thread by the service manager) nsSocketTransportService::nsSocketTransportService() : mThread(nsnull) , mThreadEvent(nsnull) , mAutodialEnabled(PR_FALSE) , mLock(PR_NewLock()) , mInitialized(PR_FALSE) , mShuttingDown(PR_FALSE) , mActiveCount(0) , mIdleCount(0) { #if defined(PR_LOGGING) gSocketTransportLog = PR_NewLogModule("nsSocketTransport"); #endif NS_ASSERTION(NS_IsMainThread(), "wrong thread"); NS_ASSERTION(!gSocketTransportService, "must not instantiate twice"); gSocketTransportService = this; } nsSocketTransportService::~nsSocketTransportService() { NS_ASSERTION(NS_IsMainThread(), "wrong thread"); NS_ASSERTION(!mInitialized, "not shutdown properly"); if (mLock) PR_DestroyLock(mLock); if (mThreadEvent) PR_DestroyPollableEvent(mThreadEvent); gSocketTransportService = nsnull; } //----------------------------------------------------------------------------- // event queue (any thread) already_AddRefed nsSocketTransportService::GetThreadSafely() { nsAutoLock lock(mLock); nsIThread* result = mThread; NS_IF_ADDREF(result); return result; } NS_IMETHODIMP nsSocketTransportService::Dispatch(nsIRunnable *event, PRUint32 flags) { LOG(("STS dispatch [%p]\n", event)); nsCOMPtr thread = GetThreadSafely(); NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED); nsresult rv = thread->Dispatch(event, flags); if (rv == NS_ERROR_UNEXPECTED) { // Thread is no longer accepting events. We must have just shut it // down on the main thread. Pretend we never saw it. rv = NS_ERROR_NOT_INITIALIZED; } return rv; } NS_IMETHODIMP nsSocketTransportService::IsOnCurrentThread(PRBool *result) { nsCOMPtr thread = GetThreadSafely(); NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED); return thread->IsOnCurrentThread(result); } //----------------------------------------------------------------------------- // socket api (socket thread only) NS_IMETHODIMP nsSocketTransportService::NotifyWhenCanAttachSocket(nsIRunnable *event) { LOG(("nsSocketTransportService::NotifyWhenCanAttachSocket\n")); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (CanAttachSocket()) { return Dispatch(event, NS_DISPATCH_NORMAL); } mPendingSocketQ.PutEvent(event); return NS_OK; } NS_IMETHODIMP nsSocketTransportService::AttachSocket(PRFileDesc *fd, nsASocketHandler *handler) { LOG(("nsSocketTransportService::AttachSocket [handler=%x]\n", handler)); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (!CanAttachSocket()) { return NS_ERROR_NOT_AVAILABLE; } SocketContext sock; sock.mFD = fd; sock.mHandler = handler; sock.mElapsedTime = 0; nsresult rv = AddToIdleList(&sock); if (NS_SUCCEEDED(rv)) NS_ADDREF(handler); return rv; } nsresult nsSocketTransportService::DetachSocket(SocketContext *sock) { LOG(("nsSocketTransportService::DetachSocket [handler=%x]\n", sock->mHandler)); // inform the handler that this socket is going away sock->mHandler->OnSocketDetached(sock->mFD); // cleanup sock->mFD = nsnull; NS_RELEASE(sock->mHandler); // find out what list this is on. PRUint32 index = sock - mActiveList; if (index < NS_SOCKET_MAX_COUNT) RemoveFromPollList(sock); else RemoveFromIdleList(sock); // NOTE: sock is now an invalid pointer // // notify the first element on the pending socket queue... // nsCOMPtr event; if (mPendingSocketQ.GetPendingEvent(getter_AddRefs(event))) { // move event from pending queue to dispatch queue return Dispatch(event, NS_DISPATCH_NORMAL); } return NS_OK; } nsresult nsSocketTransportService::AddToPollList(SocketContext *sock) { LOG(("nsSocketTransportService::AddToPollList [handler=%x]\n", sock->mHandler)); if (mActiveCount == NS_SOCKET_MAX_COUNT) { NS_ERROR("too many active sockets"); return NS_ERROR_UNEXPECTED; } mActiveList[mActiveCount] = *sock; mActiveCount++; mPollList[mActiveCount].fd = sock->mFD; mPollList[mActiveCount].in_flags = sock->mHandler->mPollFlags; mPollList[mActiveCount].out_flags = 0; LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount)); return NS_OK; } void nsSocketTransportService::RemoveFromPollList(SocketContext *sock) { LOG(("nsSocketTransportService::RemoveFromPollList [handler=%x]\n", sock->mHandler)); PRUint32 index = sock - mActiveList; NS_ASSERTION(index < NS_SOCKET_MAX_COUNT, "invalid index"); LOG((" index=%u mActiveCount=%u\n", index, mActiveCount)); if (index != mActiveCount-1) { mActiveList[index] = mActiveList[mActiveCount-1]; mPollList[index+1] = mPollList[mActiveCount]; } mActiveCount--; LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount)); } nsresult nsSocketTransportService::AddToIdleList(SocketContext *sock) { LOG(("nsSocketTransportService::AddToIdleList [handler=%x]\n", sock->mHandler)); if (mIdleCount == NS_SOCKET_MAX_COUNT) { NS_ERROR("too many idle sockets"); return NS_ERROR_UNEXPECTED; } mIdleList[mIdleCount] = *sock; mIdleCount++; LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount)); return NS_OK; } void nsSocketTransportService::RemoveFromIdleList(SocketContext *sock) { LOG(("nsSocketTransportService::RemoveFromIdleList [handler=%x]\n", sock->mHandler)); PRUint32 index = sock - &mIdleList[0]; NS_ASSERTION(index < NS_SOCKET_MAX_COUNT, "invalid index"); if (index != mIdleCount-1) mIdleList[index] = mIdleList[mIdleCount-1]; mIdleCount--; LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount)); } void nsSocketTransportService::MoveToIdleList(SocketContext *sock) { nsresult rv = AddToIdleList(sock); if (NS_FAILED(rv)) DetachSocket(sock); else RemoveFromPollList(sock); } void nsSocketTransportService::MoveToPollList(SocketContext *sock) { nsresult rv = AddToPollList(sock); if (NS_FAILED(rv)) DetachSocket(sock); else RemoveFromIdleList(sock); } PRIntervalTime nsSocketTransportService::PollTimeout() { if (mActiveCount == 0) return NS_SOCKET_POLL_TIMEOUT; // compute minimum time before any socket timeout expires. PRUint32 minR = PR_UINT16_MAX; for (PRUint32 i=0; imPollTimeout) ? s.mHandler->mPollTimeout - s.mElapsedTime : 0; if (r < minR) minR = r; } LOG(("poll timeout: %lu\n", minR)); return PR_SecondsToInterval(minR); } PRInt32 nsSocketTransportService::Poll(PRBool wait, PRUint32 *interval) { PRPollDesc *pollList; PRUint32 pollCount; PRIntervalTime pollTimeout; if (mPollList[0].fd) { mPollList[0].out_flags = 0; pollList = mPollList; pollCount = mActiveCount + 1; pollTimeout = PollTimeout(); } else { // no pollable event, so busy wait... pollCount = mActiveCount; if (pollCount) pollList = &mPollList[1]; else pollList = nsnull; pollTimeout = PR_MillisecondsToInterval(25); } if (!wait) pollTimeout = PR_INTERVAL_NO_WAIT; PRIntervalTime ts = PR_IntervalNow(); LOG((" timeout = %i milliseconds\n", PR_IntervalToMilliseconds(pollTimeout))); PRInt32 rv = PR_Poll(pollList, pollCount, pollTimeout); PRIntervalTime passedInterval = PR_IntervalNow() - ts; LOG((" ...returned after %i milliseconds\n", PR_IntervalToMilliseconds(passedInterval))); *interval = PR_IntervalToSeconds(passedInterval); return rv; } //----------------------------------------------------------------------------- // xpcom api NS_IMPL_THREADSAFE_ISUPPORTS5(nsSocketTransportService, nsISocketTransportService, nsIEventTarget, nsIThreadObserver, nsIRunnable, nsPISocketTransportService) // called from main thread only NS_IMETHODIMP nsSocketTransportService::Init() { NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY); if (!NS_IsMainThread()) { NS_ERROR("wrong thread"); return NS_ERROR_UNEXPECTED; } if (mInitialized) return NS_OK; if (mShuttingDown) return NS_ERROR_UNEXPECTED; if (!mThreadEvent) { mThreadEvent = PR_NewPollableEvent(); // // NOTE: per bug 190000, this failure could be caused by Zone-Alarm // or similar software. // // NOTE: per bug 191739, this failure could also be caused by lack // of a loopback device on Windows and OS/2 platforms (NSPR creates // a loopback socket pair on these platforms to implement a pollable // event object). if we can't create a pollable event, then we'll // have to "busy wait" to implement the socket event queue :-( // if (!mThreadEvent) { NS_WARNING("running socket transport thread without a pollable event"); LOG(("running socket transport thread without a pollable event")); } } nsCOMPtr thread; nsresult rv = NS_NewThread(getter_AddRefs(thread), this); if (NS_FAILED(rv)) return rv; { nsAutoLock lock(mLock); // Install our mThread, protecting against concurrent readers thread.swap(mThread); } mInitialized = PR_TRUE; return NS_OK; } // called from main thread only NS_IMETHODIMP nsSocketTransportService::Shutdown() { LOG(("nsSocketTransportService::Shutdown\n")); NS_ENSURE_STATE(NS_IsMainThread()); if (!mInitialized) return NS_OK; if (mShuttingDown) return NS_ERROR_UNEXPECTED; { nsAutoLock lock(mLock); // signal the socket thread to shutdown mShuttingDown = PR_TRUE; if (mThreadEvent) PR_SetPollableEvent(mThreadEvent); // else wait for Poll timeout } // join with thread mThread->Shutdown(); { nsAutoLock lock(mLock); // Drop our reference to mThread and make sure that any concurrent // readers are excluded mThread = nsnull; } mInitialized = PR_FALSE; mShuttingDown = PR_FALSE; return NS_OK; } NS_IMETHODIMP nsSocketTransportService::CreateTransport(const char **types, PRUint32 typeCount, const nsACString &host, PRInt32 port, nsIProxyInfo *proxyInfo, nsISocketTransport **result) { NS_ENSURE_TRUE(mInitialized, NS_ERROR_OFFLINE); NS_ENSURE_TRUE(port >= 0 && port <= 0xFFFF, NS_ERROR_ILLEGAL_VALUE); nsSocketTransport *trans = new nsSocketTransport(); if (!trans) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(trans); nsresult rv = trans->Init(types, typeCount, host, port, proxyInfo); if (NS_FAILED(rv)) { NS_RELEASE(trans); return rv; } *result = trans; return NS_OK; } NS_IMETHODIMP nsSocketTransportService::GetAutodialEnabled(PRBool *value) { *value = mAutodialEnabled; return NS_OK; } NS_IMETHODIMP nsSocketTransportService::SetAutodialEnabled(PRBool value) { mAutodialEnabled = value; return NS_OK; } NS_IMETHODIMP nsSocketTransportService::OnDispatchedEvent(nsIThreadInternal *thread) { nsAutoLock lock(mLock); if (mThreadEvent) PR_SetPollableEvent(mThreadEvent); return NS_OK; } NS_IMETHODIMP nsSocketTransportService::OnProcessNextEvent(nsIThreadInternal *thread, PRBool mayWait, PRUint32 depth) { // DoPollIteration doesn't support being called recursively. This case // should only happen when someone (e.g., PSM) is issuing a synchronous // proxy call from this thread to the main thread. if (depth > 1) return NS_OK; // Favor processing existing sockets before other events. DoPollIteration(PR_FALSE); PRBool val; while (mayWait && NS_SUCCEEDED(thread->HasPendingEvents(&val)) && !val) DoPollIteration(PR_TRUE); return NS_OK; } NS_IMETHODIMP nsSocketTransportService::AfterProcessNextEvent(nsIThreadInternal* thread, PRUint32 depth) { return NS_OK; } NS_IMETHODIMP nsSocketTransportService::Run() { LOG(("STS thread init\n")); gSocketThread = PR_GetCurrentThread(); // add thread event to poll list (mThreadEvent may be NULL) mPollList[0].fd = mThreadEvent; mPollList[0].in_flags = PR_POLL_READ; mPollList[0].out_flags = 0; nsIThread *thread = NS_GetCurrentThread(); // hook ourselves up to observe event processing for this thread nsCOMPtr threadInt = do_QueryInterface(thread); threadInt->SetObserver(this); for (;;) { // process all pending events NS_ProcessPendingEvents(thread); // now that our event queue is empty, check to see if we should exit { nsAutoLock lock(mLock); if (mShuttingDown) break; } // wait for and process the next pending event NS_ProcessNextEvent(thread); } LOG(("STS shutting down thread\n")); // detach any sockets PRInt32 i; for (i=mActiveCount-1; i>=0; --i) DetachSocket(&mActiveList[i]); for (i=mIdleCount-1; i>=0; --i) DetachSocket(&mIdleList[i]); // Final pass over the event queue. This makes sure that events posted by // socket detach handlers get processed. NS_ProcessPendingEvents(thread); gSocketThread = nsnull; LOG(("STS thread exit\n")); return NS_OK; } nsresult nsSocketTransportService::DoPollIteration(PRBool wait) { LOG(("STS poll iter [%d]\n", wait)); PRInt32 i, count; // // poll loop // PRBool pollError = PR_FALSE; // // walk active list backwards to see if any sockets should actually be // idle, then walk the idle list backwards to see if any idle sockets // should become active. take care to check only idle sockets that // were idle to begin with ;-) // count = mIdleCount; for (i=mActiveCount-1; i>=0; --i) { //--- LOG((" active [%u] { handler=%x condition=%x pollflags=%hu }\n", i, mActiveList[i].mHandler, mActiveList[i].mHandler->mCondition, mActiveList[i].mHandler->mPollFlags)); //--- if (NS_FAILED(mActiveList[i].mHandler->mCondition)) DetachSocket(&mActiveList[i]); else { PRUint16 in_flags = mActiveList[i].mHandler->mPollFlags; if (in_flags == 0) MoveToIdleList(&mActiveList[i]); else { // update poll flags mPollList[i+1].in_flags = in_flags; mPollList[i+1].out_flags = 0; } } } for (i=count-1; i>=0; --i) { //--- LOG((" idle [%u] { handler=%x condition=%x pollflags=%hu }\n", i, mIdleList[i].mHandler, mIdleList[i].mHandler->mCondition, mIdleList[i].mHandler->mPollFlags)); //--- if (NS_FAILED(mIdleList[i].mHandler->mCondition)) DetachSocket(&mIdleList[i]); else if (mIdleList[i].mHandler->mPollFlags != 0) MoveToPollList(&mIdleList[i]); } LOG((" calling PR_Poll [active=%u idle=%u]\n", mActiveCount, mIdleCount)); // Measures seconds spent while blocked on PR_Poll PRUint32 pollInterval; PRInt32 n = Poll(wait, &pollInterval); if (n < 0) { LOG((" PR_Poll error [%d]\n", PR_GetError())); pollError = PR_TRUE; } else { // // service "active" sockets... // for (i=0; i 0 && desc.out_flags != 0) { s.mElapsedTime = 0; s.mHandler->OnSocketReady(desc.fd, desc.out_flags); } // check for timeout errors unless disabled... else if (s.mHandler->mPollTimeout != PR_UINT16_MAX) { // update elapsed time counter if (NS_UNLIKELY(pollInterval > (PR_UINT16_MAX - s.mElapsedTime))) s.mElapsedTime = PR_UINT16_MAX; else s.mElapsedTime += PRUint16(pollInterval); // check for timeout expiration if (s.mElapsedTime >= s.mHandler->mPollTimeout) { s.mElapsedTime = 0; s.mHandler->OnSocketReady(desc.fd, -1); } } } // // check for "dead" sockets and remove them (need to do this in // reverse order obviously). // for (i=mActiveCount-1; i>=0; --i) { if (NS_FAILED(mActiveList[i].mHandler->mCondition)) DetachSocket(&mActiveList[i]); } if (n != 0 && mPollList[0].out_flags == PR_POLL_READ) { // acknowledge pollable event (wait should not block) if (PR_WaitForPollableEvent(mThreadEvent) != PR_SUCCESS) { // On Windows, the TCP loopback connection in the // pollable event may become broken when a laptop // switches between wired and wireless networks or // wakes up from hibernation. We try to create a // new pollable event. If that fails, we fall back // on "busy wait". { nsAutoLock lock(mLock); PR_DestroyPollableEvent(mThreadEvent); mThreadEvent = PR_NewPollableEvent(); } if (!mThreadEvent) { NS_WARNING("running socket transport thread without " "a pollable event"); LOG(("running socket transport thread without " "a pollable event")); } mPollList[0].fd = mThreadEvent; // mPollList[0].in_flags was already set to PR_POLL_READ // in Run(). mPollList[0].out_flags = 0; } } } return NS_OK; }