/* 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 "LazyIdleThread.h"

#include "nsIObserverService.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"

#ifdef DEBUG
#  define ASSERT_OWNING_THREAD()                           \
    do {                                                   \
      MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); \
    } while (0)
#else
#  define ASSERT_OWNING_THREAD() /* nothing */
#endif

namespace mozilla {

LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, StaticString aName,
                               ShutdownMethod aShutdownMethod)
    : mOwningEventTarget(GetCurrentSerialEventTarget()),
      mThreadPool(new nsThreadPool()),
      mTaskQueue(TaskQueue::Create(do_AddRef(mThreadPool), aName)) {
  // Configure the threadpool to host a single thread. It will be responsible
  // for managing the thread's lifetime.
  MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetThreadLimit(1));
  MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetIdleThreadLimit(1));
  MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetIdleThreadGraceTimeout(0));
  MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetIdleThreadMaximumTimeout(aIdleTimeoutMS));
  MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetName(nsDependentCString(aName)));

  if (aShutdownMethod == ShutdownMethod::AutomaticShutdown &&
      NS_IsMainThread()) {
    if (nsCOMPtr<nsIObserverService> obs =
            do_GetService(NS_OBSERVERSERVICE_CONTRACTID)) {
      MOZ_ALWAYS_SUCCEEDS(
          obs->AddObserver(this, "xpcom-shutdown-threads", false));
    }
  }
}

static void LazyIdleThreadShutdown(nsThreadPool* aThreadPool,
                                   TaskQueue* aTaskQueue) {
  aTaskQueue->BeginShutdown();
  aTaskQueue->AwaitShutdownAndIdle();
  aThreadPool->Shutdown();
}

LazyIdleThread::~LazyIdleThread() {
  if (!mShutdown) {
    mOwningEventTarget->Dispatch(NS_NewRunnableFunction(
        "LazyIdleThread::~LazyIdleThread",
        [threadPool = mThreadPool, taskQueue = mTaskQueue] {
          LazyIdleThreadShutdown(threadPool, taskQueue);
        }));
  }
}

void LazyIdleThread::Shutdown() {
  ASSERT_OWNING_THREAD();

  if (!mShutdown) {
    mShutdown = true;
    LazyIdleThreadShutdown(mThreadPool, mTaskQueue);
  }
}

nsresult LazyIdleThread::SetListener(nsIThreadPoolListener* aListener) {
  return mThreadPool->SetListener(aListener);
}

NS_IMPL_ISUPPORTS(LazyIdleThread, nsIEventTarget, nsISerialEventTarget,
                  nsIObserver)

NS_IMETHODIMP
LazyIdleThread::DispatchFromScript(nsIRunnable* aEvent, DispatchFlags aFlags) {
  return Dispatch(do_AddRef(aEvent), aFlags);
}

NS_IMETHODIMP
LazyIdleThread::Dispatch(already_AddRefed<nsIRunnable> aEvent,
                         DispatchFlags aFlags) {
  return mTaskQueue->Dispatch(std::move(aEvent), aFlags);
}

NS_IMETHODIMP
LazyIdleThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
LazyIdleThread::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
  return mTaskQueue->RegisterShutdownTask(aTask);
}

NS_IMETHODIMP
LazyIdleThread::UnregisterShutdownTask(nsITargetShutdownTask* aTask) {
  return mTaskQueue->UnregisterShutdownTask(aTask);
}

nsIEventTarget::FeatureFlags LazyIdleThread::GetFeatures() {
  return mTaskQueue->GetFeatures();
}

NS_IMETHODIMP
LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread) {
  return mTaskQueue->IsOnCurrentThread(aIsOnCurrentThread);
}

NS_IMETHODIMP_(bool)
LazyIdleThread::IsOnCurrentThreadInfallible() {
  return mTaskQueue->IsOnCurrentThreadInfallible();
}

NS_IMETHODIMP
LazyIdleThread::Observe(nsISupports* /* aSubject */, const char* aTopic,
                        const char16_t* /* aData */) {
  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
  MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!");

  Shutdown();
  return NS_OK;
}

}  // namespace mozilla
