リモートの場所から項目をリストする Java イテレーターがあります。アイテムのリストは「ページ」で表示され、「次のページを取得する」操作はかなり遅くなります。(具体的には、イテレータが呼び出されS3Find
、Amazon S3 からオブジェクトを一覧表示します)。
そこで、処理を高速化するために、1 つのリスト ページをプリフェッチしたいと考えました。これを行うために、ExecutorService
およびCallable
/Future
パターンを使用してアイテムの「ページ」をプリフェッチしました。問題は、その反復子の呼び出し元が、クラスに通知せずにいつでも操作を放棄する可能性があることです。たとえば、次のループを考えてみましょう。
for (S3URL f : new S3Find(topdir).withRecurse(true)) {
// do something with f
if (some_condition) break;
}
その結果、リソース リークが発生しExecutorService
ます。サブミットに使用するCallable
は、含まれている への参照がなくなってS3Find
も (そして次のプリフェッチが完了しても) 実行されたままになるためです。
これを処理する適切な方法は何ですか?間違ったアプローチを使用していますか? プリフェッチごとに新しいベアスレッドを放棄ExecutorService
して使用する必要がありますか (プリフェッチが完了したらスレッドを強制終了します)? ページの各フェッチには約 500 ミリ秒かかるため、毎回新しいスレッドを作成することはおそらく無視できることに注意してください。私が望んでいないことの 1 つは、反復処理が完了したことを呼び出し元に明示的に通知S3Find
することを要求することです (確実に忘れてしまう人もいるからです)。
現在のプリフェッチ コードは次のとおりです ( 内S3Find
)。
/**
* This class holds one ObjectListing (one "page"), and also pre-fetches
* the next page using a {@link S3Find#NextPageGetter} Callable on a
* separate thread.
*/
private static class Pager {
private final AmazonS3 s3;
private ObjectListing currentList;
private Future<ObjectListing> future;
private final ExecutorService exec;
public Pager(AmazonS3 s3, ListObjectsRequest request) {
this.s3 = s3;
currentList = s3.listObjects(request);
exec = Executors.newSingleThreadExecutor();
future = submitPrefetch();
}
public ObjectListing getCurrentPage() {
return currentList;
}
/**
* Move currentList to the next page, and returns it.
*/
public ObjectListing getNextPage() {
if (future == null) return null;
try {
currentList = future.get();
future = submitPrefetch();
} catch (InterruptedException|ExecutionException e) {
e.printStackTrace();
}
return currentList;
}
private Future<ObjectListing> submitPrefetch() {
if (currentList == null || !currentList.isTruncated()) {
exec.shutdown();
return null;
} else {
NextPageGetter worker = new NextPageGetter(s3, currentList);
return exec.submit(worker);
}
}
}
/**
* This class retrieves the "next page" of a truncated ObjectListing.
* It is meant to be called in a Callable/Future pattern.
*/
private static class NextPageGetter implements Callable<ObjectListing> {
private final ObjectListing currentList;
private final AmazonS3 s3;
public NextPageGetter(AmazonS3 s3, ObjectListing currentList) {
super();
this.s3 = s3;
this.currentList = currentList;
if (currentList == null || !currentList.isTruncated()) {
throw new IllegalArgumentException(currentList==null ?
"null List" : "List is not truncated");
}
}
@Override
public ObjectListing call() throws Exception {
ObjectListing nextList = s3.listNextBatchOfObjects(currentList);
return nextList;
}
}