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
@@ -81,11 +84,9 @@ public void testSingleAccess() throws Exception {
8184 }
8285
8386 public void testSingleAccessAsynchronous () throws Exception {
84- ExecutorService executor = Executors .newFixedThreadPool (3 );
8587 IndexInput indexInput = null ;
8688 try {
87- indexInput = asyncFetchBlobWithName ("file" , executor );
88- ;
89+ indexInput = asyncFetchBlobWithName ("file" );
8990 assertIndexInputIsFunctional (indexInput );
9091 MatcherAssert .assertThat (fileCache .activeUsage (), equalTo ((long ) EIGHT_MB ));
9192 } finally {
@@ -95,7 +96,6 @@ public void testSingleAccessAsynchronous() throws Exception {
9596 }
9697 MatcherAssert .assertThat (fileCache .activeUsage (), equalTo (0L ));
9798 MatcherAssert .assertThat (fileCache .usage (), equalTo ((long ) EIGHT_MB ));
98- executor .shutdownNow ();
9999 }
100100
101101 public void testConcurrentAccess () throws Exception {
@@ -127,21 +127,21 @@ public void testConcurrentAccess() throws Exception {
127127 public void testConcurrentAccessAsynchronous () throws Exception {
128128 // Kick off multiple threads that all concurrently request the same resource
129129 final String blobname = "file" ;
130- ExecutorService executor = Executors .newFixedThreadPool (3 );
130+ ExecutorService executor = Executors .newFixedThreadPool (8 );
131131 try {
132- final List <CompletableFuture <IndexInput >> futures = new ArrayList <>();
132+ final List <Future <IndexInput >> futures = new ArrayList <>();
133133 for (int i = 0 ; i < 8 ; i ++) {
134- futures .add (asyncFetchBlob ( blobname , executor ));
134+ futures .add (executor . submit (() -> asyncFetchBlobWithName ( blobname ) ));
135135 }
136136 // Wait for all threads to complete
137- for (CompletableFuture <IndexInput > future : futures ) {
137+ for (Future <IndexInput > future : futures ) {
138138 future .get (1 , TimeUnit .SECONDS );
139139 }
140140 // Assert that all IndexInputs are independently positioned by seeking
141141 // to the end and closing each one. If not independent, then this would
142142 // result in EOFExceptions and/or NPEs.
143- for (CompletableFuture <IndexInput > future : futures ) {
144- try (IndexInput i = future .join (). clone ()) {
143+ for (Future <IndexInput > future : futures ) {
144+ try (IndexInput i = future .get ()) {
145145 assertIndexInputIsFunctional (i );
146146 }
147147 }
@@ -198,10 +198,10 @@ public void testOverflowDisabled() throws Exception {
198198 public void testOverflowDisabledAsynchronous () throws Exception {
199199 ExecutorService executor = Executors .newFixedThreadPool (3 );
200200 initializeTransferManager ();
201- IndexInput i1 = asyncFetchBlobWithName ("1" , executor );
202- IndexInput i2 = asyncFetchBlobWithName ("2" , executor );
201+ IndexInput i1 = asyncFetchBlobWithName ("1" );
202+ IndexInput i2 = asyncFetchBlobWithName ("2" );
203203
204- assertThrows (IOException .class , () -> { IndexInput i3 = asyncFetchBlobWithName ("3" , executor ); });
204+ assertThrows (IOException .class , () -> { IndexInput i3 = asyncFetchBlobWithName ("3" ); });
205205 executor .shutdownNow ();
206206 }
207207
@@ -219,19 +219,19 @@ public void testDownloadFails() throws Exception {
219219
220220 public void testDownloadFailsAsyncDownload () throws Exception {
221221 mockExceptionWhileReading ();
222- ExecutorService executor = Executors .newFixedThreadPool (3 );
223222 List <BlobFetchRequest .BlobPart > blobParts = new ArrayList <>();
224223 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- );
224+ expectThrows (IOException .class , () -> {
225+ BlobFetchRequest blobFetchRequest = BlobFetchRequest .builder ()
226+ .fileName ("file" )
227+ .directory (directory )
228+ .blobParts (blobParts )
229+ .build ();
230+ transferManager .fetchBlobAsync (blobFetchRequest );
231+ try (IndexInput indexInput = transferManager .fetchBlob (blobFetchRequest )) {}
232+ });
232233 MatcherAssert .assertThat (fileCache .activeUsage (), equalTo (0L ));
233234 MatcherAssert .assertThat (fileCache .usage (), equalTo (0L ));
234- executor .shutdownNow ();
235235 }
236236
237237 public void testFetchesToDifferentBlobsDoNotBlockOnEachOther () throws Exception {
@@ -285,7 +285,7 @@ public void testAsyncFetchesToDifferentBlobsDoNotBlockOnEachOther() throws Excep
285285 blockingThread .start ();
286286
287287 // Assert that a different blob can be fetched and will not block on the first blob
288- try (IndexInput i = asyncFetchBlobWithName ("file" , executor )) {
288+ try (IndexInput i = asyncFetchBlobWithName ("file" )) {
289289 assertIndexInputIsFunctional (i );
290290 }
291291
@@ -296,6 +296,22 @@ public void testAsyncFetchesToDifferentBlobsDoNotBlockOnEachOther() throws Excep
296296 executor .shutdownNow ();
297297 }
298298
299+ public void testRefCount () throws IOException , InterruptedException {
300+ List <BlobFetchRequest .BlobPart > blobParts = new ArrayList <>();
301+ String blobname = "test-blob" ;
302+ blobParts .add (new BlobFetchRequest .BlobPart ("blob" , 0 , EIGHT_MB ));
303+ BlobFetchRequest blobFetchRequest = BlobFetchRequest .builder ().fileName (blobname ).directory (directory ).blobParts (blobParts ).build ();
304+ // It will trigger async load
305+ transferManager .fetchBlobAsync (blobFetchRequest );
306+ Thread .sleep (2000 );
307+ assertEquals (Optional .of (0 ), Optional .of (fileCache .getRef (blobFetchRequest .getFilePath ())));
308+ assertNotNull (fileCache .get (blobFetchRequest .getFilePath ()));
309+ fileCache .decRef (blobFetchRequest .getFilePath ());
310+ // Making the read call to fetch from file cache
311+ IndexInput indexInput = transferManager .fetchBlob (blobFetchRequest );
312+ assertEquals (Optional .of (1 ), Optional .of (fileCache .getRef (blobFetchRequest .getFilePath ())));
313+ }
314+
299315 protected abstract void initializeTransferManager () throws IOException ;
300316
301317 protected abstract void mockExceptionWhileReading () throws IOException ;
@@ -308,23 +324,12 @@ private IndexInput fetchBlobWithName(String blobname) throws IOException {
308324 return transferManager .fetchBlob (BlobFetchRequest .builder ().fileName (blobname ).directory (directory ).blobParts (blobParts ).build ());
309325 }
310326
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 {
327+ private IndexInput asyncFetchBlobWithName (String blobname ) throws IOException {
322328 List <BlobFetchRequest .BlobPart > blobParts = new ArrayList <>();
323329 blobParts .add (new BlobFetchRequest .BlobPart ("blob" , 0 , EIGHT_MB ));
324330 BlobFetchRequest blobFetchRequest = BlobFetchRequest .builder ().fileName (blobname ).directory (directory ).blobParts (blobParts ).build ();
325331 // It will trigger async load
326- transferManager .fetchBlobAsync (blobFetchRequest , executor );
327-
332+ transferManager .fetchBlobAsync (blobFetchRequest );
328333 assertNotNull (fileCache .get (blobFetchRequest .getFilePath ()));
329334 fileCache .decRef (blobFetchRequest .getFilePath ());
330335 // Making the read call to fetch from file cache
0 commit comments