2727import java .io .IOException ;
2828import java .util .ArrayList ;
2929import java .util .List ;
30- import java .util .concurrent .CompletableFuture ;
31- import java .util .concurrent .CompletionException ;
30+ import java .util .Optional ;
3231import java .util .concurrent .CountDownLatch ;
33- import java .util .concurrent .Executor ;
3432import java .util .concurrent .ExecutorService ;
3533import java .util .concurrent .Executors ;
3634import java .util .concurrent .Future ;
3735import java .util .concurrent .TimeUnit ;
3836
3937import static org .hamcrest .Matchers .equalTo ;
4038import static org .hamcrest .Matchers .greaterThan ;
39+ import static org .mockito .Mockito .doReturn ;
4140import static org .mockito .Mockito .mock ;
4241
4342@ ThreadLeakFilters (filters = CleanerDaemonThreadLeakFilter .class )
@@ -51,17 +50,21 @@ public abstract class TransferManagerTestCase extends OpenSearchTestCase {
5150 protected MMapDirectory directory ;
5251 protected TransferManager transferManager ;
5352 protected ThreadPool threadPool ;
53+ protected ExecutorService executorService ;
5454
5555 @ Before
5656 public void setUp () throws Exception {
5757 super .setUp ();
5858 threadPool = mock (ThreadPool .class );
5959 directory = new MMapDirectory (createTempDir (), SimpleFSLockFactory .INSTANCE );
6060 initializeTransferManager ();
61+ executorService = Executors .newFixedThreadPool (3 );
62+ doReturn (executorService ).when (threadPool ).executor (ThreadPool .Names .REMOTE_RECOVERY );
6163 }
6264
6365 @ After
6466 public void tearDown () throws Exception {
67+ executorService .shutdownNow ();
6568 super .tearDown ();
6669 }
6770
@@ -84,8 +87,7 @@ public void testSingleAccessAsynchronous() throws Exception {
8487 ExecutorService executor = Executors .newFixedThreadPool (3 );
8588 IndexInput indexInput = null ;
8689 try {
87- indexInput = asyncFetchBlobWithName ("file" , executor );
88- ;
90+ indexInput = asyncFetchBlobWithName ("file" );
8991 assertIndexInputIsFunctional (indexInput );
9092 MatcherAssert .assertThat (fileCache .activeUsage (), equalTo ((long ) EIGHT_MB ));
9193 } finally {
@@ -127,21 +129,21 @@ public void testConcurrentAccess() throws Exception {
127129 public void testConcurrentAccessAsynchronous () throws Exception {
128130 // Kick off multiple threads that all concurrently request the same resource
129131 final String blobname = "file" ;
130- ExecutorService executor = Executors .newFixedThreadPool (3 );
132+ ExecutorService executor = Executors .newFixedThreadPool (8 );
131133 try {
132- final List <CompletableFuture <IndexInput >> futures = new ArrayList <>();
134+ final List <Future <IndexInput >> futures = new ArrayList <>();
133135 for (int i = 0 ; i < 8 ; i ++) {
134- futures .add (asyncFetchBlob ( blobname , executor ));
136+ futures .add (executor . submit (() -> asyncFetchBlobWithName ( blobname ) ));
135137 }
136138 // Wait for all threads to complete
137- for (CompletableFuture <IndexInput > future : futures ) {
139+ for (Future <IndexInput > future : futures ) {
138140 future .get (1 , TimeUnit .SECONDS );
139141 }
140142 // Assert that all IndexInputs are independently positioned by seeking
141143 // to the end and closing each one. If not independent, then this would
142144 // result in EOFExceptions and/or NPEs.
143- for (CompletableFuture <IndexInput > future : futures ) {
144- try (IndexInput i = future .join (). clone ()) {
145+ for (Future <IndexInput > future : futures ) {
146+ try (IndexInput i = future .get ()) {
145147 assertIndexInputIsFunctional (i );
146148 }
147149 }
@@ -198,10 +200,10 @@ public void testOverflowDisabled() throws Exception {
198200 public void testOverflowDisabledAsynchronous () throws Exception {
199201 ExecutorService executor = Executors .newFixedThreadPool (3 );
200202 initializeTransferManager ();
201- IndexInput i1 = asyncFetchBlobWithName ("1" , executor );
202- IndexInput i2 = asyncFetchBlobWithName ("2" , executor );
203+ IndexInput i1 = asyncFetchBlobWithName ("1" );
204+ IndexInput i2 = asyncFetchBlobWithName ("2" );
203205
204- assertThrows (IOException .class , () -> { IndexInput i3 = asyncFetchBlobWithName ("3" , executor ); });
206+ assertThrows (IOException .class , () -> { IndexInput i3 = asyncFetchBlobWithName ("3" ); });
205207 executor .shutdownNow ();
206208 }
207209
@@ -222,13 +224,15 @@ public void testDownloadFailsAsyncDownload() throws Exception {
222224 ExecutorService executor = Executors .newFixedThreadPool (3 );
223225 List <BlobFetchRequest .BlobPart > blobParts = new ArrayList <>();
224226 blobParts .add (new BlobFetchRequest .BlobPart ("failure-blob" , 0 , EIGHT_MB ));
225- expectThrows (
226- CompletionException .class ,
227- () -> transferManager .fetchBlobAsync (
228- BlobFetchRequest .builder ().fileName ("file" ).directory (directory ).blobParts (blobParts ).build (),
229- executor
230- ).join ().clone ()
231- );
227+ expectThrows (IOException .class , () -> {
228+ BlobFetchRequest blobFetchRequest = BlobFetchRequest .builder ()
229+ .fileName ("file" )
230+ .directory (directory )
231+ .blobParts (blobParts )
232+ .build ();
233+ transferManager .fetchBlobAsync (blobFetchRequest );
234+ try (IndexInput indexInput = transferManager .fetchBlob (blobFetchRequest )) {}
235+ });
232236 MatcherAssert .assertThat (fileCache .activeUsage (), equalTo (0L ));
233237 MatcherAssert .assertThat (fileCache .usage (), equalTo (0L ));
234238 executor .shutdownNow ();
@@ -285,7 +289,7 @@ public void testAsyncFetchesToDifferentBlobsDoNotBlockOnEachOther() throws Excep
285289 blockingThread .start ();
286290
287291 // Assert that a different blob can be fetched and will not block on the first blob
288- try (IndexInput i = asyncFetchBlobWithName ("file" , executor )) {
292+ try (IndexInput i = asyncFetchBlobWithName ("file" )) {
289293 assertIndexInputIsFunctional (i );
290294 }
291295
@@ -296,6 +300,22 @@ public void testAsyncFetchesToDifferentBlobsDoNotBlockOnEachOther() throws Excep
296300 executor .shutdownNow ();
297301 }
298302
303+ public void testRefCount () throws IOException , InterruptedException {
304+ List <BlobFetchRequest .BlobPart > blobParts = new ArrayList <>();
305+ String blobname = "test-blob" ;
306+ blobParts .add (new BlobFetchRequest .BlobPart ("blob" , 0 , EIGHT_MB ));
307+ BlobFetchRequest blobFetchRequest = BlobFetchRequest .builder ().fileName (blobname ).directory (directory ).blobParts (blobParts ).build ();
308+ // It will trigger async load
309+ transferManager .fetchBlobAsync (blobFetchRequest );
310+ Thread .sleep (2000 );
311+ assertEquals (Optional .of (0 ), Optional .of (fileCache .getRef (blobFetchRequest .getFilePath ())));
312+ assertNotNull (fileCache .get (blobFetchRequest .getFilePath ()));
313+ fileCache .decRef (blobFetchRequest .getFilePath ());
314+ // Making the read call to fetch from file cache
315+ IndexInput indexInput = transferManager .fetchBlob (blobFetchRequest );
316+ assertEquals (Optional .of (1 ), Optional .of (fileCache .getRef (blobFetchRequest .getFilePath ())));
317+ }
318+
299319 protected abstract void initializeTransferManager () throws IOException ;
300320
301321 protected abstract void mockExceptionWhileReading () throws IOException ;
@@ -308,23 +328,12 @@ private IndexInput fetchBlobWithName(String blobname) throws IOException {
308328 return transferManager .fetchBlob (BlobFetchRequest .builder ().fileName (blobname ).directory (directory ).blobParts (blobParts ).build ());
309329 }
310330
311- private CompletableFuture <IndexInput > asyncFetchBlob (String blobname , Executor executor ) throws IOException {
312- List <BlobFetchRequest .BlobPart > blobParts = new ArrayList <>();
313- blobParts .add (new BlobFetchRequest .BlobPart ("blob" , 0 , EIGHT_MB ));
314- // It will trigger async load
315- return transferManager .fetchBlobAsync (
316- BlobFetchRequest .builder ().fileName (blobname ).directory (directory ).blobParts (blobParts ).build (),
317- executor
318- );
319- }
320-
321- private IndexInput asyncFetchBlobWithName (String blobname , Executor executor ) throws IOException {
331+ private IndexInput asyncFetchBlobWithName (String blobname ) throws IOException {
322332 List <BlobFetchRequest .BlobPart > blobParts = new ArrayList <>();
323333 blobParts .add (new BlobFetchRequest .BlobPart ("blob" , 0 , EIGHT_MB ));
324334 BlobFetchRequest blobFetchRequest = BlobFetchRequest .builder ().fileName (blobname ).directory (directory ).blobParts (blobParts ).build ();
325335 // It will trigger async load
326- transferManager .fetchBlobAsync (blobFetchRequest , executor );
327-
336+ transferManager .fetchBlobAsync (blobFetchRequest );
328337 assertNotNull (fileCache .get (blobFetchRequest .getFilePath ()));
329338 fileCache .decRef (blobFetchRequest .getFilePath ());
330339 // Making the read call to fetch from file cache
0 commit comments