diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/base/PollableEvent.cpp | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/netwerk/base/PollableEvent.cpp b/netwerk/base/PollableEvent.cpp new file mode 100644 index 0000000000..a99d6d88f5 --- /dev/null +++ b/netwerk/base/PollableEvent.cpp @@ -0,0 +1,399 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSocketTransportService2.h" +#include "PollableEvent.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Logging.h" +#include "mozilla/net/DNS.h" +#include "prerror.h" +#include "prio.h" +#include "private/pprio.h" +#include "prnetdb.h" + +#ifdef XP_WIN +# include "ShutdownLayer.h" +#else +# include <fcntl.h> +# define USEPIPE 1 +#endif + +namespace mozilla { +namespace net { + +#ifndef USEPIPE +static PRDescIdentity sPollableEventLayerIdentity; +static PRIOMethods sPollableEventLayerMethods; +static PRIOMethods* sPollableEventLayerMethodsPtr = nullptr; + +static void LazyInitSocket() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (sPollableEventLayerMethodsPtr) { + return; + } + sPollableEventLayerIdentity = PR_GetUniqueIdentity("PollableEvent Layer"); + sPollableEventLayerMethods = *PR_GetDefaultIOMethods(); + sPollableEventLayerMethodsPtr = &sPollableEventLayerMethods; +} + +static bool NewTCPSocketPair(PRFileDesc* fd[], bool aSetRecvBuff) { + // this is a replacement for PR_NewTCPSocketPair that manually + // sets the recv buffer to 64K. A windows bug (1248358) + // can result in using an incompatible rwin and window + // scale option on localhost pipes if not set before connect. + + SOCKET_LOG(("NewTCPSocketPair %s a recv buffer tuning\n", + aSetRecvBuff ? "with" : "without")); + + PRFileDesc* listener = nullptr; + PRFileDesc* writer = nullptr; + PRFileDesc* reader = nullptr; + PRSocketOptionData recvBufferOpt; + recvBufferOpt.option = PR_SockOpt_RecvBufferSize; + recvBufferOpt.value.recv_buffer_size = 65535; + + PRSocketOptionData nodelayOpt; + nodelayOpt.option = PR_SockOpt_NoDelay; + nodelayOpt.value.no_delay = true; + + PRSocketOptionData noblockOpt; + noblockOpt.option = PR_SockOpt_Nonblocking; + noblockOpt.value.non_blocking = true; + + listener = PR_OpenTCPSocket(PR_AF_INET); + if (!listener) { + goto failed; + } + + if (aSetRecvBuff) { + PR_SetSocketOption(listener, &recvBufferOpt); + } + PR_SetSocketOption(listener, &nodelayOpt); + + PRNetAddr listenAddr; + memset(&listenAddr, 0, sizeof(listenAddr)); + if ((PR_InitializeNetAddr(PR_IpAddrLoopback, 0, &listenAddr) == PR_FAILURE) || + (PR_Bind(listener, &listenAddr) == PR_FAILURE) || + (PR_GetSockName(listener, &listenAddr) == + PR_FAILURE) || // learn the dynamic port + (PR_Listen(listener, 5) == PR_FAILURE)) { + goto failed; + } + + writer = PR_OpenTCPSocket(PR_AF_INET); + if (!writer) { + goto failed; + } + if (aSetRecvBuff) { + PR_SetSocketOption(writer, &recvBufferOpt); + } + PR_SetSocketOption(writer, &nodelayOpt); + PR_SetSocketOption(writer, &noblockOpt); + PRNetAddr writerAddr; + if (PR_InitializeNetAddr(PR_IpAddrLoopback, ntohs(listenAddr.inet.port), + &writerAddr) == PR_FAILURE) { + goto failed; + } + + if (PR_Connect(writer, &writerAddr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) { + if ((PR_GetError() != PR_IN_PROGRESS_ERROR) || + (PR_ConnectContinue(writer, PR_POLL_WRITE) == PR_FAILURE)) { + goto failed; + } + } + PR_SetFDInheritable(writer, false); + + reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200)); + if (!reader) { + goto failed; + } + PR_SetFDInheritable(reader, false); + if (aSetRecvBuff) { + PR_SetSocketOption(reader, &recvBufferOpt); + } + PR_SetSocketOption(reader, &nodelayOpt); + PR_SetSocketOption(reader, &noblockOpt); + PR_Close(listener); + + fd[0] = reader; + fd[1] = writer; + return true; + +failed: + if (listener) { + PR_Close(listener); + } + if (reader) { + PR_Close(reader); + } + if (writer) { + PR_Close(writer); + } + return false; +} + +#endif + +PollableEvent::PollableEvent() + +{ + MOZ_COUNT_CTOR(PollableEvent); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + // create pair of prfiledesc that can be used as a poll()ble + // signal. on windows use a localhost socket pair, and on + // unix use a pipe. +#ifdef USEPIPE + SOCKET_LOG(("PollableEvent() using pipe\n")); + if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) { + // make the pipe non blocking. NSPR asserts at + // trying to use SockOpt here + PROsfd fd = PR_FileDesc2NativeHandle(mReadFD); + int flags = fcntl(fd, F_GETFL, 0); + (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK); + fd = PR_FileDesc2NativeHandle(mWriteFD); + flags = fcntl(fd, F_GETFL, 0); + (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } else { + mReadFD = nullptr; + mWriteFD = nullptr; + SOCKET_LOG(("PollableEvent() pipe failed\n")); + } +#else + SOCKET_LOG(("PollableEvent() using socket pair\n")); + PRFileDesc* fd[2]; + LazyInitSocket(); + + // Try with a increased recv buffer first (bug 1248358). + if (NewTCPSocketPair(fd, true)) { + mReadFD = fd[0]; + mWriteFD = fd[1]; + // If the previous fails try without recv buffer increase (bug 1305436). + } else if (NewTCPSocketPair(fd, false)) { + mReadFD = fd[0]; + mWriteFD = fd[1]; + // If both fail, try the old version. + } else if (PR_NewTCPSocketPair(fd) == PR_SUCCESS) { + mReadFD = fd[0]; + mWriteFD = fd[1]; + + PRSocketOptionData socket_opt; + DebugOnly<PRStatus> status; + socket_opt.option = PR_SockOpt_NoDelay; + socket_opt.value.no_delay = true; + PR_SetSocketOption(mWriteFD, &socket_opt); + PR_SetSocketOption(mReadFD, &socket_opt); + socket_opt.option = PR_SockOpt_Nonblocking; + socket_opt.value.non_blocking = true; + status = PR_SetSocketOption(mWriteFD, &socket_opt); + MOZ_ASSERT(status == PR_SUCCESS); + status = PR_SetSocketOption(mReadFD, &socket_opt); + MOZ_ASSERT(status == PR_SUCCESS); + } + + if (mReadFD && mWriteFD) { + // compatibility with LSPs such as McAfee that assume a NSPR + // layer for read ala the nspr Pollable Event - Bug 698882. This layer is a + // nop. + PRFileDesc* topLayer = PR_CreateIOLayerStub(sPollableEventLayerIdentity, + sPollableEventLayerMethodsPtr); + if (topLayer) { + if (PR_PushIOLayer(fd[0], PR_TOP_IO_LAYER, topLayer) == PR_FAILURE) { + topLayer->dtor(topLayer); + } else { + SOCKET_LOG(("PollableEvent() nspr layer ok\n")); + mReadFD = topLayer; + } + } + + } else { + SOCKET_LOG(("PollableEvent() socketpair failed\n")); + } +#endif + + if (mReadFD && mWriteFD) { + // prime the system to deal with races invovled in [dc]tor cycle + SOCKET_LOG(("PollableEvent() ctor ok\n")); + mSignaled = true; + MarkFirstSignalTimestamp(); + PR_Write(mWriteFD, "I", 1); + } +} + +PollableEvent::~PollableEvent() { + MOZ_COUNT_DTOR(PollableEvent); + if (mWriteFD) { +#if defined(XP_WIN) + AttachShutdownLayer(mWriteFD); +#endif + PR_Close(mWriteFD); + } + if (mReadFD) { +#if defined(XP_WIN) + AttachShutdownLayer(mReadFD); +#endif + PR_Close(mReadFD); + } +} + +// we do not record signals on the socket thread +// because the socket thread can reliably look at its +// own runnable queue before selecting a poll time +// this is the "service the network without blocking" comment in +// nsSocketTransportService2.cpp +bool PollableEvent::Signal() { + SOCKET_LOG(("PollableEvent::Signal\n")); + + if (!mWriteFD) { + SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n")); + return false; + } +#ifndef XP_WIN + // On windows poll can hang and this became worse when we introduced the + // patch for bug 698882 (see also bug 1292181), therefore we reverted the + // behavior on windows to be as before bug 698882, e.g. write to the socket + // also if an event dispatch is on the socket thread and writing to the + // socket for each event. See bug 1292181. + if (OnSocketThread()) { + SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n")); + return true; + } +#endif + +#ifndef XP_WIN + // To wake up the poll writing once is enough, but for Windows that can cause + // hangs so we will write for every event. + // For non-Windows systems it is enough to write just once. + if (mSignaled) { + return true; + } +#endif + + if (!mSignaled) { + mSignaled = true; + MarkFirstSignalTimestamp(); + } + + int32_t status = PR_Write(mWriteFD, "M", 1); + SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status)); + if (status != 1) { + NS_WARNING("PollableEvent::Signal Failed\n"); + SOCKET_LOG(("PollableEvent::Signal Failed\n")); + mSignaled = false; + mWriteFailed = true; + } else { + mWriteFailed = false; + } + return (status == 1); +} + +bool PollableEvent::Clear() { + // necessary because of the "dont signal on socket thread" optimization + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + SOCKET_LOG(("PollableEvent::Clear\n")); + + if (!mFirstSignalAfterClear.IsNull()) { + SOCKET_LOG(("PollableEvent::Clear time to signal %ums", + (uint32_t)(TimeStamp::NowLoRes() - mFirstSignalAfterClear) + .ToMilliseconds())); + } + + mFirstSignalAfterClear = TimeStamp(); + mSignalTimestampAdjusted = false; + mSignaled = false; + + if (!mReadFD) { + SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n")); + return false; + } + + char buf[2048]; + int32_t status; +#ifdef XP_WIN + // On Windows we are writing to the socket for each event, to be sure that we + // do not have any deadlock read from the socket as much as we can. + while (true) { + status = PR_Read(mReadFD, buf, 2048); + SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status)); + if (status == 0) { + SOCKET_LOG(("PollableEvent::Clear EOF!\n")); + return false; + } + if (status < 0) { + PRErrorCode code = PR_GetError(); + if (code == PR_WOULD_BLOCK_ERROR) { + return true; + } else { + SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code)); + return false; + } + } + } +#else + status = PR_Read(mReadFD, buf, 2048); + SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status)); + + if (status == 1) { + return true; + } + if (status == 0) { + SOCKET_LOG(("PollableEvent::Clear EOF!\n")); + return false; + } + if (status > 1) { + MOZ_ASSERT(false); + SOCKET_LOG(("PollableEvent::Clear Unexpected events\n")); + Clear(); + return true; + } + PRErrorCode code = PR_GetError(); + if (code == PR_WOULD_BLOCK_ERROR) { + return true; + } + SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code)); + return false; +#endif // XP_WIN +} + +void PollableEvent::MarkFirstSignalTimestamp() { + if (mFirstSignalAfterClear.IsNull()) { + SOCKET_LOG(("PollableEvent::MarkFirstSignalTimestamp")); + mFirstSignalAfterClear = TimeStamp::NowLoRes(); + } +} + +void PollableEvent::AdjustFirstSignalTimestamp() { + if (!mSignalTimestampAdjusted && !mFirstSignalAfterClear.IsNull()) { + SOCKET_LOG(("PollableEvent::AdjustFirstSignalTimestamp")); + mFirstSignalAfterClear = TimeStamp::NowLoRes(); + mSignalTimestampAdjusted = true; + } +} + +bool PollableEvent::IsSignallingAlive(TimeDuration const& timeout) { + if (mWriteFailed) { + return false; + } + +#ifdef DEBUG + // The timeout would be just a disturbance in a debug build. + return true; +#else + if (!mSignaled || mFirstSignalAfterClear.IsNull() || + timeout == TimeDuration()) { + return true; + } + + TimeDuration delay = (TimeStamp::NowLoRes() - mFirstSignalAfterClear); + bool timedOut = delay > timeout; + + return !timedOut; +#endif // DEBUG +} + +} // namespace net +} // namespace mozilla |