From b50eb0c6639aa1a336c9fa2b2f486e47180bc40a Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Fri, 21 Jan 2022 21:23:09 +0100 Subject: [PATCH] Add a faster Windows specific rb_io_wait() implementation It is based on the code that was removed in commit https://github.com/ged/ruby-pg/pull/397/commits/6c885e8ba0b2f0f83d0476c186d69aa88ee7e2c4 Fixes #416 --- ext/extconf.rb | 1 + ext/pg_connection.c | 104 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/ext/extconf.rb b/ext/extconf.rb index a96950974..96194345e 100755 --- a/ext/extconf.rb +++ b/ext/extconf.rb @@ -155,6 +155,7 @@ module PG # unistd.h confilicts with ruby/win32.h when cross compiling for win32 and ruby 1.9.1 have_header 'unistd.h' have_header 'inttypes.h' +have_header('ruby/fiber/scheduler.h') if RUBY_PLATFORM=~/mingw|mswin/ checking_for "C99 variable length arrays" do $defs.push( "-DHAVE_VARIABLE_LENGTH_ARRAYS" ) if try_compile('void test_vla(int l){ int vla[l]; }') diff --git a/ext/pg_connection.c b/ext/pg_connection.c index cb5c1103b..a90e6c198 100644 --- a/ext/pg_connection.c +++ b/ext/pg_connection.c @@ -2207,8 +2207,110 @@ pgconn_notifies(VALUE self) return hash; } +#if defined(_WIN32) + +/* We use a specialized implementation of rb_io_wait() on Windows. + * This is because rb_io_wait() and rb_wait_for_single_fd() are very slow on Windows. + */ + +#if defined(HAVE_RUBY_FIBER_SCHEDULER_H) +#include +#endif + +typedef enum { + PG_RUBY_IO_READABLE = RB_WAITFD_IN, + PG_RUBY_IO_WRITABLE = RB_WAITFD_OUT, + PG_RUBY_IO_PRIORITY = RB_WAITFD_PRI, +} pg_rb_io_event_t; + +int rb_w32_wait_events( HANDLE *events, int num, DWORD timeout ); + +static VALUE +pg_rb_thread_io_wait(VALUE io, VALUE events, VALUE timeout) { + rb_io_t *fptr; + struct timeval ptimeout; + + struct timeval aborttime={0,0}, currtime, waittime; + DWORD timeout_milisec = INFINITE; + HANDLE hEvent = WSACreateEvent(); + + long rb_events = NUM2UINT(events); + long w32_events = 0; + DWORD wait_ret; + + GetOpenFile((io), fptr); + if( !NIL_P(timeout) ){ + ptimeout.tv_sec = (time_t)(NUM2DBL(timeout)); + ptimeout.tv_usec = (time_t)(NUM2DBL(timeout) - (double)ptimeout.tv_sec); + + gettimeofday(&currtime, NULL); + timeradd(&currtime, &ptimeout, &aborttime); + } + + if(rb_events & PG_RUBY_IO_READABLE) { + w32_events |= FD_READ | FD_ACCEPT | FD_CLOSE; + } else if(rb_events & PG_RUBY_IO_WRITABLE) { + w32_events |= FD_WRITE | FD_CONNECT; + } else if(rb_events & PG_RUBY_IO_PRIORITY) { + w32_events |= FD_OOB; + } + + for(;;) { + if ( WSAEventSelect(_get_osfhandle(fptr->fd), hEvent, w32_events) == SOCKET_ERROR ) { + WSACloseEvent( hEvent ); + rb_raise( rb_eConnectionBad, "WSAEventSelect socket error: %d", WSAGetLastError() ); + } + + if ( !NIL_P(timeout) ) { + gettimeofday(&currtime, NULL); + timersub(&aborttime, &currtime, &waittime); + timeout_milisec = (DWORD)( waittime.tv_sec * 1e3 + waittime.tv_usec / 1e3 ); + } + + if( NIL_P(timeout) || (waittime.tv_sec >= 0 && waittime.tv_usec >= 0) ){ + /* Wait for the socket to become readable before checking again */ + wait_ret = rb_w32_wait_events( &hEvent, 1, timeout_milisec ); + } else { + wait_ret = WAIT_TIMEOUT; + } + + if ( wait_ret == WAIT_TIMEOUT ) { + WSACloseEvent( hEvent ); + return UINT2NUM(0); + } else if ( wait_ret == WAIT_OBJECT_0 ) { + WSACloseEvent( hEvent ); + /* The event we were waiting for. */ + return UINT2NUM(rb_events); + } else if ( wait_ret == WAIT_OBJECT_0 + 1) { + /* This indicates interruption from timer thread, GC, exception + * from other threads etc... */ + rb_thread_check_ints(); + } else if ( wait_ret == WAIT_FAILED ) { + WSACloseEvent( hEvent ); + rb_raise( rb_eConnectionBad, "Wait on socket error (WaitForMultipleObjects): %lu", GetLastError() ); + } else { + WSACloseEvent( hEvent ); + rb_raise( rb_eConnectionBad, "Wait on socket abandoned (WaitForMultipleObjects)" ); + } + } +} + +static VALUE +pg_rb_io_wait(VALUE io, VALUE events, VALUE timeout) { +#if defined(HAVE_RUBY_FIBER_SCHEDULER_H) + /* We don't support Fiber.scheduler on Windows ruby-3.0 because there is no fast way to check whether a scheduler is active. + * Fortunatelly ruby-3.1 offers a C-API for it. + */ + VALUE scheduler = rb_fiber_scheduler_current(); + + if (!NIL_P(scheduler)) { + return rb_io_wait(io, events, timeout); + } +#endif + return pg_rb_thread_io_wait(io, events, timeout); +} -#if defined(HAVE_RB_IO_WAIT) +#elif defined(HAVE_RB_IO_WAIT) /* Use our own function and constants names, to avoid conflicts with truffleruby-head on its road to ruby-3.0 compatibility. */ #define pg_rb_io_wait rb_io_wait