#include "ipc_imp.h" #if __linux__ #include #include #endif #ifdef __ANDROID__ #include #include #endif namespace ipc_imp { namespace { //! max count if shm #define MAX_SHM_COUNT 15 struct ServerConfig { #ifdef _LITE_SUPPORT_IPC pid_t server_id; #else size_t server_id; #endif void* cb; int fd_s[2]; int fd_c[2]; fd_set select_s; fd_set select_c; void* shm_ptr[MAX_SHM_COUNT]; size_t shm_mem_conut; //! all shm use the same shm_size size_t shm_size; }; static LITE_MUTEX m_mtx; static ServerConfig server_config; #ifdef _LITE_SUPPORT_IPC static size_t config_shm_memory() { //! default config to 10MB size_t shm_size = 10 * 1024 * 1024; //! env to config LITE_DEBUG_IPC_SHM_SIZE //! for example , export LITE_DEBUG_IPC_SHM_SIZE=20, means config SHM size 20MB if (auto env = ::std::getenv("LITE_DEBUG_IPC_SHM_SIZE")) shm_size = std::stoi(env) * 1024 * 1024; #ifdef __ANDROID__ //! special for Android prop, attention: getprop may need permission char buf[PROP_VALUE_MAX]; if (__system_property_get("LITE_DEBUG_IPC_SHM_SIZE", buf) > 0) { shm_size = std::stoi(buf) * 1024 * 1024; } #endif return shm_size; } //! FIXME: detail at create_server(), at this stage, we only support enable lite fork //! debug by: LITE_enable_lite_ipc_debug, after issue fix, may support env: //! LITE_ENABLE_C_API_WITH_FORK_MODE // bool config_enable_debug_fork() { // //! debug off, we only support enable fork debug mode with env // //! LITE_ENABLE_C_API_WITH_FORK_MODE, not support api to config // //! as we will fork as soon as possible by __attribute__((constructor)), // //! user may not have to chance to call any normal api before it // bool ret = false; // //! env to config LITE_ENABLE_C_API_WITH_FORK_MODE // //! for example , export LITE_ENABLE_C_API_WITH_FORK_MODE=1, means enable LITE c // api // //! with fork mode // if (auto env = ::std::getenv("LITE_ENABLE_C_API_WITH_FORK_MODE")) { // if (std::stoi(env) > 0) { // ret = true; // } // } // //#ifdef __ANDROID__ // //! special for Android prop, attention: getprop may need permission // char buf[PROP_VALUE_MAX]; // if (__system_property_get("LITE_ENABLE_C_API_WITH_FORK_MODE", buf) > 0) { // ret = std::stoi(buf); // if (std::stoi(buf) > 0) { // ret = true; // } // } //#endif // // return ret; //} #endif static bool is_enable_debug_fork = false; //! internal recycle server when IPC_ASSERT happen static void recycle_server() { static struct timeval tv = {1, 0}; struct MsgBody msg; msg.type = IPC_SERVER_EXIT; if (server_config.server_id > 0) { send_msg(&msg, &tv); } } #define ipc_unlikely(v) __builtin_expect((v), 0) #define IPC_ASSERT(expr, msg) \ do { \ if (ipc_unlikely(!(expr))) { \ LITE_ERROR("ipc fatal error: assert failed: %s with msg: %s", #expr, msg); \ recycle_server(); \ __builtin_trap(); \ } \ } while (0) #ifdef _LITE_SUPPORT_IPC static size_t config_shm_memory_count() { //! default config to 2 size_t shm_cnt = 2; //! env to config LITE_DEBUG_IPC_SHM_COUNT //! for example , export LITE_DEBUG_IPC_SHM_COUNT=8, means config SHM count 8 if (auto env = ::std::getenv("LITE_DEBUG_IPC_SHM_COUNT")) shm_cnt = std::stoi(env); #ifdef __ANDROID__ //! special for Android prop, attention: getprop may need permission char buf[PROP_VALUE_MAX]; if (__system_property_get("LITE_DEBUG_IPC_SHM_COUNT", buf) > 0) { shm_cnt = std::stoi(buf); } #endif IPC_ASSERT( shm_cnt >= 2 && shm_cnt <= MAX_SHM_COUNT, "error config LITE_DEBUG_IPC_SHM_COUNT, should be range of [2, " "MAX_SHM_COUNT]"); return shm_cnt; } #endif #ifdef _LITE_SUPPORT_IPC static void handle_remote_call(struct MsgBody* msg) { LITE_DEBUG("into %s: %d", __func__, __LINE__); IPC_ASSERT( server_config.cb, "handle_remote_call failed: can not find valid " "remote_call_cb, please call " "register_remote_call_cb firstly!!"); remote_call_cb cb = (remote_call_cb)server_config.cb; cb(msg); } static void* ipc_mmap( void* addr, size_t length, int prot, int flags, int fd, off_t offset) { void* ret = mmap(addr, length, prot, flags, fd, offset); IPC_ASSERT(ret != MAP_FAILED, "call mmap failed"); return ret; } static int ipc_munmap(void* addr, size_t length) { int ret = munmap(addr, length); IPC_ASSERT(0 == ret, "call munmap failed"); return ret; } #endif //! start server as soon as possible //! FIXME: when use __attribute__((constructor)) on clang, will init before all //! static class object, which will lead to flatbuffer runtime issue, env config //! with init_priority, do not take effect on diff cpp src file on clang // static __attribute__((constructor)) void create_server() { void create_server() { #ifdef _LITE_SUPPORT_IPC LITE_LOCK_GUARD(m_mtx); LITE_LOG("try to config lite fork debug model"); if (is_enable_debug_fork) return; is_enable_debug_fork = true; //! is_enable_debug_fork = config_enable_debug_fork(); //! init default server_config server_config.server_id = 0; if (!is_enable_debug_fork) return; server_config.cb = nullptr; server_config.shm_size = config_shm_memory(); server_config.shm_mem_conut = config_shm_memory_count(); for (size_t i = 0; i < server_config.shm_mem_conut; i++) { server_config.shm_ptr[i] = ipc_mmap( NULL, server_config.shm_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); } IPC_ASSERT(-1 != pipe(server_config.fd_s), "create server pipe failed"); IPC_ASSERT(-1 != pipe(server_config.fd_c), "create client pipe failed"); FD_ZERO(&server_config.select_s); FD_ZERO(&server_config.select_c); //! config server and client FD_SET(server_config.fd_s[0], &server_config.select_s); FD_SET(server_config.fd_c[0], &server_config.select_c); server_config.server_id = fork(); IPC_ASSERT(server_config.server_id >= 0, "call fork failed"); if (server_config.server_id > 0) { LITE_LOG("create lite_ipc_server success pid is: %d", server_config.server_id); } else { std::string server_name = "lite_ipc_server"; // TODO: __APPLE__ do not support PR_SET_NAME and PR_SET_PDEATHSIG // if caller crash, no have chance to send IPC_SERVER_EXIT #if __linux__ LITE_LOG("start server with name: %s....", server_name.c_str()); prctl(PR_SET_NAME, (unsigned long)server_name.c_str(), 0, 0, 0); //! auto die if father crash prctl(PR_SET_PDEATHSIG, SIGKILL); #else LITE_LOG("start server...."); #endif while (1) { LITE_DEBUG("lite_ipc_server wait msg now....."); int res = select(server_config.fd_s[0] + 1, &server_config.select_s, NULL, NULL, NULL); IPC_ASSERT( res > 0, "select issue happened or timeout(but we do not support timeout)"); struct MsgBody msg; size_t r_size = read(server_config.fd_s[0], &msg, sizeof(msg)); IPC_ASSERT(r_size == sizeof(msg), "broken pipe msg"); struct MsgBody response; response.type = IPC_SERVER_RESPONSE; switch (msg.type) { case IPC_CALL_REMOTE_API: LITE_DEBUG("handle remote call"); handle_remote_call(&msg); break; case IPC_CONFIG_REMOTE_HANDLE_API: LITE_DEBUG("handle register remote cb"); server_config.cb = msg.cb; break; default: IPC_ASSERT(IPC_SERVER_EXIT == msg.type, "code issue happened!!"); } size_t w_size = write(server_config.fd_c[1], &response, sizeof(response)); IPC_ASSERT(w_size == sizeof(response), "write pip failed"); if (IPC_SERVER_EXIT == msg.type) { LITE_DEBUG("handle exit now"); for (size_t i = 0; i < server_config.shm_mem_conut; i++) { ipc_munmap(server_config.shm_ptr[i], server_config.shm_size); } exit(0); } } } #else //! TODO: imp for Windows with CreateProcess server_config.server_id = 0; LITE_ERROR("lite do not support fork debug ipc on this PLATFORM"); __builtin_trap(); return; #endif } } // namespace //////////////////////////////////////////////// api imp ///////////////////////// void register_remote_call_cb(remote_call_cb cb) { IPC_ASSERT(!server_config.cb, "already register remote_call_cb"); IPC_ASSERT(cb, "invalid remote_call_cb"); IPC_ASSERT(server_config.server_id, "register cb need server already up"); server_config.cb = (void*)cb; static struct timeval tv = {5, 0}; struct MsgBody msg; msg.type = IPC_CONFIG_REMOTE_HANDLE_API; msg.cb = (void*)cb; send_msg(&msg, &tv); } struct MsgBody send_msg(struct MsgBody* msg, struct timeval* timeout) { #ifdef _LITE_SUPPORT_IPC IPC_ASSERT(server_config.server_id > 0, "server not ready"); if (IPC_CALL_REMOTE_API == msg->type) { IPC_ASSERT( server_config.cb, "can not find valid remote_call_cb, please " "call register_remote_call_cb firstly!!"); } //! send msg to server size_t w_size = write(server_config.fd_s[1], msg, sizeof(struct MsgBody)); IPC_ASSERT(w_size == sizeof(struct MsgBody), "write pipe failed"); //! now wait server response struct MsgBody response; LITE_DEBUG("wait server response"); int res = select( server_config.fd_c[0] + 1, &server_config.select_c, NULL, NULL, timeout); if (0 == res) { LITE_ERROR("wait server timeout"); } IPC_ASSERT(res > 0, "select issue happened or timeout"); size_t r_size = read(server_config.fd_c[0], &response, sizeof(response)); IPC_ASSERT(sizeof(response) == r_size, "broken pipe msg"); IPC_ASSERT(IPC_SERVER_RESPONSE == response.type, "error server response type"); return response; #else struct MsgBody response; LITE_ERROR("This code should not be called"); __builtin_trap(); return response; #endif } bool is_server() { return !server_config.server_id; } bool is_enable_ipc() { return is_enable_debug_fork; } void join_server() { #ifdef _LITE_SUPPORT_IPC if (!is_enable_debug_fork) return; int ret; waitpid(server_config.server_id, &ret, 0); if (ret) { //! when server crash, we mark server_config.server_id to 0 //! to prevent handle more msg, for example recycle_server server_config.server_id = 0; } IPC_ASSERT( ret == 0, "child process exit !zero, please check server log for detail"); #endif } void* base_get_shm_ptr(size_t index) { return server_config.shm_ptr[index]; } size_t base_get_shm_count() { return server_config.shm_mem_conut; } size_t base_get_shm_size() { return server_config.shm_size; } void enable_lite_ipc_debug() { create_server(); } } // namespace ipc_imp