tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsNamedPipeService.cpp (8128B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "mozilla/Services.h"
      7 #include "nsCOMPtr.h"
      8 #include "nsIObserverService.h"
      9 #include "nsNamedPipeService.h"
     10 #include "nsNetCID.h"
     11 #include "nsThreadUtils.h"
     12 #include "mozilla/ClearOnShutdown.h"
     13 #include "mozilla/Logging.h"
     14 
     15 namespace mozilla {
     16 namespace net {
     17 
     18 static mozilla::LazyLogModule gNamedPipeServiceLog("NamedPipeWin");
     19 #define LOG_NPS_DEBUG(...) \
     20  MOZ_LOG(gNamedPipeServiceLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
     21 #define LOG_NPS_ERROR(...) \
     22  MOZ_LOG(gNamedPipeServiceLog, mozilla::LogLevel::Error, (__VA_ARGS__))
     23 
     24 StaticRefPtr<NamedPipeService> NamedPipeService::gSingleton;
     25 
     26 NS_IMPL_ISUPPORTS(NamedPipeService, nsINamedPipeService, nsIObserver,
     27                  nsIRunnable)
     28 
     29 NamedPipeService::NamedPipeService()
     30    : mIocp(nullptr), mIsShutdown(false), mLock("NamedPipeServiceLock") {}
     31 
     32 nsresult NamedPipeService::Init() {
     33  MOZ_ASSERT(!mIsShutdown);
     34 
     35  nsresult rv;
     36 
     37  // nsIObserverService must be accessed in main thread.
     38  // register shutdown event to stop NamedPipeSrv thread.
     39  nsCOMPtr<nsIObserver> self(this);
     40  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
     41      "NamedPipeService::Init", [self = std::move(self)]() -> void {
     42        MOZ_ASSERT(NS_IsMainThread());
     43 
     44        nsCOMPtr<nsIObserverService> svc =
     45            mozilla::services::GetObserverService();
     46 
     47        if (NS_WARN_IF(!svc)) {
     48          return;
     49        }
     50 
     51        if (NS_WARN_IF(NS_FAILED(svc->AddObserver(
     52                self, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)))) {
     53          return;
     54        }
     55      });
     56 
     57  if (NS_IsMainThread()) {
     58    rv = r->Run();
     59  } else {
     60    rv = NS_DispatchToMainThread(r);
     61  }
     62  if (NS_WARN_IF(NS_FAILED(rv))) {
     63    return rv;
     64  }
     65 
     66  mIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1);
     67  if (NS_WARN_IF(!mIocp || mIocp == INVALID_HANDLE_VALUE)) {
     68    Shutdown();
     69    return NS_ERROR_FAILURE;
     70  }
     71 
     72  rv = NS_NewNamedThread("NamedPipeSrv", getter_AddRefs(mThread));
     73  if (NS_WARN_IF(NS_FAILED(rv))) {
     74    Shutdown();
     75    return rv;
     76  }
     77 
     78  return NS_OK;
     79 }
     80 
     81 // static
     82 already_AddRefed<nsINamedPipeService> NamedPipeService::GetOrCreate() {
     83  MOZ_ASSERT(NS_IsMainThread());
     84 
     85  RefPtr<NamedPipeService> inst;
     86  if (gSingleton) {
     87    inst = gSingleton;
     88  } else {
     89    inst = new NamedPipeService();
     90    nsresult rv = inst->Init();
     91    NS_ENSURE_SUCCESS(rv, nullptr);
     92    gSingleton = inst;
     93    ClearOnShutdown(&gSingleton);
     94  }
     95 
     96  return inst.forget();
     97 }
     98 
     99 void NamedPipeService::Shutdown() {
    100  MOZ_ASSERT(NS_IsMainThread());
    101 
    102  // remove observer
    103  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    104  if (obs) {
    105    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
    106  }
    107 
    108  // stop thread
    109  if (mThread && !mIsShutdown) {
    110    mIsShutdown = true;
    111 
    112    // invoke ERROR_ABANDONED_WAIT_0 to |GetQueuedCompletionStatus|
    113    CloseHandle(mIocp);
    114    mIocp = nullptr;
    115 
    116    mThread->Shutdown();
    117  }
    118 
    119  // close I/O Completion Port
    120  if (mIocp && mIocp != INVALID_HANDLE_VALUE) {
    121    CloseHandle(mIocp);
    122    mIocp = nullptr;
    123  }
    124 }
    125 
    126 void NamedPipeService::RemoveRetiredObjects() {
    127  MOZ_ASSERT(NS_GetCurrentThread() == mThread);
    128  mLock.AssertCurrentThreadOwns();
    129 
    130  if (!mRetiredHandles.IsEmpty()) {
    131    for (auto& handle : mRetiredHandles) {
    132      CloseHandle(handle);
    133    }
    134    mRetiredHandles.Clear();
    135  }
    136 
    137  mRetiredObservers.Clear();
    138 }
    139 
    140 /**
    141 * Implement nsINamedPipeService
    142 */
    143 
    144 NS_IMETHODIMP
    145 NamedPipeService::AddDataObserver(void* aHandle,
    146                                  nsINamedPipeDataObserver* aObserver) {
    147  if (!aHandle || aHandle == INVALID_HANDLE_VALUE || !aObserver) {
    148    return NS_ERROR_ILLEGAL_VALUE;
    149  }
    150 
    151  nsresult rv;
    152 
    153  HANDLE h = CreateIoCompletionPort(aHandle, mIocp,
    154                                    reinterpret_cast<ULONG_PTR>(aObserver), 1);
    155  if (NS_WARN_IF(!h)) {
    156    LOG_NPS_ERROR("CreateIoCompletionPort error (%lu)", GetLastError());
    157    return NS_ERROR_FAILURE;
    158  }
    159  if (NS_WARN_IF(h != mIocp)) {
    160    LOG_NPS_ERROR(
    161        "CreateIoCompletionPort got unexpected value %p (should be %p)", h,
    162        mIocp);
    163    CloseHandle(h);
    164    return NS_ERROR_FAILURE;
    165  }
    166 
    167  {
    168    MutexAutoLock lock(mLock);
    169    MOZ_ASSERT(!mObservers.Contains(aObserver));
    170 
    171    mObservers.AppendElement(aObserver);
    172 
    173    // start event loop
    174    if (mObservers.Length() == 1) {
    175      rv = mThread->Dispatch(this, NS_DISPATCH_NORMAL);
    176      if (NS_WARN_IF(NS_FAILED(rv))) {
    177        LOG_NPS_ERROR("Dispatch to thread failed (%08x)", uint32_t(rv));
    178        mObservers.Clear();
    179        return rv;
    180      }
    181    }
    182  }
    183 
    184  return NS_OK;
    185 }
    186 
    187 NS_IMETHODIMP
    188 NamedPipeService::RemoveDataObserver(void* aHandle,
    189                                     nsINamedPipeDataObserver* aObserver) {
    190  MutexAutoLock lock(mLock);
    191  mObservers.RemoveElement(aObserver);
    192 
    193  mRetiredHandles.AppendElement(aHandle);
    194  mRetiredObservers.AppendElement(aObserver);
    195 
    196  return NS_OK;
    197 }
    198 
    199 NS_IMETHODIMP
    200 NamedPipeService::IsOnCurrentThread(bool* aRetVal) {
    201  MOZ_ASSERT(mThread);
    202  MOZ_ASSERT(aRetVal);
    203 
    204  if (!mThread) {
    205    *aRetVal = false;
    206    return NS_OK;
    207  }
    208 
    209  return mThread->IsOnCurrentThread(aRetVal);
    210 }
    211 
    212 /**
    213 * Implement nsIObserver
    214 */
    215 
    216 NS_IMETHODIMP
    217 NamedPipeService::Observe(nsISupports* aSubject, const char* aTopic,
    218                          const char16_t* aData) {
    219  MOZ_ASSERT(NS_IsMainThread());
    220 
    221  if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) {
    222    Shutdown();
    223  }
    224 
    225  return NS_OK;
    226 }
    227 
    228 /**
    229 * Implement nsIRunnable
    230 */
    231 
    232 NS_IMETHODIMP
    233 NamedPipeService::Run() {
    234  MOZ_ASSERT(NS_GetCurrentThread() == mThread);
    235  MOZ_ASSERT(mIocp && mIocp != INVALID_HANDLE_VALUE);
    236 
    237  while (!mIsShutdown) {
    238    {
    239      MutexAutoLock lock(mLock);
    240      if (mObservers.IsEmpty()) {
    241        LOG_NPS_DEBUG("no observer, stop loop");
    242        break;
    243      }
    244 
    245      RemoveRetiredObjects();
    246    }
    247 
    248    DWORD bytesTransferred = 0;
    249    ULONG_PTR key = 0;
    250    LPOVERLAPPED overlapped = nullptr;
    251    BOOL success =
    252        GetQueuedCompletionStatus(mIocp, &bytesTransferred, &key, &overlapped,
    253                                  1000);  // timeout, 1s
    254    auto err = GetLastError();
    255    if (!success) {
    256      if (err == WAIT_TIMEOUT) {
    257        continue;
    258      } else if (err == ERROR_ABANDONED_WAIT_0) {  // mIocp was closed
    259        break;
    260      } else if (!overlapped) {
    261        /**
    262         * Did not dequeue a completion packet from the completion port, and
    263         * bytesTransferred/key are meaningless.
    264         * See remarks of |GetQueuedCompletionStatus| API.
    265         */
    266 
    267        LOG_NPS_ERROR("invalid overlapped (%lu)", err);
    268        continue;
    269      }
    270 
    271      MOZ_ASSERT(key);
    272    }
    273 
    274    /**
    275     * Windows doesn't provide a method to remove created I/O Completion Port,
    276     * all we can do is just close the handle we monitored before.
    277     * In some cases, there's race condition that the monitored handle has an
    278     * I/O status after the observer is being removed and destroyed.
    279     * To avoid changing the ref-count of a dangling pointer, don't use nsCOMPtr
    280     * here.
    281     */
    282    nsINamedPipeDataObserver* target =
    283        reinterpret_cast<nsINamedPipeDataObserver*>(key);
    284 
    285    nsCOMPtr<nsINamedPipeDataObserver> obs;
    286    {
    287      MutexAutoLock lock(mLock);
    288 
    289      auto idx = mObservers.IndexOf(target);
    290      if (idx == decltype(mObservers)::NoIndex) {
    291        LOG_NPS_ERROR("observer %p not found", target);
    292        continue;
    293      }
    294      obs = target;
    295    }
    296 
    297    MOZ_ASSERT(obs.get());
    298 
    299    if (success) {
    300      LOG_NPS_DEBUG("OnDataAvailable: obs=%p, bytes=%lu", obs.get(),
    301                    bytesTransferred);
    302      obs->OnDataAvailable(bytesTransferred, overlapped);
    303    } else {
    304      LOG_NPS_ERROR("GetQueuedCompletionStatus %p failed, error=%lu", obs.get(),
    305                    err);
    306      obs->OnError(err, overlapped);
    307    }
    308  }
    309 
    310  {
    311    MutexAutoLock lock(mLock);
    312    RemoveRetiredObjects();
    313  }
    314 
    315  return NS_OK;
    316 }
    317 
    318 }  // namespace net
    319 }  // namespace mozilla