diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/socket/nsNamedPipeService.cpp | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/netwerk/socket/nsNamedPipeService.cpp b/netwerk/socket/nsNamedPipeService.cpp new file mode 100644 index 0000000000..4cf41680d5 --- /dev/null +++ b/netwerk/socket/nsNamedPipeService.cpp @@ -0,0 +1,319 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/Services.h" +#include "nsCOMPtr.h" +#include "nsIObserverService.h" +#include "nsNamedPipeService.h" +#include "nsNetCID.h" +#include "nsThreadUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Logging.h" + +namespace mozilla { +namespace net { + +static mozilla::LazyLogModule gNamedPipeServiceLog("NamedPipeWin"); +#define LOG_NPS_DEBUG(...) \ + MOZ_LOG(gNamedPipeServiceLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) +#define LOG_NPS_ERROR(...) \ + MOZ_LOG(gNamedPipeServiceLog, mozilla::LogLevel::Error, (__VA_ARGS__)) + +StaticRefPtr<NamedPipeService> NamedPipeService::gSingleton; + +NS_IMPL_ISUPPORTS(NamedPipeService, nsINamedPipeService, nsIObserver, + nsIRunnable) + +NamedPipeService::NamedPipeService() + : mIocp(nullptr), mIsShutdown(false), mLock("NamedPipeServiceLock") {} + +nsresult NamedPipeService::Init() { + MOZ_ASSERT(!mIsShutdown); + + nsresult rv; + + // nsIObserverService must be accessed in main thread. + // register shutdown event to stop NamedPipeSrv thread. + nsCOMPtr<nsIObserver> self(this); + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "NamedPipeService::Init", [self = std::move(self)]() -> void { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIObserverService> svc = + mozilla::services::GetObserverService(); + + if (NS_WARN_IF(!svc)) { + return; + } + + if (NS_WARN_IF(NS_FAILED(svc->AddObserver( + self, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)))) { + return; + } + }); + + if (NS_IsMainThread()) { + rv = r->Run(); + } else { + rv = NS_DispatchToMainThread(r); + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1); + if (NS_WARN_IF(!mIocp || mIocp == INVALID_HANDLE_VALUE)) { + Shutdown(); + return NS_ERROR_FAILURE; + } + + rv = NS_NewNamedThread("NamedPipeSrv", getter_AddRefs(mThread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + Shutdown(); + return rv; + } + + return NS_OK; +} + +// static +already_AddRefed<nsINamedPipeService> NamedPipeService::GetOrCreate() { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<NamedPipeService> inst; + if (gSingleton) { + inst = gSingleton; + } else { + inst = new NamedPipeService(); + nsresult rv = inst->Init(); + NS_ENSURE_SUCCESS(rv, nullptr); + gSingleton = inst; + ClearOnShutdown(&gSingleton); + } + + return inst.forget(); +} + +void NamedPipeService::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + + // remove observer + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + + // stop thread + if (mThread && !mIsShutdown) { + mIsShutdown = true; + + // invoke ERROR_ABANDONED_WAIT_0 to |GetQueuedCompletionStatus| + CloseHandle(mIocp); + mIocp = nullptr; + + mThread->Shutdown(); + } + + // close I/O Completion Port + if (mIocp && mIocp != INVALID_HANDLE_VALUE) { + CloseHandle(mIocp); + mIocp = nullptr; + } +} + +void NamedPipeService::RemoveRetiredObjects() { + MOZ_ASSERT(NS_GetCurrentThread() == mThread); + mLock.AssertCurrentThreadOwns(); + + if (!mRetiredHandles.IsEmpty()) { + for (auto& handle : mRetiredHandles) { + CloseHandle(handle); + } + mRetiredHandles.Clear(); + } + + mRetiredObservers.Clear(); +} + +/** + * Implement nsINamedPipeService + */ + +NS_IMETHODIMP +NamedPipeService::AddDataObserver(void* aHandle, + nsINamedPipeDataObserver* aObserver) { + if (!aHandle || aHandle == INVALID_HANDLE_VALUE || !aObserver) { + return NS_ERROR_ILLEGAL_VALUE; + } + + nsresult rv; + + HANDLE h = CreateIoCompletionPort(aHandle, mIocp, + reinterpret_cast<ULONG_PTR>(aObserver), 1); + if (NS_WARN_IF(!h)) { + LOG_NPS_ERROR("CreateIoCompletionPort error (%lu)", GetLastError()); + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(h != mIocp)) { + LOG_NPS_ERROR( + "CreateIoCompletionPort got unexpected value %p (should be %p)", h, + mIocp); + CloseHandle(h); + return NS_ERROR_FAILURE; + } + + { + MutexAutoLock lock(mLock); + MOZ_ASSERT(!mObservers.Contains(aObserver)); + + mObservers.AppendElement(aObserver); + + // start event loop + if (mObservers.Length() == 1) { + rv = mThread->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG_NPS_ERROR("Dispatch to thread failed (%08x)", rv); + mObservers.Clear(); + return rv; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +NamedPipeService::RemoveDataObserver(void* aHandle, + nsINamedPipeDataObserver* aObserver) { + MutexAutoLock lock(mLock); + mObservers.RemoveElement(aObserver); + + mRetiredHandles.AppendElement(aHandle); + mRetiredObservers.AppendElement(aObserver); + + return NS_OK; +} + +NS_IMETHODIMP +NamedPipeService::IsOnCurrentThread(bool* aRetVal) { + MOZ_ASSERT(mThread); + MOZ_ASSERT(aRetVal); + + if (!mThread) { + *aRetVal = false; + return NS_OK; + } + + return mThread->IsOnCurrentThread(aRetVal); +} + +/** + * Implement nsIObserver + */ + +NS_IMETHODIMP +NamedPipeService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) { + Shutdown(); + } + + return NS_OK; +} + +/** + * Implement nsIRunnable + */ + +NS_IMETHODIMP +NamedPipeService::Run() { + MOZ_ASSERT(NS_GetCurrentThread() == mThread); + MOZ_ASSERT(mIocp && mIocp != INVALID_HANDLE_VALUE); + + while (!mIsShutdown) { + { + MutexAutoLock lock(mLock); + if (mObservers.IsEmpty()) { + LOG_NPS_DEBUG("no observer, stop loop"); + break; + } + + RemoveRetiredObjects(); + } + + DWORD bytesTransferred = 0; + ULONG_PTR key = 0; + LPOVERLAPPED overlapped = nullptr; + BOOL success = + GetQueuedCompletionStatus(mIocp, &bytesTransferred, &key, &overlapped, + 1000); // timeout, 1s + auto err = GetLastError(); + if (!success) { + if (err == WAIT_TIMEOUT) { + continue; + } else if (err == ERROR_ABANDONED_WAIT_0) { // mIocp was closed + break; + } else if (!overlapped) { + /** + * Did not dequeue a completion packet from the completion port, and + * bytesTransferred/key are meaningless. + * See remarks of |GetQueuedCompletionStatus| API. + */ + + LOG_NPS_ERROR("invalid overlapped (%lu)", err); + continue; + } + + MOZ_ASSERT(key); + } + + /** + * Windows doesn't provide a method to remove created I/O Completion Port, + * all we can do is just close the handle we monitored before. + * In some cases, there's race condition that the monitored handle has an + * I/O status after the observer is being removed and destroyed. + * To avoid changing the ref-count of a dangling pointer, don't use nsCOMPtr + * here. + */ + nsINamedPipeDataObserver* target = + reinterpret_cast<nsINamedPipeDataObserver*>(key); + + nsCOMPtr<nsINamedPipeDataObserver> obs; + { + MutexAutoLock lock(mLock); + + auto idx = mObservers.IndexOf(target); + if (idx == decltype(mObservers)::NoIndex) { + LOG_NPS_ERROR("observer %p not found", target); + continue; + } + obs = target; + } + + MOZ_ASSERT(obs.get()); + + if (success) { + LOG_NPS_DEBUG("OnDataAvailable: obs=%p, bytes=%lu", obs.get(), + bytesTransferred); + obs->OnDataAvailable(bytesTransferred, overlapped); + } else { + LOG_NPS_ERROR("GetQueuedCompletionStatus %p failed, error=%lu", obs.get(), + err); + obs->OnError(err, overlapped); + } + } + + { + MutexAutoLock lock(mLock); + RemoveRetiredObjects(); + } + + return NS_OK; +} + +} // namespace net +} // namespace mozilla |