megenginelite-sys 1.8.2

A safe megenginelite wrapper in Rust
Documentation
/**
 * \file src/core/include/megbrain/system.h
 * MegEngine is Licensed under the Apache License, Version 2.0 (the "License")
 *
 * Copyright (c) 2014-2021 Megvii Inc. All rights reserved.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 */

#pragma once

#include "megbrain/utils/metahelper.h"
#include "megbrain/utils/thin/function.h"

#include <memory>
#include <string>
#include <thread>
#include <vector>

#include <cstring>

namespace mgb {
namespace sys {

//! set name of caller thread
MGE_WIN_DECLSPEC_FUC void set_thread_name(const std::string& name);

#if !__DEPLOY_ON_XP_SP2__
/*!
 * \brief get name of of given thread
 * \param tid thread id, or None to for the caller thread
 */
MGE_WIN_DECLSPEC_FUC std::string get_thread_name(Maybe<std::thread::id> tid = None);
#endif

//! get number of CPU cores on this system
MGE_WIN_DECLSPEC_FUC int get_cpu_count();

//! set cpu affinity for caller thread
MGE_WIN_DECLSPEC_FUC void set_cpu_affinity(const std::vector<int>& cpuset);

//! whether stderr supports ansi color code
MGE_WIN_DECLSPEC_FUC bool stderr_ansi_color();

//! get total ram and free ram in bytes
MGE_WIN_DECLSPEC_FUC std::pair<size_t, size_t> get_ram_status_bytes();

/*!
 * \brief invoke a function with time limit
 *
 * This class should be accessed via the singleton ins().
 *
 * It is currently used to implement algorithm profiling because:
 *
 *      1. Some algos may be much slower (sometimes even more than 1000x)
 *         than others. Therefore we want to set a time limit so current
 *         algo can take no longer best known time.
 *      2. There is no portable and elegant way to interrupt an asynchronous
 *         function. So here we proceed by invoking the function in a child
 *         process and kill the whole process on timeout.
 *      3. We use fork-exec to launch the child process rather than using a
 *         simple fork because some device drivers (e.g. CUDA) would be
 *         broken if we fork without exec.
 *
 * For SDK developers (i.e. MegBrain users):
 *
 *      1. TimedFuncInvoker is currently only implemented for linux
 *         platforms when MGB_BUILD_SLIM_SERVING is disabled.
 *      2. You need to implement a fork-exec entry point and call
 *         TimedFuncInvoker::ins().set_fork_exec_impl() to setup the system.
 *      3. An example implementation is available in the python module.
 *
 * For algorithm profiling implementations (i.e. MegBrain developers):
 *
 *      1. Register the function to be profiled via register_func(). Use
 *         invoke() to call it with a timeout.
 *      2. You may need AlgoChooserProfileCache to save the profiling
 *         result.
 *
 */
class TimedFuncInvoker : public NonCopyableObj {
    friend class TimedFuncInvokerTest;

    struct Del {
        void operator()(TimedFuncInvoker* p) { delete p; }
    };

    //! make an instance for test purpose
    static std::unique_ptr<TimedFuncInvoker, Del> make_test_ins();

protected:
    virtual ~TimedFuncInvoker() = default;

public:
    struct Result {
        size_t size = 0;
        std::unique_ptr<uint8_t[]> data;

        template <typename T>
        static Result from_pod(const T& val) {
            Result ret{sizeof(T), std::make_unique<uint8_t[]>(sizeof(T))};
            memcpy(ret.data.get(), &val, sizeof(T));
            return ret;
        }

        template <typename T>
        const T& as_single_pod() const {
            static_assert(is_location_invariant<T>::value, "bad type");
            mgb_assert(sizeof(T) == size);
            return *reinterpret_cast<const T*>(data.get());
        }
    };
    struct Param {
        size_t size = 0;
        const uint8_t* data = nullptr;

        // param is non-const ref to ensure caller has ownership; it
        // would not be modified
        template <typename T>
        static Param from_pod(T& val) {
            return {sizeof(T), reinterpret_cast<uint8_t*>(&val)};
        }

        template <typename T>
        const T& as_single_pod() const {
            static_assert(is_location_invariant<T>::value, "bad type");
            mgb_assert(sizeof(T) == size);
            return *reinterpret_cast<const T*>(data);
        }
    };
    //! exception thrown by invoke()
    class RemoteError final : public MegBrainError {
    public:
        using MegBrainError::MegBrainError;
    };

    using Func = thin_function<Result(const Param& param)>;
    using FuncInit = thin_function<void(const Param& param)>;
    using FuncId = size_t;

    /*!
     * \brief call fork() and exec(), and pass *arg* to the child
     * process, and the child process should pass *arg* back to
     * fork_exec_impl_mainloop()
     *
     * \param arg a null-terminated string argument
     * \return child process PID
     */
    using ForkExecImpl = thin_function<int(const std::string& arg)>;

    /*!
     * \brief set the function to implement fork-exec
     *
     * ForkExecImpl can not be implemented by TimedFuncInvoker because
     * it does not know the entry point of the compiled ELF.
     *
     * This method must be called from this server process, before any
     * call to invoke()
     */
    virtual void set_fork_exec_impl(const ForkExecImpl& impl) = 0;

    /*!
     * \brief to be called in the child process by ForkExecImpl
     *      registered by set_fork_exec_impl()
     *
     * \param arg the argument passed to ForkExecImpl
     */
    [[noreturn]] virtual void fork_exec_impl_mainloop(const char* arg) = 0;

    /*!
     * \brief register a function that can be invoked
     *
     * This method must be called both from the server process and from
     * the client process. It is usually called during global setup.
     *
     * \param func the function associated with \p id; its execution
     *      time can not exceed given timeout
     * \param func_init an initializer whose time would not be counted
     *      in the timeout setting
     */
    virtual void register_func(
            FuncId id, const Func& func, const FuncInit& func_init = {}) = 0;

    /*!
     * \brief invoke a function with given timeout
     *
     * This method must be called from the server process (i.e. main
     * mebrain process). This method is thread-safe.
     *
     * \param timeout timeout in seconds; if it is 0, timeout is
     *      disabled, and if worker not started yet, the function is
     *      invoked inplace
     * \return the return value if function finishes within given
     *      timeout; if the function could not finish in time, None
     *      would be returned.
     * \exception RemoteError thrown when function fails on the worker(
     *      function throws exception, not due to timeout); the error
     *      message would be forwarded to RemoteError::what()
     */
    virtual Maybe<Result> invoke(FuncId id, const Param& param, double timeout) = 0;

    /*!
     * \brief kill the worker process
     */
    virtual void kill_worker() = 0;

    //! global unique instance
    MGE_WIN_DECLSPEC_FUC static TimedFuncInvoker& ins();
};

}  // namespace sys
}  // namespace mgb

// vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}}