Files
Mozilla/mozilla/netwerk/base/src/nsSocketTransportService2.cpp
brendan%mozilla.org 95220b5330 Add shared DHashTableOps for [const] char *key use-cases, clean up dhash API abusages (214839, r=dougt, sr=dbaron).
git-svn-id: svn://10.0.0.236/trunk@145624 18797224-902f-48f8-a5cc-f745e15eee43
2003-08-05 20:09:21 +00:00

659 lines
19 KiB
C++

/* ***** 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 <darin@netscape.com>
*
* 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 "nsPrintfCString.h"
#include "nsReadableUtils.h"
#include "nsAutoLock.h"
#include "nsNetError.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()
: mInitialized(PR_FALSE)
, mThread(nsnull)
, mThreadEvent(nsnull)
, mAutodialEnabled(PR_FALSE)
, mEventQHead(nsnull)
, mEventQTail(nsnull)
, mEventQLock(PR_NewLock())
, mActiveCount(0)
, mIdleCount(0)
, mPendingQHead(nsnull)
, mPendingQTail(nsnull)
{
#if defined(PR_LOGGING)
gSocketTransportLog = PR_NewLogModule("nsSocketTransport");
#endif
NS_ASSERTION(nsIThread::IsMainThread(), "wrong thread");
gSocketTransportService = this;
}
nsSocketTransportService::~nsSocketTransportService()
{
NS_ASSERTION(nsIThread::IsMainThread(), "wrong thread");
NS_ASSERTION(!mInitialized, "not shutdown properly");
PR_DestroyLock(mEventQLock);
if (mThreadEvent)
PR_DestroyPollableEvent(mThreadEvent);
gSocketTransportService = nsnull;
}
//-----------------------------------------------------------------------------
// event queue (any thread)
NS_IMETHODIMP
nsSocketTransportService::PostEvent(nsISocketEventHandler *handler,
PRUint32 type, PRUint32 uparam,
void *vparam)
{
LOG(("nsSocketTransportService::PostEvent [handler=%x type=%u u=%x v=%x]\n",
handler, type, uparam, vparam));
NS_ASSERTION(handler, "null handler");
nsAutoLock lock(mEventQLock);
if (!mInitialized)
return NS_ERROR_OFFLINE;
SocketEvent *event = new SocketEvent(handler, type, uparam, vparam);
if (!event)
return NS_ERROR_OUT_OF_MEMORY;
// XXX generalize this into some kind of template class
if (mEventQTail)
mEventQTail->mNext = event;
mEventQTail = event;
if (!mEventQHead)
mEventQHead = event;
if (mThreadEvent)
PR_SetPollableEvent(mThreadEvent);
// else wait for Poll timeout
return NS_OK;
}
//-----------------------------------------------------------------------------
// socket api (socket thread only)
nsresult
nsSocketTransportService::NotifyWhenCanAttachSocket(nsISocketEventHandler *handler,
PRUint32 msg)
{
LOG(("nsSocketTransportService::NotifyWhenCanAttachSocket\n"));
if (CanAttachSocket()) {
NS_WARNING("should have called CanAttachSocket");
return PostEvent(handler, msg, 0, nsnull);
}
PendingSocket *ps = new PendingSocket(handler, msg);
if (!ps)
return NS_ERROR_OUT_OF_MEMORY;
// XXX generalize this into some kind of template class
if (mPendingQTail)
mPendingQTail->mNext = ps;
mPendingQTail = ps;
if (!mPendingQHead)
mPendingQHead = ps;
return NS_OK;
}
nsresult
nsSocketTransportService::AttachSocket(PRFileDesc *fd, nsASocketHandler *handler)
{
LOG(("nsSocketTransportService::AttachSocket [handler=%x]\n", handler));
SocketContext sock;
sock.mFD = fd;
sock.mHandler = handler;
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 handler queue...
//
if (mPendingQHead) {
PendingSocket *ps = mPendingQHead;
mPendingQHead = ps->mNext;
if (!mPendingQHead)
mPendingQTail = nsnull;
PostEvent(ps->mHandler, ps->mMsg, 0, nsnull);
delete ps;
}
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);
}
PRInt32
nsSocketTransportService::Poll()
{
PRPollDesc *pollList;
PRUint32 pollCount;
PRIntervalTime pollTimeout;
if (mPollList[0].fd) {
mPollList[0].out_flags = 0;
pollList = mPollList;
pollCount = mActiveCount + 1;
pollTimeout = NS_SOCKET_POLL_TIMEOUT;
}
else {
// no pollable event, so busy wait...
pollCount = mActiveCount;
if (pollCount)
pollList = &mPollList[1];
else
pollList = nsnull;
pollTimeout = PR_MillisecondsToInterval(25);
}
return PR_Poll(pollList, pollCount, pollTimeout);
}
PRBool
nsSocketTransportService::ServiceEventQ()
{
PRBool keepGoing;
// grab the event queue
SocketEvent *head = nsnull, *event;
{
nsAutoLock lock(mEventQLock);
head = mEventQHead;
mEventQHead = nsnull;
mEventQTail = nsnull;
// check to see if we're supposed to shutdown
keepGoing = mInitialized;
}
// service the event queue
while (head) {
head->mHandler->OnSocketEvent(head->mType,
head->mUparam,
head->mVparam);
// delete head of queue
event = head->mNext;
delete head;
head = event;
}
return keepGoing;
}
//-----------------------------------------------------------------------------
// host:port -> ipaddr cache
PLDHashTableOps nsSocketTransportService::ops =
{
PL_DHashAllocTable,
PL_DHashFreeTable,
PL_DHashGetKeyStub,
PL_DHashStringKey,
PL_DHashMatchStringKey,
PL_DHashMoveEntryStub,
PL_DHashFreeStringKey,
PL_DHashFinalizeStub,
nsnull
};
nsresult
nsSocketTransportService::LookupHost(const nsACString &host, PRUint16 port, PRIPv6Addr *addr)
{
NS_ASSERTION(!host.IsEmpty(), "empty host");
NS_ASSERTION(addr, "null addr");
PLDHashEntryHdr *hdr;
nsCAutoString hostport(host + nsPrintfCString(":%d", port));
hdr = PL_DHashTableOperate(&mHostDB, hostport.get(), PL_DHASH_LOOKUP);
if (PL_DHASH_ENTRY_IS_BUSY(hdr)) {
// found match
nsHostEntry *ent = NS_REINTERPRET_CAST(nsHostEntry *, hdr);
memcpy(addr, &ent->addr, sizeof(ent->addr));
return NS_OK;
}
return NS_ERROR_UNKNOWN_HOST;
}
nsresult
nsSocketTransportService::RememberHost(const nsACString &host, PRUint16 port, PRIPv6Addr *addr)
{
// remember hostname
PLDHashEntryHdr *hdr;
nsCAutoString hostport(host + nsPrintfCString(":%d", port));
hdr = PL_DHashTableOperate(&mHostDB, hostport.get(), PL_DHASH_ADD);
if (!hdr)
return NS_ERROR_FAILURE;
NS_ASSERTION(PL_DHASH_ENTRY_IS_BUSY(hdr), "entry not busy");
nsHostEntry *ent = NS_REINTERPRET_CAST(nsHostEntry *, hdr);
if (ent->key == nsnull) {
ent->key = (const void *) ToNewCString(hostport);
memcpy(&ent->addr, addr, sizeof(ent->addr));
}
#ifdef DEBUG
else {
// verify that the existing entry is in fact a perfect match
NS_ASSERTION(PL_strcmp(ent->hostport(), hostport.get()) == 0, "bad match");
NS_ASSERTION(memcmp(&ent->addr, addr, sizeof(ent->addr)) == 0, "bad match");
}
#endif
return NS_OK;
}
//-----------------------------------------------------------------------------
// xpcom api
NS_IMPL_THREADSAFE_ISUPPORTS2(nsSocketTransportService,
nsISocketTransportService,
nsIRunnable)
// called from main thread only
NS_IMETHODIMP
nsSocketTransportService::Init()
{
NS_ASSERTION(nsIThread::IsMainThread(), "wrong thread");
if (mInitialized)
return NS_OK;
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 :-(
//
NS_WARN_IF_FALSE(mThreadEvent,
"running socket transport thread without a pollable event");
}
nsresult rv = NS_NewThread(&mThread, this, 0, PR_JOINABLE_THREAD);
if (NS_FAILED(rv)) return rv;
mInitialized = PR_TRUE;
return NS_OK;
}
// called from main thread only
NS_IMETHODIMP
nsSocketTransportService::Shutdown()
{
LOG(("nsSocketTransportService::Shutdown\n"));
NS_ASSERTION(nsIThread::IsMainThread(), "wrong thread");
if (!mInitialized)
return NS_OK;
{
nsAutoLock lock(mEventQLock);
// signal uninitialized to block further events
mInitialized = PR_FALSE;
if (mThreadEvent)
PR_SetPollableEvent(mThreadEvent);
// else wait for Poll timeout
}
// join with thread
mThread->Join();
NS_RELEASE(mThread);
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::Run()
{
LOG(("nsSocketTransportService::Run"));
gSocketThread = PR_GetCurrentThread();
//
// Initialize hostname database
//
PL_DHashTableInit(&mHostDB, &ops, nsnull, sizeof(nsHostEntry), 0);
//
// add thread event to poll list (mThreadEvent may be NULL)
//
mPollList[0].fd = mThreadEvent;
mPollList[0].in_flags = PR_POLL_READ;
PRInt32 i, count;
//
// poll loop
//
PRBool active = PR_TRUE;
while (active) {
//
// 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));
PRInt32 n = Poll();
if (n < 0) {
LOG((" PR_Poll error [%d]\n", PR_GetError()));
active = PR_FALSE;
}
else if (n > 0) {
//
// service "active" sockets...
//
for (i=0; i<PRInt32(mActiveCount); ++i) {
PRPollDesc &desc = mPollList[i+1];
if (desc.out_flags != 0) {
nsASocketHandler *handler = mActiveList[i].mHandler;
handler->OnSocketReady(desc.fd, desc.out_flags);
}
}
//
// 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]);
}
//
// service the event queue (mPollList[0].fd == mThreadEvent)
//
if (mPollList[0].out_flags == PR_POLL_READ) {
// acknowledge pollable event (wait should not block)
PR_WaitForPollableEvent(mThreadEvent);
active = ServiceEventQ();
}
}
else {
LOG((" PR_Poll timed out\n"));
//
// service event queue whenever PR_Poll times out.
//
active = ServiceEventQ();
}
}
//
// shutdown thread
//
LOG(("shutting down socket transport thread...\n"));
// detach any sockets
for (i=mActiveCount-1; i>=0; --i)
DetachSocket(&mActiveList[i]);
for (i=mIdleCount-1; i>=0; --i)
DetachSocket(&mIdleList[i]);
// clear the hostname database
PL_DHashTableFinish(&mHostDB);
gSocketThread = nsnull;
return NS_OK;
}