/* -*- 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 "prerror.h" #include "prio.h" #include "private/pprio.h" #include "prnetdb.h" #ifdef XP_WIN # include "ShutdownLayer.h" #else # include # 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 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