X-Git-Url: http://repo.macrolet.net/gitweb/?a=blobdiff_plain;f=src%2Fruntime%2Fwin32-os.c;h=b9b1ebed564c9b05b436a988998e291fc48d64ae;hb=eac461c1f1ca91cfe282c779291d582ed6b336cb;hp=f27f5edd170c06fd112c0c07d9bdaefa896ad5a0;hpb=7572e0506af331534e6f97b027d56e8bea09410c;p=sbcl.git diff --git a/src/runtime/win32-os.c b/src/runtime/win32-os.c index f27f5ed..b9b1ebe 100644 --- a/src/runtime/win32-os.c +++ b/src/runtime/win32-os.c @@ -54,6 +54,7 @@ #include #include +#include #include "validate.h" #include "thread.h" @@ -377,6 +378,28 @@ void os_preinit() int os_number_of_processors = 1; +BOOL WINAPI CancelIoEx(HANDLE handle, LPOVERLAPPED overlapped); +typeof(CancelIoEx) *ptr_CancelIoEx; +BOOL WINAPI CancelSynchronousIo(HANDLE threadHandle); +typeof(CancelSynchronousIo) *ptr_CancelSynchronousIo; + +#define RESOLVE(hmodule,fn) \ + do { \ + ptr_##fn = (typeof(ptr_##fn)) \ + GetProcAddress(hmodule,#fn); \ + } while (0) + +static void resolve_optional_imports() +{ + HMODULE kernel32 = GetModuleHandleA("kernel32"); + if (kernel32) { + RESOLVE(kernel32,CancelIoEx); + RESOLVE(kernel32,CancelSynchronousIo); + } +} + +#undef RESOLVE + void os_init(char *argv[], char *envp[]) { SYSTEM_INFO system_info; @@ -389,6 +412,8 @@ void os_init(char *argv[], char *envp[]) os_number_of_processors = system_info.dwNumberOfProcessors; base_seh_frame = get_seh_frame(); + + resolve_optional_imports(); } static inline boolean local_thread_stack_address_p(os_vm_address_t address) @@ -1102,6 +1127,387 @@ console_handle_p(HANDLE handle) ((((int)(intptr_t)handle)&3)==3); } +/* Atomically mark current thread as (probably) doing synchronous I/O + * on handle, if no cancellation is requested yet (and return TRUE), + * otherwise clear thread's I/O cancellation flag and return false. + */ +static +boolean io_begin_interruptible(HANDLE handle) +{ + /* No point in doing it unless OS supports cancellation from other + * threads */ + if (!ptr_CancelIoEx) + return 1; + + if (!__sync_bool_compare_and_swap(&this_thread->synchronous_io_handle_and_flag, + 0, handle)) { + ResetEvent(this_thread->private_events.events[0]); + this_thread->synchronous_io_handle_and_flag = 0; + return 0; + } + return 1; +} + +/* Unmark current thread as (probably) doing synchronous I/O; if an + * I/O cancellation was requested, postpone it until next + * io_begin_interruptible */ +static void +io_end_interruptible(HANDLE handle) +{ + if (!ptr_CancelIoEx) + return; + __sync_bool_compare_and_swap(&this_thread->synchronous_io_handle_and_flag, + handle, 0); +} + +/* Documented limit for ReadConsole/WriteConsole is 64K bytes. + Real limit observed on W2K-SP3 is somewhere in between 32KiB and 64Kib... +*/ +#define MAX_CONSOLE_TCHARS 16384 + +int +win32_write_unicode_console(HANDLE handle, void * buf, int count) +{ + DWORD written = 0; + DWORD nchars; + BOOL result; + nchars = count>>1; + if (nchars>MAX_CONSOLE_TCHARS) nchars = MAX_CONSOLE_TCHARS; + + if (!io_begin_interruptible(handle)) { + errno = EINTR; + return -1; + } + result = WriteConsoleW(handle,buf,nchars,&written,NULL); + io_end_interruptible(handle); + + if (result) { + if (!written) { + errno = EINTR; + return -1; + } else { + return 2*written; + } + } else { + DWORD err = GetLastError(); + odxprint(io,"WriteConsole fails => %u\n", err); + errno = (err==ERROR_OPERATION_ABORTED ? EINTR : EIO); + return -1; + } +} + +/* + * (AK writes:) + * + * It may be unobvious, but (probably) the most straightforward way of + * providing some sane CL:LISTEN semantics for line-mode console + * channel requires _dedicated input thread_. + * + * LISTEN should return true iff the next (READ-CHAR) won't have to + * wait. As our console may be shared with another process, entirely + * out of our control, looking at the events in PeekConsoleEvent + * result (and searching for #\Return) doesn't cut it. + * + * We decided that console input thread must do something smarter than + * a bare loop of continuous ReadConsoleW(). On Unix, user experience + * with the terminal is entirely unaffected by the fact that some + * process does (or doesn't) call read(); the situation on MS Windows + * is different. + * + * Echo output and line editing present on MS Windows while some + * process is waiting in ReadConsole(); otherwise all input events are + * buffered. If our thread were calling ReadConsole() all the time, it + * would feel like Unix cooked mode. + * + * But we don't write a Unix emulator here, even if it sometimes feels + * like that; therefore preserving this aspect of console I/O seems a + * good thing to us. + * + * LISTEN itself becomes trivial with dedicated input thread, but the + * goal stated above -- provide `native' user experience with blocked + * console -- don't play well with this trivial implementation. + * + * What's currently implemented is a compromise, looking as something + * in between Unix cooked mode and Win32 line mode. + * + * 1. As long as no console I/O function is called (incl. CL:LISTEN), + * console looks `blocked': no echo, no line editing. + * + * 2. (READ-CHAR), (READ-SEQUENCE) and other functions doing real + * input result in the ReadConsole request (in a dedicated thread); + * + * 3. Once ReadConsole is called, it is not cancelled in the + * middle. In line mode, it returns when key is hit (or + * something like that happens). Therefore, if line editing and echo + * output had a chance to happen, console won't look `blocked' until + * the line is entered (even if line input was triggered by + * (READ-CHAR)). + * + * 4. LISTEN may request ReadConsole too (if no other thread is + * reading the console and no data are queued). It's the only case + * when the console becomes `unblocked' without any actual input + * requested by Lisp code. LISTEN check if there is at least one + * input event in PeekConsole queue; unless there is such an event, + * ReadConsole is not triggered by LISTEN. + * + * 5. Console-reading Lisp thread now may be interrupted immediately; + * ReadConsole call itself, however, continues until the line is + * entered. + */ + +struct { + WCHAR buffer[MAX_CONSOLE_TCHARS]; + DWORD head, tail; + pthread_mutex_t lock; + pthread_cond_t cond_has_data; + pthread_cond_t cond_has_client; + pthread_t thread; + boolean initialized; + HANDLE handle; + boolean in_progress; +} ttyinput = {.lock = PTHREAD_MUTEX_INITIALIZER}; + +static void* +tty_read_line_server() +{ + pthread_mutex_lock(&ttyinput.lock); + while (ttyinput.handle) { + DWORD nchars; + BOOL ok; + + while (!ttyinput.in_progress) + pthread_cond_wait(&ttyinput.cond_has_client,&ttyinput.lock); + + pthread_mutex_unlock(&ttyinput.lock); + + ok = ReadConsoleW(ttyinput.handle, + &ttyinput.buffer[ttyinput.tail], + MAX_CONSOLE_TCHARS-ttyinput.tail, + &nchars,NULL); + + pthread_mutex_lock(&ttyinput.lock); + + if (ok) { + ttyinput.tail += nchars; + pthread_cond_broadcast(&ttyinput.cond_has_data); + } + ttyinput.in_progress = 0; + } + pthread_mutex_unlock(&ttyinput.lock); + return NULL; +} + +static boolean +tty_maybe_initialize_unlocked(HANDLE handle) +{ + if (!ttyinput.initialized) { + if (!DuplicateHandle(GetCurrentProcess(),handle, + GetCurrentProcess(),&ttyinput.handle, + 0,FALSE,DUPLICATE_SAME_ACCESS)) { + return 0; + } + pthread_cond_init(&ttyinput.cond_has_data,NULL); + pthread_cond_init(&ttyinput.cond_has_client,NULL); + pthread_create(&ttyinput.thread,NULL,tty_read_line_server,NULL); + ttyinput.initialized = 1; + } + return 1; +} + +boolean +win32_tty_listen(HANDLE handle) +{ + boolean result = 0; + INPUT_RECORD ir; + DWORD nevents; + pthread_mutex_lock(&ttyinput.lock); + if (!tty_maybe_initialize_unlocked(handle)) + result = 0; + + if (ttyinput.in_progress) { + result = 0; + } else { + if (ttyinput.head != ttyinput.tail) { + result = 1; + } else { + if (PeekConsoleInput(ttyinput.handle,&ir,1,&nevents) && nevents) { + ttyinput.in_progress = 1; + pthread_cond_broadcast(&ttyinput.cond_has_client); + } + } + } + pthread_mutex_unlock(&ttyinput.lock); + return result; +} + +static int +tty_read_line_client(HANDLE handle, void* buf, int count) +{ + int result = 0; + int nchars = count / sizeof(WCHAR); + sigset_t pendset; + + if (!nchars) + return 0; + if (nchars>MAX_CONSOLE_TCHARS) + nchars=MAX_CONSOLE_TCHARS; + + count = nchars*sizeof(WCHAR); + + pthread_mutex_lock(&ttyinput.lock); + + if (!tty_maybe_initialize_unlocked(handle)) { + result = -1; + errno = EIO; + goto unlock; + } + + while (!result) { + while (ttyinput.head == ttyinput.tail) { + if (!io_begin_interruptible(ttyinput.handle)) { + ttyinput.in_progress = 0; + result = -1; + errno = EINTR; + goto unlock; + } else { + if (!ttyinput.in_progress) { + /* We are to wait */ + ttyinput.in_progress=1; + /* wake console reader */ + pthread_cond_broadcast(&ttyinput.cond_has_client); + } + pthread_cond_wait(&ttyinput.cond_has_data, &ttyinput.lock); + io_end_interruptible(ttyinput.handle); + } + } + result = sizeof(WCHAR)*(ttyinput.tail-ttyinput.head); + if (result > count) { + result = count; + } + if (result) { + if (result > 0) { + DWORD nch,offset = 0; + LPWSTR ubuf = buf; + + memcpy(buf,&ttyinput.buffer[ttyinput.head],count); + ttyinput.head += (result / sizeof(WCHAR)); + if (ttyinput.head == ttyinput.tail) + ttyinput.head = ttyinput.tail = 0; + + for (nch=0;nch + * ... stuffs its handle into its structure. + * B.. + * ... calls us to wake the thread, finds the handle. + * But just before we actually call CancelSynchronousIo/CancelIoEx, + * something weird happens in the scheduler and the system is + * so extremely busy that the interrupter doesn't get scheduled + * for a while, giving the interruptee lots of time to continue. + * A.. Didn't actually have to block, calls io_end_interruptible (in + * which the handle flag already invalid, but it doesn't care + * about that and still continues). + * ... Proceeds to do unrelated I/O, e.g. goes into FFI code + * (possible, because the CSP page hasn't been armed yet), which + * does I/O from a C library, completely unrelated to SBCL's + * routines. + * B.. The scheduler gives us time for the interrupter again. + * We call CancelSynchronousIo/CancelIoEx. + * A.. Interruptee gets an expected error in unrelated I/O during FFI. + * Interruptee's C code is unhappy and dies. + * + * Note that CancelSynchronousIo and CancelIoEx have a rather different + * effect here. In the normal (CancelIoEx) case, we only ever kill + * I/O on the file handle in question. I think we could ask users + * to please not both use Lisp streams (unix-read/write) _and_ FFI code + * on the same file handle in quick succession. + * + * CancelSynchronousIo seems more dangerous though. Here we interrupt + * I/O on any other handle, even ones we're not actually responsible for, + * because this functions deals with the thread handle, not the file + * handle. + * + * Options: + * - Use mutexes. Somewhere, somehow. Presumably one mutex per + * target thread, acquired around win32_maybe_interrupt_io and + * io_end_interruptible. (That's one mutex use per I/O + * operation, but I can't imagine that compared to our FFI overhead + * that's much of a problem.) + * - In io_end_interruptible, detect that the flag has been + * invalidated, and in that case, do something clever (what?) to + * wait for the imminent gc_stop_the_world, which implicitly tells + * us that win32_maybe_interrupt_io must have exited. Except if + * some _third_ thread is also beginning to call interrupt-thread + * and wake_thread at the same time...? + * - Revert the whole CancelSynchronousIo business after all. + * - I'm wrong and everything is OK already. + */ + if (ptr_CancelIoEx) { + HANDLE h = (HANDLE) + InterlockedExchangePointer((volatile LPVOID *) + &th->synchronous_io_handle_and_flag, + (LPVOID)INVALID_HANDLE_VALUE); + if (h && (h!=INVALID_HANDLE_VALUE)) { + if (console_handle_p(h)) { + pthread_mutex_lock(&ttyinput.lock); + pthread_cond_broadcast(&ttyinput.cond_has_data); + pthread_mutex_unlock(&ttyinput.lock); + } + if (ptr_CancelSynchronousIo) { + pthread_mutex_lock(&th->os_thread->fiber_lock); + done = ptr_CancelSynchronousIo(th->os_thread->fiber_group->handle); + pthread_mutex_unlock(&th->os_thread->fiber_lock); + } + return (!!done)|(!!ptr_CancelIoEx(h,NULL)); + } + } + return 0; +} + static const LARGE_INTEGER zero_large_offset = {.QuadPart = 0LL}; int @@ -1118,7 +1524,7 @@ win32_unix_write(FDTYPE fd, void * buf, int count) handle =(HANDLE)maybe_get_osfhandle(fd); if (console_handle_p(handle)) - return write(fd, buf, count); + return win32_write_unicode_console(handle,buf,count); overlapped.hEvent = self->private_events.events[0]; seekable = SetFilePointerEx(handle, @@ -1132,12 +1538,23 @@ win32_unix_write(FDTYPE fd, void * buf, int count) overlapped.Offset = 0; overlapped.OffsetHigh = 0; } + if (!io_begin_interruptible(handle)) { + errno = EINTR; + return -1; + } ok = WriteFile(handle, buf, count, &written_bytes, &overlapped); + io_end_interruptible(handle); if (ok) { goto done_something; } else { - if (GetLastError()!=ERROR_IO_PENDING) { + DWORD errorCode = GetLastError(); + if (errorCode==ERROR_OPERATION_ABORTED) { + GetOverlappedResult(handle,&overlapped,&written_bytes,FALSE); + errno = EINTR; + return -1; + } + if (errorCode!=ERROR_IO_PENDING) { errno = EIO; return -1; } else { @@ -1183,13 +1600,9 @@ win32_unix_read(FDTYPE fd, void * buf, int count) handle = (HANDLE)maybe_get_osfhandle(fd); - if (console_handle_p(handle)) { - /* 1. Console is a singleton. - 2. The only way to cancel console handle I/O is to close it. - */ if (console_handle_p(handle)) - return read(fd, buf, count); - } + return win32_read_unicode_console(handle,buf,count); + overlapped.hEvent = self->private_events.events[0]; /* If it has a position, we won't try overlapped */ seekable = SetFilePointerEx(handle, @@ -1203,7 +1616,12 @@ win32_unix_read(FDTYPE fd, void * buf, int count) overlapped.Offset = 0; overlapped.OffsetHigh = 0; } + if (!io_begin_interruptible(handle)) { + errno = EINTR; + return -1; + } ok = ReadFile(handle,buf,count,&read_bytes, &overlapped); + io_end_interruptible(handle); if (ok) { /* immediately */ goto done_something; @@ -1215,6 +1633,11 @@ win32_unix_read(FDTYPE fd, void * buf, int count) read_bytes = 0; goto done_something; } + if (errorCode==ERROR_OPERATION_ABORTED) { + GetOverlappedResult(handle,&overlapped,&read_bytes,FALSE); + errno = EINTR; + return -1; + } if (errorCode!=ERROR_IO_PENDING) { /* is it some _real_ error? */ errno = EIO; @@ -1304,6 +1727,7 @@ void scratch(void) UnmapViewOfFile(0); FlushViewOfFile(0,0); SetFilePointerEx(0, la, 0, 0); + DuplicateHandle(0, 0, 0, 0, 0, 0, 0); #ifndef LISP_FEATURE_SB_UNICODE CreateDirectoryA(0,0); CreateFileMappingA(0,0,0,0,0,0);