dart-sdk/runtime/bin/process_win.cc
asiva be3c482bc3 Reinitialize the static fields in the corresponding Init functions so
that we do not have any state mismatch when the Dart VM is initialzed
and cleaned up multiple times.

TEST=existing unit test in the engine which is failing in a flaky manner

Change-Id: I073ca2b53a8c3d386be46b5222d547a29513714a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/207642
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Siva Annamalai <asiva@google.com>
2021-07-21 18:12:34 +00:00

1147 lines
36 KiB
C++

// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "platform/globals.h"
#if defined(DART_HOST_OS_WINDOWS)
#include "bin/process.h"
#include <process.h> // NOLINT
#include <psapi.h> // NOLINT
#include <vector>
#include "bin/builtin.h"
#include "bin/dartutils.h"
#include "bin/eventhandler.h"
#include "bin/lockers.h"
#include "bin/socket.h"
#include "bin/thread.h"
#include "bin/utils.h"
#include "bin/utils_win.h"
#include "platform/syslog.h"
namespace dart {
namespace bin {
static const int kReadHandle = 0;
static const int kWriteHandle = 1;
int Process::global_exit_code_ = 0;
Mutex* Process::global_exit_code_mutex_ = nullptr;
Process::ExitHook Process::exit_hook_ = NULL;
// ProcessInfo is used to map a process id to the process handle,
// wait handle for registered exit code event and the pipe used to
// communicate the exit code of the process to Dart.
// ProcessInfo objects are kept in the static singly-linked
// ProcessInfoList.
class ProcessInfo {
public:
ProcessInfo(DWORD process_id,
HANDLE process_handle,
HANDLE wait_handle,
HANDLE exit_pipe)
: process_id_(process_id),
process_handle_(process_handle),
wait_handle_(wait_handle),
exit_pipe_(exit_pipe) {}
~ProcessInfo() {
BOOL success = CloseHandle(process_handle_);
if (!success) {
FATAL("Failed to close process handle");
}
success = CloseHandle(exit_pipe_);
if (!success) {
FATAL("Failed to close process exit code pipe");
}
}
DWORD pid() { return process_id_; }
HANDLE process_handle() { return process_handle_; }
HANDLE wait_handle() { return wait_handle_; }
HANDLE exit_pipe() { return exit_pipe_; }
ProcessInfo* next() { return next_; }
void set_next(ProcessInfo* next) { next_ = next; }
private:
// Process id.
DWORD process_id_;
// Process handle.
HANDLE process_handle_;
// Wait handle identifying the exit-code wait operation registered
// with RegisterWaitForSingleObject.
HANDLE wait_handle_;
// File descriptor for pipe to report exit code.
HANDLE exit_pipe_;
// Link to next ProcessInfo object in the singly-linked list.
ProcessInfo* next_;
DISALLOW_COPY_AND_ASSIGN(ProcessInfo);
};
// Singly-linked list of ProcessInfo objects for all active processes
// started from Dart.
class ProcessInfoList {
public:
static void Init();
static void Cleanup();
static void AddProcess(DWORD pid, HANDLE handle, HANDLE pipe) {
// Register a callback to extract the exit code, when the process
// is signaled. The callback runs in a independent thread from the OS pool.
// Because the callback depends on the process list containing
// the process, lock the mutex until the process is added to the list.
MutexLocker locker(mutex_);
HANDLE wait_handle = INVALID_HANDLE_VALUE;
BOOL success = RegisterWaitForSingleObject(
&wait_handle, handle, &ExitCodeCallback, reinterpret_cast<PVOID>(pid),
INFINITE, WT_EXECUTEONLYONCE);
if (!success) {
FATAL("Failed to register exit code wait operation.");
}
ProcessInfo* info = new ProcessInfo(pid, handle, wait_handle, pipe);
// Mutate the process list under the mutex.
info->set_next(active_processes_);
active_processes_ = info;
}
static bool LookupProcess(DWORD pid,
HANDLE* handle,
HANDLE* wait_handle,
HANDLE* pipe) {
MutexLocker locker(mutex_);
ProcessInfo* current = active_processes_;
while (current != NULL) {
if (current->pid() == pid) {
*handle = current->process_handle();
*wait_handle = current->wait_handle();
*pipe = current->exit_pipe();
return true;
}
current = current->next();
}
return false;
}
static void RemoveProcess(DWORD pid) {
MutexLocker locker(mutex_);
ProcessInfo* prev = NULL;
ProcessInfo* current = active_processes_;
while (current != NULL) {
if (current->pid() == pid) {
if (prev == NULL) {
active_processes_ = current->next();
} else {
prev->set_next(current->next());
}
delete current;
return;
}
prev = current;
current = current->next();
}
}
private:
// Callback called when an exit code is available from one of the
// processes in the list.
static void CALLBACK ExitCodeCallback(PVOID data, BOOLEAN timed_out) {
if (timed_out) {
return;
}
DWORD pid = reinterpret_cast<UINT_PTR>(data);
HANDLE handle;
HANDLE wait_handle;
HANDLE exit_pipe;
bool success = LookupProcess(pid, &handle, &wait_handle, &exit_pipe);
if (!success) {
FATAL("Failed to lookup process in list of active processes");
}
// Unregister the event in a non-blocking way.
BOOL ok = UnregisterWait(wait_handle);
if (!ok && (GetLastError() != ERROR_IO_PENDING)) {
FATAL("Failed unregistering wait operation");
}
// Get and report the exit code to Dart.
int exit_code;
ok = GetExitCodeProcess(handle, reinterpret_cast<DWORD*>(&exit_code));
if (!ok) {
FATAL1("GetExitCodeProcess failed %d\n", GetLastError());
}
int negative = 0;
if (exit_code < 0) {
exit_code = abs(exit_code);
negative = 1;
}
int message[2] = {exit_code, negative};
DWORD written;
ok = WriteFile(exit_pipe, message, sizeof(message), &written, NULL);
// If the process has been closed, the read end of the exit
// pipe has been closed. It is therefore not a problem that
// WriteFile fails with a closed pipe error
// (ERROR_NO_DATA). Other errors should not happen.
if (ok && (written != sizeof(message))) {
FATAL("Failed to write entire process exit message");
} else if (!ok && (GetLastError() != ERROR_NO_DATA)) {
FATAL1("Failed to write exit code: %d", GetLastError());
}
// Remove the process from the list of active processes.
RemoveProcess(pid);
}
// Linked list of ProcessInfo objects for all active processes
// started from Dart code.
static ProcessInfo* active_processes_;
// Mutex protecting all accesses to the linked list of active
// processes.
static Mutex* mutex_;
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(ProcessInfoList);
};
ProcessInfo* ProcessInfoList::active_processes_ = NULL;
Mutex* ProcessInfoList::mutex_ = nullptr;
// Types of pipes to create.
enum NamedPipeType { kInheritRead, kInheritWrite, kInheritNone };
// Create a pipe for communicating with a new process. The handles array
// will contain the read and write ends of the pipe. Based on the type
// one of the handles will be inheritable.
// NOTE: If this function returns false the handles might have been allocated
// and the caller should make sure to close them in case of an error.
static bool CreateProcessPipe(HANDLE handles[2],
wchar_t* pipe_name,
NamedPipeType type) {
// Security attributes describing an inheritable handle.
SECURITY_ATTRIBUTES inherit_handle;
inherit_handle.nLength = sizeof(SECURITY_ATTRIBUTES);
inherit_handle.bInheritHandle = TRUE;
inherit_handle.lpSecurityDescriptor = NULL;
if (type == kInheritRead) {
handles[kWriteHandle] =
CreateNamedPipeW(pipe_name, PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_WAIT,
1, // Number of pipes
1024, // Out buffer size
1024, // In buffer size
0, // Timeout in ms
NULL);
if (handles[kWriteHandle] == INVALID_HANDLE_VALUE) {
Syslog::PrintErr("CreateNamedPipe failed %d\n", GetLastError());
return false;
}
handles[kReadHandle] =
CreateFileW(pipe_name, GENERIC_READ, 0, &inherit_handle, OPEN_EXISTING,
FILE_READ_ATTRIBUTES | FILE_FLAG_OVERLAPPED, NULL);
if (handles[kReadHandle] == INVALID_HANDLE_VALUE) {
Syslog::PrintErr("CreateFile failed %d\n", GetLastError());
return false;
}
} else {
ASSERT((type == kInheritWrite) || (type == kInheritNone));
handles[kReadHandle] =
CreateNamedPipeW(pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_WAIT,
1, // Number of pipes
1024, // Out buffer size
1024, // In buffer size
0, // Timeout in ms
NULL);
if (handles[kReadHandle] == INVALID_HANDLE_VALUE) {
Syslog::PrintErr("CreateNamedPipe failed %d\n", GetLastError());
return false;
}
handles[kWriteHandle] = CreateFileW(
pipe_name, GENERIC_WRITE, 0,
(type == kInheritWrite) ? &inherit_handle : NULL, OPEN_EXISTING,
FILE_WRITE_ATTRIBUTES | FILE_FLAG_OVERLAPPED, NULL);
if (handles[kWriteHandle] == INVALID_HANDLE_VALUE) {
Syslog::PrintErr("CreateFile failed %d\n", GetLastError());
return false;
}
}
return true;
}
static void CloseProcessPipe(HANDLE handles[2]) {
for (int i = kReadHandle; i < kWriteHandle; i++) {
if (handles[i] != INVALID_HANDLE_VALUE) {
if (!CloseHandle(handles[i])) {
Syslog::PrintErr("CloseHandle failed %d\n", GetLastError());
}
handles[i] = INVALID_HANDLE_VALUE;
}
}
}
static void CloseProcessPipes(HANDLE handles1[2],
HANDLE handles2[2],
HANDLE handles3[2],
HANDLE handles4[2]) {
CloseProcessPipe(handles1);
CloseProcessPipe(handles2);
CloseProcessPipe(handles3);
CloseProcessPipe(handles4);
}
static int SetOsErrorMessage(char** os_error_message) {
int error_code = GetLastError();
const int kMaxMessageLength = 256;
wchar_t message[kMaxMessageLength];
FormatMessageIntoBuffer(error_code, message, kMaxMessageLength);
*os_error_message = StringUtilsWin::WideToUtf8(message);
return error_code;
}
// Open an inheritable handle to NUL.
static HANDLE OpenNul() {
SECURITY_ATTRIBUTES inherit_handle;
inherit_handle.nLength = sizeof(SECURITY_ATTRIBUTES);
inherit_handle.bInheritHandle = TRUE;
inherit_handle.lpSecurityDescriptor = NULL;
HANDLE nul = CreateFile(L"NUL", GENERIC_READ | GENERIC_WRITE, 0,
&inherit_handle, OPEN_EXISTING, 0, NULL);
if (nul == INVALID_HANDLE_VALUE) {
Syslog::PrintErr("CloseHandle failed %d\n", GetLastError());
}
return nul;
}
typedef BOOL(WINAPI* InitProcThreadAttrListFn)(LPPROC_THREAD_ATTRIBUTE_LIST,
DWORD,
DWORD,
PSIZE_T);
typedef BOOL(WINAPI* UpdateProcThreadAttrFn)(LPPROC_THREAD_ATTRIBUTE_LIST,
DWORD,
DWORD_PTR,
PVOID,
SIZE_T,
PVOID,
PSIZE_T);
typedef VOID(WINAPI* DeleteProcThreadAttrListFn)(LPPROC_THREAD_ATTRIBUTE_LIST);
static InitProcThreadAttrListFn init_proc_thread_attr_list = NULL;
static UpdateProcThreadAttrFn update_proc_thread_attr = NULL;
static DeleteProcThreadAttrListFn delete_proc_thread_attr_list = NULL;
static Mutex* initialized_mutex = nullptr;
static bool load_attempted = false;
static bool EnsureInitialized() {
HMODULE kernel32_module = GetModuleHandleW(L"kernel32.dll");
if (!load_attempted) {
MutexLocker locker(initialized_mutex);
if (load_attempted) {
return (delete_proc_thread_attr_list != NULL);
}
init_proc_thread_attr_list = reinterpret_cast<InitProcThreadAttrListFn>(
GetProcAddress(kernel32_module, "InitializeProcThreadAttributeList"));
update_proc_thread_attr = reinterpret_cast<UpdateProcThreadAttrFn>(
GetProcAddress(kernel32_module, "UpdateProcThreadAttribute"));
delete_proc_thread_attr_list = reinterpret_cast<DeleteProcThreadAttrListFn>(
GetProcAddress(kernel32_module, "DeleteProcThreadAttributeList"));
load_attempted = true;
return (delete_proc_thread_attr_list != NULL);
}
return (delete_proc_thread_attr_list != NULL);
}
const int kMaxPipeNameSize = 80;
template <int Count>
static int GenerateNames(wchar_t pipe_names[Count][kMaxPipeNameSize]) {
UUID uuid;
RPC_STATUS status = UuidCreateSequential(&uuid);
if ((status != RPC_S_OK) && (status != RPC_S_UUID_LOCAL_ONLY)) {
return status;
}
RPC_WSTR uuid_string;
status = UuidToStringW(&uuid, &uuid_string);
if (status != RPC_S_OK) {
return status;
}
for (int i = 0; i < Count; i++) {
static const wchar_t* prefix = L"\\\\.\\Pipe\\dart";
_snwprintf(pipe_names[i], kMaxPipeNameSize, L"%s_%s_%d", prefix,
uuid_string, i + 1);
}
status = RpcStringFreeW(&uuid_string);
if (status != RPC_S_OK) {
return status;
}
return 0;
}
class ProcessStarter {
public:
ProcessStarter(const char* path,
char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
intptr_t environment_length,
ProcessStartMode mode,
intptr_t* in,
intptr_t* out,
intptr_t* err,
intptr_t* id,
intptr_t* exit_handler,
char** os_error_message)
: path_(path),
working_directory_(working_directory),
mode_(mode),
in_(in),
out_(out),
err_(err),
id_(id),
exit_handler_(exit_handler),
os_error_message_(os_error_message) {
stdin_handles_[kReadHandle] = INVALID_HANDLE_VALUE;
stdin_handles_[kWriteHandle] = INVALID_HANDLE_VALUE;
stdout_handles_[kReadHandle] = INVALID_HANDLE_VALUE;
stdout_handles_[kWriteHandle] = INVALID_HANDLE_VALUE;
stderr_handles_[kReadHandle] = INVALID_HANDLE_VALUE;
stderr_handles_[kWriteHandle] = INVALID_HANDLE_VALUE;
exit_handles_[kReadHandle] = INVALID_HANDLE_VALUE;
exit_handles_[kWriteHandle] = INVALID_HANDLE_VALUE;
// Transform input strings to system format.
const wchar_t* system_path = StringUtilsWin::Utf8ToWide(path_);
wchar_t** system_arguments;
system_arguments = reinterpret_cast<wchar_t**>(
Dart_ScopeAllocate(arguments_length * sizeof(*system_arguments)));
for (int i = 0; i < arguments_length; i++) {
system_arguments[i] = StringUtilsWin::Utf8ToWide(arguments[i]);
}
// Compute command-line length.
int command_line_length = wcslen(system_path);
for (int i = 0; i < arguments_length; i++) {
command_line_length += wcslen(system_arguments[i]);
}
// Account for null termination and one space per argument.
command_line_length += arguments_length + 1;
// Put together command-line string.
command_line_ = reinterpret_cast<wchar_t*>(
Dart_ScopeAllocate(command_line_length * sizeof(*command_line_)));
int len = 0;
int remaining = command_line_length;
int written =
_snwprintf(command_line_ + len, remaining, L"%s", system_path);
len += written;
remaining -= written;
ASSERT(remaining >= 0);
for (int i = 0; i < arguments_length; i++) {
written = _snwprintf(command_line_ + len, remaining, L" %s",
system_arguments[i]);
len += written;
remaining -= written;
ASSERT(remaining >= 0);
}
// Create environment block if an environment is supplied.
environment_block_ = NULL;
if (environment != NULL) {
wchar_t** system_environment;
system_environment = reinterpret_cast<wchar_t**>(
Dart_ScopeAllocate(environment_length * sizeof(*system_environment)));
// Convert environment strings to system strings.
for (intptr_t i = 0; i < environment_length; i++) {
system_environment[i] = StringUtilsWin::Utf8ToWide(environment[i]);
}
// An environment block is a sequence of zero-terminated strings
// followed by a block-terminating zero char.
intptr_t block_size = 1;
for (intptr_t i = 0; i < environment_length; i++) {
block_size += wcslen(system_environment[i]) + 1;
}
environment_block_ = reinterpret_cast<wchar_t*>(
Dart_ScopeAllocate(block_size * sizeof(*environment_block_)));
intptr_t block_index = 0;
for (intptr_t i = 0; i < environment_length; i++) {
intptr_t len = wcslen(system_environment[i]);
intptr_t result = _snwprintf(environment_block_ + block_index, len,
L"%s", system_environment[i]);
ASSERT(result == len);
block_index += len;
environment_block_[block_index++] = '\0';
}
// Block-terminating zero char.
environment_block_[block_index++] = '\0';
ASSERT(block_index == block_size);
}
system_working_directory_ = NULL;
if (working_directory_ != NULL) {
system_working_directory_ =
StringUtilsWin::Utf8ToWide(working_directory_);
}
attribute_list_ = NULL;
}
~ProcessStarter() {
if (attribute_list_ != NULL) {
delete_proc_thread_attr_list(attribute_list_);
}
}
int Start() {
// Create pipes required.
int err = CreatePipes();
if (err != 0) {
return err;
}
// Setup info structures.
STARTUPINFOEXW startup_info;
ZeroMemory(&startup_info, sizeof(startup_info));
startup_info.StartupInfo.cb = sizeof(startup_info);
if (mode_ != kInheritStdio) {
startup_info.StartupInfo.hStdInput = stdin_handles_[kReadHandle];
startup_info.StartupInfo.hStdOutput = stdout_handles_[kWriteHandle];
startup_info.StartupInfo.hStdError = stderr_handles_[kWriteHandle];
startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
bool supports_proc_thread_attr_lists = EnsureInitialized();
if (supports_proc_thread_attr_lists) {
// Setup the handles to inherit. We only want to inherit the three
// handles for stdin, stdout and stderr.
SIZE_T size = 0;
// The call to determine the size of an attribute list always fails with
// ERROR_INSUFFICIENT_BUFFER and that error should be ignored.
if (!init_proc_thread_attr_list(NULL, 1, 0, &size) &&
(GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
return CleanupAndReturnError();
}
attribute_list_ = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(
Dart_ScopeAllocate(size));
ZeroMemory(attribute_list_, size);
if (!init_proc_thread_attr_list(attribute_list_, 1, 0, &size)) {
return CleanupAndReturnError();
}
inherited_handles_ = {stdin_handles_[kReadHandle],
stdout_handles_[kWriteHandle],
stderr_handles_[kWriteHandle]};
if (!update_proc_thread_attr(
attribute_list_, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
inherited_handles_.data(),
inherited_handles_.size() * sizeof(HANDLE), NULL, NULL)) {
return CleanupAndReturnError();
}
startup_info.lpAttributeList = attribute_list_;
}
}
PROCESS_INFORMATION process_info;
ZeroMemory(&process_info, sizeof(process_info));
// Create process.
DWORD creation_flags =
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT;
if (!Process::ModeIsAttached(mode_)) {
creation_flags |= DETACHED_PROCESS;
}
BOOL result = CreateProcessW(
NULL, // ApplicationName
command_line_,
NULL, // ProcessAttributes
NULL, // ThreadAttributes
TRUE, // InheritHandles
creation_flags, environment_block_, system_working_directory_,
reinterpret_cast<STARTUPINFOW*>(&startup_info), &process_info);
if (result == 0) {
return CleanupAndReturnError();
}
if (mode_ != kInheritStdio) {
CloseHandle(stdin_handles_[kReadHandle]);
CloseHandle(stdout_handles_[kWriteHandle]);
CloseHandle(stderr_handles_[kWriteHandle]);
}
if (Process::ModeIsAttached(mode_)) {
ProcessInfoList::AddProcess(process_info.dwProcessId,
process_info.hProcess,
exit_handles_[kWriteHandle]);
}
if (mode_ != kDetached) {
// Connect the three stdio streams.
if (Process::ModeHasStdio(mode_)) {
FileHandle* stdin_handle = new FileHandle(stdin_handles_[kWriteHandle]);
FileHandle* stdout_handle =
new FileHandle(stdout_handles_[kReadHandle]);
FileHandle* stderr_handle =
new FileHandle(stderr_handles_[kReadHandle]);
*in_ = reinterpret_cast<intptr_t>(stdout_handle);
*out_ = reinterpret_cast<intptr_t>(stdin_handle);
*err_ = reinterpret_cast<intptr_t>(stderr_handle);
}
if (Process::ModeIsAttached(mode_)) {
FileHandle* exit_handle = new FileHandle(exit_handles_[kReadHandle]);
*exit_handler_ = reinterpret_cast<intptr_t>(exit_handle);
}
}
CloseHandle(process_info.hThread);
// Return process id.
*id_ = process_info.dwProcessId;
return 0;
}
int CreatePipes() {
// Generate unique pipe names for the four named pipes needed.
wchar_t pipe_names[4][kMaxPipeNameSize];
int status = GenerateNames<4>(pipe_names);
if (status != 0) {
SetOsErrorMessage(os_error_message_);
Syslog::PrintErr("UuidCreateSequential failed %d\n", status);
return status;
}
if (mode_ != kDetached) {
// Open pipes for stdin, stdout, stderr and for communicating the exit
// code.
if (Process::ModeHasStdio(mode_)) {
if (!CreateProcessPipe(stdin_handles_, pipe_names[0], kInheritRead) ||
!CreateProcessPipe(stdout_handles_, pipe_names[1], kInheritWrite) ||
!CreateProcessPipe(stderr_handles_, pipe_names[2], kInheritWrite)) {
return CleanupAndReturnError();
}
}
// Only open exit code pipe for non detached processes.
if (Process::ModeIsAttached(mode_)) {
if (!CreateProcessPipe(exit_handles_, pipe_names[3], kInheritNone)) {
return CleanupAndReturnError();
}
}
} else {
// Open NUL for stdin, stdout, and stderr.
stdin_handles_[kReadHandle] = OpenNul();
if (stdin_handles_[kReadHandle] == INVALID_HANDLE_VALUE) {
return CleanupAndReturnError();
}
stdout_handles_[kWriteHandle] = OpenNul();
if (stdout_handles_[kWriteHandle] == INVALID_HANDLE_VALUE) {
return CleanupAndReturnError();
}
stderr_handles_[kWriteHandle] = OpenNul();
if (stderr_handles_[kWriteHandle] == INVALID_HANDLE_VALUE) {
return CleanupAndReturnError();
}
}
return 0;
}
int CleanupAndReturnError() {
int error_code = SetOsErrorMessage(os_error_message_);
CloseProcessPipes(stdin_handles_, stdout_handles_, stderr_handles_,
exit_handles_);
return error_code;
}
HANDLE stdin_handles_[2];
HANDLE stdout_handles_[2];
HANDLE stderr_handles_[2];
HANDLE exit_handles_[2];
const wchar_t* system_working_directory_;
wchar_t* command_line_;
wchar_t* environment_block_;
std::vector<HANDLE> inherited_handles_;
LPPROC_THREAD_ATTRIBUTE_LIST attribute_list_;
const char* path_;
const char* working_directory_;
ProcessStartMode mode_;
intptr_t* in_;
intptr_t* out_;
intptr_t* err_;
intptr_t* id_;
intptr_t* exit_handler_;
char** os_error_message_;
private:
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(ProcessStarter);
};
int Process::Start(Namespace* namespc,
const char* path,
char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
intptr_t environment_length,
ProcessStartMode mode,
intptr_t* in,
intptr_t* out,
intptr_t* err,
intptr_t* id,
intptr_t* exit_handler,
char** os_error_message) {
ProcessStarter starter(path, arguments, arguments_length, working_directory,
environment, environment_length, mode, in, out, err,
id, exit_handler, os_error_message);
return starter.Start();
}
class BufferList : public BufferListBase {
public:
BufferList() : read_pending_(true) {}
// Indicate that data has been read into the buffer provided to
// overlapped read.
void DataIsRead(intptr_t size) {
ASSERT(read_pending_ == true);
set_data_size(data_size() + size);
set_free_size(free_size() - size);
ASSERT(free_size() >= 0);
read_pending_ = false;
}
// The access to the read buffer for overlapped read.
bool GetReadBuffer(uint8_t** buffer, intptr_t* size) {
ASSERT(!read_pending_);
if (free_size() == 0) {
if (!Allocate()) {
return false;
}
}
ASSERT(free_size() > 0);
ASSERT(free_size() <= kBufferSize);
*buffer = FreeSpaceAddress();
*size = free_size();
read_pending_ = true;
return true;
}
intptr_t GetDataSize() { return data_size(); }
uint8_t* GetFirstDataBuffer() {
ASSERT(head() != NULL);
ASSERT(head() == tail());
ASSERT(data_size() <= kBufferSize);
return head()->data();
}
void FreeDataBuffer() { Free(); }
private:
bool read_pending_;
DISALLOW_COPY_AND_ASSIGN(BufferList);
};
class OverlappedHandle {
public:
OverlappedHandle() {}
void Init(HANDLE handle, HANDLE event) {
handle_ = handle;
event_ = event;
ClearOverlapped();
}
bool HasEvent(HANDLE event) { return (event_ == event); }
bool Read() {
// Get the data read as a result of a completed overlapped operation.
if (overlapped_.InternalHigh > 0) {
buffer_.DataIsRead(overlapped_.InternalHigh);
} else {
buffer_.DataIsRead(0);
}
// Keep reading until error or pending operation.
while (true) {
ClearOverlapped();
uint8_t* buffer;
intptr_t buffer_size;
if (!buffer_.GetReadBuffer(&buffer, &buffer_size)) {
return false;
}
BOOL ok = ReadFile(handle_, buffer, buffer_size, NULL, &overlapped_);
if (!ok) {
return (GetLastError() == ERROR_IO_PENDING);
}
buffer_.DataIsRead(overlapped_.InternalHigh);
}
}
Dart_Handle GetData() { return buffer_.GetData(); }
intptr_t GetDataSize() { return buffer_.GetDataSize(); }
uint8_t* GetFirstDataBuffer() { return buffer_.GetFirstDataBuffer(); }
void FreeDataBuffer() { return buffer_.FreeDataBuffer(); }
#if defined(DEBUG)
bool IsEmpty() const { return buffer_.IsEmpty(); }
#endif
void Close() {
CloseHandle(handle_);
CloseHandle(event_);
handle_ = INVALID_HANDLE_VALUE;
overlapped_.hEvent = INVALID_HANDLE_VALUE;
}
private:
void ClearOverlapped() {
memset(&overlapped_, 0, sizeof(overlapped_));
overlapped_.hEvent = event_;
}
OVERLAPPED overlapped_;
HANDLE handle_;
HANDLE event_;
BufferList buffer_;
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(OverlappedHandle);
};
bool Process::Wait(intptr_t pid,
intptr_t in,
intptr_t out,
intptr_t err,
intptr_t exit_event,
ProcessResult* result) {
// Close input to the process right away.
reinterpret_cast<FileHandle*>(in)->Close();
// All pipes created to the sub-process support overlapped IO.
FileHandle* stdout_handle = reinterpret_cast<FileHandle*>(out);
ASSERT(stdout_handle->SupportsOverlappedIO());
FileHandle* stderr_handle = reinterpret_cast<FileHandle*>(err);
ASSERT(stderr_handle->SupportsOverlappedIO());
FileHandle* exit_handle = reinterpret_cast<FileHandle*>(exit_event);
ASSERT(exit_handle->SupportsOverlappedIO());
// Create three events for overlapped IO. These are created as already
// signalled to ensure they have read called at least once.
static const int kHandles = 3;
HANDLE events[kHandles];
for (int i = 0; i < kHandles; i++) {
events[i] = CreateEvent(NULL, FALSE, TRUE, NULL);
}
// Setup the structure for handling overlapped IO.
OverlappedHandle oh[kHandles];
oh[0].Init(stdout_handle->handle(), events[0]);
oh[1].Init(stderr_handle->handle(), events[1]);
oh[2].Init(exit_handle->handle(), events[2]);
// Continue until all handles are closed.
int alive = kHandles;
while (alive > 0) {
// Blocking call waiting for events from the child process.
DWORD wait_result = WaitForMultipleObjects(alive, events, FALSE, INFINITE);
// Find the handle signalled.
int index = wait_result - WAIT_OBJECT_0;
for (int i = 0; i < kHandles; i++) {
if (oh[i].HasEvent(events[index])) {
bool ok = oh[i].Read();
if (!ok) {
if (GetLastError() == ERROR_BROKEN_PIPE) {
oh[i].Close();
alive--;
if (index < alive) {
events[index] = events[alive];
}
} else if (err != ERROR_IO_PENDING) {
DWORD e = GetLastError();
oh[0].Close();
oh[1].Close();
oh[2].Close();
SetLastError(e);
return false;
}
}
break;
}
}
}
// All handles closed and all data read.
result->set_stdout_data(oh[0].GetData());
result->set_stderr_data(oh[1].GetData());
DEBUG_ASSERT(oh[0].IsEmpty());
DEBUG_ASSERT(oh[1].IsEmpty());
// Calculate the exit code.
ASSERT(oh[2].GetDataSize() == 8);
uint32_t exit_codes[2];
memmove(&exit_codes, oh[2].GetFirstDataBuffer(), sizeof(exit_codes));
oh[2].FreeDataBuffer();
intptr_t exit_code = exit_codes[0];
intptr_t negative = exit_codes[1];
if (negative != 0) {
exit_code = -exit_code;
}
result->set_exit_code(exit_code);
return true;
}
bool Process::Kill(intptr_t id, int signal) {
USE(signal); // signal is not used on Windows.
HANDLE process_handle;
HANDLE wait_handle;
HANDLE exit_pipe;
// First check the process info list for the process to get a handle to it.
bool success = ProcessInfoList::LookupProcess(id, &process_handle,
&wait_handle, &exit_pipe);
// For detached processes we don't have the process registered in the
// process info list. Try to look it up through the OS.
if (!success) {
process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, id);
// The process is already dead.
if (process_handle == INVALID_HANDLE_VALUE) {
return false;
}
}
BOOL result = TerminateProcess(process_handle, -1);
return result ? true : false;
}
void Process::TerminateExitCodeHandler() {
// Nothing needs to be done on Windows.
}
intptr_t Process::CurrentProcessId() {
return static_cast<intptr_t>(GetCurrentProcessId());
}
int64_t Process::CurrentRSS() {
// Although the documentation at
// https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getprocessmemoryinfo
// claims that GetProcessMemoryInfo is UWP compatible, it is actually not
// hence this function cannot work when compiled in UWP mode.
#ifdef DART_TARGET_OS_WINDOWS_UWP
return -1;
#else
PROCESS_MEMORY_COUNTERS pmc;
if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
return -1;
}
return pmc.WorkingSetSize;
#endif
}
int64_t Process::MaxRSS() {
#ifdef DART_TARGET_OS_WINDOWS_UWP
return -1;
#else
PROCESS_MEMORY_COUNTERS pmc;
if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
return -1;
}
return pmc.PeakWorkingSetSize;
#endif
}
static SignalInfo* signal_handlers = NULL;
static Mutex* signal_mutex = nullptr;
SignalInfo::~SignalInfo() {
FileHandle* file_handle = reinterpret_cast<FileHandle*>(fd_);
file_handle->Close();
file_handle->Release();
}
BOOL WINAPI SignalHandler(DWORD signal) {
MutexLocker lock(signal_mutex);
const SignalInfo* handler = signal_handlers;
bool handled = false;
while (handler != NULL) {
if (handler->signal() == signal) {
int value = 0;
SocketBase::Write(handler->fd(), &value, 1, SocketBase::kAsync);
handled = true;
}
handler = handler->next();
}
return handled;
}
intptr_t GetWinSignal(intptr_t signal) {
switch (signal) {
case kSighup:
return CTRL_CLOSE_EVENT;
case kSigint:
return CTRL_C_EVENT;
default:
return -1;
}
}
intptr_t Process::SetSignalHandler(intptr_t signal) {
signal = GetWinSignal(signal);
if (signal == -1) {
SetLastError(ERROR_NOT_SUPPORTED);
return -1;
}
// Generate a unique pipe name for the named pipe.
wchar_t pipe_name[kMaxPipeNameSize];
int status = GenerateNames<1>(&pipe_name);
if (status != 0) {
return status;
}
HANDLE fds[2];
if (!CreateProcessPipe(fds, pipe_name, kInheritNone)) {
int error_code = GetLastError();
CloseProcessPipe(fds);
SetLastError(error_code);
return -1;
}
MutexLocker lock(signal_mutex);
FileHandle* write_handle = new FileHandle(fds[kWriteHandle]);
write_handle->EnsureInitialized(EventHandler::delegate());
intptr_t write_fd = reinterpret_cast<intptr_t>(write_handle);
if (signal_handlers == NULL) {
if (SetConsoleCtrlHandler(SignalHandler, true) == 0) {
int error_code = GetLastError();
// Since SetConsoleCtrlHandler failed, the IO completion port will
// never receive an event for this handle, and will therefore never
// release the reference Retained by EnsureInitialized(). So, we
// have to do a second Release() here.
write_handle->Release();
write_handle->Release();
CloseProcessPipe(fds);
SetLastError(error_code);
return -1;
}
}
signal_handlers =
new SignalInfo(write_fd, signal, /*oldact=*/nullptr, signal_handlers);
return reinterpret_cast<intptr_t>(new FileHandle(fds[kReadHandle]));
}
void Process::ClearSignalHandler(intptr_t signal, Dart_Port port) {
signal = GetWinSignal(signal);
if (signal == -1) {
return;
}
MutexLocker lock(signal_mutex);
SignalInfo* handler = signal_handlers;
while (handler != NULL) {
bool remove = false;
if (handler->signal() == signal) {
if ((port == ILLEGAL_PORT) || (handler->port() == port)) {
if (signal_handlers == handler) {
signal_handlers = handler->next();
}
handler->Unlink();
FileHandle* file_handle = reinterpret_cast<FileHandle*>(handler->fd());
file_handle->Release();
remove = true;
}
}
SignalInfo* next = handler->next();
if (remove) {
delete handler;
}
handler = next;
}
if (signal_handlers == NULL) {
USE(SetConsoleCtrlHandler(SignalHandler, false));
}
}
void Process::ClearSignalHandlerByFd(intptr_t fd, Dart_Port port) {
MutexLocker lock(signal_mutex);
SignalInfo* handler = signal_handlers;
while (handler != NULL) {
bool remove = false;
if (handler->fd() == fd) {
if ((port == ILLEGAL_PORT) || (handler->port() == port)) {
if (signal_handlers == handler) {
signal_handlers = handler->next();
}
handler->Unlink();
FileHandle* file_handle = reinterpret_cast<FileHandle*>(handler->fd());
file_handle->Release();
remove = true;
}
}
SignalInfo* next = handler->next();
if (remove) {
delete handler;
}
handler = next;
}
if (signal_handlers == NULL) {
USE(SetConsoleCtrlHandler(SignalHandler, false));
}
}
void ProcessInfoList::Init() {
active_processes_ = nullptr;
ASSERT(ProcessInfoList::mutex_ == nullptr);
ProcessInfoList::mutex_ = new Mutex();
}
void ProcessInfoList::Cleanup() {
ASSERT(ProcessInfoList::mutex_ != nullptr);
delete ProcessInfoList::mutex_;
ProcessInfoList::mutex_ = nullptr;
}
void Process::Init() {
ProcessInfoList::Init();
signal_handlers = NULL;
ASSERT(signal_mutex == nullptr);
signal_mutex = new Mutex();
ASSERT(initialized_mutex == nullptr);
initialized_mutex = new Mutex();
load_attempted = false;
ASSERT(Process::global_exit_code_mutex_ == nullptr);
Process::global_exit_code_mutex_ = new Mutex();
}
void Process::Cleanup() {
ClearAllSignalHandlers();
ASSERT(signal_mutex != nullptr);
delete signal_mutex;
signal_mutex = nullptr;
ASSERT(initialized_mutex != nullptr);
delete initialized_mutex;
initialized_mutex = nullptr;
ASSERT(Process::global_exit_code_mutex_ != nullptr);
delete Process::global_exit_code_mutex_;
Process::global_exit_code_mutex_ = nullptr;
ProcessInfoList::Cleanup();
}
} // namespace bin
} // namespace dart
#endif // defined(DART_HOST_OS_WINDOWS)