tor-browser

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

RemoteWorkerService.cpp (15171B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "RemoteWorkerService.h"
      8 
      9 #include "RemoteWorkerController.h"
     10 #include "RemoteWorkerDebuggerManagerChild.h"
     11 #include "RemoteWorkerDebuggerManagerParent.h"
     12 #include "RemoteWorkerServiceChild.h"
     13 #include "RemoteWorkerServiceParent.h"
     14 #include "mozilla/SchedulerGroup.h"
     15 #include "mozilla/Services.h"
     16 #include "mozilla/SpinEventLoopUntil.h"
     17 #include "mozilla/StaticMutex.h"
     18 #include "mozilla/StaticPtr.h"
     19 #include "mozilla/dom/PRemoteWorkerDebuggerParent.h"
     20 #include "mozilla/dom/PRemoteWorkerParent.h"
     21 #include "mozilla/ipc/BackgroundChild.h"
     22 #include "mozilla/ipc/BackgroundParent.h"
     23 #include "mozilla/ipc/PBackgroundChild.h"
     24 #include "mozilla/ipc/PBackgroundParent.h"
     25 #include "nsIObserverService.h"
     26 #include "nsIThread.h"
     27 #include "nsThreadUtils.h"
     28 #include "nsXPCOMPrivate.h"
     29 
     30 namespace mozilla {
     31 
     32 using namespace ipc;
     33 
     34 namespace dom {
     35 
     36 namespace {
     37 
     38 StaticMutex sRemoteWorkerServiceMutex;
     39 StaticRefPtr<RemoteWorkerService> sRemoteWorkerService;
     40 
     41 }  // namespace
     42 
     43 /**
     44 * Block shutdown until the RemoteWorkers have shutdown so that we do not try
     45 * and shutdown the RemoteWorkerService "Worker Launcher" thread until they have
     46 * cleanly shutdown.
     47 *
     48 * Note that this shutdown blocker is not used to initiate shutdown of any of
     49 * the workers directly; their shutdown is initiated from PBackground in the
     50 * parent process.  The shutdown blocker just exists to avoid races around
     51 * shutting down the worker launcher thread after all of the workers have
     52 * shutdown and torn down their actors.
     53 *
     54 * Currently, it should be the case that the ContentParent should want to keep
     55 * the content processes alive until the RemoteWorkers have all reported their
     56 * shutdown over IPC (on the "Worker Launcher" thread).  So for an orderly
     57 * content process shutdown that is waiting for there to no longer be a reason
     58 * to keep the content process alive, this blocker should only hang around for
     59 * a brief period of time, helping smooth out lifecycle edge cases.
     60 *
     61 * In the event the content process is trying to shutdown while the
     62 * RemoteWorkers think they should still be alive, it's possible that this
     63 * blocker could expose the relevant logic error in the parent process if no
     64 * attempt is made to shutdown the RemoteWorker.
     65 *
     66 * ## Major Implementation Note: This is not actually an nsIAsyncShutdownClient
     67 *
     68 * Until https://bugzilla.mozilla.org/show_bug.cgi?id=1760855 provides us with a
     69 * non-JS implementation of nsIAsyncShutdownService, this implementation
     70 * actually uses event loop spinning.  The patch on
     71 * https://bugzilla.mozilla.org/show_bug.cgi?id=1775784 that changed us to use
     72 * this hack can be reverted when the time is right.
     73 *
     74 * Event loop spinning is handled by `RemoteWorkerService::Observe` and it calls
     75 * our exposed `ShouldBlockShutdown()` to know when to stop spinning.
     76 */
     77 class RemoteWorkerServiceShutdownBlocker final {
     78  ~RemoteWorkerServiceShutdownBlocker() = default;
     79 
     80 public:
     81  explicit RemoteWorkerServiceShutdownBlocker(RemoteWorkerService* aService)
     82      : mService(aService), mBlockShutdown(true) {}
     83 
     84  void RemoteWorkersAllGoneAllowShutdown() {
     85    mService->FinishShutdown();
     86    mService = nullptr;
     87 
     88    mBlockShutdown = false;
     89  }
     90 
     91  bool ShouldBlockShutdown() { return mBlockShutdown; }
     92 
     93  NS_INLINE_DECL_REFCOUNTING(RemoteWorkerServiceShutdownBlocker);
     94 
     95  RefPtr<RemoteWorkerService> mService;
     96  bool mBlockShutdown;
     97 };
     98 
     99 RemoteWorkerServiceKeepAlive::RemoteWorkerServiceKeepAlive(
    100    RemoteWorkerServiceShutdownBlocker* aBlocker)
    101    : mBlocker(aBlocker) {
    102  MOZ_ASSERT(NS_IsMainThread());
    103 }
    104 
    105 RemoteWorkerServiceKeepAlive::~RemoteWorkerServiceKeepAlive() {
    106  // Dispatch a runnable to the main thread to tell the Shutdown Blocker to
    107  // remove itself and notify the RemoteWorkerService it can finish its
    108  // shutdown.  We dispatch this to the main thread even if we are already on
    109  // the main thread.
    110  nsCOMPtr<nsIRunnable> r =
    111      NS_NewRunnableFunction(__func__, [blocker = std::move(mBlocker)] {
    112        blocker->RemoteWorkersAllGoneAllowShutdown();
    113      });
    114  MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget()));
    115 }
    116 
    117 /* static */
    118 void RemoteWorkerService::InitializeParent() {
    119  MOZ_ASSERT(NS_IsMainThread());
    120  MOZ_ASSERT(XRE_IsParentProcess());
    121 
    122  StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
    123  MOZ_ASSERT(!sRemoteWorkerService);
    124 
    125  RefPtr<RemoteWorkerService> service = new RemoteWorkerService();
    126 
    127  // ## Parent Process Initialization Case
    128  //
    129  // Otherwise we are in the parent process and were invoked by
    130  // nsLayoutStatics::Initialize.  We wait until profile-after-change to kick
    131  // off the Worker Launcher thread and have it connect to PBackground.  This is
    132  // an appropriate time for remote worker APIs to come online, especially
    133  // because the PRemoteWorkerService mechanism needs processes to eagerly
    134  // register themselves with PBackground since the design explicitly intends to
    135  // avoid blocking on the main threads.  (Disclaimer: Currently, things block
    136  // on the main thread.)
    137 
    138  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    139  if (NS_WARN_IF(!obs)) {
    140    return;
    141  }
    142 
    143  nsresult rv = obs->AddObserver(service, "profile-after-change", false);
    144  if (NS_WARN_IF(NS_FAILED(rv))) {
    145    return;
    146  }
    147 
    148  sRemoteWorkerService = service;
    149 }
    150 
    151 /* static */
    152 void RemoteWorkerService::InitializeChild(
    153    mozilla::ipc::Endpoint<PRemoteWorkerServiceChild> aEndpoint,
    154    mozilla::ipc::Endpoint<PRemoteWorkerDebuggerManagerChild>
    155        aDebuggerChildEp) {
    156  MOZ_ASSERT(NS_IsMainThread());
    157  MOZ_ASSERT(!XRE_IsParentProcess());
    158 
    159  StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
    160  MOZ_ASSERT(!sRemoteWorkerService);
    161 
    162  RefPtr<RemoteWorkerService> service = new RemoteWorkerService();
    163 
    164  // ## Content Process Initialization Case
    165  //
    166  // We are being told to initialize now that we know what our remote type is.
    167  // Now is a fine time to call InitializeOnMainThread.
    168 
    169  nsresult rv = service->InitializeOnMainThread(std::move(aEndpoint),
    170                                                std::move(aDebuggerChildEp));
    171  if (NS_WARN_IF(NS_FAILED(rv))) {
    172    return;
    173  }
    174 
    175  sRemoteWorkerService = service;
    176 }
    177 
    178 /* static */
    179 nsIThread* RemoteWorkerService::Thread() {
    180  StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
    181  MOZ_ASSERT(sRemoteWorkerService);
    182  MOZ_ASSERT(sRemoteWorkerService->mThread);
    183  return sRemoteWorkerService->mThread;
    184 }
    185 
    186 /* static */
    187 void RemoteWorkerService::RegisterRemoteDebugger(
    188    RemoteWorkerDebuggerInfo aDebuggerInfo,
    189    mozilla::ipc::Endpoint<PRemoteWorkerDebuggerParent> aDebuggerParentEp) {
    190  StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
    191  MOZ_ASSERT(sRemoteWorkerService);
    192  MOZ_ASSERT(sRemoteWorkerService->mThread);
    193 
    194  // If we are on WorkerLauncher thread, direcly call
    195  // RemoteWorkerDebuggerManager::SendRegister.
    196  if (sRemoteWorkerService->mThread->IsOnCurrentThread()) {
    197    MOZ_ASSERT(sRemoteWorkerService->mDebuggerManagerChild);
    198    (void)sRemoteWorkerService->mDebuggerManagerChild->SendRegister(
    199        std::move(aDebuggerInfo), std::move(aDebuggerParentEp));
    200    return;
    201  }
    202 
    203  // For top-level workers in parent process, directly call RecvRegister().
    204  if (XRE_IsParentProcess() && NS_IsMainThread()) {
    205    MOZ_ASSERT(sRemoteWorkerService->mDebuggerManagerParent);
    206    (void)sRemoteWorkerService->mDebuggerManagerParent->RecvRegister(
    207        std::move(aDebuggerInfo), std::move(aDebuggerParentEp));
    208    return;
    209  }
    210 
    211  // We are on other thread in the case of this is a Child worker. Dispatch this
    212  // method to WorkerLauncher thread.
    213  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
    214      "RemoteWorkerService::RegisterRemoteDebugger",
    215      [debuggerInfo = std::move(aDebuggerInfo),
    216       debuggerParentEp = std::move(aDebuggerParentEp)]() mutable {
    217        RemoteWorkerService::RegisterRemoteDebugger(
    218            std::move(debuggerInfo), std::move(debuggerParentEp));
    219      });
    220  (void)NS_WARN_IF(
    221      NS_FAILED(sRemoteWorkerService->mThread->Dispatch(r.forget())));
    222 }
    223 
    224 /* static */
    225 already_AddRefed<RemoteWorkerServiceKeepAlive>
    226 RemoteWorkerService::MaybeGetKeepAlive() {
    227  StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
    228  // In normal operation no one should be calling this without a service
    229  // existing, so assert, but we'll also handle this being null as it is a
    230  // plausible shutdown race.
    231  MOZ_ASSERT(sRemoteWorkerService);
    232  if (!sRemoteWorkerService) {
    233    return nullptr;
    234  }
    235 
    236  // Note that this value can be null, but this all handles that.
    237  auto lockedKeepAlive = sRemoteWorkerService->mKeepAlive.Lock();
    238  RefPtr<RemoteWorkerServiceKeepAlive> extraRef = *lockedKeepAlive;
    239  return extraRef.forget();
    240 }
    241 
    242 nsresult RemoteWorkerService::InitializeOnMainThread(
    243    mozilla::ipc::Endpoint<PRemoteWorkerServiceChild> aEndpoint,
    244    mozilla::ipc::Endpoint<PRemoteWorkerDebuggerManagerChild>
    245        aDebuggerChildEp) {
    246  // I would like to call this thread "DOM Remote Worker Launcher", but the max
    247  // length is 16 chars.
    248  nsresult rv = NS_NewNamedThread("Worker Launcher", getter_AddRefs(mThread));
    249  if (NS_WARN_IF(NS_FAILED(rv))) {
    250    return rv;
    251  }
    252 
    253  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    254  if (NS_WARN_IF(!obs)) {
    255    return NS_ERROR_FAILURE;
    256  }
    257 
    258  rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
    259  if (NS_WARN_IF(NS_FAILED(rv))) {
    260    return rv;
    261  }
    262 
    263  mShutdownBlocker = new RemoteWorkerServiceShutdownBlocker(this);
    264 
    265  {
    266    RefPtr<RemoteWorkerServiceKeepAlive> keepAlive =
    267        new RemoteWorkerServiceKeepAlive(mShutdownBlocker);
    268 
    269    auto lockedKeepAlive = mKeepAlive.Lock();
    270    *lockedKeepAlive = std::move(keepAlive);
    271  }
    272 
    273  RefPtr<RemoteWorkerService> self = this;
    274  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
    275      "InitializeThread",
    276      [self, endpoint = std::move(aEndpoint),
    277       debuggerChildEp = std::move(aDebuggerChildEp)]() mutable {
    278        self->InitializeOnTargetThread(std::move(endpoint),
    279                                       std::move(debuggerChildEp));
    280      });
    281 
    282  rv = mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
    283  if (NS_WARN_IF(NS_FAILED(rv))) {
    284    return rv;
    285  }
    286 
    287  return NS_OK;
    288 }
    289 
    290 RemoteWorkerService::RemoteWorkerService()
    291    : mKeepAlive(nullptr, "RemoteWorkerService::mKeepAlive") {
    292  MOZ_ASSERT(NS_IsMainThread());
    293 }
    294 
    295 RemoteWorkerService::~RemoteWorkerService() = default;
    296 
    297 void RemoteWorkerService::InitializeOnTargetThread(
    298    mozilla::ipc::Endpoint<PRemoteWorkerServiceChild> aEndpoint,
    299    mozilla::ipc::Endpoint<PRemoteWorkerDebuggerManagerChild>
    300        aDebuggerMgrEndpoint) {
    301  MOZ_ASSERT(mThread);
    302  MOZ_ASSERT(mThread->IsOnCurrentThread());
    303 
    304  RefPtr<RemoteWorkerDebuggerManagerChild> debuggerManagerActor =
    305      MakeRefPtr<RemoteWorkerDebuggerManagerChild>();
    306  if (NS_WARN_IF(!aDebuggerMgrEndpoint.Bind(debuggerManagerActor))) {
    307    return;
    308  }
    309 
    310  RefPtr<RemoteWorkerServiceChild> serviceActor =
    311      MakeAndAddRef<RemoteWorkerServiceChild>();
    312  if (NS_WARN_IF(!aEndpoint.Bind(serviceActor))) {
    313    return;
    314  }
    315 
    316  mDebuggerManagerChild = std::move(debuggerManagerActor);
    317  mActor = std::move(serviceActor);
    318 }
    319 
    320 void RemoteWorkerService::CloseActorOnTargetThread() {
    321  MOZ_ASSERT(mThread);
    322  MOZ_ASSERT(mThread->IsOnCurrentThread());
    323 
    324  // If mActor is nullptr it means that initialization failed.
    325  if (mActor) {
    326    // Here we need to shutdown the IPC protocol.
    327    mActor->Close();
    328    mActor = nullptr;
    329  }
    330  if (mDebuggerManagerChild) {
    331    mDebuggerManagerChild->Close();
    332    mDebuggerManagerChild = nullptr;
    333  }
    334 }
    335 
    336 NS_IMETHODIMP
    337 RemoteWorkerService::Observe(nsISupports* aSubject, const char* aTopic,
    338                             const char16_t* aData) {
    339  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
    340    MOZ_ASSERT(mThread);
    341 
    342    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    343    if (obs) {
    344      obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
    345    }
    346 
    347    // Note that nsObserverList::NotifyObservers will hold a strong reference to
    348    // our instance throughout the entire duration of this call, so it is not
    349    // necessary for us to hold a kungFuDeathGrip here.
    350 
    351    // Drop our keep-alive.  This could immediately result in our blocker saying
    352    // it's okay for us to shutdown.  SpinEventLoopUntil checks the predicate
    353    // before spinning, so in the ideal case we will not spin the loop at all.
    354    BeginShutdown();
    355 
    356    MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
    357        "RemoteWorkerService::Observe"_ns,
    358        [&]() { return !mShutdownBlocker->ShouldBlockShutdown(); }));
    359 
    360    mShutdownBlocker = nullptr;
    361 
    362    return NS_OK;
    363  }
    364 
    365  MOZ_ASSERT(!strcmp(aTopic, "profile-after-change"));
    366  MOZ_ASSERT(XRE_IsParentProcess());
    367 
    368  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    369  if (obs) {
    370    obs->RemoveObserver(this, "profile-after-change");
    371  }
    372 
    373  Endpoint<PRemoteWorkerServiceChild> childEp;
    374  RefPtr<RemoteWorkerServiceParent> parentActor =
    375      RemoteWorkerServiceParent::CreateForProcess(nullptr, &childEp);
    376  NS_ENSURE_TRUE(parentActor, NS_ERROR_FAILURE);
    377 
    378  Endpoint<PRemoteWorkerDebuggerManagerChild> debuggerChildEp;
    379  mDebuggerManagerParent =
    380      RemoteWorkerDebuggerManagerParent::CreateForProcess(&debuggerChildEp);
    381  NS_ENSURE_TRUE(mDebuggerManagerParent, NS_ERROR_FAILURE);
    382 
    383  return InitializeOnMainThread(std::move(childEp), std::move(debuggerChildEp));
    384 }
    385 
    386 void RemoteWorkerService::BeginShutdown() {
    387  // Drop our keepalive reference which may allow near-immediate removal of the
    388  // blocker.
    389  auto lockedKeepAlive = mKeepAlive.Lock();
    390  *lockedKeepAlive = nullptr;
    391 }
    392 
    393 void RemoteWorkerService::FinishShutdown() {
    394  // Clear the singleton before spinning the event loop when shutting down the
    395  // thread so that MaybeGetKeepAlive() can assert if there are any late calls
    396  // and to better reflect the actual state.
    397  //
    398  // Our caller, the RemoteWorkerServiceShutdownBlocker, will continue to hold a
    399  // strong reference to us until we return from this call, so there are no
    400  // lifecycle implications to dropping this reference.
    401  {
    402    StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
    403    sRemoteWorkerService = nullptr;
    404  }
    405 
    406  RefPtr<RemoteWorkerService> self = this;
    407  nsCOMPtr<nsIRunnable> r =
    408      NS_NewRunnableFunction("RemoteWorkerService::CloseActorOnTargetThread",
    409                             [self]() { self->CloseActorOnTargetThread(); });
    410 
    411  mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
    412 
    413  // We've posted a shutdown message; now shutdown the thread.  This will spin
    414  // a nested event loop waiting for the thread to process all pending events
    415  // (including the just dispatched CloseActorOnTargetThread which will close
    416  // the actor), ensuring to block main thread shutdown long enough to avoid
    417  // races.
    418  mThread->Shutdown();
    419  mThread = nullptr;
    420 }
    421 
    422 NS_IMPL_ISUPPORTS(RemoteWorkerService, nsIObserver)
    423 
    424 }  // namespace dom
    425 }  // namespace mozilla