263 lines
9.7 KiB
C++
263 lines
9.7 KiB
C++
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
#include "win32_process.h"
|
|
|
|
#include <conio.h>
|
|
#include <tvm/runtime/logging.h>
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
|
|
#include <cstdio>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
#include "rpc_server.h"
|
|
|
|
using namespace std::chrono;
|
|
using namespace tvm::runtime;
|
|
|
|
namespace {
|
|
// The prefix path for the memory mapped file used to store IPC information
|
|
const std::string kMemoryMapPrefix = "/MAPPED_FILE/TVM_RPC";
|
|
// Used to construct unique names for named resources in the parent process
|
|
const std::string kParent = "parent";
|
|
// Used to construct unique names for named resources in the child process
|
|
const std::string kChild = "child";
|
|
// The timeout of the WIN32 events, in the parent and the child
|
|
const milliseconds kEventTimeout(2000);
|
|
|
|
// Used to create unique WIN32 mmap paths and event names
|
|
int child_counter_ = 0;
|
|
|
|
/*!
|
|
* \brief HandleDeleter Deleter for UniqueHandle smart pointer
|
|
* \param handle The WIN32 HANDLE to manage
|
|
*/
|
|
struct HandleDeleter {
|
|
void operator()(HANDLE handle) const {
|
|
if (handle != INVALID_HANDLE_VALUE && handle != nullptr) {
|
|
CloseHandle(handle);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*!
|
|
* \brief UniqueHandle Smart pointer to manage a WIN32 HANDLE
|
|
*/
|
|
using UniqueHandle = std::unique_ptr<void, HandleDeleter>;
|
|
|
|
/*!
|
|
* \brief MakeUniqueHandle Helper method to construct a UniqueHandle
|
|
* \param handle The WIN32 HANDLE to manage
|
|
*/
|
|
UniqueHandle MakeUniqueHandle(HANDLE handle) {
|
|
if (handle == INVALID_HANDLE_VALUE || handle == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
return UniqueHandle(handle);
|
|
}
|
|
|
|
/*!
|
|
* \brief GetSocket Gets the socket info from the parent process and duplicates the socket
|
|
* \param mmap_path The path to the memory mapped info set by the parent
|
|
*/
|
|
SOCKET GetSocket(const std::string& mmap_path) {
|
|
WSAPROTOCOL_INFO protocol_info;
|
|
|
|
const std::string parent_event_name = mmap_path + kParent;
|
|
const std::string child_event_name = mmap_path + kChild;
|
|
|
|
// Open the events
|
|
UniqueHandle parent_file_mapping_event;
|
|
if ((parent_file_mapping_event = MakeUniqueHandle(
|
|
OpenEventA(SYNCHRONIZE, false, parent_event_name.c_str()))) == nullptr) {
|
|
TVM_FFI_THROW(InternalError) << "OpenEvent() failed: " << GetLastError();
|
|
}
|
|
|
|
UniqueHandle child_file_mapping_event;
|
|
if ((child_file_mapping_event = MakeUniqueHandle(
|
|
OpenEventA(EVENT_MODIFY_STATE, false, child_event_name.c_str()))) == nullptr) {
|
|
TVM_FFI_THROW(InternalError) << "OpenEvent() failed: " << GetLastError();
|
|
}
|
|
|
|
// Wait for the parent to set the event, notifying WSAPROTOCOL_INFO is ready to be read
|
|
if (WaitForSingleObject(parent_file_mapping_event.get(), uint32_t(kEventTimeout.count())) !=
|
|
WAIT_OBJECT_0) {
|
|
TVM_FFI_THROW(InternalError) << "WaitForSingleObject() failed: " << GetLastError();
|
|
}
|
|
|
|
const UniqueHandle file_map =
|
|
MakeUniqueHandle(OpenFileMappingA(FILE_MAP_READ | FILE_MAP_WRITE, false, mmap_path.c_str()));
|
|
if (!file_map) {
|
|
LOG(INFO) << "CreateFileMapping() failed: " << GetLastError();
|
|
}
|
|
|
|
void* map_view = MapViewOfFile(file_map.get(), FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
|
|
|
|
SOCKET sock_duplicated = INVALID_SOCKET;
|
|
|
|
if (map_view != nullptr) {
|
|
memcpy(&protocol_info, map_view, sizeof(WSAPROTOCOL_INFO));
|
|
UnmapViewOfFile(map_view);
|
|
|
|
// Creates the duplicate socket, that was created in the parent
|
|
sock_duplicated =
|
|
WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, &protocol_info, 0, 0);
|
|
|
|
// Let the parent know we are finished duplicating the socket
|
|
SetEvent(child_file_mapping_event.get());
|
|
} else {
|
|
TVM_FFI_THROW(InternalError) << "MapViewOfFile() failed: " << GetLastError();
|
|
}
|
|
|
|
return sock_duplicated;
|
|
}
|
|
} // Anonymous namespace
|
|
|
|
namespace tvm {
|
|
namespace runtime {
|
|
/*!
|
|
* \brief SpawnRPCChild Spawns a child process with a given timeout to run
|
|
* \param fd The client socket to duplicate in the child
|
|
* \param timeout The time in seconds to wait for the child to complete before termination
|
|
*/
|
|
void SpawnRPCChild(SOCKET fd, seconds timeout) {
|
|
STARTUPINFOA startup_info;
|
|
|
|
memset(&startup_info, 0, sizeof(startup_info));
|
|
startup_info.cb = sizeof(startup_info);
|
|
|
|
std::string file_map_path = kMemoryMapPrefix + std::to_string(child_counter_++);
|
|
|
|
const std::string parent_event_name = file_map_path + kParent;
|
|
const std::string child_event_name = file_map_path + kChild;
|
|
|
|
// Create an event to let the child know the socket info was set to the mmap file
|
|
UniqueHandle parent_file_mapping_event;
|
|
if ((parent_file_mapping_event = MakeUniqueHandle(
|
|
CreateEventA(nullptr, true, false, parent_event_name.c_str()))) == nullptr) {
|
|
TVM_FFI_THROW(InternalError) << "CreateEvent for parent file mapping failed";
|
|
}
|
|
|
|
UniqueHandle child_file_mapping_event;
|
|
// An event to let the parent know the socket info was read from the mmap file
|
|
if ((child_file_mapping_event = MakeUniqueHandle(
|
|
CreateEventA(nullptr, true, false, child_event_name.c_str()))) == nullptr) {
|
|
TVM_FFI_THROW(InternalError) << "CreateEvent for child file mapping failed";
|
|
}
|
|
|
|
char current_executable[MAX_PATH];
|
|
|
|
// Get the full path of the current executable
|
|
GetModuleFileNameA(nullptr, current_executable, MAX_PATH);
|
|
|
|
std::string child_command_line = current_executable;
|
|
child_command_line += " server --child_proc=";
|
|
child_command_line += file_map_path;
|
|
|
|
// CreateProcessA requires a non const char*, so we copy our std::string
|
|
auto command_line_ptr = std::make_unique<char[]>(child_command_line.size() + 1);
|
|
strcpy(command_line_ptr.get(), child_command_line.c_str());
|
|
|
|
PROCESS_INFORMATION child_process_info;
|
|
if (CreateProcessA(nullptr, command_line_ptr.get(), nullptr, nullptr, false, CREATE_NO_WINDOW,
|
|
nullptr, nullptr, &startup_info, &child_process_info)) {
|
|
// Child process and thread handles must be closed, so wrapped in RAII
|
|
auto child_process_handle = MakeUniqueHandle(child_process_info.hProcess);
|
|
auto child_process_thread_handle = MakeUniqueHandle(child_process_info.hThread);
|
|
|
|
WSAPROTOCOL_INFO protocol_info;
|
|
// Get info needed to duplicate the socket
|
|
if (WSADuplicateSocket(fd, child_process_info.dwProcessId, &protocol_info) == SOCKET_ERROR) {
|
|
TVM_FFI_THROW(InternalError) << "WSADuplicateSocket(): failed. Error =" << WSAGetLastError();
|
|
}
|
|
|
|
// Create a mmap file to store the info needed for duplicating the SOCKET in the child proc
|
|
UniqueHandle file_map =
|
|
MakeUniqueHandle(CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
|
|
sizeof(WSAPROTOCOL_INFO), file_map_path.c_str()));
|
|
if (!file_map) {
|
|
LOG(INFO) << "CreateFileMapping() failed: " << GetLastError();
|
|
}
|
|
|
|
if (GetLastError() == ERROR_ALREADY_EXISTS) {
|
|
TVM_FFI_THROW(InternalError) << "CreateFileMapping(): mapping file already exists";
|
|
} else {
|
|
void* map_view = MapViewOfFile(file_map.get(), FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
|
|
|
|
if (map_view != nullptr) {
|
|
memcpy(map_view, &protocol_info, sizeof(WSAPROTOCOL_INFO));
|
|
UnmapViewOfFile(map_view);
|
|
|
|
// Let child proc know the mmap file is ready to be read
|
|
SetEvent(parent_file_mapping_event.get());
|
|
|
|
// Wait for the child to finish reading mmap file
|
|
if (WaitForSingleObject(child_file_mapping_event.get(), uint32_t(kEventTimeout.count())) !=
|
|
WAIT_OBJECT_0) {
|
|
TerminateProcess(child_process_handle.get(), 0);
|
|
TVM_FFI_THROW(InternalError)
|
|
<< "WaitForSingleObject for child file mapping timed out. Terminating child "
|
|
"process.";
|
|
}
|
|
} else {
|
|
TerminateProcess(child_process_handle.get(), 0);
|
|
TVM_FFI_THROW(InternalError) << "MapViewOfFile() failed: " << GetLastError();
|
|
}
|
|
}
|
|
|
|
const DWORD process_timeout =
|
|
timeout.count() ? uint32_t(duration_cast<milliseconds>(timeout).count()) : INFINITE;
|
|
|
|
// Wait for child process to exit, or hit configured timeout
|
|
if (WaitForSingleObject(child_process_handle.get(), process_timeout) != WAIT_OBJECT_0) {
|
|
LOG(INFO) << "Child process timeout. Terminating.";
|
|
TerminateProcess(child_process_handle.get(), 0);
|
|
}
|
|
} else {
|
|
LOG(INFO) << "Create child process failed: " << GetLastError();
|
|
}
|
|
}
|
|
/*!
|
|
* \brief ChildProcSocketHandler Ran from the child process and runs server to handle the client
|
|
* socket \param mmap_path The memory mapped file path that will contain the information to
|
|
* duplicate the client socket from the parent
|
|
*/
|
|
void ChildProcSocketHandler(const std::string& mmap_path) {
|
|
SOCKET socket;
|
|
|
|
// Set high thread priority to avoid the thread scheduler from
|
|
// interfering with any measurements in the RPC server.
|
|
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
|
|
|
if ((socket = GetSocket(mmap_path)) != INVALID_SOCKET) {
|
|
tvm::runtime::ServerLoopFromChild(socket);
|
|
} else {
|
|
TVM_FFI_THROW(InternalError) << "GetSocket() failed";
|
|
}
|
|
}
|
|
} // namespace runtime
|
|
} // namespace tvm
|