/* 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/. */

#ifndef mozilla_dom_workers_jsexecutionmanager_h_
#define mozilla_dom_workers_jsexecutionmanager_h_

#include <stdint.h>

#include <deque>

#include "MainThreadUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/CondVar.h"
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "nsISupports.h"

class nsIGlobalObject;
namespace mozilla {

class ErrorResult;

namespace dom {
class WorkerPrivate;

// The code in this file is responsible for throttling JS execution. It does
// this by introducing a JSExecutionManager class. An execution manager may be
// applied to any number of worker threads or a DocGroup on the main thread.
//
// JS environments associated with a JS execution manager may only execute on a
// certain amount of CPU cores in parallel.
//
// Whenever the main thread, or a worker thread begins executing JS it should
// make sure AutoRequestJSThreadExecution is present on the stack, in practice
// this is done by it being part of AutoEntryScript.
//
// Whenever the main thread may end up blocking on the activity of a worker
// thread, it should make sure to have an AutoYieldJSThreadExecution object
// on the stack.
//
// Whenever a worker thread may end up blocking on the main thread or the
// activity of another worker thread, it should make sure to have an
// AutoYieldJSThreadExecution object on the stack.
//
// Failure to do this may result in a deadlock. When a deadlock occurs due
// to these circumstances we will crash after 20 seconds.
//
// For the main thread this class should only be used in the case of an
// emergency surrounding exploitability of SharedArrayBuffers. A single
// execution manager will then be shared between all Workers and the main
// thread doc group containing the SharedArrayBuffer and ensure all this code
// only runs in a serialized manner. On the main thread we therefore may have
// 1 execution manager per DocGroup, as this is the granularity at which
// SharedArrayBuffers may be present.

class AutoRequestJSThreadExecution;
class AutoYieldJSThreadExecution;

// This class is used to regulate JS execution when for whatever reason we wish
// to throttle execution of multiple JS environments to occur with a certain
// maximum of synchronously executing threads. This should be used through
// the stack helper classes.
class JSExecutionManager {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JSExecutionManager)

  explicit JSExecutionManager(int32_t aMaxRunning = 1)
      : mMaxRunning(aMaxRunning) {}

  enum class RequestState { Granted, ExecutingAlready };

  static void Initialize();
  static void Shutdown();

  static JSExecutionManager* GetSABSerializationManager();

 private:
  friend class AutoRequestJSThreadExecution;
  friend class AutoYieldJSThreadExecution;
  ~JSExecutionManager() = default;

  // Methods used by Auto*JSThreadExecution

  // Request execution permission, returns ExecutingAlready if execution was
  // already granted or does not apply to this thread.
  RequestState RequestJSThreadExecution();

  // Yield JS execution, this asserts that permission is actually granted.
  void YieldJSThreadExecution();

  // Yield JS execution if permission was granted. This returns false if no
  // permission is granted. This method is needed because an execution manager
  // may have been set in between the ctor and dtor of
  // AutoYieldJSThreadExecution.
  bool YieldJSThreadExecutionIfGranted();

  RequestState RequestJSThreadExecutionMainThread();

  // Execution manager currently managing the main thread.
  // MainThread access only.
  static JSExecutionManager* mCurrentMTManager;

  // Workers waiting to be given permission for execution.
  // Guarded by mExecutionQueueMutex.
  std::deque<WorkerPrivate*> mExecutionQueue
      MOZ_GUARDED_BY(mExecutionQueueMutex);

  // Number of threads currently executing concurrently for this manager.
  // Guarded by mExecutionQueueMutex.
  int32_t mRunning MOZ_GUARDED_BY(mExecutionQueueMutex) = 0;

  // Number of threads allowed to run concurrently for environments managed
  // by this manager.
  // Guarded by mExecutionQueueMutex.
  int32_t mMaxRunning MOZ_GUARDED_BY(mExecutionQueueMutex) = 1;

  // Mutex that guards the execution queue and associated state.
  Mutex mExecutionQueueMutex =
      Mutex{"JSExecutionManager::sExecutionQueueMutex"};

  // ConditionVariables that blocked threads wait for.
  CondVar mExecutionQueueCondVar =
      CondVar{mExecutionQueueMutex, "JSExecutionManager::sExecutionQueueMutex"};

  // Whether the main thread is currently executing for this manager.
  // MainThread access only.
  bool mMainThreadIsExecuting = false;

  // Whether the main thread is currently awaiting permission to execute. Main
  // thread execution is always prioritized.
  // Guarded by mExecutionQueueMutex.
  bool mMainThreadAwaitingExecution MOZ_GUARDED_BY(mExecutionQueueMutex) =
      false;
};

// Helper for managing execution requests and allowing re-entrant permission
// requests.
class MOZ_STACK_CLASS AutoRequestJSThreadExecution {
 public:
  explicit AutoRequestJSThreadExecution(nsIGlobalObject* aGlobalObject,
                                        bool aIsMainThread);

  ~AutoRequestJSThreadExecution() {
    if (mExecutionGrantingManager) {
      mExecutionGrantingManager->YieldJSThreadExecution();
    }
    if (mIsMainThread) {
      if (mOldGrantingManager) {
        mOldGrantingManager->RequestJSThreadExecution();
      }
      JSExecutionManager::mCurrentMTManager = mOldGrantingManager;
    }
  }

 private:
  // The manager we obtained permission from. nullptr if permission was already
  // granted.
  RefPtr<JSExecutionManager> mExecutionGrantingManager;
  // The manager we had permission from before, and where permission should be
  // re-requested upon destruction.
  RefPtr<JSExecutionManager> mOldGrantingManager;

  // We store this for performance reasons.
  bool mIsMainThread;
};

// Class used to wrap code which essentially exits JS execution and may block
// on other threads.
class MOZ_STACK_CLASS AutoYieldJSThreadExecution {
 public:
  AutoYieldJSThreadExecution();

  ~AutoYieldJSThreadExecution() {
    if (mExecutionGrantingManager) {
      mExecutionGrantingManager->RequestJSThreadExecution();
      if (NS_IsMainThread()) {
        JSExecutionManager::mCurrentMTManager = mExecutionGrantingManager;
      }
    }
  }

 private:
  // Set to the granting manager if we were granted permission here.
  RefPtr<JSExecutionManager> mExecutionGrantingManager;
};

}  // namespace dom
}  // namespace mozilla

#endif  // mozilla_dom_workers_jsexecutionmanager_h_
