From fc3137ef54562c3c3290245c0f62e0bb193c3145 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 19 Oct 2022 18:53:38 +1300 Subject: [PATCH] Add support for anonymous shared IO buffers. (#6580) --- include/ruby/io/buffer.h | 3 +++ io_buffer.c | 37 ++++++++++++++++++++++++++++++++++--- test/ruby/test_io_buffer.rb | 15 +++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/include/ruby/io/buffer.h b/include/ruby/io/buffer.h index dd92db5bbe..88e5598066 100644 --- a/include/ruby/io/buffer.h +++ b/include/ruby/io/buffer.h @@ -37,6 +37,9 @@ enum rb_io_buffer_flags { // A non-private mapping is marked as external. RB_IO_BUFFER_MAPPED = 4, + // A mapped buffer that is also shared. + RB_IO_BUFFER_SHARED = 8, + // The buffer is locked and cannot be resized. // More specifically, it means we can't change the base address or size. // A buffer is typically locked before a system call that uses the data. diff --git a/io_buffer.c b/io_buffer.c index bc5fac8118..e252af3513 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -47,7 +47,7 @@ struct rb_io_buffer { }; static inline void * -io_buffer_map_memory(size_t size) +io_buffer_map_memory(size_t size, int flags) { #if defined(_WIN32) void * base = VirtualAlloc(0, size, MEM_COMMIT, PAGE_READWRITE); @@ -56,7 +56,14 @@ io_buffer_map_memory(size_t size) rb_sys_fail("io_buffer_map_memory:VirtualAlloc"); } #else - void * base = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + int mmap_flags = MAP_ANONYMOUS; + if (flags & RB_IO_BUFFER_SHARED) { + mmap_flags |= MAP_SHARED; + } else { + mmap_flags |= MAP_PRIVATE; + } + + void * base = mmap(NULL, size, PROT_READ | PROT_WRITE, mmap_flags, -1, 0); if (base == MAP_FAILED) { rb_sys_fail("io_buffer_map_memory:mmap"); @@ -93,6 +100,7 @@ io_buffer_map_file(struct rb_io_buffer *data, int descriptor, size_t size, rb_of else { // This buffer refers to external data. data->flags |= RB_IO_BUFFER_EXTERNAL; + data->flags |= RB_IO_BUFFER_SHARED; } void *base = MapViewOfFile(mapping, access, (DWORD)(offset >> 32), (DWORD)(offset & 0xFFFFFFFF), size); @@ -119,6 +127,7 @@ io_buffer_map_file(struct rb_io_buffer *data, int descriptor, size_t size, rb_of else { // This buffer refers to external data. data->flags |= RB_IO_BUFFER_EXTERNAL; + data->flags |= RB_IO_BUFFER_SHARED; access |= MAP_SHARED; } @@ -184,7 +193,7 @@ io_buffer_initialize(struct rb_io_buffer *data, void *base, size_t size, enum rb base = calloc(size, 1); } else if (flags & RB_IO_BUFFER_MAPPED) { - base = io_buffer_map_memory(size); + base = io_buffer_map_memory(size, flags); } if (!base) { @@ -655,6 +664,10 @@ rb_io_buffer_to_s(VALUE self) rb_str_cat2(result, " MAPPED"); } + if (data->flags & RB_IO_BUFFER_SHARED) { + rb_str_cat2(result, " SHARED"); + } + if (data->flags & RB_IO_BUFFER_LOCKED) { rb_str_cat2(result, " LOCKED"); } @@ -879,6 +892,22 @@ rb_io_buffer_mapped_p(VALUE self) return RBOOL(data->flags & RB_IO_BUFFER_MAPPED); } +/* + * call-seq: shared? -> true or false + * + * If the buffer is _shared_, meaning it references memory that can be shared + * with other processes (and thus might change without being modified + * locally). + */ +static VALUE +rb_io_buffer_shared_p(VALUE self) +{ + struct rb_io_buffer *data = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + + return RBOOL(data->flags & RB_IO_BUFFER_SHARED); +} + /* * call-seq: locked? -> true or false * @@ -3176,6 +3205,7 @@ Init_IO_Buffer(void) rb_define_const(rb_cIOBuffer, "EXTERNAL", RB_INT2NUM(RB_IO_BUFFER_EXTERNAL)); rb_define_const(rb_cIOBuffer, "INTERNAL", RB_INT2NUM(RB_IO_BUFFER_INTERNAL)); rb_define_const(rb_cIOBuffer, "MAPPED", RB_INT2NUM(RB_IO_BUFFER_MAPPED)); + rb_define_const(rb_cIOBuffer, "SHARED", RB_INT2NUM(RB_IO_BUFFER_SHARED)); rb_define_const(rb_cIOBuffer, "LOCKED", RB_INT2NUM(RB_IO_BUFFER_LOCKED)); rb_define_const(rb_cIOBuffer, "PRIVATE", RB_INT2NUM(RB_IO_BUFFER_PRIVATE)); rb_define_const(rb_cIOBuffer, "READONLY", RB_INT2NUM(RB_IO_BUFFER_READONLY)); @@ -3191,6 +3221,7 @@ Init_IO_Buffer(void) rb_define_method(rb_cIOBuffer, "external?", rb_io_buffer_external_p, 0); rb_define_method(rb_cIOBuffer, "internal?", rb_io_buffer_internal_p, 0); rb_define_method(rb_cIOBuffer, "mapped?", rb_io_buffer_mapped_p, 0); + rb_define_method(rb_cIOBuffer, "shared?", rb_io_buffer_shared_p, 0); rb_define_method(rb_cIOBuffer, "locked?", rb_io_buffer_locked_p, 0); rb_define_method(rb_cIOBuffer, "readonly?", io_buffer_readonly_p, 0); diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index 95ed98e1f4..2204c3db09 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -403,4 +403,19 @@ class TestIOBuffer < Test::Unit::TestCase assert_equal IO::Buffer.for("\x00\x01\x004\x00\x01\x004\x00\x01"), source.dup.xor!(mask) assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), source.dup.not! end + + def test_shared + message = "Hello World" + buffer = IO::Buffer.new(64, IO::Buffer::MAPPED | IO::Buffer::SHARED) + + pid = fork do + buffer.set_string(message) + end + + Process.wait(pid) + string = buffer.get_string(0, message.bytesize) + assert_equal message, string + rescue NotImplementedError + omit "Fork/shared memory is not supported." + end end