github-advanced-security[bot] commented on code in PR #20073:
URL: https://github.com/apache/druid/pull/20073#discussion_r3847182158


##########
server/src/test/java/org/apache/druid/segment/loading/SegmentLocalCacheManagerPartialAcquireTest.java:
##########
@@ -962,6 +965,188 @@
     );
   }
 
+  @Test
+  void testAbandonedAcquireLeavesAConcurrentAcquiresEntryAlone()
+      throws ExecutionException, InterruptedException, IOException
+  {
+    final StorageLocation location = manager.getLocations().get(0);
+    final SegmentCacheEntryIdentifier id = new 
SegmentCacheEntryIdentifier(SEGMENT_ID);
+
+    // Two acquires holding the same freshly reserved entry, neither of which 
resolves its future, so nothing mounts
+    // it. The acquire that created the entry gives up first - a canceled or 
timed out query, say.
+    final PartialSegmentMetadataCacheEntry entry;
+    final Closer acquires = Closer.create();
+    try {
+      // The first acquire reserves the entry; the second joins it. Both are 
registered with the closer so a failed
+      // assertion cannot leak a hold into the shared manager fixture; 
AcquireSegmentAction.close() is idempotent, so
+      // closing the first one early below is safe.
+      final AcquireSegmentAction creator = acquires.register(
+          manager.acquireSegment(partialSegment, AcquireMode.PARTIAL)
+      );
+      acquires.register(manager.acquireSegment(partialSegment, 
AcquireMode.PARTIAL));
+      entry = 
Assertions.assertInstanceOf(PartialSegmentMetadataCacheEntry.class, 
location.getCacheEntry(id));
+      Assertions.assertFalse(entry.isMounted());
+
+      creator.close();
+      Assertions.assertSame(
+          entry,
+          location.getCacheEntry(id),
+          "the entry the second acquire is holding must survive the first 
giving up"
+      );
+    }
+    finally {
+      acquires.close();
+    }
+    // The second acquire letting go does not remove it either - removal is 
the creating hold's job - so it is left
+    // registered and unmounted, reclaimable, and reusable by the next acquire.
+    Assertions.assertSame(entry, location.getCacheEntry(id));
+    Assertions.assertFalse(entry.isMounted());
+
+    // A later acquire mounts that same entry and serves the segment from it.
+    try (AcquireSegmentAction action = manager.acquireSegment(partialSegment, 
AcquireMode.PARTIAL)) {
+      final AcquireSegmentResult result = action.getSegmentFuture().get();
+      try (Segment segment = 
result.getReferenceProvider().acquireReference().orElseThrow()) {
+        Assertions.assertEquals(SEGMENT_ID, segment.getId());
+        final TimeBoundaryInspector inspector = 
segment.as(TimeBoundaryInspector.class);
+        Assertions.assertNotNull(inspector);
+        Assertions.assertEquals(TIME, inspector.getMinTime());
+        Assertions.assertEquals(TIME.plusMinutes(3), inspector.getMaxTime());
+      }
+      Assertions.assertSame(entry, location.getCacheEntry(id));
+      Assertions.assertTrue(entry.isMounted());
+    }
+  }
+
+  @Test
+  void testAbandonedAcquireWhoseMountFailsResolvesToAnUnavailableSegmentToo() 
throws Exception
+  {
+    // Deep storage that can produce a range reader now but not serve a read 
later: openRangeReader() checks the V10
+    // file at acquire time, and the test deletes it before the load task gets 
to run.
+    final File vanishingStorage = 
temporaryFolder.newFolder("vanishing_storage");
+    final File v10File = new File(vanishingStorage, IndexIO.V10_FILE_NAME);
+    Files.copy(new File(DEEP_STORAGE_DIR, IndexIO.V10_FILE_NAME).toPath(), 
v10File.toPath());
+    final DataSegment vanishingSegment = DataSegment.builder(SEGMENT_ID)
+                                                    
.shardSpec(NoneShardSpec.instance())
+                                                    .loadSpec(Map.of("type", 
"local", "path", vanishingStorage.getAbsolutePath()))
+                                                    .size(0)
+                                                    .build();
+
+    final File gatedCacheRoot = 
temporaryFolder.newFolder("gated_cache_mount_failure");
+    final SegmentLoaderConfig gatedConfig = SegmentLoaderConfig.builder()
+        .locations(new StorageLocationConfig(gatedCacheRoot, 1024L * 1024L * 
1024L, null))
+        .virtualStorage(true)
+        .virtualStoragePartialDownloadsEnabled(true)
+        .virtualStorageUseVirtualThreads(false)
+        .virtualStorageLoadThreads(1)
+        .build();
+    final List<StorageLocation> storageLocations = 
gatedConfig.toStorageLocations();
+    final StorageLoadingThreadPool gatedPool = 
StorageLoadingThreadPool.createFromConfig(gatedConfig);
+    final SegmentLocalCacheManager gatedManager = new SegmentLocalCacheManager(
+        storageLocations,
+        gatedConfig,
+        gatedPool,
+        new LeastBytesUsedStorageLocationSelectorStrategy(storageLocations),
+        TestHelper.getTestIndexIO(jsonMapper, ColumnConfig.DEFAULT),
+        jsonMapper
+    );
+
+    final CountDownLatch atGate = new CountDownLatch(1);
+    final CountDownLatch openGate = new CountDownLatch(1);
+    try {
+      // (intentionally unused) local so errorprone's CheckReturnValue is 
satisfied
+      @SuppressWarnings("unused")
+      ListenableFuture<?> unused = gatedPool.getExecutorService().submit(() -> 
{
+        atGate.countDown();
+        return openGate.await(30, TimeUnit.SECONDS);
+      });
+      Assertions.assertTrue(atGate.await(30, TimeUnit.SECONDS), "loading 
thread must reach the gate");
+
+      final ListenableFuture<AcquireSegmentResult> future;
+      try (AcquireSegmentAction action = 
gatedManager.acquireSegment(vanishingSegment, AcquireMode.PARTIAL)) {
+        future = action.getSegmentFuture();
+      }
+      // The mount will now fail on its header read rather than rolling back 
cleanly, so the loss surfaces from
+      // mount() instead of from the pin - which is no reason to fail a query 
that has already given up.
+      Assertions.assertTrue(v10File.delete(), "test needs the deep-storage 
file gone before the mount runs");
+      openGate.countDown();
+
+      final AcquireSegmentResult result = future.get(30, TimeUnit.SECONDS);
+      Assertions.assertTrue(
+          result.getReferenceProvider().acquireReference().isEmpty(),
+          "an abandoned acquire whose mount failed must resolve to an 
unavailable segment"
+      );
+    }
+    finally {
+      openGate.countDown();
+      gatedManager.drop(vanishingSegment);
+      gatedManager.shutdown();
+      gatedPool.stop();
+    }
+  }
+
+  @Test
+  void testAbandonedAcquireResolvesToAnUnavailableSegmentRatherThanFailing() 
throws Exception
+  {
+    // One fixed loading thread, so the test can hold the load task at a gate 
while it abandons the acquire.
+    final File gatedCacheRoot = temporaryFolder.newFolder("gated_cache");
+    final SegmentLoaderConfig gatedConfig = SegmentLoaderConfig.builder()
+        .locations(new StorageLocationConfig(gatedCacheRoot, 1024L * 1024L * 
1024L, null))
+        .virtualStorage(true)
+        .virtualStoragePartialDownloadsEnabled(true)
+        .virtualStorageUseVirtualThreads(false)
+        .virtualStorageLoadThreads(1)
+        .build();
+    final List<StorageLocation> storageLocations = 
gatedConfig.toStorageLocations();
+    final StorageLoadingThreadPool gatedPool = 
StorageLoadingThreadPool.createFromConfig(gatedConfig);
+    final SegmentLocalCacheManager gatedManager = new SegmentLocalCacheManager(
+        storageLocations,
+        gatedConfig,
+        gatedPool,
+        new LeastBytesUsedStorageLocationSelectorStrategy(storageLocations),
+        TestHelper.getTestIndexIO(jsonMapper, ColumnConfig.DEFAULT),
+        jsonMapper
+    );
+
+    final CountDownLatch atGate = new CountDownLatch(1);
+    final CountDownLatch openGate = new CountDownLatch(1);
+    try {
+      // (intentionally unused) local so errorprone's CheckReturnValue is 
satisfied
+      @SuppressWarnings("unused")
+      ListenableFuture<?> unused = gatedPool.getExecutorService().submit(() -> 
{
+        atGate.countDown();
+        return openGate.await(30, TimeUnit.SECONDS);
+      });

Review Comment:
   ## CodeQL / Unread local variable
   
   Variable 'ListenableFuture<?> unused' is never read.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11910)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to