Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e6910ac

Browse files
committed
Ensure that forked process do not see invalid rb_io_blocking_operation list entries.
1 parent a4ce863 commit e6910ac

File tree

5 files changed

+65
-13
lines changed

5 files changed

+65
-13
lines changed

internal/io.h

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct rb_io;
1515

1616
#include "ruby/io.h" /* for rb_io_t */
1717
#include "ccan/list/list.h"
18+
#include "serial.h"
1819

1920
#define IO_WITHOUT_GVL(func, arg) rb_nogvl(func, arg, RUBY_UBF_IO, 0, RB_NOGVL_OFFLOAD_SAFE)
2021
#define IO_WITHOUT_GVL_INT(func, arg) (int)(VALUE)IO_WITHOUT_GVL(func, arg)
@@ -130,6 +131,9 @@ struct rb_io {
130131
struct ccan_list_head blocking_operations;
131132
struct rb_execution_context_struct *closing_ec;
132133
VALUE wakeup_mutex;
134+
135+
// The fork generation of the the blocking operations list.
136+
rb_serial_t fork_generation;
133137
};
134138

135139
/* io.c */

io.c

+4
Original file line numberDiff line numberDiff line change
@@ -8570,6 +8570,7 @@ rb_io_init_copy(VALUE dest, VALUE io)
85708570
ccan_list_head_init(&fptr->blocking_operations);
85718571
fptr->closing_ec = NULL;
85728572
fptr->wakeup_mutex = Qnil;
8573+
fptr->fork_generation = GET_VM()->fork_gen;
85738574

85748575
if (!NIL_P(orig->pathv)) fptr->pathv = orig->pathv;
85758576
fptr_copy_finalizer(fptr, orig);
@@ -9311,6 +9312,7 @@ rb_io_open_descriptor(VALUE klass, int descriptor, int mode, VALUE path, VALUE t
93119312
ccan_list_head_init(&io->blocking_operations);
93129313
io->closing_ec = NULL;
93139314
io->wakeup_mutex = Qnil;
9315+
io->fork_generation = GET_VM()->fork_gen;
93149316

93159317
if (encoding) {
93169318
io->encs = *encoding;
@@ -9454,6 +9456,7 @@ rb_io_fptr_new(void)
94549456
ccan_list_head_init(&fp->blocking_operations);
94559457
fp->closing_ec = NULL;
94569458
fp->wakeup_mutex = Qnil;
9459+
fp->fork_generation = GET_VM()->fork_gen;
94579460
return fp;
94589461
}
94599462

@@ -9587,6 +9590,7 @@ io_initialize(VALUE io, VALUE fnum, VALUE vmode, VALUE opt)
95879590
ccan_list_head_init(&fp->blocking_operations);
95889591
fp->closing_ec = NULL;
95899592
fp->wakeup_mutex = Qnil;
9593+
fp->fork_generation = GET_VM()->fork_gen;
95909594
clear_codeconv(fp);
95919595
io_check_tty(fp);
95929596
if (fileno(stdin) == fd)

test/ruby/test_io.rb

+25
Original file line numberDiff line numberDiff line change
@@ -4416,4 +4416,29 @@ def test_blocking_timeout
44164416
end
44174417
RUBY
44184418
end
4419+
4420+
def test_fork_close
4421+
omit "fork is not supported" unless Process.respond_to?(:fork)
4422+
4423+
assert_separately([], <<~'RUBY')
4424+
r, w = IO.pipe
4425+
4426+
thread = Thread.new do
4427+
r.read
4428+
end
4429+
4430+
Thread.pass until thread.status == "sleep"
4431+
4432+
pid = fork do
4433+
r.close
4434+
end
4435+
4436+
w.close
4437+
4438+
status = Process.wait2(pid).last
4439+
thread.join
4440+
4441+
assert_predicate(status, :success?)
4442+
RUBY
4443+
end
44194444
end

thread.c

+32-12
Original file line numberDiff line numberDiff line change
@@ -1693,13 +1693,32 @@ waitfd_to_waiting_flag(int wfd_event)
16931693
return wfd_event << 1;
16941694
}
16951695

1696+
static struct ccan_list_head *
1697+
rb_io_blocking_operations(struct rb_io *io)
1698+
{
1699+
rb_serial_t fork_generation = GET_VM()->fork_gen;
1700+
1701+
// On fork, all existing entries in this list (which are stack allocated) become invalid. Therefore, we re-initialize the list which clears it.
1702+
if (io->fork_generation != fork_generation) {
1703+
ccan_list_head_init(&io->blocking_operations);
1704+
io->fork_generation = fork_generation;
1705+
}
1706+
1707+
return &io->blocking_operations;
1708+
}
1709+
1710+
static void
1711+
rb_io_blocking_operation_enter(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation) {
1712+
ccan_list_add(rb_io_blocking_operations(io), &blocking_operation->list);
1713+
}
1714+
16961715
struct io_blocking_operation_arguments {
16971716
struct rb_io *io;
16981717
struct rb_io_blocking_operation *blocking_operation;
16991718
};
17001719

17011720
static VALUE
1702-
io_blocking_operation_release(VALUE _arguments) {
1721+
io_blocking_operation_exit(VALUE _arguments) {
17031722
struct io_blocking_operation_arguments *arguments = (void*)_arguments;
17041723
struct rb_io_blocking_operation *blocking_operation = arguments->blocking_operation;
17051724

@@ -1719,7 +1738,7 @@ io_blocking_operation_release(VALUE _arguments) {
17191738
}
17201739

17211740
static void
1722-
rb_io_blocking_operation_release(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation)
1741+
rb_io_blocking_operation_exit(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation)
17231742
{
17241743
VALUE wakeup_mutex = io->wakeup_mutex;
17251744

@@ -1729,7 +1748,7 @@ rb_io_blocking_operation_release(struct rb_io *io, struct rb_io_blocking_operati
17291748
.blocking_operation = blocking_operation
17301749
};
17311750

1732-
rb_mutex_synchronize(wakeup_mutex, io_blocking_operation_release, (VALUE)&arguments);
1751+
rb_mutex_synchronize(wakeup_mutex, io_blocking_operation_exit, (VALUE)&arguments);
17331752
} else {
17341753
ccan_list_del(&blocking_operation->list);
17351754
}
@@ -1824,7 +1843,7 @@ rb_thread_io_blocking_call(struct rb_io* io, rb_blocking_function_t *func, void
18241843
struct rb_io_blocking_operation blocking_operation = {
18251844
.ec = ec,
18261845
};
1827-
ccan_list_add(&io->blocking_operations, &blocking_operation.list);
1846+
rb_io_blocking_operation_enter(io, &blocking_operation);
18281847

18291848
{
18301849
EC_PUSH_TAG(ec);
@@ -1851,7 +1870,7 @@ rb_thread_io_blocking_call(struct rb_io* io, rb_blocking_function_t *func, void
18511870
th->mn_schedulable = prev_mn_schedulable;
18521871
}
18531872

1854-
rb_io_blocking_operation_release(io, &blocking_operation);
1873+
rb_io_blocking_operation_exit(io, &blocking_operation);
18551874

18561875
if (state) {
18571876
EC_JUMP_TAG(ec, state);
@@ -2658,10 +2677,11 @@ thread_io_close_notify_all(struct rb_io *io)
26582677
VALUE error = vm->special_exceptions[ruby_error_stream_closed];
26592678

26602679
struct rb_io_blocking_operation *blocking_operation;
2661-
ccan_list_for_each(&io->blocking_operations, blocking_operation, list) {
2680+
ccan_list_for_each(rb_io_blocking_operations(io), blocking_operation, list) {
26622681
rb_execution_context_t *ec = blocking_operation->ec;
26632682

26642683
rb_thread_t *thread = ec->thread_ptr;
2684+
26652685
rb_threadptr_pending_interrupt_enque(thread, error);
26662686

26672687
// This operation is slow:
@@ -2684,7 +2704,7 @@ rb_thread_io_close_interrupt(struct rb_io *io)
26842704
}
26852705

26862706
// If there are no blocking operations, we are done:
2687-
if (ccan_list_empty(&io->blocking_operations)) {
2707+
if (ccan_list_empty(rb_io_blocking_operations(io))) {
26882708
return 0;
26892709
}
26902710

@@ -2709,7 +2729,7 @@ rb_thread_io_close_wait(struct rb_io* io)
27092729
}
27102730

27112731
rb_mutex_lock(wakeup_mutex);
2712-
while (!ccan_list_empty(&io->blocking_operations)) {
2732+
while (!ccan_list_empty(rb_io_blocking_operations(io))) {
27132733
rb_mutex_sleep(wakeup_mutex, Qnil);
27142734
}
27152735
rb_mutex_unlock(wakeup_mutex);
@@ -4435,7 +4455,7 @@ thread_io_wait(struct rb_io *io, int fd, int events, struct timeval *timeout)
44354455

44364456
if (io) {
44374457
blocking_operation.ec = ec;
4438-
ccan_list_add(&io->blocking_operations, &blocking_operation.list);
4458+
rb_io_blocking_operation_enter(io, &blocking_operation);
44394459
}
44404460

44414461
if (timeout == NULL && thread_io_wait_events(th, fd, events, NULL)) {
@@ -4461,7 +4481,7 @@ thread_io_wait(struct rb_io *io, int fd, int events, struct timeval *timeout)
44614481
}
44624482

44634483
if (io) {
4464-
rb_io_blocking_operation_release(io, &blocking_operation);
4484+
rb_io_blocking_operation_exit(io, &blocking_operation);
44654485
}
44664486

44674487
if (state) {
@@ -4539,7 +4559,7 @@ select_single_cleanup(VALUE ptr)
45394559
struct select_args *args = (struct select_args *)ptr;
45404560

45414561
if (args->blocking_operation) {
4542-
rb_io_blocking_operation_release(args->io, args->blocking_operation);
4562+
rb_io_blocking_operation_exit(args->io, args->blocking_operation);
45434563
}
45444564

45454565
if (args->read) rb_fd_term(args->read);
@@ -4572,7 +4592,7 @@ thread_io_wait(struct rb_io *io, int fd, int events, struct timeval *timeout)
45724592
if (io) {
45734593
args.io = io;
45744594
blocking_operation.ec = GET_EC();
4575-
ccan_list_add(&io->blocking_operations, &blocking_operation.list);
4595+
rb_io_blocking_operation_enter(io, &blocking_operation);
45764596
args.blocking_operation = &blocking_operation;
45774597
} else {
45784598
args.io = NULL;

vm_core.h

-1
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,6 @@ typedef struct rb_vm_struct {
730730
#endif
731731

732732
rb_serial_t fork_gen;
733-
struct ccan_list_head waiting_fds; /* <=> struct waiting_fd */
734733

735734
/* set in single-threaded processes only: */
736735
volatile int ubf_async_safe;

0 commit comments

Comments
 (0)