rubyの非同期タスクスケジューラー実装時に必要になったのでメモる。
See: https://ruby-doc.org/core-3.1.0/Fiber/SchedulerInterface.html 。
このbufferはプログラマーが管理するbufferであり、IOオブジェクト内のbufferではない、はず。
non blocking IOとは?
Outputの方で説明する。システムコールの結果は以下の二種類。
- カーネルバッファにスペースがあれば、バッファに書けるだけ書く。結果は書き込めたバイト数が返ってくる。
- もし全部書き込みされていなかったら、プログラムは再度writeを試みることになる。
- カーネルバッファにスペースがなければ、システムコールの結果としてエラーが返ってくる。
- プログラムは再度writeを試みることになる。
- IO::Bufferに保持されるバイト数は固定。(可変長ではなく、固定長を最初に確保する)
- 今まで何バイト書き込んだか(=これから何バイト目に書き込むのか)を、offset変数によって管理する。
string <----> io::Buffer object <---> IO object <--(fd)--> kernel buffer <---> disk
(programmerが管理) | (IO classが管理) | (OSが管理) |
(IO objectのも内部にbufferを持っている。そのどこまでデータが入っているのかはIO objectが保持してる?)
Read from IO::Buffer to string
タイトルにはnon blockingに書いているが、以下ブロッキングするread_partialの例を挙げてしまった。
non blockingにするには、read_nonblock をwriteの例と同じ様に使用する必要がある。
# 読み出したいバイト数
length = 123
# 読み出した結果を保持するbuffer。ここに書きこんでいく。
buffer = IO::Buffer.new(length)
# 読み出し元
io = ~~~
# 現在何バイトまで読み出したか (io の中でもトラッキングされているはず?)
offset = 0
while offset < length
read_string = ''
# 最大でbuffer.size-offset(つまりバッファの残り分)を取り出し、read_stringに上書きする。
# 最低は決まっていない。1バイトでも読み出せるものがあればブロックせずにそれを読み出す。
# See: https://docs.ruby-lang.org/ja/latest/method/IO/i/readpartial.html
io.read_partial(buffer.size-offset, read_string)
# bufferの中のoffsetバイト目以降に、read_stringを書き出す。
offset += buffer.set_string(read_string, offset)
# Use buffer and do something here.
# 書き出し終わったIO::Bufferをうまく使う。
read_particalはブロックをしないわけではない。以下の条件を満たす場合のみしかブロッキングをしない、ということ。
- IO オブジェクト内のバッファが空
- ストリームにデータが到着していない
- ストリームが EOF になっていない
from. https://docs.ruby-lang.org/ja/latest/method/IO/i/readpartial.html
Write from string to IO::Buffer
# https://docs.ruby-lang.org/ja/latest/method/IO/i/write_nonblock.html
# 書き込みたいstring(my_string)を含むbufferを作成する
length = len(my_string)
write_string = IO::Buffer.new(length)
## ここでwrite_string bufferにmy_stringをかく
# 書き込み先
io = ~~~
offset = 0
while offset < length
begin
# バッファからioに書き込むメソッドは#write_nonblockしか見つからなかった。
# (readの場合は、bufferへの読み出しを行うメソッドはread_nonblockとread_nonpartialがある)
result = io.write_nonblock(buffer.get_string(offset), exception: false)
rescue SystemCallError => e
return -e.errno
end
case result
when :wait_writable
sleep(0.1) # 非同期プログラミングではこの間に別の処理を行う。
else
offset += result
end
end