/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=2 autoindent cindent expandtab: */
/* 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 __NUWA_H_
#define __NUWA_H_

#include <setjmp.h>
#include <unistd.h>

#include "mozilla/Types.h"

/**
 * Nuwa is a goddess who created mankind according to her figure in the
 * ancient Chinese mythology.
 */

// Max number of top level protocol that can be handled by Nuwa process.
#ifndef NUWA_TOPLEVEL_MAX
#define NUWA_TOPLEVEL_MAX 8
#endif

struct NuwaProtoFdInfo {
    int protoId;
    int originFd;
    int newFds[2];
};

#define NUWA_NEWFD_PARENT 0
#define NUWA_NEWFD_CHILD 1

extern "C" {

/**
 * The following 3 methods are used to synchronously spawn the Nuwa process.
 * The typical usage is:
 *
 *   The IPC thread                                The main thread
 *   1. Receives the request.
 *   2. NuwaSpawnPrepare().
 *   3. Request main thread to
 *      spawn. --------------------------------------+
 *   4. NuwaSpawnWait().                             V
 *                                                 5. NuwaSpawn() to clone all
 *                                                    the threads in the Nuwa
 *                                                    process (including main
 *                                                    & IPC threads).
 *                                                 6. NuwaSpawn() finishes and
 *            +-------------------------------------- signals NuwaSpawnWait().
 *            V
 *   7. NuwaSpawnWait() returns.
 */

/**
 * Prepare for spawning a new process. The calling thread (typically the IPC
 * thread) will block further requests to spawn a new process.
 */
MFBT_API void NuwaSpawnPrepare();

/**
 * Called on the thread that receives the request. Used to wait until
 * NuwaSpawn() finishes and maintain the stack of the calling thread.
 */
MFBT_API void NuwaSpawnWait();

/**
 * Spawn a new content process, called in the Nuwa process. This has to run on
 * the main thread.
 *
 * @return The process ID of the new process.
 */
MFBT_API pid_t NuwaSpawn();

/**
 * The following methods are for marking threads created in the Nuwa process so
 * they can be frozen and then recreated in the spawned process. All threads
 * except the main thread must be marked by one of the following 4 methods;
 * otherwise, we have a thread that is created but unknown to us. The Nuwa
 * process will never become ready and this needs to be fixed.
 */

/**
 * Mark the current thread as supporting Nuwa. The thread will implicitly freeze
 * by calling a blocking method such as pthread_mutex_lock() or epoll_wait().
 *
 * @param recreate The custom function that will be called in the spawned
 *                 process, after the thread is recreated. Can be nullptr if no
 *                 custom function to be called after the thread is recreated.
 *                 Note that this function is called duing thread recreation
 *                 while other threads are frozen. It must not perform any
 *                 action (e.g. acquiring a mutex) that might depend on another
 *                 thread that is still blocked.
 * @param arg The argument passed to the custom function. Can be nullptr.
 */
MFBT_API void NuwaMarkCurrentThread(void (*recreate)(void *), void *arg);

/**
 * Mark the current thread as not supporting Nuwa. The calling thread will not
 * be automatically cloned from the Nuwa process to spawned process. If this
 * thread needs to be created in the spawned process, use NuwaAddConstructor()
 * or NuwaAddFinalConstructor() to do it.
 */
MFBT_API void NuwaSkipCurrentThread();

/**
 * Force the current thread to freeze explicitly at the current point.
 */
MFBT_API void NuwaFreezeCurrentThread();

/**
 * Helper functions for the NuwaCheckpointCurrentThread() macro.
 */
MFBT_API jmp_buf* NuwaCheckpointCurrentThread1();

MFBT_API bool NuwaCheckpointCurrentThread2(int setjmpCond);

/**
 * Set the point to recover after thread recreation. The calling thread is not
 * blocked. This is used for the thread that cannot freeze (i.e. the IPC
 * thread).
 */
#define NuwaCheckpointCurrentThread() \
  NuwaCheckpointCurrentThread2(setjmp(*NuwaCheckpointCurrentThread1()))

/**
 * The following methods are called in the initialization stage of the Nuwa
 * process.
 */

/**
 * Prepare for making the Nuwa process.
 */
MFBT_API void PrepareNuwaProcess();

/**
 * Make the current process a Nuwa process. Start to freeze the threads.
 */
MFBT_API void MakeNuwaProcess();

/**
 * Register a method to be invoked after a new process is spawned. The method
 * will be invoked on the main thread *before* recreating the other threads.
 * The registered method must not perform any action (e.g. acquiring a mutex)
 * that might depend on another thread that has not yet been recreated.
 *
 * @param construct The method to be invoked.
 * @param arg The argument passed to the method.
 */
MFBT_API void NuwaAddConstructor(void (*construct)(void *), void *arg);


/**
 * Register a method to be invoked after a new process is spawned and threads
 * are recreated. The method will be invoked on the main thread *after*
 * the other threads are recreated and fully functional.
 *
 * @param construct The method to be invoked.
 * @param arg The argument passed to the method.
 */
MFBT_API void NuwaAddFinalConstructor(void (*construct)(void *), void *arg);


/**
 * Register a method to be invoked after the current thread is recreated in the
 * spawned process. Note that this function is called while other threads are
 * frozen. It must not perform any action (e.g. acquiring a mutex) that might
 * depend on another thread that is still blocked.
 *
 * @param construct The method to be invoked.
 * @param arg The argument passed to the method.
 */
MFBT_API void NuwaAddThreadConstructor(void (*construct)(void *), void *arg);

/**
 * The methods to query the Nuwa-related states.
 */

/**
 * @return If the current process is the Nuwa process.
 */
MFBT_API bool IsNuwaProcess();

/**
 * @return If the Nuwa process is ready for spawning new processes.
 */
MFBT_API bool IsNuwaReady();

#if defined(DEBUG) || defined(ENABLE_TESTS)
/**
 * Asserts that aThread is not frozen.
 */
MFBT_API void NuwaAssertNotFrozen(unsigned int aThread,
                                  const char* aThreadName);
#else
#define NuwaAssertNotFrozen(aThread, aThreadName) do {} while(0);
#endif
};

#endif /* __NUWA_H_ */
