Skip to content

Commit 425fa0a

Browse files
authored
Make waiting_fd behaviour per-IO. (ruby#13127)
- `rb_thread_fd_close` is deprecated and now a no-op. - IO operations (including close) no longer take a vm-wide lock.
1 parent a6435be commit 425fa0a

File tree

14 files changed

+216
-379
lines changed

14 files changed

+216
-379
lines changed

NEWS.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,19 @@ The following bundled gems are updated.
102102

103103
## C API updates
104104

105+
* IO
106+
107+
* `rb_thread_fd_close` is deprecated and now a no-op. If you need to expose
108+
file descriptors from C extensions to Ruby code, create an `IO` instance
109+
using `RUBY_IO_MODE_EXTERNAL` and use `rb_io_close(io)` to close it (this
110+
also interrupts and waits for all pending operations on the `IO`
111+
instance). Directly closing file descriptors does not interrupt pending
112+
operations, and may lead to undefined beahviour. In other words, if two
113+
`IO` objects share the same file descriptor, closing one does not affect
114+
the other. [[Feature #18455]]
115+
116+
[[Feature #18455]]
117+
105118
## Implementation improvements
106119

107120
## JIT
@@ -112,3 +125,4 @@ The following bundled gems are updated.
112125
[Bug #21049]: https://bugs.ruby-lang.org/issues/21049
113126
[Feature #21216]: https://bugs.ruby-lang.org/issues/21216
114127
[Feature #21258]: https://bugs.ruby-lang.org/issues/21258
128+
[Feature #18455]: https://bugs.ruby-lang.org/issues/18455

ext/-test-/thread_fd/depend

Lines changed: 0 additions & 161 deletions
This file was deleted.

ext/-test-/thread_fd/extconf.rb

Lines changed: 0 additions & 2 deletions
This file was deleted.

ext/-test-/thread_fd/thread_fd.c

Lines changed: 0 additions & 30 deletions
This file was deleted.

gc.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3204,6 +3204,7 @@ rb_gc_mark_children(void *objspace, VALUE obj)
32043204
gc_mark_internal(RFILE(obj)->fptr->encs.ecopts);
32053205
gc_mark_internal(RFILE(obj)->fptr->write_lock);
32063206
gc_mark_internal(RFILE(obj)->fptr->timeout);
3207+
gc_mark_internal(RFILE(obj)->fptr->wakeup_mutex);
32073208
}
32083209
break;
32093210

@@ -4185,6 +4186,8 @@ rb_gc_update_object_references(void *objspace, VALUE obj)
41854186
UPDATE_IF_MOVED(objspace, RFILE(obj)->fptr->writeconv_pre_ecopts);
41864187
UPDATE_IF_MOVED(objspace, RFILE(obj)->fptr->encs.ecopts);
41874188
UPDATE_IF_MOVED(objspace, RFILE(obj)->fptr->write_lock);
4189+
UPDATE_IF_MOVED(objspace, RFILE(obj)->fptr->timeout);
4190+
UPDATE_IF_MOVED(objspace, RFILE(obj)->fptr->wakeup_mutex);
41884191
}
41894192
break;
41904193
case T_REGEXP:

include/ruby/internal/intern/thread.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ int rb_thread_wait_fd(int fd);
6161
int rb_thread_fd_writable(int fd);
6262

6363
/**
64-
* Notifies a closing of a file descriptor to other threads. Multiple threads
65-
* can wait for the given file descriptor at once. If such file descriptor is
66-
* closed, threads need to start propagating their exceptions. This is the API
67-
* to kick that process.
64+
* This funciton is now a no-op. It was previously used to interrupt threads
65+
* that were using the given file descriptor and wait for them to finish.
66+
*
67+
* @deprecated Use IO with RUBY_IO_MODE_EXTERNAL and `rb_io_close` instead.
6868
*
6969
* @param[in] fd A file descriptor.
7070
* @note This function blocks until all the threads waiting for such fd

internal/io.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,20 @@
1414
struct rb_io;
1515

1616
#include "ruby/io.h" /* for rb_io_t */
17+
#include "ccan/list/list.h"
1718

1819
#define IO_WITHOUT_GVL(func, arg) rb_nogvl(func, arg, RUBY_UBF_IO, 0, RB_NOGVL_OFFLOAD_SAFE)
1920
#define IO_WITHOUT_GVL_INT(func, arg) (int)(VALUE)IO_WITHOUT_GVL(func, arg)
2021

22+
// Represents an in-flight blocking operation:
23+
struct rb_io_blocking_operation {
24+
// The linked list data structure.
25+
struct ccan_list_node list;
26+
27+
// The execution context of the blocking operation:
28+
struct rb_execution_context_struct *ec;
29+
};
30+
2131
/** Ruby's IO, metadata and buffers. */
2232
struct rb_io {
2333

@@ -111,6 +121,15 @@ struct rb_io {
111121
* The timeout associated with this IO when performing blocking operations.
112122
*/
113123
VALUE timeout;
124+
125+
/**
126+
* Threads that are performing a blocking operation without the GVL using
127+
* this IO. On calling IO#close, these threads will be interrupted so that
128+
* the operation can be cancelled.
129+
*/
130+
struct ccan_list_head blocking_operations;
131+
struct rb_execution_context_struct *closing_ec;
132+
VALUE wakeup_mutex;
114133
};
115134

116135
/* io.c */
@@ -125,7 +144,7 @@ VALUE rb_io_prep_stdin(void);
125144
VALUE rb_io_prep_stdout(void);
126145
VALUE rb_io_prep_stderr(void);
127146

128-
int rb_io_fptr_finalize(struct rb_io *fptr);
147+
int rb_io_notify_close(struct rb_io *fptr);
129148

130149
RUBY_SYMBOL_EXPORT_BEGIN
131150
/* io.c (export) */

internal/thread.h

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "ccan/list/list.h" /* for list in rb_io_close_wait_list */
1414

1515
struct rb_thread_struct; /* in vm_core.h */
16+
struct rb_io;
1617

1718
#define RB_VM_SAVE_MACHINE_CONTEXT(th) \
1819
do { \
@@ -58,14 +59,8 @@ void ruby_mn_threads_params(void);
5859
int rb_thread_io_wait(struct rb_io *io, int events, struct timeval * timeout);
5960
int rb_thread_wait_for_single_fd(int fd, int events, struct timeval * timeout);
6061

61-
struct rb_io_close_wait_list {
62-
struct ccan_list_head pending_fd_users;
63-
VALUE closing_thread;
64-
VALUE closing_fiber;
65-
VALUE wakeup_mutex;
66-
};
67-
int rb_notify_fd_close(int fd, struct rb_io_close_wait_list *busy);
68-
void rb_notify_fd_close_wait(struct rb_io_close_wait_list *busy);
62+
size_t rb_thread_io_close_interrupt(struct rb_io *);
63+
void rb_thread_io_close_wait(struct rb_io *);
6964

7065
void rb_ec_check_ints(struct rb_execution_context_struct *ec);
7166

0 commit comments

Comments
 (0)