ruby-changes:70428
From: Samuel <ko1@a...>
Date: Wed, 22 Dec 2021 06:58:03 +0900 (JST)
Subject: [ruby-changes:70428] e30920354f (master): Extended interface for IO::Buffer & documentation.
https://git.ruby-lang.org/ruby.git/commit/?id=e30920354f From e30920354f8c4513150c61385220a8e75448d833 Mon Sep 17 00:00:00 2001 From: Samuel Williams <samuel.williams@o...> Date: Wed, 22 Dec 2021 10:57:34 +1300 Subject: Extended interface for IO::Buffer & documentation. --- common.mk | 1 + include/ruby/io/buffer.h | 8 +- io_buffer.c | 812 +++++++++++++++++++++++++++++++++++++++----- test/ruby/test_io_buffer.rb | 6 +- update.rb | 20 ++ 5 files changed, 763 insertions(+), 84 deletions(-) create mode 100755 update.rb diff --git a/common.mk b/common.mk index 14ef9cb4ec3..8fc0a590f8a 100644 --- a/common.mk +++ b/common.mk @@ -7586,6 +7586,7 @@ io_buffer.$(OBJEXT): {$(VPATH)}config.h https://github.com/ruby/ruby/blob/trunk/common.mk#L7586 io_buffer.$(OBJEXT): {$(VPATH)}defines.h io_buffer.$(OBJEXT): {$(VPATH)}encoding.h io_buffer.$(OBJEXT): {$(VPATH)}intern.h +io_buffer.$(OBJEXT): {$(VPATH)}internal.h io_buffer.$(OBJEXT): {$(VPATH)}internal/anyargs.h io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic.h io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/char.h diff --git a/include/ruby/io/buffer.h b/include/ruby/io/buffer.h index 1154b98bdd8..4826a7a76f6 100644 --- a/include/ruby/io/buffer.h +++ b/include/ruby/io/buffer.h @@ -69,12 +69,12 @@ VALUE rb_io_buffer_map(VALUE io, size_t size, off_t offset, enum rb_io_buffer_fl https://github.com/ruby/ruby/blob/trunk/include/ruby/io/buffer.h#L69 VALUE rb_io_buffer_lock(VALUE self); VALUE rb_io_buffer_unlock(VALUE self); +int rb_io_buffer_try_unlock(VALUE self); VALUE rb_io_buffer_free(VALUE self); -int rb_io_buffer_readonly_p(VALUE self); - -void rb_io_buffer_get(VALUE self, void **base, size_t *size); -void rb_io_buffer_get_readonly(VALUE self, const void **base, size_t *size); +int rb_io_buffer_get_bytes(VALUE self, void **base, size_t *size); +void rb_io_buffer_get_bytes_for_reading(VALUE self, const void **base, size_t *size); +void rb_io_buffer_get_bytes_for_writing(VALUE self, void **base, size_t *size); VALUE rb_io_buffer_transfer(VALUE self); void rb_io_buffer_resize(VALUE self, size_t size); diff --git a/io_buffer.c b/io_buffer.c index a9aaa8d6267..4487cab7736 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -9,6 +9,7 @@ https://github.com/ruby/ruby/blob/trunk/io_buffer.c#L9 #include "ruby/io.h" #include "ruby/io/buffer.h" +#include "internal.h" #include "internal/string.h" #include "internal/bits.h" #include "internal/error.h" @@ -16,7 +17,7 @@ https://github.com/ruby/ruby/blob/trunk/io_buffer.c#L17 VALUE rb_cIOBuffer; VALUE rb_eIOBufferLockedError; VALUE rb_eIOBufferAllocationError; -VALUE rb_eIOBufferMutationError; +VALUE rb_eIOBufferAccessError; VALUE rb_eIOBufferInvalidatedError; size_t RUBY_IO_BUFFER_PAGE_SIZE; @@ -155,41 +156,45 @@ io_buffer_experimental(void) https://github.com/ruby/ruby/blob/trunk/io_buffer.c#L156 } static void -io_buffer_initialize(struct rb_io_buffer *data, void *base, size_t size, enum rb_io_buffer_flags flags, VALUE source) +io_buffer_zero(struct rb_io_buffer *data) { - data->flags = flags; - data->size = size; + data->base = NULL; + data->size = 0; +#if defined(_WIN32) + data->mapping = NULL; +#endif + data->source = Qnil; +} +static void +io_buffer_initialize(struct rb_io_buffer *data, void *base, size_t size, enum rb_io_buffer_flags flags, VALUE source) +{ if (base) { - data->base = base; + // If we are provided a pointer, we use it. } - else { - if (data->flags & RB_IO_BUFFER_INTERNAL) { - data->base = calloc(data->size, 1); + else if (size) { + // If we are provided a non-zero size, we allocate it: + if (flags & RB_IO_BUFFER_INTERNAL) { + base = calloc(size, 1); } - else if (data->flags & RB_IO_BUFFER_MAPPED) { - data->base = io_buffer_map_memory(data->size); + else if (flags & RB_IO_BUFFER_MAPPED) { + base = io_buffer_map_memory(size); } - } - if (!data->base) { - rb_raise(rb_eIOBufferAllocationError, "Could not allocate buffer!"); + if (!base) { + rb_raise(rb_eIOBufferAllocationError, "Could not allocate buffer!"); + } + } else { + // Otherwise we don't do anything. + return; } + data->base = base; + data->size = size; + data->flags = flags; data->source = source; } -static void -io_buffer_zero(struct rb_io_buffer *data) -{ - data->base = NULL; - data->size = 0; -#if defined(_WIN32) - data->mapping = NULL; -#endif - data->source = Qnil; -} - static int io_buffer_free(struct rb_io_buffer *data) { @@ -214,8 +219,9 @@ io_buffer_free(struct rb_io_buffer *data) https://github.com/ruby/ruby/blob/trunk/io_buffer.c#L219 data->mapping = NULL; } #endif - data->size = 0; + data->flags = 0; + data->source = Qnil; return 1; } @@ -275,6 +281,31 @@ rb_io_buffer_type_allocate(VALUE self) https://github.com/ruby/ruby/blob/trunk/io_buffer.c#L281 return instance; } +/* + * call-seq: IO::Buffer.for(string) -> io_buffer + * + * Creates a IO::Buffer from the given string's memory. The buffer remains + * associated with the string, and writing to a buffer will update the string's + * contents. + * + * Until #free is invoked on the buffer, either explicitly or via the garbage + * collector, the source string will be locked and cannot be modified. + * + * If the string is frozen, it will create a read-only buffer which cannot be + * modified. + * + * string = 'test' + * buffer = IO::Buffer.for(str) + * buffer.external? #=> true + * + * buffer.get_string(0, 1) + * # => "t" + * string + * # => "best" + * + * buffer.resize(100) + * # in `resize': Cannot resize external buffer! (IO::Buffer::AccessError) + */ VALUE rb_io_buffer_type_for(VALUE klass, VALUE string) { @@ -329,6 +360,44 @@ rb_io_buffer_map(VALUE io, size_t size, off_t offset, enum rb_io_buffer_flags fl https://github.com/ruby/ruby/blob/trunk/io_buffer.c#L360 return instance; } +/* + * call-seq: IO::Buffer.map(file, [size, [offset, [flags]]]) -> io_buffer + * + * Create an IO::Buffer for reading from +file+ by memory-mapping the file. + * +file_io+ should be a +File+ instance, opened for reading. + * + * Optional +size+ and +offset+ of mapping can be specified. + * + * By default, the buffer would be immutable (read only); to create a writable + * mapping, you need to open a file in read-write mode, and explicitly pass + * +flags+ argument without IO::Buffer::IMMUTABLE. + * + * File.write('test.txt', 'test') + * + * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) + * # => #<IO::Buffer 0x00000001014a0000+4 MAPPED READONLY> + * + * buffer.readonly? # => true + * + * buffer.get_string + * # => "test" + * + * buffer.set_string('b', 0) + * # `set_string': Buffer is not writable! (IO::Buffer::AccessError) + * + * # create read/write mapping: length 4 bytes, offset 0, flags 0 + * buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 4, 0) + * buffer.set_string('b', 0) + * # => 1 + * + * # Check it + * File.read('test.txt') + * # => "best" + * + * Note that some operating systems may not have cache coherency between mapped + * buffers and file reads. + * + */ static VALUE io_buffer_map(int argc, VALUE *argv, VALUE klass) { @@ -336,10 +405,11 @@ io_buffer_map(int argc, VALUE *argv, VALUE klass) https://github.com/ruby/ruby/blob/trunk/io_buffer.c#L405 rb_error_arity(argc, 2, 4); } + // We might like to handle a string path? VALUE io = argv[0]; size_t size; - if (argc >= 2) { + if (argc >= 2 && !RB_NIL_P(argv[1])) { size = RB_NUM2SIZE(argv[1]); } else { @@ -354,7 +424,7 @@ io_buffer_map(int argc, VALUE *argv, VALUE klass) https://github.com/ruby/ruby/blob/trunk/io_buffer.c#L424 rb_raise(rb_eArgError, "File larger than address space!"); } else { - // This conversion shoud be safe: + // This conversion should be safe: size = (size_t)file_size; } } @@ -364,7 +434,7 @@ io_buffer_map(int argc, VALUE *argv, VALUE klass) https://github.com/ruby/ruby/blob/trunk/io_buffer.c#L434 offset = NUM2OFFT(argv[2]); } - enum rb_io_buffer_flags flags = RB_IO_BUFFER_READONLY; + enum rb_io_buffer_flags flags = 0; if (argc >= 4) { flags = RB_NUM2UINT(argv[3]); } @@ -376,13 +446,40 @@ io_buffer_map(int argc, VALUE *argv, VALUE klass) https://github.com/ruby/ruby/blob/trunk/io_buffer.c#L446 static inline enum rb_io_buffer_flags io_flags_for_size(size_t size) { - if (size > RUBY_IO_BUFFER_PAGE_SIZE) { + if (size >= RUBY_IO_BUFFER_PAGE_SIZE) { return RB_IO_BUFFER_MAPPED; } return RB_IO_BUFFER_INTERNAL; } +/* + * call-seq: IO::Buffer.new([size = DEFAULT_SIZE, [flags = 0]]) -> io_buffer + * + * Create a new zero-filled IO::Buffer of +size+ bytes. + * By default, the buffer will be _internal_: directly allocated chunk + * of the memory. But if the requested +size+ is more than OS-specific + * IO::Bufer::PAGE_SIZE, the buffer would be allocated using the + * virtual memory mechanism (anonymous +mmap+ on Unix, +VirtualAlloc+ + * on Windows). The behavior can be forced by passing IO::Buffer::MAPPED + * as a second parameter. + * + * Examples + * + * buffer = IO::Buffer.new(4) + * # => + * # #<IO::Buffer 0x000055b34497ea10+4 INTERNAL> + * # 0x00000000 00 00 00 00 .... + * + * buffer.get_string(0, 1) # => "\x00" + * + * buffer.set_string("test") + * buffer + * # => + * # #<IO::Buffer 0x000055b34497ea10+4 INTERNAL> + * # 0x00000000 74 65 73 74 test + * + */ VALUE rb_io_buffer_initialize(int argc, VALUE *arg (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/