<Goのパッケージ放浪記> ioパッケージに定義されている「Closerインターフェイス」について
今日は前回のWriterインターフェイスに続き、ioパッケージのCloserインターフェイスまわりを覗いていきます。
Closerインターフェイス
早速Closerインターフェイスの内容をみていきます。
// Closer is the interface that wraps the basic Close method.
//
// The behavior of Close after the first call is undefined.
// Specific implementations may document their own behavior.
type Closer interface {
Close() error
}
Closerの初回以降の呼び出しはどうなるか定義されてないようです。
Closerインターフェイスの実装を確認する
Closerインターフェイスの実装として今回は、osパッケージのFileを調査して実装を探っていきます。
まず見ていくファイルは、 os/types.go です。
// File represents an open file descriptor.
type File struct {
*file // os specific
}
こちらにFile構造体が定義されています。
*file にはOS固有の構造体を指定して埋め込み(Embedding)しているようです。
今回はLinux環境での file の定義を見たいので、 os/file_unix.go を見ていきます。
このファイルは冒頭にビルド制約(ビルドタグ)が書かれていて、特定の条件時のみビルド対象にするように設定されています。
// +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris
// file is the real representation of *File.
// The extra level of indirection ensures that no clients of os
// can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor.
type file struct {
pfd poll.FD
name string
dirinfo *dirInfo // nil unless directory being read
nonblock bool // whether we set nonblocking mode
stdoutOrErr bool // whether this is stdout or stderr
appendMode bool // whether file is opened for appending
}
pollやepollのファイルディスクリプタと思われる pfd を持っています。
file 構造体は file 構造体に埋め込まれているので、 file 構造体を通してCloseメソッドを実装しているのがわかります。
// Close closes the File, rendering it unusable for I/O.
// On files that support SetDeadline, any pending I/O operations will
// be canceled and return immediately with an error.
// Close will return an error if it has already been called.
func (f *File) Close() error {
if f == nil {
return ErrInvalid
}
return f.file.close()
}
func (file *file) close() error {
// ファイルが無い場合はエラーを返す
if file == nil {
return syscall.EINVAL
}
// ディレクトリ情報を持っている場合はそちらもCloseする
if file.dirinfo != nil {
file.dirinfo.close()
}
var err error
// file構造体のpfdのCloseメソッドを呼び出す
if e := file.pfd.Close(); e != nil {
// エラーがpoll.ErrFileClosing変数と同じ場合はeにエラーを設定する
if e == poll.ErrFileClosing {
e = ErrClosed
}
// PathError構造体に、行った操作、パス、エラーを設定する
err = &PathError{"close", file.name, e}
}
// no need for a finalizer anymore
// fileに関連付けられているすべてのfinalizerをクリアする
runtime.SetFinalizer(file, nil)
return err
}
こちらも、Reader、Writer同様に同じインターフェイスを呼び出しています。
この先の処理も気になるのですが、今回の目的から反れるのでここまでにしておきます。
失敗した場合の処理は、 pfd が定義されているpollパッケージ内の変数とエラーを比較しています。
poll.ErrFileClosing と同じ場合は、 e を ErrClosed にしてPathError構造体に入れています。
PathError構造体は以下のようになっています。
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
func (e *PathError) Unwrap() error { return e.Err }
行った操作や、発生したパス、エラーを含む構造体です。
最後に、Closeメソッドの成功、失敗にかかわらず SetFinalizer 関数で、すべてのファイナライザをクリアしているのがわかります。
こちらの説明によると、Closeを呼ばなかった場合としてファイナライザを設定しているようです。
明示的にCloseした場合は、保険用のファイナライザが必要なくなるのでクリアしているのかもしれません。
このあたりの挙動は、fileやpollパッケージを覗くときに理解していきたいと思います。
まとめ
Closeメソッド自体はシンプルでしたが、それを取り巻くFile構造体まわりはOSの差分を吸収するために、ビルド制約や構造体の埋め込みを利用して対応しているのが理解できました。
ファイルディスクリプタを扱う部分はシステムコールが多く、理解しにくそうですが追々覗いていきたいと思います。
細かいところでは、エラーの判別やラップの仕方が勉強になりました。
この記事を書いた人
-
2008年にアーティスへ入社。
システムエンジニアとして、SI案件のシステム開発に携わる。
その後、事業開発部の立ち上げから自社サービスの開発、保守をメインに従事。
ドメイン駆動設計(DDD)を中心にドメインを重視しながら、保守可能なソフトウェア開発を探求している。
この執筆者の最新記事
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー