LoginSignup
7

More than 5 years have passed since last update.

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

Posted at

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"

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
7