mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	There is no implicit conversion for scoped enums, so using them without an explicit conversion in varargs functions is undefined behavior. GCC has had a warning about this for a long while, but clang only gained this a few days ago on trunk. Differential Revision: https://phabricator.services.mozilla.com/D181723
		
			
				
	
	
		
			319 lines
		
	
	
	
		
			7.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			319 lines
		
	
	
	
		
			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"
 | 
						|
#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)", uint32_t(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
 |