Help us understand the problem. What is going on with this article?

slurpで/proc/statを読み込めない理由

More than 5 years have passed since last update.

Clojure on Linuxで、CPUの使用率を求めようと、slurpで/proc/statを読み込もうとしたところ、

user=> (slurp "/proc/stat")
IOException Invalid argument  java.io.FileInputStream.available (FileInputStream.java:-2)

のExceptionが発生します。同様の現象がMLにありましたが、ノーレスです。
https://groups.google.com/forum/#!topic/clojure/3465V9GtkGE

ので、調べてみました。

slurpでIOExceptionが発生するのはなぜ?

まず、FileInputStream.availableを/proc下のファイルに対して呼ぶとIOExceptionが発生するわけですが、これがなぜslurp関数で発生するかという点についてです。

slurpでは内部でclojure.java.io/readerでファイルをオープンしています。

user=> (source slurp)
(defn slurp
  "Opens a reader on f and reads all its contents, returning a string.
  See clojure.java.io/reader for a complete list of supported arguments."
  {:added "1.0"}
  ([f & opts]
     (let [opts (normalize-slurp-opts opts)
           sb (StringBuilder.)]
       (with-open [#^java.io.Reader r (apply jio/reader f opts)]
         (loop [c (.read r)]
           (if (neg? c)
             (str sb)
             (do
               (.append sb (char c))
               (recur (.read r)))))))))

このreaderでBufferedReaderを作るのですが、
FileInputStream → BufferedInputStream → InputStreamReader → BufferedReader
の順でデコレイトされます。BufferedInputStreamが余分に感じますね!

そして、このBufferedInputStreamがこの場合悪さをします。BufferedInputStreamは、Socketからの読み込みのような断続的にデータが読み込み可能となるようなストリームを扱うので、readメソッドの中でavailable()を呼びます。そのため、今回のIOExeptionが発生してしまうのです。

BufferedInputStream.java
    public synchronized int read(byte b[], int off, int len)
        throws IOException
    {
        getBufIfOpen(); // Check for closed stream
        if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int n = 0;
        for (;;) {
            int nread = read1(b, off + n, len - n);
            if (nread <= 0)
                return (n == 0) ? nread : n;
            n += nread;
            if (n >= len)
                return n;
            // if not closed but no bytes available, return
            InputStream input = in;
            if (input != null && input.available() <= 0)
                return n;
        }
    }

/proc/statでFileInputStream.availableを呼ぶとIOExceptionが発生するのはなぜ?

FileInputStream.availableのソースを見てみます。が、このメソッドはnativeなので、実際はOSごとに処理がことなります。Linux用のコードは以下のところにあります。

hotspot/src/os/linux/vm/os_linux.cpp
int os::available(int fd, jlong *bytes) {
  jlong cur, end;
  int mode;
  struct stat64 buf64;

  if (::fstat64(fd, &buf64) >= 0) {
    mode = buf64.st_mode;
    if (S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode)) {
      /*
      * XXX: is the following call interruptible? If so, this might
      * need to go through the INTERRUPT_IO() wrapper as for other
      * blocking, interruptible calls in this file.
      */
      int n;
      if (::ioctl(fd, FIONREAD, &n) >= 0) {
        *bytes = n;
        return 1;
      }
    }
  }
  if ((cur = ::lseek64(fd, 0L, SEEK_CUR)) == -1) {
    return 0;
  } else if ((end = ::lseek64(fd, 0L, SEEK_END)) == -1) {
    return 0;
  } else if (::lseek64(fd, cur, SEEK_SET) == -1) {
    return 0;
  }
  *bytes = end - cur;
  return 1;
}

ここで0が返ると、IOExceptionになります。で、調べると

  } else if ((end = ::lseek64(fd, 0L, SEEK_END)) == -1) {

ここでlseek64が-1を返しているようです。/procはprocfsで実際のファイルシステムではないので、ファイルの終了位置にseekすることができない、という事情のようです。

まとめ

slurpやclojure.java.ioの関数をつかうと、どうしてもBufferedInputStreamでラッピングされてしまうので、/procのファイルを読む場合は、自前でFileReaderおよびBufferedReaderしましょう。

user=> (with-open [rdr (-> (java.io.FileReader. "/proc/stat") java.io.BufferedReader.)] (.readLine rdr))
"cpu  1496745 1567 428721 5810283 13961 68 6011 0 0 0"
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした