Skip to content

Commit c49f2db

Browse files
committed
[GR-36556] Retry select when interrupted.
PullRequest: truffleruby/3323
2 parents 659bc65 + 751da23 commit c49f2db

File tree

4 files changed

+143
-7
lines changed

4 files changed

+143
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Compatibility:
6868
* Use `$PAGER` for `--help` and `--help*`, similar to CRuby (#2542, @Strech).
6969
* Ensure all headers are warnings-free (#2662, @eregon).
7070
* All `IO` instances should have `T_FILE` as their `rb_type()`, not only `File` instances (#2662, @eregon).
71+
* Make `rb_fd_select` retry on `EINTR` (#1584, @aardvark179).
7172

7273
Performance:
7374

lib/cext/ABI_check.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
5
1+
6

src/main/c/cext/fd.c

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
*/
1010
#include <truffleruby-impl.h>
1111
#include <fcntl.h>
12+
#include <errno.h>
1213
#include <ruby/thread.h>
14+
#include <time.h>
1315

1416
// For howmany()
1517
#ifdef HAVE_SYS_PARAM_H
@@ -105,6 +107,65 @@ void rb_fd_dup(rb_fdset_t *dst, const rb_fdset_t *src) {
105107
memcpy(dst->fdset, src->fdset, size);
106108
}
107109

110+
void rb_fd_init_copy(rb_fdset_t *dst, rb_fdset_t *src) {
111+
size_t size = howmany(rb_fd_max(src), NFDBITS) * sizeof(fd_mask);
112+
113+
if (size < sizeof(fd_set)) {
114+
size = sizeof(fd_set);
115+
}
116+
dst->maxfd = src->maxfd;
117+
dst->fdset = xmalloc(size);
118+
memcpy(dst->fdset, src->fdset, size);
119+
}
120+
121+
static bool timespec_subtract(struct timespec *result, struct timespec x, struct timespec y) {
122+
/* Perform the carry for the later subtraction by updating y. */
123+
if (x.tv_nsec < y.tv_nsec) {
124+
long nsec = (y.tv_nsec - x.tv_nsec) / 1000000000 + 1;
125+
y.tv_nsec -= 1000000000 * nsec;
126+
y.tv_sec += nsec;
127+
}
128+
if (x.tv_nsec - y.tv_nsec > 1000000000) {
129+
long nsec = (x.tv_nsec - y.tv_nsec) / 1000000000;
130+
y.tv_nsec += 1000000000 * nsec;
131+
y.tv_sec -= nsec;
132+
}
133+
134+
/* Compute the time remaining to wait.
135+
tv_nsec is certainly positive. */
136+
result->tv_sec = x.tv_sec - y.tv_sec;
137+
result->tv_nsec = x.tv_nsec - y.tv_nsec;
138+
139+
/* Return 1 if result is negative. */
140+
return x.tv_sec < y.tv_sec;
141+
}
142+
143+
static bool timeval_subtract(struct timeval *result, struct timeval x, struct timeval y) {
144+
/* Perform the carry for the later subtraction by updating y. */
145+
if (x.tv_usec < y.tv_usec) {
146+
long usec = (y.tv_usec - x.tv_usec) / 1000000 + 1;
147+
y.tv_usec -= 1000000 * usec;
148+
y.tv_sec += usec;
149+
}
150+
if (x.tv_usec - y.tv_usec > 1000000) {
151+
long usec = (x.tv_usec - y.tv_usec) / 1000000;
152+
y.tv_usec += 1000000 * usec;
153+
y.tv_sec -= usec;
154+
}
155+
156+
/* Compute the time remaining to wait.
157+
tv_usec is certainly positive. */
158+
result->tv_sec = x.tv_sec - y.tv_sec;
159+
result->tv_usec = x.tv_usec - y.tv_usec;
160+
161+
/* Return 1 if result is negative. */
162+
return x.tv_sec < y.tv_sec;
163+
}
164+
165+
static int should_retry(int result) {
166+
return (result < 0) && (errno == EINTR);
167+
}
168+
108169
int rb_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds, struct timeval *timeout) {
109170
fd_set *r = NULL, *w = NULL, *e = NULL;
110171
if (readfds) {
@@ -122,21 +183,90 @@ int rb_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *e
122183
return select(n, r, w, e, timeout);
123184
}
124185

186+
125187
// NOTE: MRI's version has more fields
126188
struct select_set {
127189
int max;
128190
rb_fdset_t *rset;
129191
rb_fdset_t *wset;
130192
rb_fdset_t *eset;
193+
rb_fdset_t *orig_rset;
194+
rb_fdset_t *orig_wset;
195+
rb_fdset_t *orig_eset;
131196
struct timeval *timeout;
197+
struct timeval *orig_timeout;
132198
};
133199

200+
static inline void restore_fds(rb_fdset_t *dst, rb_fdset_t *src) {
201+
if (dst) {
202+
rb_fd_dup(dst, src);
203+
}
204+
}
205+
206+
static bool update_timeout(struct timeval *timeout, struct timeval *orig_timeout, struct timespec *starttime) {
207+
struct timespec currenttime;
208+
struct timespec difftime;
209+
struct timeval difftimeout;
210+
bool timeleft = true;
211+
if (timeout) {
212+
clock_gettime(CLOCK_MONOTONIC, &currenttime);
213+
timespec_subtract(&difftime, currenttime, *starttime);
214+
difftimeout.tv_sec = difftime.tv_sec;
215+
difftimeout.tv_usec = difftime.tv_nsec / 1000;
216+
timeleft = timeval_subtract(timeout, *orig_timeout, difftimeout);
217+
}
218+
219+
return timeleft;
220+
}
221+
134222
static void* rb_thread_fd_select_blocking(void *data) {
135223
struct select_set *set = (struct select_set*)data;
136-
int result = rb_fd_select(set->max, set->rset, set->wset, set->eset, set->timeout);
224+
struct timespec starttime;
225+
226+
if (set->timeout) {
227+
clock_gettime(CLOCK_MONOTONIC, &starttime);
228+
}
229+
230+
int result = 0;
231+
bool timeleft = true;
232+
do {
233+
restore_fds(set->rset, set->orig_rset);
234+
restore_fds(set->wset, set->orig_wset);
235+
restore_fds(set->eset, set->orig_eset);
236+
timeleft = update_timeout(set->timeout, set->orig_timeout, &starttime);
237+
if (!timeleft) {
238+
break;
239+
}
240+
result = rb_fd_select(set->max, set->rset, set->wset, set->eset, set->timeout);
241+
} while (should_retry(result));
137242
return (void*)(long)result;
138243
}
139244

245+
static void* rb_thread_fd_select_internal(void *sets) {
246+
return rb_thread_call_without_gvl(rb_thread_fd_select_blocking, sets, RUBY_UBF_IO, 0);
247+
}
248+
249+
static void rb_thread_fd_select_set_free(struct select_set *sets) {
250+
if (sets->orig_rset) {
251+
rb_fd_term(sets->orig_rset);
252+
}
253+
if (sets->orig_wset) {
254+
rb_fd_term(sets->orig_wset);
255+
}
256+
if (sets->orig_eset) {
257+
rb_fd_term(sets->orig_eset);
258+
}
259+
}
260+
261+
static void fd_init_copy(rb_fdset_t *dst, int max, rb_fdset_t *src) {
262+
if (src) {
263+
rb_fd_resize(max - 1, src);
264+
if (dst != src) {
265+
rb_fd_init_copy(dst, src);
266+
}
267+
}
268+
}
269+
140270
int rb_thread_fd_select(int max, rb_fdset_t *read, rb_fdset_t *write, rb_fdset_t *except, struct timeval *timeout) {
141271
// NOTE: MRI has more logic in here
142272
struct select_set set;
@@ -145,7 +275,12 @@ int rb_thread_fd_select(int max, rb_fdset_t *read, rb_fdset_t *write, rb_fdset_t
145275
set.wset = write;
146276
set.eset = except;
147277
set.timeout = timeout;
278+
fd_init_copy(set.orig_rset, set.max, set.rset);
279+
fd_init_copy(set.orig_wset, set.max, set.wset);
280+
fd_init_copy(set.orig_eset, set.max, set.eset);
281+
struct timeval orig_timeval = *timeout;
282+
set.orig_timeout = &orig_timeval;
148283

149-
void* result = rb_thread_call_without_gvl(rb_thread_fd_select_blocking, (void*)(&set), RUBY_UBF_IO, 0);
284+
void* result = rb_ensure(rb_thread_fd_select_internal, (VALUE)&set, rb_thread_fd_select_set_free, (VALUE)&set);
150285
return (int)(long)result;
151286
}

src/main/ruby/truffleruby/core/truffle/io_operations.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,11 @@ def self.select(readables, readable_ios, writables, writable_ios, errorables, er
139139
buffer, readables_pointer, writables_pointer, errorables_pointer =
140140
Truffle::FFI::Pool.stack_alloc(readables.size * SIZEOF_INT, writables.size * SIZEOF_INT, errorables.size * SIZEOF_INT)
141141
begin
142-
to_fds(readable_ios, readables_pointer)
143-
to_fds(writable_ios, writables_pointer)
144-
to_fds(errorable_ios, errorables_pointer)
145-
146142
begin
143+
to_fds(readable_ios, readables_pointer)
144+
to_fds(writable_ios, writables_pointer)
145+
to_fds(errorable_ios, errorables_pointer)
146+
147147
primitive_result = Primitive.thread_run_blocking_nfi_system_call(Truffle::POSIX::SELECT, [
148148
readables.size, readables_pointer,
149149
writables.size, writables_pointer,

0 commit comments

Comments
 (0)