Skip to content

IO::Buffer: 状態問い合わせの 10 メソッドを追加#3305

Merged
znz merged 3 commits into
rurema:masterfrom
Watson1978:io-buffer-state-queries
Jul 26, 2026
Merged

IO::Buffer: 状態問い合わせの 10 メソッドを追加#3305
znz merged 3 commits into
rurema:masterfrom
Watson1978:io-buffer-state-queries

Conversation

@Watson1978

@Watson1978 Watson1978 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

概要

[c:IO::Buffer] の状態を問い合わせる 10 メソッドを追加しました。
#3278 / #3295 / #3301 の続きです。

empty? / null? / valid? / internal? / external? / readonly? /
mapped? / locked? / shared? / private?

レビューを受けて、同じファイル内のリンク切れの解消と、IO::Buffer.new
説明の誤り (1 バイトのずれ) の修正も含んでいます。

登場バージョン

実機で確認したところ、8 個は IO::Buffer が導入された Ruby 3.1 からありますが、
2 個は後の版で追加されていました。#@since で分岐しています。

3.1.6:  8/10  (shared? private? が無い)
3.2.11: 9/10  (private? が無い)
3.3.12: 10/10
3.4.10: 10/10
4.0.6:  10/10
  • shared? … 3.2 から
  • private? … 3.3 から

readonly? は Ruby 本体の rdoc も直しました

rdoc には「Frozen strings and read-only files create read-only buffers.」と
だけ書かれていましたが、IO::Buffer.forブロックを渡さない場合は、元の
文字列が freeze されていなくても常に読み取り専用
になります。これが一番よく
あるケースなのに説明が抜けていたため、Ruby 本体にも修正を送り、
ruby/ruby#18062 としてマージされました。

るりま側もこの区別を説明する形にしています (3.1.6 / 3.2.11 / 3.4.10 / 4.0.6 で
同じ挙動を確認)。

$ ruby -e 'p IO::Buffer.for("test").readonly?'
true
$ ruby -e 'p(IO::Buffer.for("test") { |buf| buf.readonly? })'
false
$ ruby -e 'p(IO::Buffer.for("test".freeze) { |buf| buf.readonly? })'
true
$ ruby -e 'p IO::Buffer.new(4).readonly?'
false

ブロックなしの場合は内部で作った文字列の複製をバッファの元として使うため
(rb_io_buffer_type_for がその経路で RB_IO_BUFFER_READONLY を無条件に渡す)、
元の文字列が freeze されているかどうかによらず読み取り専用になります。

未収録 API へのリンクは平文にしました (レビュー対応)

IO::Buffer は段階的に追加している途中で、まだ収録していないメソッドを
参照している箇所がありました。リンクにすると存在しない URL を指してしまうため、
収録する回まではコードスパンにし、戻し忘れないよう #@# コメントを添えています。

参照 対応
IO::Buffer#set_value 平文にした
IO::Buffer.map 平文にした
IO::Buffer.for 平文にした (4 箇所。うち 2 箇所は #3301 由来)
IO::Buffer#locked locked? への案内に書き換えた (#3295 由来)
Fiber::Scheduler rdoc へのリンクにした (#3299 と同じ扱い)

IO::Buffer.new の PAGE_SIZE の境界を修正しました (レビュー対応)

IO::Buffer.new の説明が「size が PAGE_SIZE より大きい場合」となって
いましたが、実装は >= です。

    if (size >= RUBY_IO_BUFFER_PAGE_SIZE) {
$ ruby -e 'ps = IO::Buffer::PAGE_SIZE; p [IO::Buffer.new(ps - 1).mapped?, IO::Buffer.new(ps).mapped?]'
[false, true]

3.1.6 / 3.2.11 / 3.3.12 / 3.4.10 / 4.0.6 のいずれも同じ結果で、「PAGE_SIZE
以上の場合」に修正しました。

例について

例に使ったコードは 3.1 / 3.2 / 3.3 / 4.0 で実行し、すべて同じ出力になることを
確認しました。

valid? / mapped? / locked? / shared? / private? は、簡潔で安定した例を
作るのが難しい(ファイルのマッピングや slice の無効化などが必要になる)ため、
説明のみとしています。

検証

  • rake check_format / check_blank_lines / check_indent_in_samplecode /
    check_single_space_indent
  • bitclust update --markdowntree=manual/api を 3.1 / 3.2 / 3.3 / 4.0 で実行し、
    エラーが出ないこと、登録されるメソッドが 8 / 9 / 10 / 10 件
    上記の追加バージョンどおりになることを確認しました。
  • IO__Buffer.md 由来の全エントリについて [m:...] / [c:...] を DB と
    突き合わせ、リンク切れが 0 件であることを確認しました。

🤖 Generated with Claude Code

empty? / null? / valid? / internal? / external? / readonly? / mapped? /
locked? / shared? / private? を追加した。

登場バージョンを実機で確認したところ、8 個は IO::Buffer が導入された 3.1 から
あるが、shared? は 3.2、private? は 3.3 からで、それぞれ #@SInCE で分岐した。

readonly? の説明は実機に合わせた。rdoc には「Frozen strings and read-only
files create read-only buffers.」とあるが、IO::Buffer.for は凍結していない
文字列から作っても読み取り専用のバッファになる (3.1〜4.0 で確認)。
ブロックを渡した場合のみ書き込み可能になる。

例に使ったコードは 3.1 / 3.2 / 3.3 / 4.0 で実行し、すべて同じ出力になることを
確認している。valid? / mapped? / locked? / shared? / private? は、簡潔で
安定した例を作るのが難しいため、説明のみとした。

bitclust のデータベース生成を 3.1 / 3.2 / 3.3 / 4.0 で実行してエラーが出ないこと、
登録されるメソッドが 8 / 9 / 10 / 10 件と追加バージョンどおりになることを確認済み。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@znz

znz commented Jul 25, 2026

Copy link
Copy Markdown
Member

レビューしました。since 版・挙動はすべて実測どおりで、1 点だけ確認いただきたい点があります(リンク切れ)。

検証(実機 3.1.7 / 3.2.11 / 3.3.12 / 4.0.6 + bitclust ミニ描画)

  • since: shared? は 3.2 から(3.1 に無い)・private? は 3.3 から(3.2 に無い)・他 8 つ(empty?/null?/valid?/internal?/external?/readonly?/mapped?/locked?)は 3.1 から、を確認(#@since と一致)。描画でも版ゲートが正しく効きます。
  • 挙動: empty?(new(0)=true/new(4)=false)・null?(free 後 true)・internal?/external?(for=external・new=internal)・readonly?(for=true/new=false)・mapped?(new(4)=false・new(MAPPED)=true)・locked?(locked{} 内で true)・shared?/private?(通常 false)をすべて確認。valid? も slice 元を free した後に false になることを確認。
  • compileerror なし。

確認いただきたい点: set_value / map への参照がリンク切れ

  • readonly? の本文の [m:IO::Buffer#set_value] と、mapped? の本文の [m:IO::Buffer.map] は、実 Ruby には存在するメソッドですが、doctree の IO__Buffer.md にまだエントリが無いため、現状ではリンク切れ(404)になります(ミニ描画で /method/IO=Buffer/i/set_value/s/map の未収録 URL を確認)。
  • IO::Buffer は段階的に追加いただいている途中なので、これらのメソッドを追加する回でリンクが繋がる想定であれば問題ありません。もし当分先になるなら、その回まで平文にしておくか、set_value / map を先に足す手もあります(ご判断にお任せします)。

参考(本 PR 対象外)

mapped? の「大きさが [m:IO::Buffer::PAGE_SIZE] 以上の場合」は実測(size == PAGE_SIZE ちょうどで既に mapped?=true)と一致していて正確です。一方、既存の IO::Buffer.new の説明は「size が PAGE_SIZE より大きい場合」となっていて 1 バイトずれています(本 PR の範囲外なので参考まで)。

🤖 Generated with Claude Code

リンク切れの指摘への対応。IO::Buffer は段階的に追加している途中なので、
未収録のものは収録する回までコードスパンにし、#@# コメントを残した。

- IO::Buffer#set_value / IO::Buffer.map (指摘のあった2件)
- IO::Buffer.for (4箇所。うち2箇所は rurema#3301 由来)
- IO::Buffer#locked (rurema#3295 由来)。LOCKED 定数の説明が
  「[m:IO::Buffer#locked] を参照してください」だけだったので、
  本 PR で追加する locked? で調べられる旨の説明に書き換えた
- Fiber::Scheduler (rurema#3278 由来) は rdoc へのリンクにした (rurema#3299 と同じ扱い)

あわせて、参考として指摘のあった IO::Buffer.new の説明を
「PAGE_SIZE より大きい場合」から「PAGE_SIZE 以上の場合」に修正した。
io_buffer.c は size >= RUBY_IO_BUFFER_PAGE_SIZE で判定しており、
実機でも 3.1〜4.0 のすべてで size == PAGE_SIZE のとき mapped? が true になる。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Watson1978

Copy link
Copy Markdown
Contributor Author

レビューありがとうございます。ご指摘の 2 点とも対応しました。

リンク切れ: 収録するまで平文にしました

IO::Buffer は段階的に追加している途中で、set_value / map を足すのが
いつになるか確約できないので、その回まで平文(コードスパン)にしておく方針にしました。
収録する回でリンクへ戻せるよう、各箇所に #@# コメントを残しています。

あわせて全参照を機械的に突き合わせたところ、ご指摘の 2 つ以外にも
未収録 API へのリンクが 3 種類残っていたので、同じ扱いに揃えました。

参照 状況 対応
IO::Buffer#set_value 未収録 平文にした (ご指摘)
IO::Buffer.map 未収録 平文にした (ご指摘)
IO::Buffer.for 未収録 平文にした (4 箇所。うち 2 箇所は #3301 由来)
IO::Buffer#locked 未収録 locked? への案内に書き換えた (#3295 由来)
Fiber::Scheduler 未収録 rdoc へのリンクにした (#3299 と同じ扱い)

IO::Buffer#lockedLOCKED 定数の説明からの参照でしたが、
「[m:IO::Buffer#locked] を参照してください」だけの文だったので、
本 PR で追加する locked? で状態を調べられる旨の説明に書き換えました。
locked を収録する回で言及を足します。

突き合わせは、4.0 の DB に対して bitclust lookup を全参照
([m:...] / [c:...]#@# 行は除外)に対して回す方法で行い、
現在は NG 0 件です。

参考でご指摘の PAGE_SIZE の境界

こちらも同じファイルなので、この PR で直しました。
IO::Buffer.new の説明を「PAGE_SIZE より大きい場合」から
「PAGE_SIZE 以上の場合」に修正しています。

io_buffer.c の判定は >= で、

    if (size >= RUBY_IO_BUFFER_PAGE_SIZE) {

実機でも PAGE_SIZE ちょうどで mapped? が true になります。

$ ruby -e 'ps = IO::Buffer::PAGE_SIZE; p [IO::Buffer.new(ps - 1).mapped?, IO::Buffer.new(ps).mapped?]'
[false, true]

3.1.6 / 3.2.11 / 3.3.12 / 3.4.10 / 4.0.6 のすべてで同じ結果でした。
なお rdoc 側は IO::Buffer.new の説明が "more than IO::Buffer::PAGE_SIZE"、
mapped? の説明が "at least IO::Buffer::PAGE_SIZE" と食い違っており、
実装と一致しているのは後者です。

別 PR に分けたほうがよければ、そのように直します。

🤖 Generated with Claude Code

ruby/ruby#18062 (マージ済み) で IO::Buffer#readonly? の rdoc が明確化された
のに合わせる。従来の記述は「IO::Buffer.for で作ったバッファ」とだけ書いており、
ブロックを渡した場合に読み取り専用にならない点が抜けていた。

実機 (3.1.6 / 3.2.11 / 3.4.10 / 4.0.6 で同じ) の挙動:

  IO::Buffer.for("test").readonly?                        # => true
  IO::Buffer.for("test") { |buf| buf.readonly? }          # => false
  IO::Buffer.for("test".freeze) { |buf| buf.readonly? }   # => true
  IO::Buffer.new(4).readonly?                             # => false

ブロックなしの場合は内部で作った文字列の複製を元にするため、元の文字列が
freeze されていなくても常に読み取り専用になる。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Watson1978

Copy link
Copy Markdown
Contributor Author

レビュー中に恐縮ですが、readonly? の説明を 1 件追記しました (bc8fbae)。

経緯

IO::Buffer#readonly? の rdoc が

Frozen strings and read-only files create read-only buffers.

となっていて、IO::Buffer.for にブロックを渡さない場合は、元の文字列が
freeze されていなくても常に読み取り専用になる
という一番よくあるケースが
抜けていました。ruby/ruby へ報告したところ ruby/ruby#18062 としてマージされ、
rdoc 側が明確化されたので、るりまの記述も揃えました。

従来の記述は「IO::Buffer.for で作ったバッファや、読み取り専用のファイルから
作ったバッファがこれにあたります」で、ブロックを渡した場合に読み取り専用に
ならない点が抜けていました。

実機の挙動 (3.1.6 / 3.2.11 / 3.4.10 / 4.0.6 で同じ)

$ ruby -e 'p IO::Buffer.for("test").readonly?'
true
$ ruby -e 'p(IO::Buffer.for("test") { |buf| buf.readonly? })'
false
$ ruby -e 'p(IO::Buffer.for("test".freeze) { |buf| buf.readonly? })'
true
$ ruby -e 'p IO::Buffer.new(4).readonly?'
false

ブロックなしの場合は内部で作った文字列の複製をバッファの元として使うため
(rb_io_buffer_type_for がその経路で RB_IO_BUFFER_READONLY を無条件に渡す)、
元の文字列が freeze されているかどうかによらず読み取り専用になります。
ブロックありの場合は元の文字列のメモリを直接参照するので、その文字列が
freeze されている場合にだけ読み取り専用になります。

記述はこの区別を説明する形に書き直し、例も 3 ケース (ブロックなし /
ブロックあり非 frozen / ブロックあり frozen) に増やしました。
記載した例はそのままの形で 4 つの版で実行し、出力が一致することを確認しています。

rake check_blank_lines / check_indent_in_samplecode /
check_single_space_indent と DB 生成、リンクの突き合わせも通っています。

🤖 Generated with Claude Code

@znz

znz commented Jul 25, 2026

Copy link
Copy Markdown
Member

対応ありがとうございます。2 点とも確認しました。

  • 未収録 API への参照(IO::Buffer.for / #set_value / .map / #locked)は平文コードスパンになっていて、各所に #@# で「収録したらリンクに戻す」旨のコメントが残っている形を確認しました。既存(ARGF.class.mdBinding.md 等)の #@# 運用とも揃っています。残る [m:IO::Buffer#...] 参照はすべて同ファイル内の ### def / ### const に解決することも確認しました。
  • readonly? の追記(bc8fbae)は実機と一致しています。Ruby 3.4.8 / 4.0.1 で確認: IO::Buffer.for("test").readonly? # => trueIO::Buffer.for("test"){|b| b.readonly?} # => falseIO::Buffer.for("test".freeze){|b| b.readonly?} # => trueIO::Buffer.new(4).readonly? # => false。ブロック無しの .for で元文字列が freeze されていなくても read-only になる、という一番よくあるケースが補われていて良いと思います。[DOC] Clarify when IO::Buffer#readonly? is true ruby/ruby#18062 の反映もありがとうございます。

4.0 で statichtml をビルドして compileerror 0 を確認済みです。マージ可と考えます。

🤖 Generated with Claude Code

@znz
znz merged commit a465d00 into rurema:master Jul 26, 2026
9 checks passed
@Watson1978
Watson1978 deleted the io-buffer-state-queries branch July 26, 2026 17:49
znz pushed a commit that referenced this pull request Jul 27, 2026
IO::Buffer.for / IO::Buffer.map / IO::Buffer.string を追加した。
IO::Buffer.string は 3.3 で追加されたため #@SInCE 3.3 で分岐する。

実機で確認した挙動:

- for はブロックを渡さない場合、内容を複製した凍結済みの文字列を元にするため、
  あとから元の文字列を変更してもバッファは変わらない。
- for にブロックを渡した場合は元の文字列自身を参照し、ブロックの実行中は
  元の文字列を変更できない (RuntimeError)。
- map は既定で書き込み可能かつ共有のマップになるため、読み込み専用で開いた
  ファイルをそのまま渡すと Errno::EACCES になる。
- offset はシステム依存で、多くの環境ではページサイズの倍数である必要がある。

あわせて以下を修正した。

- #3305 で平文にしていた IO::Buffer.for / IO::Buffer.map への参照をリンクに戻した。
- PAGE_SIZE 定数の説明が「size がこの値より大きい場合」のままだった。
  #3305 で IO::Buffer.new 側は「以上」に直したが、定数側が残っていた。
- get_string の例の期待値 #<Encoding:BINARY (ASCII-8BIT)> は 3.4 以降の表示で、
  3.1〜3.3 では #<Encoding:ASCII-8BIT> になる。.encoding.name に変えて
  全版で一致するようにした。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants