0
0

More than 3 years have passed since last update.

Google Cloud Firestore で IllegalStateException: Firestore client has already been closed が発生する

Last updated at Posted at 2019-11-24

概要

  • Google Cloud Firestore API Client Library for Java で IllegalStateException: Firestore client has already been closed が発生する
  • Firestore オブジェクトの close メソッドを呼び出した後で、 FirestoreOptions の getService メソッドで取得した Firestore オブジェクトを使用すると発生する
  • 今回の環境: Java 11 + Google Cloud Firestore API Client Library for Java 1.31.0

エラーメッセージ例

java.lang.IllegalStateException: Firestore client has already been closed
  at com.google.common.base.Preconditions.checkState(Preconditions.java:511)
  at com.google.cloud.firestore.FirestoreImpl.streamRequest(FirestoreImpl.java:473)
  at com.google.cloud.firestore.Query.stream(Query.java:1141)
  at com.google.cloud.firestore.Query.get(Query.java:1181)
  at com.google.cloud.firestore.Query.get(Query.java:1151)

Firestore インターフェースの close メソッド

close メソッドをコールすると、関連付けられた gRPC チャネルがクローズされ、この Firestore オブジェクトが使用できなくなる。

Firestore (Google Cloud 0.119.0-alpha API)

Closes the gRPC channels associated with this instance and frees up their resources. This method blocks until all channels are closed. Once this method is called, this Firestore client is no longer usable.

Firestore インターフェースは AutoCloseable を継承しているため、try-with-resources で自動的に close メソッドを呼び出すことができる。

try-with-resources 文

try-with-resources 文は、1 つ以上のリソースを宣言する try 文です。リソースは、プログラムでの使用が終わったら閉じられなければいけないオブジェクトです。try-with-resources 文は、文の終わりで各リソースが確実に閉じられるようにします。java.io.Closeable を実装しているすべてのオブジェクトも含め、java.lang.AutoCloseable インタフェースを実装しているオブジェクトはリソースとして使用できます。

エラー発生コードの例

以下のようなコードでエラーが発生していた。

import com.google.api.core.ApiFuture;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.firestore.CollectionReference;
import com.google.cloud.firestore.DocumentSnapshot;
import com.google.cloud.firestore.Firestore;
import com.google.cloud.firestore.FirestoreOptions;
import com.google.cloud.firestore.Query;
import com.google.cloud.firestore.QuerySnapshot;

public class MyFirestore {

  private final String projectId;
  private final GoogleCredentials credentials;
  private Firestore firestore;
  private final String collectionPath;
  private final FirestoreOptions firestoreOptions;

  public MyFirestore(String projectId, GoogleCredentials credentials, String collectionPath) {
    this.projectId = projectId;
    this.credentials = credentials;
    this.collectionPath = collectionPath;
    this.firestoreOptions =
      FirestoreOptions.newBuilder()
        .setCredentials(credentials)
        .setProjectId(projectId)
        .build();
  }

  public <T> T get(String id, Class<T> type) throws Exception {
    // try-with-resources: 使用後に自動で Firestore#close を呼び出す
    try (Firestore firestore = firestoreOptions.getService()) {
      // コレクションを取得
      CollectionReference c = firestore.collection(collectionPath);
      // コレクションからドキュメントを取得
      Query query = c.whereEqualTo("mykey", id);
      ApiFuture<QuerySnapshot> querySnapshot = query.get();
      for (DocumentSnapshot doc : querySnapshot.get().getDocuments()) {
        return doc.toObject(type);
      }
      return null;
    }
  }
}

解決策

都度 FirestoreOptions を生成する

Firestore オブジェクトを close した場合は、再度 FirestoreOptions を生成する必要がある。

毎回 Firestore#close をコールするのであれば、以下のように FirestoreOptions も都度生成して Firestore を取得するようにする。

private static Firestore getFirestore(String projectId, GoogleCredentials credentials) {
  FirestoreOptions firestoreOptions =
    FirestoreOptions.newBuilder()
      .setCredentials(credentials)
      .setProjectId(projectId)
      .build();
  return firestoreOptions.getService();
}

Firestore を使い回す

もうひとつの解決策として、Firestore を close せずにシングルトンパターンなどでオブジェクトを使い回しても良い。

FirestoreOptions クラスの getService メソッド

FirestoreOptions のドキュメントには何も載っていない。

FirestoreOptions (Google Cloud 0.119.0-alpha API)

FirestoreOptions の親クラスである ServiceOptions には発生する例外についての説明は載っていない。

ServiceOptions (Google Cloud 0.107.0-alpha API)

Returns a Service object for the current service. For instance, when using Google Cloud Storage, it returns a Storage object.

ソースコードを見ると getService メソッドが他のクラスに委譲してオブジェクトを生成して返しているのがわかる。

java-core/ServiceOptions.java at v1.91.2 · googleapis/java-core · GitHub

private transient ServiceT service;
public ServiceT getService() {
  if (shouldRefreshService(service)) {
    service = serviceFactory.create((OptionsT) this);
  }
  return service;
}

生成した Service オブジェクトは ServiceOptions オブジェクトが所有している。
再度 getService メソッドが呼び出されたときにはすでに生成済みの同じ Service オブジェクトを返す仕組みになっている。

参考資料

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
0
0