LoginSignup
0
0

More than 1 year has passed since last update.

Rubyで non blockingにIOする際に IO::Bufferオブジェクトを補助的に使用する

Last updated at Posted at 2022-01-03

rubyの非同期タスクスケジューラー実装時に必要になったのでメモる。
See: https://ruby-doc.org/core-3.1.0/Fiber/SchedulerInterface.html

:warning: この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

:warning: タイトルには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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0