/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 : */ /* 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 "ClosingService.h" #include "nsIOService.h" #ifdef MOZ_NUWA_PROCESS #include "ipc/Nuwa.h" #endif class ClosingLayerSecret { public: explicit ClosingLayerSecret(mozilla::net::ClosingService *aClosingService) : mClosingService(aClosingService) { } ~ClosingLayerSecret() { mClosingService = nullptr; } RefPtr mClosingService; }; namespace mozilla { namespace net { static PRIOMethods sTcpUdpPRCloseLayerMethods; static PRIOMethods *sTcpUdpPRCloseLayerMethodsPtr = nullptr; static PRDescIdentity sTcpUdpPRCloseLayerId; static PRStatus TcpUdpPRCloseLayerClose(PRFileDesc *aFd) { if (!aFd) { return PR_FAILURE; } PRFileDesc* layer = PR_PopIOLayer(aFd, PR_TOP_IO_LAYER); MOZ_RELEASE_ASSERT(layer && layer->identity == sTcpUdpPRCloseLayerId, "Closing Layer not on top of stack"); ClosingLayerSecret *closingLayerSecret = reinterpret_cast(layer->secret); PRStatus status = PR_SUCCESS; if (aFd) { // If this is called during shutdown do not call ..method->close(fd) and // let it leak. if (gIOService->IsShutdown()) { // If the ClosingService layer is the first layer above PR_NSPR_IO_LAYER // we are not going to leak anything, but the PR_Close will not be called. PR_Free(aFd); } else if (closingLayerSecret->mClosingService) { closingLayerSecret->mClosingService->PostRequest(aFd); } else { // Socket is created before closing service has been started or there was // a problem with starting it. PR_Close(aFd); } } layer->secret = nullptr; layer->dtor(layer); delete closingLayerSecret; return status; } ClosingService* ClosingService::sInstance = nullptr; ClosingService::ClosingService() : mShutdown(false) , mMonitor("ClosingService.mMonitor") { MOZ_ASSERT(!sInstance, "multiple ClosingService instances!"); } // static void ClosingService::Start() { if (!sTcpUdpPRCloseLayerMethodsPtr) { sTcpUdpPRCloseLayerId = PR_GetUniqueIdentity("TCP and UDP PRClose layer"); PR_ASSERT(PR_INVALID_IO_LAYER != sTcpUdpPRCloseLayerId); sTcpUdpPRCloseLayerMethods = *PR_GetDefaultIOMethods(); sTcpUdpPRCloseLayerMethods.close = TcpUdpPRCloseLayerClose; sTcpUdpPRCloseLayerMethodsPtr = &sTcpUdpPRCloseLayerMethods; } if (!sInstance) { ClosingService* service = new ClosingService(); if (NS_SUCCEEDED(service->StartInternal())) { NS_ADDREF(service); sInstance = service; } else { delete service; } } } nsresult ClosingService::StartInternal() { mThread = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 32 * 1024); if (!mThread) { return NS_ERROR_FAILURE; } return NS_OK; } // static nsresult ClosingService::AttachIOLayer(PRFileDesc *aFd) { // We are going to remove ClosingService soon. // This change is going to turn it off, so ClosingService is not used. // Bug 1238010. return NS_OK; if (!sTcpUdpPRCloseLayerMethodsPtr) { return NS_OK; } PRFileDesc * layer; PRStatus status; layer = PR_CreateIOLayerStub(sTcpUdpPRCloseLayerId, sTcpUdpPRCloseLayerMethodsPtr); if (!layer) { return NS_OK; } ClosingLayerSecret *secret = new ClosingLayerSecret(sInstance); layer->secret = reinterpret_cast(secret); status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer); if (status == PR_FAILURE) { delete secret; PR_DELETE(layer); } return NS_OK; } void ClosingService::PostRequest(PRFileDesc *aFd) { mozilla::MonitorAutoLock mon(mMonitor); // Check if shutdown is called. if (mShutdown) { // Let the socket leak. We are in shutdown and some PRClose can take a long // time. To prevent shutdown crash (bug 1152046) do not accept sockets any // more. // If the ClosingService layer is the first layer above PR_NSPR_IO_LAYER // we are not going to leak anything, but PR_Close will not be called. PR_Free(aFd); return; } mQueue.AppendElement(aFd); if (mQueue.Length() == 1) { mon.Notify(); } } // static void ClosingService::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); if (sInstance) { sInstance->ShutdownInternal(); NS_RELEASE(sInstance); } } void ClosingService::ShutdownInternal() { { mozilla::MonitorAutoLock mon(mMonitor); if (mShutdown) { // This should not happen. return; } mShutdown = true; // If it is waiting on the empty queue, wake it up. if (mQueue.Length() == 0) { mon.Notify(); } } if (mThread) { PR_JoinThread(mThread); mThread = nullptr; } } void ClosingService::ThreadFunc() { PR_SetCurrentThreadName("Closing Service"); #ifdef MOZ_NUWA_PROCESS if (IsNuwaProcess()) { NuwaMarkCurrentThread(nullptr, nullptr); } #endif for (;;) { PRFileDesc *fd; { mozilla::MonitorAutoLock mon(mMonitor); while (!mShutdown && (mQueue.Length() == 0)) { mon.Wait(); } if (mShutdown) { // If we are in shutdown leak the rest of the sockets. for (uint32_t i = 0; i < mQueue.Length(); i++) { fd = mQueue[i]; // If the ClosingService layer is the first layer above // PR_NSPR_IO_LAYER we are not going to leak anything, but PR_Close // will not be called. PR_Free(fd); } mQueue.Clear(); return; } fd = mQueue[0]; mQueue.RemoveElementAt(0); } // Leave lock before closing socket. It can block for a long time and in // case we accidentally attach this layer twice this would cause deadlock. bool tcp = (PR_GetDescType(PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER)) == PR_DESC_SOCKET_TCP); PRIntervalTime closeStarted = PR_IntervalNow(); fd->methods->close(fd); // Post telemetry. if (tcp) { SendPRCloseTelemetry(closeStarted, Telemetry::PRCLOSE_TCP_BLOCKING_TIME_NORMAL, Telemetry::PRCLOSE_TCP_BLOCKING_TIME_SHUTDOWN, Telemetry::PRCLOSE_TCP_BLOCKING_TIME_CONNECTIVITY_CHANGE, Telemetry::PRCLOSE_TCP_BLOCKING_TIME_LINK_CHANGE, Telemetry::PRCLOSE_TCP_BLOCKING_TIME_OFFLINE); } else { SendPRCloseTelemetry(closeStarted, Telemetry::PRCLOSE_UDP_BLOCKING_TIME_NORMAL, Telemetry::PRCLOSE_UDP_BLOCKING_TIME_SHUTDOWN, Telemetry::PRCLOSE_UDP_BLOCKING_TIME_CONNECTIVITY_CHANGE, Telemetry::PRCLOSE_UDP_BLOCKING_TIME_LINK_CHANGE, Telemetry::PRCLOSE_UDP_BLOCKING_TIME_OFFLINE); } } } void ClosingService::SendPRCloseTelemetry(PRIntervalTime aStart, mozilla::Telemetry::ID aIDNormal, mozilla::Telemetry::ID aIDShutdown, mozilla::Telemetry::ID aIDConnectivityChange, mozilla::Telemetry::ID aIDLinkChange, mozilla::Telemetry::ID aIDOffline) { PRIntervalTime now = PR_IntervalNow(); if (gIOService->IsShutdown()) { Telemetry::Accumulate(aIDShutdown, PR_IntervalToMilliseconds(now - aStart)); } else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange()) < 60) { Telemetry::Accumulate(aIDConnectivityChange, PR_IntervalToMilliseconds(now - aStart)); } else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange()) < 60) { Telemetry::Accumulate(aIDLinkChange, PR_IntervalToMilliseconds(now - aStart)); } else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange()) < 60) { Telemetry::Accumulate(aIDOffline, PR_IntervalToMilliseconds(now - aStart)); } else { Telemetry::Accumulate(aIDNormal, PR_IntervalToMilliseconds(now - aStart)); } } } //namwspacw mozilla } //namespace net