Source code
Revision control
Copy as Markdown
Other Tools
// vim:set sw=2 sts=2 et cin:
/* 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
#include "nsSocketTransportService2.h"
#include "mozilla/Atomics.h"
#include "mozilla/ChaosMode.h"
#include "mozilla/glean/GleanMetrics.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Likely.h"
#include "mozilla/PodOperations.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/ProfilerThreadSleep.h"
#include "mozilla/PublicSSL.h"
#include "mozilla/ReverseIterator.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/Tokenizer.h"
#include "mozilla/Telemetry.h"
#include "nsASocketHandler.h"
#include "nsError.h"
#include "nsIFile.h"
#include "nsINetworkLinkService.h"
#include "nsIOService.h"
#include "nsIObserverService.h"
#include "nsIWidget.h"
#include "nsServiceManagerUtils.h"
#include "nsSocketTransport2.h"
#include "nsThreadUtils.h"
#include "prerror.h"
#include "prnetdb.h"
namespace mozilla {
namespace net {
#define SOCKET_THREAD_LONGTASK_MS 3
LazyLogModule gSocketTransportLog("nsSocketTransport");
LazyLogModule gUDPSocketLog("UDPSocket");
LazyLogModule gTCPSocketLog("TCPSocket");
nsSocketTransportService* gSocketTransportService = nullptr;
static Atomic<PRThread*, Relaxed> gSocketThread(nullptr);
#define SEND_BUFFER_PREF "network.tcp.sendbuffer"
#define KEEPALIVE_ENABLED_PREF "network.tcp.keepalive.enabled"
#define KEEPALIVE_IDLE_TIME_PREF "network.tcp.keepalive.idle_time"
#define KEEPALIVE_RETRY_INTERVAL_PREF "network.tcp.keepalive.retry_interval"
#define KEEPALIVE_PROBE_COUNT_PREF "network.tcp.keepalive.probe_count"
#define SOCKET_LIMIT_TARGET 1000U
#define MAX_TIME_BETWEEN_TWO_POLLS \
"network.sts.max_time_for_events_between_two_polls"
#define POLL_BUSY_WAIT_PERIOD "network.sts.poll_busy_wait_period"
#define POLL_BUSY_WAIT_PERIOD_TIMEOUT \
"network.sts.poll_busy_wait_period_timeout"
#define MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN \
"network.sts.max_time_for_pr_close_during_shutdown"
#define POLLABLE_EVENT_TIMEOUT "network.sts.pollable_event_timeout"
#define REPAIR_POLLABLE_EVENT_TIME 10
uint32_t nsSocketTransportService::gMaxCount;
PRCallOnceType nsSocketTransportService::gMaxCountInitOnce;
// Utility functions
bool OnSocketThread() { return PR_GetCurrentThread() == gSocketThread; }
//-----------------------------------------------------------------------------
bool nsSocketTransportService::SocketContext::IsTimedOut(
PRIntervalTime now) const {
return TimeoutIn(now) == 0;
}
void nsSocketTransportService::SocketContext::EnsureTimeout(
PRIntervalTime now) {
SOCKET_LOG(("SocketContext::EnsureTimeout socket=%p", mHandler.get()));
if (!mPollStartEpoch) {
SOCKET_LOG((" engaging"));
mPollStartEpoch = now;
}
}
void nsSocketTransportService::SocketContext::DisengageTimeout() {
SOCKET_LOG(("SocketContext::DisengageTimeout socket=%p", mHandler.get()));
mPollStartEpoch = 0;
}
PRIntervalTime nsSocketTransportService::SocketContext::TimeoutIn(
PRIntervalTime now) const {
SOCKET_LOG(("SocketContext::TimeoutIn socket=%p, timeout=%us", mHandler.get(),
mHandler->mPollTimeout));
if (mHandler->mPollTimeout == UINT16_MAX || !mPollStartEpoch) {
SOCKET_LOG((" not engaged"));
return NS_SOCKET_POLL_TIMEOUT;
}
PRIntervalTime elapsed = (now - mPollStartEpoch);
PRIntervalTime timeout = PR_SecondsToInterval(mHandler->mPollTimeout);
if (elapsed >= timeout) {
SOCKET_LOG((" timed out!"));
return 0;
}
SOCKET_LOG((" remains %us", PR_IntervalToSeconds(timeout - elapsed)));
return timeout - elapsed;
}
void nsSocketTransportService::SocketContext::MaybeResetEpoch() {
if (mPollStartEpoch && mHandler->mPollTimeout == UINT16_MAX) {
mPollStartEpoch = 0;
}
}
//-----------------------------------------------------------------------------
// ctor/dtor (called on the main/UI thread by the service manager)
nsSocketTransportService::nsSocketTransportService()
: mPollableEventTimeout(TimeDuration::FromSeconds(6)),
mMaxTimeForPrClosePref(PR_SecondsToInterval(5)),
mNetworkLinkChangeBusyWaitPeriod(PR_SecondsToInterval(50)),
mNetworkLinkChangeBusyWaitTimeout(PR_SecondsToInterval(7)) {
NS_ASSERTION(NS_IsMainThread(), "wrong thread");
PR_CallOnce(&gMaxCountInitOnce, DiscoverMaxCount);
NS_ASSERTION(!gSocketTransportService, "must not instantiate twice");
gSocketTransportService = this;
// The Poll list always has an entry at [0]. The rest of the
// list is a duplicate of the Active list's PRFileDesc file descriptors.
PRPollDesc entry = {nullptr, PR_POLL_READ | PR_POLL_EXCEPT, 0};
mPollList.InsertElementAt(0, entry);
}
void nsSocketTransportService::ApplyPortRemap(uint16_t* aPort) {
MOZ_ASSERT(IsOnCurrentThreadInfallible());
if (!mPortRemapping) {
return;
}
// Reverse the array to make later rules override earlier rules.
for (auto const& portMapping : Reversed(*mPortRemapping)) {
if (*aPort < std::get<0>(portMapping)) {
continue;
}
if (*aPort > std::get<1>(portMapping)) {
continue;
}
*aPort = std::get<2>(portMapping);
return;
}
}
bool nsSocketTransportService::UpdatePortRemapPreference(
nsACString const& aPortMappingPref) {
TPortRemapping portRemapping;
auto consumePreference = [&]() -> bool {
Tokenizer tokenizer(aPortMappingPref);
tokenizer.SkipWhites();
if (tokenizer.CheckEOF()) {
return true;
}
nsTArray<std::tuple<uint16_t, uint16_t>> ranges(2);
while (true) {
uint16_t loPort;
tokenizer.SkipWhites();
if (!tokenizer.ReadInteger(&loPort)) {
break;
}
uint16_t hiPort;
tokenizer.SkipWhites();
if (tokenizer.CheckChar('-')) {
tokenizer.SkipWhites();
if (!tokenizer.ReadInteger(&hiPort)) {
break;
}
} else {
hiPort = loPort;
}
ranges.AppendElement(std::make_tuple(loPort, hiPort));
tokenizer.SkipWhites();
if (tokenizer.CheckChar(',')) {
continue; // another port or port range is expected
}
if (tokenizer.CheckChar('=')) {
uint16_t targetPort;
tokenizer.SkipWhites();
if (!tokenizer.ReadInteger(&targetPort)) {
break;
}
// Storing reversed, because the most common cases (like 443) will very
// likely be listed as first, less common cases will be added to the end
// of the list mapping to the same port. As we iterate the whole
// remapping array from the end, this may have a small perf win by
// hitting the most common cases earlier.
for (auto const& range : Reversed(ranges)) {
portRemapping.AppendElement(std::make_tuple(
std::get<0>(range), std::get<1>(range), targetPort));
}
ranges.Clear();
tokenizer.SkipWhites();
if (tokenizer.CheckChar(';')) {
continue; // more mappings (or EOF) expected
}
if (tokenizer.CheckEOF()) {
return true;
}
}
// Anything else is unexpected.
break;
}
// 'break' from the parsing loop means ill-formed preference
portRemapping.Clear();
return false;
};
bool rv = consumePreference();
if (!IsOnCurrentThread()) {
nsCOMPtr<nsIThread> thread = GetThreadSafely();
if (!thread) {
// Init hasn't been called yet. Could probably just assert.
// If shutdown, the dispatch below will just silently fail.
NS_ASSERTION(false, "ApplyPortRemapPreference before STS::Init");
return false;
}
thread->Dispatch(NewRunnableMethod<TPortRemapping>(
"net::ApplyPortRemapping", this,
&nsSocketTransportService::ApplyPortRemapPreference, portRemapping));
} else {
ApplyPortRemapPreference(portRemapping);
}
return rv;
}
nsSocketTransportService::~nsSocketTransportService() {
NS_ASSERTION(NS_IsMainThread(), "wrong thread");
NS_ASSERTION(!mInitialized, "not shutdown properly");
gSocketTransportService = nullptr;
}
//-----------------------------------------------------------------------------
// event queue (any thread)
already_AddRefed<nsIThread> nsSocketTransportService::GetThreadSafely() {
MutexAutoLock lock(mLock);
nsCOMPtr<nsIThread> result = mThread;
return result.forget();
}
NS_IMETHODIMP
nsSocketTransportService::DispatchFromScript(nsIRunnable* event,
uint32_t flags) {
nsCOMPtr<nsIRunnable> event_ref(event);
return Dispatch(event_ref.forget(), flags);
}
NS_IMETHODIMP
nsSocketTransportService::Dispatch(already_AddRefed<nsIRunnable> event,
uint32_t flags) {
nsCOMPtr<nsIRunnable> event_ref(event);
SOCKET_LOG(("STS dispatch [%p]\n", event_ref.get()));
nsCOMPtr<nsIThread> thread = GetThreadSafely();
nsresult rv;
rv = thread ? thread->Dispatch(event_ref.forget(), flags)
: NS_ERROR_NOT_INITIALIZED;
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::DelayedDispatch(already_AddRefed<nsIRunnable>,
uint32_t) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsSocketTransportService::RegisterShutdownTask(nsITargetShutdownTask* task) {
nsCOMPtr<nsIThread> thread = GetThreadSafely();
return thread ? thread->RegisterShutdownTask(task) : NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP
nsSocketTransportService::UnregisterShutdownTask(nsITargetShutdownTask* task) {
nsCOMPtr<nsIThread> thread = GetThreadSafely();
return thread ? thread->UnregisterShutdownTask(task) : NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP
nsSocketTransportService::IsOnCurrentThread(bool* result) {
*result = OnSocketThread();
return NS_OK;
}
NS_IMETHODIMP_(bool)
nsSocketTransportService::IsOnCurrentThreadInfallible() {
return OnSocketThread();
}
//-----------------------------------------------------------------------------
// nsIDirectTaskDispatcher
already_AddRefed<nsIDirectTaskDispatcher>
nsSocketTransportService::GetDirectTaskDispatcherSafely() {
MutexAutoLock lock(mLock);
nsCOMPtr<nsIDirectTaskDispatcher> result = mDirectTaskDispatcher;
return result.forget();
}
NS_IMETHODIMP
nsSocketTransportService::DispatchDirectTask(
already_AddRefed<nsIRunnable> aEvent) {
nsCOMPtr<nsIDirectTaskDispatcher> dispatcher =
GetDirectTaskDispatcherSafely();
NS_ENSURE_TRUE(dispatcher, NS_ERROR_NOT_INITIALIZED);
return dispatcher->DispatchDirectTask(std::move(aEvent));
}
NS_IMETHODIMP nsSocketTransportService::DrainDirectTasks() {
nsCOMPtr<nsIDirectTaskDispatcher> dispatcher =
GetDirectTaskDispatcherSafely();
if (!dispatcher) {
// nothing to drain.
return NS_OK;
}
return dispatcher->DrainDirectTasks();
}
NS_IMETHODIMP nsSocketTransportService::HaveDirectTasks(bool* aValue) {
nsCOMPtr<nsIDirectTaskDispatcher> dispatcher =
GetDirectTaskDispatcherSafely();
if (!dispatcher) {
*aValue = false;
return NS_OK;
}
return dispatcher->HaveDirectTasks(aValue);
}
//-----------------------------------------------------------------------------
// socket api (socket thread only)
NS_IMETHODIMP
nsSocketTransportService::NotifyWhenCanAttachSocket(nsIRunnable* event) {
SOCKET_LOG(("nsSocketTransportService::NotifyWhenCanAttachSocket\n"));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (CanAttachSocket()) {
return Dispatch(event, NS_DISPATCH_NORMAL);
}
auto* runnable = new LinkedRunnableEvent(event);
mPendingSocketQueue.insertBack(runnable);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransportService::AttachSocket(PRFileDesc* fd,
nsASocketHandler* handler) {
SOCKET_LOG(
("nsSocketTransportService::AttachSocket [handler=%p]\n", handler));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (!CanAttachSocket()) {
return NS_ERROR_NOT_AVAILABLE;
}
SocketContext sock{fd, handler, 0};
AddToIdleList(&sock);
return NS_OK;
}
// the number of sockets that can be attached at any given time is
// limited. this is done because some operating systems (e.g., Win9x)
// limit the number of sockets that can be created by an application.
// AttachSocket will fail if the limit is exceeded. consumers should
// call CanAttachSocket and check the result before creating a socket.
bool nsSocketTransportService::CanAttachSocket() {
MOZ_ASSERT(!mShuttingDown);
uint32_t total = mActiveList.Length() + mIdleList.Length();
bool rv = total < gMaxCount;
if (!rv) {
static bool reported_socket_limit_reached = false;
if (!reported_socket_limit_reached) {
mozilla::glean::networking::os_socket_limit_reached.Add(1);
reported_socket_limit_reached = true;
}
SOCKET_LOG(
("nsSocketTransportService::CanAttachSocket failed - total: %d, "
"maxCount: %d\n",
total, gMaxCount));
}
MOZ_ASSERT(mInitialized);
return rv;
}
nsresult nsSocketTransportService::DetachSocket(SocketContextList& listHead,
SocketContext* sock) {
SOCKET_LOG(("nsSocketTransportService::DetachSocket [handler=%p]\n",
sock->mHandler.get()));
MOZ_ASSERT((&listHead == &mActiveList) || (&listHead == &mIdleList),
"DetachSocket invalid head");
{
// inform the handler that this socket is going away
sock->mHandler->OnSocketDetached(sock->mFD);
}
mSentBytesCount += sock->mHandler->ByteCountSent();
mReceivedBytesCount += sock->mHandler->ByteCountReceived();
// cleanup
sock->mFD = nullptr;
if (&listHead == &mActiveList) {
RemoveFromPollList(sock);
} else {
RemoveFromIdleList(sock);
}
// NOTE: sock is now an invalid pointer
//
// notify the first element on the pending socket queue...
//
nsCOMPtr<nsIRunnable> event;
LinkedRunnableEvent* runnable = mPendingSocketQueue.getFirst();
if (runnable) {
event = runnable->TakeEvent();
runnable->remove();
delete runnable;
}
if (event) {
// move event from pending queue to dispatch queue
return Dispatch(event, NS_DISPATCH_NORMAL);
}
return NS_OK;
}
// Returns the index of a SocketContext within a list, or -1 if it's
// not a pointer to a list element
// NOTE: this could be supplied by nsTArray<>
int64_t nsSocketTransportService::SockIndex(SocketContextList& aList,
SocketContext* aSock) {
ptrdiff_t index = -1;
if (!aList.IsEmpty()) {
index = aSock - &aList[0];
if (index < 0 || (size_t)index + 1 > aList.Length()) {
index = -1;
}
}
return (int64_t)index;
}
void nsSocketTransportService::AddToPollList(SocketContext* sock) {
MOZ_ASSERT(SockIndex(mActiveList, sock) == -1,
"AddToPollList Socket Already Active");
SOCKET_LOG(("nsSocketTransportService::AddToPollList %p [handler=%p]\n", sock,
sock->mHandler.get()));
sock->EnsureTimeout(PR_IntervalNow());
PRPollDesc poll;
poll.fd = sock->mFD;
poll.in_flags = sock->mHandler->mPollFlags;
poll.out_flags = 0;
if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) {
auto newSocketIndex = mActiveList.Length();
newSocketIndex = ChaosMode::randomUint32LessThan(newSocketIndex + 1);
mActiveList.InsertElementAt(newSocketIndex, *sock);
// mPollList is offset by 1
mPollList.InsertElementAt(newSocketIndex + 1, poll);
} else {
// Avoid refcount bump/decrease
mActiveList.EmplaceBack(sock->mFD, sock->mHandler.forget(),
sock->mPollStartEpoch);
mPollList.AppendElement(poll);
}
SOCKET_LOG(
(" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length()));
}
void nsSocketTransportService::RemoveFromPollList(SocketContext* sock) {
SOCKET_LOG(("nsSocketTransportService::RemoveFromPollList %p [handler=%p]\n",
sock, sock->mHandler.get()));
auto index = SockIndex(mActiveList, sock);
MOZ_RELEASE_ASSERT(index != -1, "invalid index");
SOCKET_LOG((" index=%" PRId64 " mActiveList.Length()=%zu\n", index,
mActiveList.Length()));
mActiveList.UnorderedRemoveElementAt(index);
// mPollList is offset by 1
mPollList.UnorderedRemoveElementAt(index + 1);
SOCKET_LOG(
(" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length()));
}
void nsSocketTransportService::AddToIdleList(SocketContext* sock) {
MOZ_ASSERT(SockIndex(mIdleList, sock) == -1,
"AddToIdleList Socket Already Idle");
SOCKET_LOG(("nsSocketTransportService::AddToIdleList %p [handler=%p]\n", sock,
sock->mHandler.get()));
// Avoid refcount bump/decrease
mIdleList.EmplaceBack(sock->mFD, sock->mHandler.forget(),
sock->mPollStartEpoch);
SOCKET_LOG(
(" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length()));
}
void nsSocketTransportService::RemoveFromIdleList(SocketContext* sock) {
SOCKET_LOG(("nsSocketTransportService::RemoveFromIdleList [handler=%p]\n",
sock->mHandler.get()));
auto index = SockIndex(mIdleList, sock);
MOZ_RELEASE_ASSERT(index != -1);
mIdleList.UnorderedRemoveElementAt(index);
SOCKET_LOG(
(" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length()));
}
void nsSocketTransportService::MoveToIdleList(SocketContext* sock) {
SOCKET_LOG(("nsSocketTransportService::MoveToIdleList %p [handler=%p]\n",
sock, sock->mHandler.get()));
MOZ_ASSERT(SockIndex(mIdleList, sock) == -1);
MOZ_ASSERT(SockIndex(mActiveList, sock) != -1);
AddToIdleList(sock);
RemoveFromPollList(sock);
}
void nsSocketTransportService::MoveToPollList(SocketContext* sock) {
SOCKET_LOG(("nsSocketTransportService::MoveToPollList %p [handler=%p]\n",
sock, sock->mHandler.get()));
MOZ_ASSERT(SockIndex(mIdleList, sock) != -1);
MOZ_ASSERT(SockIndex(mActiveList, sock) == -1);
AddToPollList(sock);
RemoveFromIdleList(sock);
}
void nsSocketTransportService::ApplyPortRemapPreference(
TPortRemapping const& portRemapping) {
MOZ_ASSERT(IsOnCurrentThreadInfallible());
mPortRemapping.reset();
if (!portRemapping.IsEmpty()) {
mPortRemapping.emplace(portRemapping);
}
}
PRIntervalTime nsSocketTransportService::PollTimeout(PRIntervalTime now) {
if (mActiveList.IsEmpty()) {
return NS_SOCKET_POLL_TIMEOUT;
}
// compute minimum time before any socket timeout expires.
PRIntervalTime minR = NS_SOCKET_POLL_TIMEOUT;
for (uint32_t i = 0; i < mActiveList.Length(); ++i) {
const SocketContext& s = mActiveList[i];
PRIntervalTime r = s.TimeoutIn(now);
if (r < minR) {
minR = r;
}
}
if (minR == NS_SOCKET_POLL_TIMEOUT) {
SOCKET_LOG(("poll timeout: none\n"));
return NS_SOCKET_POLL_TIMEOUT;
}
SOCKET_LOG(("poll timeout: %" PRIu32 "\n", PR_IntervalToSeconds(minR)));
return minR;
}
int32_t nsSocketTransportService::Poll(TimeDuration* pollDuration,
PRIntervalTime ts) {
MOZ_ASSERT(IsOnCurrentThread());
PRPollDesc* firstPollEntry;
uint32_t pollCount;
PRIntervalTime pollTimeout;
*pollDuration = nullptr;
// If there are pending events for this thread then
// DoPollIteration() should service the network without blocking.
bool pendingEvents = false;
mRawThread->HasPendingEvents(&pendingEvents);
if (mPollList[0].fd) {
mPollList[0].out_flags = 0;
firstPollEntry = &mPollList[0];
pollCount = mPollList.Length();
pollTimeout = pendingEvents ? PR_INTERVAL_NO_WAIT : PollTimeout(ts);
} else {
// no pollable event, so busy wait...
pollCount = mActiveList.Length();
if (pollCount) {
firstPollEntry = &mPollList[1];
} else {
firstPollEntry = nullptr;
}
pollTimeout =
pendingEvents ? PR_INTERVAL_NO_WAIT : PR_MillisecondsToInterval(25);
}
if ((ts - mLastNetworkLinkChangeTime) < mNetworkLinkChangeBusyWaitPeriod) {
// Being here means we are few seconds after a network change has
// been detected.
PRIntervalTime to = mNetworkLinkChangeBusyWaitTimeout;
if (to) {
pollTimeout = std::min(to, pollTimeout);
SOCKET_LOG((" timeout shorthened after network change event"));
}
}
TimeStamp pollStart;
if (Telemetry::CanRecordPrereleaseData()) {
pollStart = TimeStamp::NowLoRes();
}
SOCKET_LOG((" timeout = %i milliseconds\n",
PR_IntervalToMilliseconds(pollTimeout)));
int32_t n;
{
#ifdef MOZ_GECKO_PROFILER
TimeStamp startTime = TimeStamp::Now();
if (pollTimeout != PR_INTERVAL_NO_WAIT) {
// There will be an actual non-zero wait, let the profiler know about it
// by marking thread as sleeping around the polling call.
profiler_thread_sleep();
}
#endif
n = PR_Poll(firstPollEntry, pollCount, pollTimeout);
#ifdef MOZ_GECKO_PROFILER
if (pollTimeout != PR_INTERVAL_NO_WAIT) {
profiler_thread_wake();
}
if (profiler_thread_is_being_profiled_for_markers()) {
PROFILER_MARKER_TEXT(
"SocketTransportService::Poll", NETWORK,
MarkerTiming::IntervalUntilNowFrom(startTime),
pollTimeout == PR_INTERVAL_NO_TIMEOUT
? nsPrintfCString("Poll count: %u, Poll timeout: NO_TIMEOUT",
pollCount)
: pollTimeout == PR_INTERVAL_NO_WAIT
? nsPrintfCString("Poll count: %u, Poll timeout: NO_WAIT",
pollCount)
: nsPrintfCString("Poll count: %u, Poll timeout: %ums", pollCount,
PR_IntervalToMilliseconds(pollTimeout)));
}
#endif
}
if (Telemetry::CanRecordPrereleaseData() && !pollStart.IsNull()) {
*pollDuration = TimeStamp::NowLoRes() - pollStart;
}
SOCKET_LOG((" ...returned after %i milliseconds\n",
PR_IntervalToMilliseconds(PR_IntervalNow() - ts)));
return n;
}
//-----------------------------------------------------------------------------
// xpcom api
NS_IMPL_ISUPPORTS(nsSocketTransportService, nsISocketTransportService,
nsIRoutedSocketTransportService, nsIEventTarget,
nsISerialEventTarget, nsIThreadObserver, nsIRunnable,
nsPISocketTransportService, nsIObserver, nsINamed,
nsIDirectTaskDispatcher)
static const char* gCallbackPrefs[] = {
SEND_BUFFER_PREF,
KEEPALIVE_ENABLED_PREF,
KEEPALIVE_IDLE_TIME_PREF,
KEEPALIVE_RETRY_INTERVAL_PREF,
KEEPALIVE_PROBE_COUNT_PREF,
MAX_TIME_BETWEEN_TWO_POLLS,
MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN,
POLLABLE_EVENT_TIMEOUT,
"network.socket.forcePort",
nullptr,
};
/* static */
void nsSocketTransportService::UpdatePrefs(const char* aPref, void* aSelf) {
static_cast<nsSocketTransportService*>(aSelf)->UpdatePrefs();
}
static uint32_t GetThreadStackSize() {
#ifdef XP_WIN
if (!StaticPrefs::network_allow_large_stack_size_for_socket_thread()) {
return nsIThreadManager::DEFAULT_STACK_SIZE;
}
const uint32_t kWindowsThreadStackSize = 512 * 1024;
// We can remove this custom stack size when DEFAULT_STACK_SIZE is increased.
static_assert(kWindowsThreadStackSize > nsIThreadManager::DEFAULT_STACK_SIZE);
return kWindowsThreadStackSize;
#else
return nsIThreadManager::DEFAULT_STACK_SIZE;
#endif
}
// called from main thread only
NS_IMETHODIMP
nsSocketTransportService::Init() {
if (!NS_IsMainThread()) {
NS_ERROR("wrong thread");
return NS_ERROR_UNEXPECTED;
}
if (mInitialized) {
return NS_OK;
}
if (mShuttingDown) {
return NS_ERROR_UNEXPECTED;
}
nsCOMPtr<nsIThread> thread;
if (!XRE_IsContentProcess() ||
StaticPrefs::network_allow_raw_sockets_in_content_processes_AtStartup()) {
// Since we Poll, we can't use normal LongTask support in Main Process
nsresult rv = NS_NewNamedThread(
"Socket Thread", getter_AddRefs(thread), this,
{GetThreadStackSize(), false, false, Some(SOCKET_THREAD_LONGTASK_MS)});
NS_ENSURE_SUCCESS(rv, rv);
} else {
// In the child process, we just want a regular nsThread with no socket
// polling. So we don't want to run the nsSocketTransportService runnable on
// it.
nsresult rv =
NS_NewNamedThread("Socket Thread", getter_AddRefs(thread), nullptr,
{nsIThreadManager::DEFAULT_STACK_SIZE, false, false,
Some(SOCKET_THREAD_LONGTASK_MS)});
NS_ENSURE_SUCCESS(rv, rv);
// Set up some of the state that nsSocketTransportService::Run would set.
PRThread* prthread = nullptr;
thread->GetPRThread(&prthread);
gSocketThread = prthread;
mRawThread = thread;
}
{
MutexAutoLock lock(mLock);
// Install our mThread, protecting against concurrent readers
thread.swap(mThread);
mDirectTaskDispatcher = do_QueryInterface(mThread);
MOZ_DIAGNOSTIC_ASSERT(
mDirectTaskDispatcher,
"Underlying thread must support direct task dispatching");
}
Preferences::RegisterCallbacks(UpdatePrefs, gCallbackPrefs, this);
UpdatePrefs();
nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
// Note that the observr notifications are forwarded from parent process to
// socket process. We have to make sure the topics registered below are also
// registered in nsIObserver::Init().
if (obsSvc) {
obsSvc->AddObserver(this, "last-pb-context-exited", false);
obsSvc->AddObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC, true);
obsSvc->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
obsSvc->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
}
// We can now dispatch tasks to the socket thread.
mInitialized = true;
return NS_OK;
}
// called from main thread only
NS_IMETHODIMP
nsSocketTransportService::Shutdown(bool aXpcomShutdown) {
SOCKET_LOG(("nsSocketTransportService::Shutdown\n"));
NS_ENSURE_STATE(NS_IsMainThread());
if (!mInitialized || mShuttingDown) {
// We never inited, or shutdown has already started
return NS_OK;
}
{
auto observersCopy = mShutdownObservers;
for (auto& observer : observersCopy) {
observer->Observe();
}
}
mShuttingDown = true;
{
MutexAutoLock lock(mLock);
if (mPollableEvent) {
mPollableEvent->Signal();
}
}
// If we're shutting down due to going offline (rather than due to XPCOM
// shutdown), also tear down the thread. The thread will be shutdown during
// xpcom-shutdown-threads if during xpcom-shutdown proper.
if (!aXpcomShutdown) {
ShutdownThread();
}
return NS_OK;
}
nsresult nsSocketTransportService::ShutdownThread() {
SOCKET_LOG(("nsSocketTransportService::ShutdownThread\n"));
NS_ENSURE_STATE(NS_IsMainThread());
if (!mInitialized) {
return NS_OK;
}
// join with thread
nsCOMPtr<nsIThread> thread = GetThreadSafely();
thread->Shutdown();
{
MutexAutoLock lock(mLock);
// Drop our reference to mThread and make sure that any concurrent readers
// are excluded
mThread = nullptr;
mDirectTaskDispatcher = nullptr;
}
Preferences::UnregisterCallbacks(UpdatePrefs, gCallbackPrefs, this);
nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
if (obsSvc) {
obsSvc->RemoveObserver(this, "last-pb-context-exited");
obsSvc->RemoveObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC);
obsSvc->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
obsSvc->RemoveObserver(this, "xpcom-shutdown-threads");
obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
}
if (mAfterWakeUpTimer) {
mAfterWakeUpTimer->Cancel();
mAfterWakeUpTimer = nullptr;
}
mInitialized = false;
mShuttingDown = false;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransportService::GetOffline(bool* offline) {
MutexAutoLock lock(mLock);
*offline = mOffline;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransportService::SetOffline(bool offline) {
MutexAutoLock lock(mLock);
if (!mOffline && offline) {
// signal the socket thread to go offline, so it will detach sockets
mGoingOffline = true;
mOffline = true;
} else if (mOffline && !offline) {
mOffline = false;
}
if (mPollableEvent) {
mPollableEvent->Signal();
}
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransportService::GetKeepaliveIdleTime(int32_t* aKeepaliveIdleTimeS) {
MOZ_ASSERT(aKeepaliveIdleTimeS);
if (NS_WARN_IF(!aKeepaliveIdleTimeS)) {
return NS_ERROR_NULL_POINTER;
}
*aKeepaliveIdleTimeS = mKeepaliveIdleTimeS;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransportService::GetKeepaliveRetryInterval(
int32_t* aKeepaliveRetryIntervalS) {
MOZ_ASSERT(aKeepaliveRetryIntervalS);
if (NS_WARN_IF(!aKeepaliveRetryIntervalS)) {
return NS_ERROR_NULL_POINTER;
}
*aKeepaliveRetryIntervalS = mKeepaliveRetryIntervalS;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransportService::GetKeepaliveProbeCount(
int32_t* aKeepaliveProbeCount) {
MOZ_ASSERT(aKeepaliveProbeCount);
if (NS_WARN_IF(!aKeepaliveProbeCount)) {
return NS_ERROR_NULL_POINTER;
}
*aKeepaliveProbeCount = mKeepaliveProbeCount;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransportService::CreateTransport(const nsTArray<nsCString>& types,
const nsACString& host, int32_t port,
nsIProxyInfo* proxyInfo,
nsIDNSRecord* dnsRecord,
nsISocketTransport** result) {
return CreateRoutedTransport(types, host, port, ""_ns, 0, proxyInfo,
dnsRecord, result);
}
NS_IMETHODIMP
nsSocketTransportService::CreateRoutedTransport(
const nsTArray<nsCString>& types, const nsACString& host, int32_t port,
const nsACString& hostRoute, int32_t portRoute, nsIProxyInfo* proxyInfo,
nsIDNSRecord* dnsRecord, nsISocketTransport** result) {
NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(port >= 0 && port <= 0xFFFF, NS_ERROR_ILLEGAL_VALUE);
RefPtr<nsSocketTransport> trans = new nsSocketTransport();
nsresult rv = trans->Init(types, host, port, hostRoute, portRoute, proxyInfo,
dnsRecord);
if (NS_FAILED(rv)) {
return rv;
}
trans.forget(result);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransportService::CreateUnixDomainTransport(
nsIFile* aPath, nsISocketTransport** result) {
#ifdef XP_UNIX
nsresult rv;
NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
nsAutoCString path;
rv = aPath->GetNativePath(path);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<nsSocketTransport> trans = new nsSocketTransport();
rv = trans->InitWithFilename(path.get());
NS_ENSURE_SUCCESS(rv, rv);