зеркало из https://github.com/mozilla/gecko-dev.git
319 строки
7.9 KiB
C++
319 строки
7.9 KiB
C++
/* -*- 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"
|
|
|
|
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 (%d)", 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 (%d)", 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=%d", obs.get(),
|
|
bytesTransferred);
|
|
obs->OnDataAvailable(bytesTransferred, overlapped);
|
|
} else {
|
|
LOG_NPS_ERROR("GetQueuedCompletionStatus %p failed, error=%d", obs.get(),
|
|
err);
|
|
obs->OnError(err, overlapped);
|
|
}
|
|
}
|
|
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
RemoveRetiredObjects();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace net
|
|
} // namespace mozilla
|