ruby-dev (Japanese) list archive (unofficial mirror)
 help / color / mirror / Atom feed
From: elfham@gmail.com
To: ruby-dev@ruby-lang.org
Subject: [ruby-dev:50955] [Ruby master Feature#17148] stdbuf(1) support
Date: Fri, 04 Sep 2020 09:54:38 +0000 (UTC)	[thread overview]
Message-ID: <redmine.issue-17148.20200904095438.33140@ruby-lang.org> (raw)
In-Reply-To: <redmine.issue-17148.20200904095438.33140@ruby-lang.org>

Issue #17148 has been reported by os (Shigeki OHARA).

----------------------------------------
Feature #17148: stdbuf(1) support
https://bugs.ruby-lang.org/issues/17148

* Author: os (Shigeki OHARA)
* Status: Open
* Priority: Normal
----------------------------------------
# 概要

Ruby インタープリターを stdbuf(1) による stdout/stderr の出力バッファ制御に対応させたい。

# 背景

UNIX のフィルターコマンドの多くは、標準出力の先が端末かどうかによって出力バッファリングの制御を行っており、パイプを多段に繋げているとリアルタイムな出力が行われず問題になることが多々あります。

コマンドによってはバッファリング制御するオプションを設けることでバッファリングの問題を回避することができますが、必ずしもそのようなオプションがあるとは限りません。

そこで Linux (GNU coreutils) や FreeBSD には stdbuf(1) というコマンドが用意されており、

```
# vmstat 1 | stdbuf -o L awk '$2 > 1 || $3 > 1' | cat -n
```

のようにコマンドを引数に指定して起動することにより、そのコマンドの出力のバッファリングを制御することができます。

この stdbuf(1) のしくみですが、まず libstdbuf.so という共有ライブラリが用意してあり、ここには環境変数で指定したバッファリングの設定を元に stdin/stdout/stderr を setvbuf() するコードが定義してあります。

そして stdbuf(1) はバッファリングの設定値と LD_PRELOAD=libstdbuf.so を環境変数にセットしたうえで、指定したコマンドを起動します。

すると、指定したコマンドは所与の設定にて setvbuf() された状態で動作することになります。

ちなみに、 NetBSD では stdbuf(1) コマンドは用意されていませんが、 stdio に環境変数を参照して setvbuf(3) する機能が組み込まれており、所定の環境変数をセットすることで同様の効果が得られます。


翻って (現在の) Ruby の STDIN/STDOUT/STDERR は、 stdio そのまま利用しているわけではないので stdbuf(1) を使用することはできません。

# 提案

Ruby インタープリターの初期化時に stdbuf(1) がセットした環境変数を読み取って、 libstdbuf.so と同様のバッファリング制御を Ruby でエミュレーションする機能を提案します。

C ではなく Ruby のコードですみませんが、実装のイメージとしては以下のような感じとなります。

```
% cat stdbuf.rb
def stdbuf(env = ENV)
  case RUBY_PLATFORM
  when /netbsd/i
    stdbuf_all = env['STDBUF']
    {
      'STDBUF0' => STDIN,
      'STDBUF1' => STDOUT,
      'STDBUF2' => STDERR,
    }.each do |key, io|
      next unless value = env[key] || value = stdbuf_all
      case value
      when 'U', 'u', 'L', 'l', '0'
        io.sync = true
      when 'F', 'f', /\A\d+\z/
        io.sync = false
      end
    end
  else  # Linux (GNU coreutils), FreeBSD, etc...
    return if !env.key?('LD_PRELOAD') || /\blibstdbuf.so\b/ !~ env['LD_PRELOAD']
    {
      '_STDBUF_I' => STDIN,
      '_STDBUF_O' => STDOUT,
      '_STDBUF_E' => STDERR,
    }.each do |key, io|
      next unless value = env[key]
      case value
      when '0', 'L'
        io.sync = true
      when 'B', /\A\d+(?:[kKMGTPEZY]B?)?\z/
        io.sync = false
      end
    end
  end
end

stdbuf(ENV)
% ruby -I. -rstdbuf -e'loop{puts Time.now; sleep 1}' | cat
^C-e:1:in `sleep': Interrupt
        from -e:1:in `block in <main>'
        from -e:1:in `loop'
        from -e:1:in `<main>'

% stdbuf -o 0 ruby -I. -rstdbuf -e'loop{puts Time.now; sleep 1}' | cat
2018-02-23 12:43:05 +0900
2018-02-23 12:43:06 +0900
2018-02-23 12:43:07 +0900
```

libstdbuf.so が利用しているのと同じ環境変数を、 Ruby の側でも自前で読み取り、バッファリングの制御を行うイメージです。

# 議論・課題

* Ruby インタープリター本体に組み込む必要があるか?
    * 上記コード例のように require などする方向もあるかもしれませんが、
    * 既存のスクリプトに手を入れなくて良いのが stdbuf のメリットなので、
    * Ruby インタープリターに組み込むのが良いかと思います。
* コード例は IO#sync= でバッファリング制御を雑に行っています
    * 実用上これでも多くのユースケースをカバーできている気もします
    * オリジナルの libstdbuf.so 相当の細かい制御ができるとより良いとは思います
* 互換性
    * 私の確認したのは GNU coreutils (Linux, Cygwin), FreeBSD, NetBSD のみです
    * 他にもあるかもしれません
    * ビルド時に stdbuf に対応しているか判断する必要があるかもしれません




-- 
https://bugs.ruby-lang.org/

       reply	other threads:[~2020-09-04  9:54 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-09-04  9:54 elfham [this message]
2020-09-04 15:42 ` [ruby-dev:50956] " nobu
2020-09-07  4:12 ` [ruby-dev:50957] " elfham+ruby

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=redmine.issue-17148.20200904095438.33140@ruby-lang.org \
    --to=elfham@gmail.com \
    --cc=ruby-dev@ruby-lang.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).