From 2e9a743da08d96ac7861400a316e3a9d3067979f Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Tue, 2 Aug 2011 04:58:43 +0200 Subject: [PATCH] Windows: report uv_spawn() failure asynchronously --- include/uv-win.h | 1 + src/win/process.c | 151 +++++++++++++++++++++++++++++++--------------- 2 files changed, 105 insertions(+), 47 deletions(-) diff --git a/include/uv-win.h b/include/uv-win.h index ec2429a6..622bbeab 100644 --- a/include/uv-win.h +++ b/include/uv-win.h @@ -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; diff --git a/src/win/process.c b/src/win/process.c index 6ad221f7..5ce2d60e 100644 --- a/src/win/process.c +++ b/src/win/process.c @@ -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);