dom/workers/remoteworkers/RemoteWorkerManager.cpp
author Andrea Marchesini <amarchesini@mozilla.com>
Mon, 19 Nov 2018 15:18:33 -0800
changeset 503574 6f7cba5101c17b5f2ffea4501bebc9ae0dc873b4
parent 503573 2a1a9e2c2010d94954a3d346a30c9690551a7bcf
child 503575 93c96481ec70d75496045840d5c42c90ee8ec761
permissions -rw-r--r--
Bug 1438945 - Part 12: Spawning a new process if needed. r=asuth

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "RemoteWorkerManager.h"

#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/RemoteWorkerParent.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "RemoteWorkerServiceParent.h"

namespace mozilla {

using namespace ipc;

namespace dom {

namespace {

// Raw pointer because this object is kept alive by RemoteWorkerServiceParent
// actors.
RemoteWorkerManager* sRemoteWorkerManager;

} // anonymous

/* static */ already_AddRefed<RemoteWorkerManager>
RemoteWorkerManager::GetOrCreate()
{
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(XRE_IsParentProcess());

  if (!sRemoteWorkerManager) {
    sRemoteWorkerManager = new RemoteWorkerManager();
  }

  RefPtr<RemoteWorkerManager> rwm = sRemoteWorkerManager;
  return rwm.forget();
}

RemoteWorkerManager::RemoteWorkerManager()
{
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(XRE_IsParentProcess());
  MOZ_ASSERT(!sRemoteWorkerManager);
}

RemoteWorkerManager::~RemoteWorkerManager()
{
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(XRE_IsParentProcess());
  MOZ_ASSERT(sRemoteWorkerManager == this);
  sRemoteWorkerManager = nullptr;
}

void
RemoteWorkerManager::RegisterActor(RemoteWorkerServiceParent* aActor)
{
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(XRE_IsParentProcess());
  MOZ_ASSERT(aActor);
  MOZ_ASSERT(!mActors.Contains(aActor));

  mActors.AppendElement(aActor);

  if (!mPendings.IsEmpty()) {
    // Flush pending launching.
    for (const Pending& p : mPendings) {
      LaunchInternal(p.mController, aActor, p.mData);
    }

    mPendings.Clear();

    // We don't need to keep this manager alive manually. The Actor is doing it
    // for us.
    Release();
  }
}

void
RemoteWorkerManager::UnregisterActor(RemoteWorkerServiceParent* aActor)
{
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(XRE_IsParentProcess());
  MOZ_ASSERT(aActor);
  MOZ_ASSERT(mActors.Contains(aActor));

  mActors.RemoveElement(aActor);
}

void
RemoteWorkerManager::Launch(RemoteWorkerController* aController,
                            const RemoteWorkerData& aData,
                            base::ProcessId aProcessId)
{
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(XRE_IsParentProcess());

  RemoteWorkerServiceParent* targetActor = SelectTargetActor(aData, aProcessId);

  // If there is not an available actor, let's store the data, and let's spawn a
  // new process.
  if (!targetActor) {
    // If this is the first time we have a pending launching, we must keep alive
    // the manager.
    if (mPendings.IsEmpty()) {
      AddRef();
    }

    Pending* pending = mPendings.AppendElement();
    pending->mController = aController;
    pending->mData = aData;

    LaunchNewContentProcess();
    return;
  }

  LaunchInternal(aController, targetActor, aData);
}

void
RemoteWorkerManager::LaunchInternal(RemoteWorkerController* aController,
                                    RemoteWorkerServiceParent* aTargetActor,
                                    const RemoteWorkerData& aData)
{
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(XRE_IsParentProcess());
  MOZ_ASSERT(aController);
  MOZ_ASSERT(aTargetActor);
  MOZ_ASSERT(mActors.Contains(aTargetActor));

  RemoteWorkerParent* workerActor =
    static_cast<RemoteWorkerParent*>(
      aTargetActor->Manager()->SendPRemoteWorkerConstructor(aData));
  if (NS_WARN_IF(!workerActor)) {
    AsyncCreationFailed(aController);
    return;
  }

  // This makes the link better the 2 actors.
  aController->SetWorkerActor(workerActor);
  workerActor->SetController(aController);
}

void
RemoteWorkerManager::AsyncCreationFailed(RemoteWorkerController* aController)
{
  RefPtr<RemoteWorkerController> controller = aController;
  nsCOMPtr<nsIRunnable> r =
    NS_NewRunnableFunction("RemoteWorkerManager::AsyncCreationFailed",
                           [controller]() {
      controller->CreationFailed();
  });

  NS_DispatchToCurrentThread(r.forget());
}

RemoteWorkerServiceParent*
RemoteWorkerManager::SelectTargetActor(const RemoteWorkerData& aData,
                                       base::ProcessId aProcessId)
{
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(XRE_IsParentProcess());

  if (mActors.IsEmpty()) {
    return nullptr;
  }

  // System principal workers should run on the parent process.
  if (aData.principalInfo().type() == PrincipalInfo::TSystemPrincipalInfo) {
    for (RemoteWorkerServiceParent* actor : mActors) {
      if (actor->OtherPid() == 0) {
        return actor;
      }
    }
  }

  for (RemoteWorkerServiceParent* actor : mActors) {
    // Let's execute the RemoteWorker on the same process but not on the parent
    // process.
    if (aProcessId && actor->OtherPid() == aProcessId) {
      return actor;
    }
  }

  // Let's choose an actor, randomly.
  uint32_t id = uint32_t(rand()) % mActors.Length();
  return mActors[id];
}

void
RemoteWorkerManager::LaunchNewContentProcess()
{
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(XRE_IsParentProcess());

  // This runnable will spawn a new process if it doesn't exist yet.
  nsCOMPtr<nsIRunnable> r =
    NS_NewRunnableFunction("LaunchNewContentProcess", [] () {
      RefPtr<ContentParent> unused =
        ContentParent::GetNewOrUsedBrowserProcess(
          nullptr,
          NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
    }
  );

  nsCOMPtr<nsIEventTarget> target =
    SystemGroup::EventTargetFor(TaskCategory::Other);
  target->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
}

} // dom namespace
} // mozilla namespace