Go
GoogleCloudStorage

Google Cloud Storage に Write したときは Close の戻り値を必ず確認する

Go言語で Google Cloud Storage を扱う cloud.google.com/go/storage という公式のパッケージがあります。

GCS にファイルをアップロードする処理で、io.Writer のインタフェースを経由してデータをアップロードすることになります。

Writer の取得までは以下のようなコード:

client, err := storage.NewClient(ctx)
if err != nil {
    return err
}
bkt := client.Bucket("bucketname")
obj := bkt.Object("filename1")
w := obj.NewWriter(ctx)

そして Write するときに、いつもの癖で以下のように書くと……ダメです:

...
w := obj.NewWriter(ctx)
defer w.Close()

_, err := w.Write([]byte("こんにちは!こんにちは!"))
if err != nil {
    return err
}

// アップロード成功!……とは限らない

ドキュメントを読んでみましょう。
https://godoc.org/cloud.google.com/go/storage#Writer.Write

Write appends to w. It implements the io.Writer interface.

Since writes happen asynchronously, Write may return a nil error even though the write failed (or will fail). Always use the error returned from Writer.Close to determine if the upload was successful.

下の段をGoogle翻訳:

書き込みは非同期的に行われるため、書き込みが失敗した(または失敗する)にもかかわらず、書き込みがnilエラーを返すことがあります。常にアップロードが成功したかどうかを判断するには、Writer.Closeから返されたエラーを使用します。

この通り Close の戻り値を確認する必要があります。書き込みが非同期なのがポイントで、Writer は内部では io.PipeWriter に Write しているだけです。この Pipe に送られたデータは非同期で GCS に送信されます。通信でエラーが発生しても、Write は成功する場合があるのです(というよりは Write はほぼ成功します、オンメモリの操作なので)。

正しくは以下のように Close の戻り値を確認します:

client, err := storage.NewClient(ctx)
if err != nil {
    return err
}
bkt := client.Bucket("bucketname")
obj := bkt.Object("filename1")
w := obj.NewWriter(ctx)

_, err := w.Write([]byte("こんにちは!こんにちは!"))
if err != nil {
    w.Close() // これは要るかどうか?
    return err
}

// このチェックを忘れずに!
if err := w.Close(); err != nil {
    return err
}

// ここまで来たらアップロード成功!