netwerk/base/PollableEvent.cpp
author Edwin Gao <egao@mozilla.com>
Wed, 13 Mar 2019 19:47:25 +0000
changeset 463893 15c16889e3b7f6e871a518b5f3254459ed5e0213
parent 462668 007f59cfee3b8ab36730b7ee50d658fa714b663c
child 472056 e1993a1f09ac53cd1a04fdf6a87f8cad8e44f73e
permissions -rw-r--r--
Bug 1531590, 1531598, 1534811, 1336075, 1531571, 1531572, 1531572, 1531574, 1534855, 1534857, 1535082 - skip tests in mochitest-dev-tools and mochitest-browser-chrome suites for windows10-aarch64 r=jmaher Bug 1531598 - disable browser_markup_copy_image_data.js Bug 1531598 - disable browser_markup_links_04.js Bug 1531598 - disable browser_inspector_menu-01-sensitivity.js Bug 1534811 - disable accessible/tests/browser/general Bug 1336075 - disable browser_largeAllocation_non_win32.js Bug 1531571 - disable browser_jsterm_context_menu_labels.js Bug 1531572 - disable browser_jsterm_helper_dollar_x.js Bug 1531573 - disable browser_jsterm_no_input_and_tab_key_pressed.js Bug 1531574 - disable browser_jsterm_syntax_highlight_output.js Bug 1534855 - disable accessible/tests/browser/ Bug 1534857 - disable browser_ext_slow_script.js Bug 1535082 - disable browser_jsterm_helper_dollar_dollar.js Differential Revision: https://phabricator.services.mozilla.com/D23369

/* -*- 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 <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()
    : mWriteFD(nullptr),
      mReadFD(nullptr),
      mSignaled(false),
      mWriteFailed(false),
      mSignalTimestampAdjusted(false) {
  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