
Issue #21210 has been updated by hanazuki (Kasumi Hanazuki). alanwu (Alan Wu) wrote in #note-5:
Another option that maintains validity across movement (untested):
```diff diff --git a/io_buffer.c b/io_buffer.c index 0534999319..13102b561d 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -570,7 +570,7 @@ rb_io_buffer_type_for(VALUE klass, VALUE string) } else { // This internally returns the source string if it's already frozen. - string = rb_str_tmp_frozen_acquire(string); + string = rb_str_tmp_frozen_no_embed_acquire(string); return io_buffer_for_make_instance(klass, string, RB_IO_BUFFER_READONLY); } } ```
So this will copy the embedded String content to a malloc'ed memory, right? The default GC embeds up to 640 bytes minus RSTRING header (IIUC). I think we need to evaluate the performance impact of adding such size of copies. ```ruby packet = "x" * 600 buf = IO::Buffer.for(packet) buf.get_value(:U8, 32) ```
Looks like the mutable string buffer code paths pin using rb_str_locktmp().
Ah, yes. rb_str_locktmp was not relevant to `IO::Buffer.for` without a block. In case of `IO::Buffer.for` with a block, the source String is referred to by a C stack variable, and so the object will not be moved. ---------------------------------------- Bug #21210: IO::Buffer gets invalidated on GC compaction https://bugs.ruby-lang.org/issues/21210#change-112528 * Author: hanazuki (Kasumi Hanazuki) * Status: Open * ruby -v: ruby 3.5.0dev (2025-04-01T16:11:01Z master 30e5e7c005) +PRISM [x86_64-linux] * Backport: 3.1: DONTNEED, 3.2: DONTNEED, 3.3: DONTNEED, 3.4: DONTNEED ---------------------------------------- commit:6012145299cfa4ab561360c78710c7f2941a7e9d implemented compaction for `IO::Buffer`. It looks like this doesn't work well with an `IO::Buffer` that shares memory region with a String object. I think the problem is that an `IO::Buffer` holds the raw pointer to the String content, and now the content can be moved by GC when the String is embedded. ```ruby str = +"hello" buf = IO::Buffer.for(str) p buf.valid? GC.verify_compaction_references(expand_heap: true, toward: :empty) p buf.valid? #=> should be true ``` This example should print two trues. Actually: ``` % ./ruby -v --disable-gems test.rb ruby 3.5.0dev (2025-04-01T16:11:01Z master 30e5e7c005) +PRISM [x86_64-linux] true false ``` -- https://bugs.ruby-lang.org/