Windows: report uv_spawn() failure asynchronously

This commit is contained in:
Bert Belder 2011-08-02 04:58:43 +02:00 committed by Ryan Dahl
parent 6d940a3afc
commit 2e9a743da0
2 changed files with 105 additions and 47 deletions

View File

@ -178,6 +178,7 @@ typedef struct uv_buf_t {
HANDLE child_pipe; \
} stdio_pipes[3]; \
int exit_signal; \
DWORD spawn_errno; \
HANDLE wait_handle; \
HANDLE process_handle; \
HANDLE close_handle;

View File

@ -496,16 +496,16 @@ wchar_t* make_program_env(char** env_block) {
}
/*
* Called on Windows thread-pool thread to indicate that
* a child process has exited.
/*
* Called on Windows thread-pool thread to indicate that
* a child process has exited.
*/
static void CALLBACK exit_wait_callback(void* data, BOOLEAN didTimeout) {
uv_process_t* process = (uv_process_t*)data;
assert(didTimeout == FALSE);
assert(process);
memset(&process->exit_req.overlapped, 0, sizeof(process->exit_req.overlapped));
/* Post completed */
@ -518,13 +518,13 @@ static void CALLBACK exit_wait_callback(void* data, BOOLEAN didTimeout) {
}
/*
* Called on Windows thread-pool thread to indicate that
* UnregisterWaitEx has completed.
/*
* Called on Windows thread-pool thread to indicate that
* UnregisterWaitEx has completed.
*/
static void CALLBACK close_wait_callback(void* data, BOOLEAN didTimeout) {
uv_process_t* process = (uv_process_t*)data;
assert(didTimeout == FALSE);
assert(process);
@ -540,6 +540,54 @@ static void CALLBACK close_wait_callback(void* data, BOOLEAN didTimeout) {
}
/*
* Called on windows thread pool when CreateProcess failed. It writes an error
* message to the process' intended stderr and then posts a PROCESS_EXIT
* packet to the completion port.
*/
static DWORD WINAPI spawn_failure(void* data) {
char syscall[] = "CreateProcessW: ";
char unknown[] = "unknown error\n";
uv_process_t* process = (uv_process_t*) data;
HANDLE child_stderr = process->stdio_pipes[2].child_pipe;
char* buf = NULL;
DWORD count, written;
WriteFile(child_stderr, syscall, sizeof(syscall) - 1, &written, NULL);
count = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
process->spawn_errno,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR) &buf,
0,
NULL);
if (buf != NULL && count > 0) {
WriteFile(child_stderr, buf, count, &written, NULL);
LocalFree(buf);
} else {
WriteFile(child_stderr, unknown, sizeof(unknown) - 1, &written, NULL);
}
FlushFileBuffers(child_stderr);
memset(&process->exit_req.overlapped, 0, sizeof(process->exit_req.overlapped));
/* Post completed */
if (!PostQueuedCompletionStatus(LOOP->iocp,
0,
0,
&process->exit_req.overlapped)) {
uv_fatal_error(GetLastError(), "PostQueuedCompletionStatus");
}
return 0;
}
/* Called on main thread after a child process has exited. */
void uv_process_proc_exit(uv_process_t* handle) {
int i;
@ -559,15 +607,18 @@ void uv_process_proc_exit(uv_process_t* handle) {
handle->wait_handle = INVALID_HANDLE_VALUE;
}
/* Clean-up the process handle. */
if (handle->process_handle != INVALID_HANDLE_VALUE) {
/* Get the exit code. */
if (!GetExitCodeProcess(handle->process_handle, &exit_code)) {
exit_code = 1;
exit_code = 127;
}
/* Clean-up the process handle. */
CloseHandle(handle->process_handle);
handle->process_handle = INVALID_HANDLE_VALUE;
} else {
/* The process never even started in the first place. */
exit_code = 127;
}
/* Fire the exit callback. */
@ -618,10 +669,10 @@ static int uv_create_stdio_pipe_pair(uv_pipe_t* server_pipe, HANDLE* child_pipe,
char pipe_name[64];
DWORD mode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT;
if (server_pipe->type != UV_NAMED_PIPE) {
if (server_pipe->type != UV_NAMED_PIPE) {
uv_set_error(UV_EINVAL, 0);
err = -1;
goto done;
goto done;
}
/* Create server pipe handle. */
@ -681,13 +732,13 @@ done:
int uv_spawn(uv_process_t* process, uv_process_options_t options) {
int err, i;
int err = 0, i;
wchar_t* path;
int size;
wchar_t* application_path, *application, *arguments, *env, *cwd;
STARTUPINFOW startup;
PROCESS_INFORMATION info;
uv_process_init(process);
process->exit_cb = options.exit_cb;
@ -721,15 +772,15 @@ int uv_spawn(uv_process_t* process, uv_process_options_t options) {
GetEnvironmentVariableW(L"PATH", path, size * sizeof(wchar_t));
path[size - 1] = L'\0';
application_path = search_path(application,
application_path = search_path(application,
cwd,
path,
DEFAULT_PATH_EXT);
if (!application_path) {
uv_set_error(UV_EINVAL, 0);
err = -1;
goto done;
/* CreateProcess will fail, but this allows us to pass this error to */
/* the user asynchronously. */
application_path = application;
}
/* Create stdio pipes. */
@ -771,39 +822,45 @@ int uv_spawn(uv_process_t* process, uv_process_options_t options) {
startup.hStdOutput = process->stdio_pipes[1].child_pipe;
startup.hStdError = process->stdio_pipes[2].child_pipe;
if (!CreateProcessW(application_path,
arguments,
NULL,
NULL,
1,
CREATE_UNICODE_ENVIRONMENT,
env,
cwd,
&startup,
&info)) {
uv_set_sys_error(GetLastError());
err = -1;
goto done;
}
if (CreateProcessW(application_path,
arguments,
NULL,
NULL,
1,
CREATE_UNICODE_ENVIRONMENT,
env,
cwd,
&startup,
&info)) {
/* Spawn succeeded */
process->process_handle = info.hProcess;
process->pid = info.dwProcessId;
process->process_handle = info.hProcess;
process->pid = info.dwProcessId;
/* Setup notifications for when the child process exits. */
if (!RegisterWaitForSingleObject(&process->wait_handle, process->process_handle,
exit_wait_callback, (void*)process, INFINITE,
WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE)) {
uv_set_sys_error(GetLastError());
err = -1;
goto done;
}
/* Setup notifications for when the child process exits. */
if (!RegisterWaitForSingleObject(&process->wait_handle, process->process_handle,
exit_wait_callback, (void*)process, INFINITE,
WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE)) {
uv_fatal_error(GetLastError(), "RegisterWaitForSingleObject");
}
CloseHandle(info.hThread);
err = 0;
CloseHandle(info.hThread);
} else {
/* CreateProcessW failed, but this failure should be delivered */
/* asynchronously to retain unix compatibility. So pretent spawn */
/* succeeded, and start a thread instead that prints an error */
/* to the child's intended stderr. */
process->spawn_errno = GetLastError();
if (!QueueUserWorkItem(spawn_failure, process, WT_EXECUTEDEFAULT)) {
uv_fatal_error(GetLastError(), "QueueUserWorkItem");
}
}
done:
free(application_path);
free(application);
if (application_path != application) {
free(application_path);
}
free(arguments);
free(cwd);
free(env);