From d0797c50151c916e968d5320bd6a652b4bf64879 Mon Sep 17 00:00:00 2001 From: ardeujho Date: Tue, 18 Feb 2014 18:31:05 +1300 Subject: [PATCH 1/2] Use epoll instead of select on linux This removes the use of the select system call for I/O and uses epoll on Linux systems. This removes the limit of 1024 open files that select has and provides better performance. --- vm/src/unix/prims/unixPrims.cpp | 83 ++++++++++++++++++++++++++++++++- vm/src/unix/prims/unixPrims.hh | 11 +++++ vm/src/unix/prims/xlibPrims.cpp | 13 +++++- 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/vm/src/unix/prims/unixPrims.cpp b/vm/src/unix/prims/unixPrims.cpp index e738a922..a319c9ba 100644 --- a/vm/src/unix/prims/unixPrims.cpp +++ b/vm/src/unix/prims/unixPrims.cpp @@ -60,8 +60,11 @@ extern "C" { const char *UnixFile_seal = "UnixFile"; +#ifdef USE_EPOLL +int epollFD; // epoll file descriptor +#else fd_set activeFDs; // active file descriptors - +#endif static struct termios normalSettings; @@ -80,7 +83,27 @@ class IOCleanup { # else # error what? # endif +#ifdef USE_EPOLL + epollFD = epoll_create(256); + if (epollFD < 0) { + printf("epoll_create failed: %s\n", strerror(errno)); + } + else { + struct epoll_event event; + event.events = EPOLLIN | EPOLLOUT; + event.data.fd = 0; + if (epoll_ctl(epollFD, EPOLL_CTL_ADD, 0, &event) < 0) + printf("epoll_ctl for fd 0 failed: %s\n", strerror(errno)); + event.data.fd = 1; + if (epoll_ctl(epollFD, EPOLL_CTL_ADD, 1, &event) < 0) + printf("epoll_ctl for fd 1 failed: %s\n", strerror(errno)); + event.data.fd = 2; + if (epoll_ctl(epollFD, EPOLL_CTL_ADD, 2, &event) < 0) + printf("epoll_ctl for fd 2 failed: %s\n", strerror(errno)); + } +#else FD_SET(0, &activeFDs); FD_SET(1, &activeFDs); FD_SET(2, &activeFDs); +#endif } ~IOCleanup() { resetTerminal(); } }; @@ -390,7 +413,15 @@ void register_file_descriptor(int fd) { // (/dev/rsr0 does under SVR4) -- dmu if (fd < 0) return; - +#ifdef USE_EPOLL + struct epoll_event event; + event.data.fd = fd; + event.events = EPOLLIN | EPOLLOUT; + if (epoll_ctl(epollFD, EPOLL_CTL_ADD, fd, &event) < 0) { + // What to do for error? + printf("epoll_ctl failed: %s\n", strerror(errno)); + } +#else timeval nowait; nowait.tv_sec = 0; nowait.tv_usec = 0; @@ -406,6 +437,7 @@ void register_file_descriptor(int fd) { // end of check FD_SET(fd, &activeFDs); +#endif } @@ -420,8 +452,17 @@ int open_wrap(char *path, int flags, int mode) { int close_wrap(int fd) { int result = close(fd); +#ifdef USE_EPOLL + if (result != -1) { + struct epoll_event event; + if (epoll_ctl(epollFD, EPOLL_CTL_DEL, fd, &event) < 0) { + printf("epoll_ctl delete failed: %s\n", strerror(errno)); + } + } +#else if (result != -1) FD_CLR(fd, &activeFDs); +#endif return result; } @@ -431,8 +472,25 @@ int select_wrap(objVectorOop vec, int howMany, void *FH) { prim_failure(FH, BADSIZEERROR); return 0; } +#ifdef USE_EPOLL + struct epoll_event* events = (struct epoll_event*)malloc(sizeof(epoll_event) * howMany); + int ret = epoll_wait(epollFD, events, howMany, 0); + if (ret < 0) { + unix_failure(FH); + return 0; + } + + for(int i = 0; i < ret; ++i) { + struct epoll_event* event = &events[i]; + vec->obj_at_put(i, as_smiOop(event->data.fd), false); + } + + free(events); + return ret; +#else if (howMany > FD_SETSIZE) howMany = FD_SETSIZE; + fd_set r = activeFDs, w = activeFDs; timeval nowait; nowait.tv_sec = 0; @@ -448,6 +506,7 @@ int select_wrap(objVectorOop vec, int howMany, void *FH) { vec->obj_at_put(index++, as_smiOop(fd), false); } return index; +#endif } int select_read_wrap(objVectorOop vec, int howMany, void *FH) { @@ -455,6 +514,25 @@ int select_read_wrap(objVectorOop vec, int howMany, void *FH) { prim_failure(FH, BADSIZEERROR); return 0; } +#ifdef USE_EPOLL + struct epoll_event* events = (struct epoll_event*)malloc(sizeof(epoll_event) * howMany); + int ret = epoll_wait(epollFD, events, howMany, 0); + if (ret < 0) { + unix_failure(FH); + return 0; + } + + int index = 0; + for(int i = 0; i < ret; ++i) { + struct epoll_event* event = &events[i]; + if ((event->events & EPOLLIN) == EPOLLIN) { + vec->obj_at_put(index++, as_smiOop(event->data.fd), false); + } + } + + free(events); + return index; +#else if (howMany > FD_SETSIZE) howMany = FD_SETSIZE; fd_set r = activeFDs; @@ -472,6 +550,7 @@ int select_read_wrap(objVectorOop vec, int howMany, void *FH) { vec->obj_at_put(index++, as_smiOop(fd), false); } return index; +#endif } diff --git a/vm/src/unix/prims/unixPrims.hh b/vm/src/unix/prims/unixPrims.hh index 0693dbc2..86efb204 100644 --- a/vm/src/unix/prims/unixPrims.hh +++ b/vm/src/unix/prims/unixPrims.hh @@ -7,10 +7,21 @@ # pragma interface # endif +#if TARGET_OS_VERSION == LINUX_VERSION +#define USE_EPOLL +#endif + +#ifdef USE_EPOLL +#include +#endif extern const char *UnixFile_seal; +#ifdef USE_EPOLL +extern int epollFD; // epoll file descriptor +#else extern fd_set activeFDs; // active file descriptors +#endif // so glued in routines can call me extern "C" void register_file_descriptor(int fd); diff --git a/vm/src/unix/prims/xlibPrims.cpp b/vm/src/unix/prims/xlibPrims.cpp index ee16cf10..46f4aea0 100644 --- a/vm/src/unix/prims/xlibPrims.cpp +++ b/vm/src/unix/prims/xlibPrims.cpp @@ -25,14 +25,25 @@ XSetIOErrorHandler(XErrorHandlers::handle_X_IO_error); // add this file descriptor to list of all open files +#ifdef USE_EPOLL + register_file_descriptor(ConnectionNumber(result)); +#else FD_SET(ConnectionNumber(result), &activeFDs); +#endif return result; } void XCloseDisplay_wrap(Display* display) { int fd = ConnectionNumber(display); - XCloseDisplay(display); +#ifdef USE_EPOLL + struct epoll_event event; + if (epoll_ctl(epollFD, EPOLL_CTL_DEL, fd, &event) < 0) { + printf("epoll_ctl delete for X11 descriptor failed: %s\n", strerror(errno)); + } +#else FD_CLR(fd, &activeFDs); +#endif + XCloseDisplay(display); } # define WHAT_GLUE FUNCTIONS From 7fbedf33d09985d04202ceb5f07ca84471af7776 Mon Sep 17 00:00:00 2001 From: ardeujho Date: Fri, 21 Feb 2014 01:12:43 +1300 Subject: [PATCH 2/2] Don't use epoll on regular files epoll doesn't work on regular files. Calls to epoll_ctl return an error of EPERM. I work around this by detecting this case and keeping track of those file descriptors (using an fd_set). These are manually added to the result of a select call as if they were valid to read/write. A longer term solution would be to call 'select' on these file descriptors. Even better might be to move to using asynchronous IO functions (AIO or linux equivalent) on regular files but this is only compatible with epoll on later Linux versions. This needs investigating. Another possibility is using OS threads for i/o. --- vm/src/unix/prims/unixPrims.cpp | 59 +++++++++++++++++++++++++++------ vm/src/unix/prims/unixPrims.hh | 4 +-- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/vm/src/unix/prims/unixPrims.cpp b/vm/src/unix/prims/unixPrims.cpp index a319c9ba..b58146f0 100644 --- a/vm/src/unix/prims/unixPrims.cpp +++ b/vm/src/unix/prims/unixPrims.cpp @@ -62,10 +62,19 @@ const char *UnixFile_seal = "UnixFile"; #ifdef USE_EPOLL int epollFD; // epoll file descriptor -#else -fd_set activeFDs; // active file descriptors + +// epoll doesn't work on regular files. The epoll_ctl function +// returns EPERM for any attempt to add such a file descriptor. +// The workaround is to consider any regular files as always +// ready to read/write or to use AIO functions for regular +// file i/o. The latter is quite intrusive so for now we +// use the former method. This is done by making any +// failed call to epoll_ctl due to EPERM resulting in a +// fallback to assuming that read/write is ready. #endif +fd_set activeFDs; // active file descriptors + static struct termios normalSettings; class IOCleanup { @@ -86,9 +95,12 @@ class IOCleanup { #ifdef USE_EPOLL epollFD = epoll_create(256); if (epollFD < 0) { - printf("epoll_create failed: %s\n", strerror(errno)); } else { + // TODO: + // These file descriptors work with epoll unless they're + // redirected to/from a standard file. We should check + // if that's the case and fallback to select for those. struct epoll_event event; event.events = EPOLLIN | EPOLLOUT; event.data.fd = 0; @@ -417,9 +429,13 @@ void register_file_descriptor(int fd) { struct epoll_event event; event.data.fd = fd; event.events = EPOLLIN | EPOLLOUT; - if (epoll_ctl(epollFD, EPOLL_CTL_ADD, fd, &event) < 0) { + int res = epoll_ctl(epollFD, EPOLL_CTL_ADD, fd, &event); + if (res < 0 && errno == EPERM) { + FD_SET(fd, &activeFDs); + } + else if (res < 0) { // What to do for error? - printf("epoll_ctl failed: %s\n", strerror(errno)); + printf("epoll_ctl failed on fd %d: %s\n", fd, strerror(errno)); } #else timeval nowait; @@ -451,15 +467,21 @@ int open_wrap(char *path, int flags, int mode) { int close_wrap(int fd) { - int result = close(fd); #ifdef USE_EPOLL - if (result != -1) { + // Check if it's a regular file descriptor and don't perform the epoll + // call if it is. + if (FD_ISSET(fd, &activeFDs)) { + FD_CLR(fd, &activeFDs); + } + else { struct epoll_event event; if (epoll_ctl(epollFD, EPOLL_CTL_DEL, fd, &event) < 0) { - printf("epoll_ctl delete failed: %s\n", strerror(errno)); + printf("epoll_ctl delete failed on fd %d: %s\n", fd, strerror(errno)); } } + int result = close(fd); #else + int result = close(fd); if (result != -1) FD_CLR(fd, &activeFDs); #endif @@ -485,8 +507,17 @@ int select_wrap(objVectorOop vec, int howMany, void *FH) { vec->obj_at_put(i, as_smiOop(event->data.fd), false); } - free(events); - return ret; + free(events); + + // Add any regular files + howMany -= ret; + int index= ret; + for (int fd = 0; fd < FD_SETSIZE && index < howMany; fd++) { + if (FD_ISSET(fd, &activeFDs)) { + vec->obj_at_put(index++, as_smiOop(fd), false); + } + } + return index; #else if (howMany > FD_SETSIZE) howMany = FD_SETSIZE; @@ -531,6 +562,14 @@ int select_read_wrap(objVectorOop vec, int howMany, void *FH) { } free(events); + + // Add any regular files + howMany -= index; + for (int fd = 0; fd < FD_SETSIZE && index < howMany; fd++) { + if (FD_ISSET(fd, &activeFDs)) { + vec->obj_at_put(index++, as_smiOop(fd), false); + } + } return index; #else if (howMany > FD_SETSIZE) diff --git a/vm/src/unix/prims/unixPrims.hh b/vm/src/unix/prims/unixPrims.hh index 86efb204..9409e495 100644 --- a/vm/src/unix/prims/unixPrims.hh +++ b/vm/src/unix/prims/unixPrims.hh @@ -19,10 +19,10 @@ extern const char *UnixFile_seal; #ifdef USE_EPOLL extern int epollFD; // epoll file descriptor -#else -extern fd_set activeFDs; // active file descriptors #endif +extern fd_set activeFDs; // active file descriptors + // so glued in routines can call me extern "C" void register_file_descriptor(int fd);