yashmayya commented on code in PR #19238:
URL: https://github.com/apache/pinot/pull/19238#discussion_r3786174688


##########
pinot-controller/src/main/java/org/apache/pinot/controller/util/FileIngestionHelper.java:
##########
@@ -179,40 +197,104 @@ public static void copyURIToLocal(Map<String, String> 
batchConfigMap, URI source
   public static void copyURIToLocal(Map<String, String> batchConfigMap, URI 
sourceFileURI, File destFile,
       boolean allowLocalFileSystem)
       throws Exception {
+    try (ResolvedFileSystem sourceFileSystem =

Review Comment:
   The 3-arg `copyURIToLocal` just above (line 192) passes 
`allowLocalFileSystem = true` and now has no callers anywhere, in main or test 
code. Please delete it so callers always state the flag.



##########
pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotIngestionRestletResource.java:
##########
@@ -197,14 +200,12 @@ public void ingestFromURI(
     tableNameWithType = DatabaseUtils.translateTableName(tableNameWithType, 
headers);
     try {
       asyncResponse.resume(ingestData(tableNameWithType, batchConfigMapStr, 
new DataPayload(new URI(sourceURIStr))));
-    } catch (IllegalArgumentException e) {
-      asyncResponse.resume(new ControllerApplicationException(LOGGER, String
-          .format("Got illegal argument when ingesting file into table: %s. 
%s", tableNameWithType, e.getMessage()),
-          Response.Status.BAD_REQUEST, e));
-    } catch (Exception e) {
-      asyncResponse.resume(new ControllerApplicationException(LOGGER,
-          String.format("Caught exception when ingesting file into table: %s. 
%s", tableNameWithType, e.getMessage()),
-          Response.Status.INTERNAL_SERVER_ERROR, e));
+    } catch (IllegalArgumentException | URISyntaxException e) {
+      asyncResponse.resume(
+          new ControllerApplicationException(LOGGER, "Invalid ingestFromURI 
request", Response.Status.BAD_REQUEST));
+    } catch (Exception | LinkageError e) {
+      asyncResponse.resume(new ControllerApplicationException(LOGGER, "Failed 
to ingest from URI",
+          Response.Status.INTERNAL_SERVER_ERROR));

Review Comment:
   Both branches drop `e`. The 3-arg `ControllerApplicationException(logger, 
message, status)` logs only the message (`logger.info(message)` for 4xx, 
`logger.error(message)` for 5xx), so the cause never reaches the log and a 
failure here is not debuggable.
   
   Please pass `e` to the 4-arg constructor. The response body is built from 
`message` by `WebApplicationExceptionMapper`, so it stays generic either way.



##########
pinot-controller/src/main/java/org/apache/pinot/controller/util/FileIngestionHelper.java:
##########
@@ -158,12 +171,17 @@ public SuccessResponse buildSegmentAndPush(DataPayload 
payload)
       SegmentUploader segmentUploader = 
PluginManager.get().createInstance(SEGMENT_UPLOADER_CLASS);
       segmentUploader.init(tableConfigOverride);
       segmentUploader.uploadSegment(segmentTarFile.toURI(), _authProvider);
-      LOGGER.info("Uploaded tar: {} to table: {}", 
segmentTarFile.getAbsolutePath(), tableNameWithType);
+      LOGGER.info("Uploaded generated segment to table: {}", 
tableNameWithType);
 
       return new SuccessResponse(
           "Successfully ingested file into table: " + tableNameWithType + " as 
segment: " + segmentName);
     } catch (Exception e) {
-      LOGGER.error("Caught exception when ingesting file to table: {}", 
tableNameWithType, e);
+      if (payload._payloadType == PayloadType.URI) {
+        LOGGER.error("Failed URI ingestion for table: {}, exception type: {}", 
tableNameWithType,
+            e.getClass().getName());
+      } else {
+        LOGGER.error("Caught exception when ingesting file to table: {}", 
tableNameWithType, e);
+      }

Review Comment:
   For URI payloads this keeps only the exception class name. Note also that 
failures from `resolveSourceFileSystem` are thrown before this `try` block, so 
they are not logged here at all.
   
   This log is server side and is not returned to the caller, so I would log 
the full exception in both branches.



##########
pinot-plugins/pinot-file-system/pinot-hdfs/src/test/java/org/apache/pinot/plugin/filesystem/HadoopPinotFSTest.java:
##########
@@ -479,4 +480,17 @@ public void testInitWithUnauthenticatedUser()
       Assert.assertEquals(currentUser, testUser, "The Hadoop login user was 
not set correctly!");
     }
   }
+
+  @Test
+  public void testInstancesOwnDistinctHadoopFileSystems()
+      throws Exception {
+    try (HadoopPinotFS first = new HadoopPinotFS(); HadoopPinotFS second = new 
HadoopPinotFS()) {
+      first.init(new PinotConfiguration());
+      second.init(new PinotConfiguration());
+
+      Field hadoopFileSystemField = 
HadoopPinotFS.class.getDeclaredField("_hadoopFS");
+      hadoopFileSystemField.setAccessible(true);
+      Assert.assertNotSame(hadoopFileSystemField.get(first), 
hadoopFileSystemField.get(second));
+    }

Review Comment:
   This reads a private field by reflection. The behaviour that matters is that 
closing one instance leaves the other usable. Close `first`, then call 
something on `second` and assert it still works. That tests the contract 
directly and survives a field rename.



##########
pinot-plugins/pinot-file-system/pinot-hdfs/src/main/java/org/apache/pinot/plugin/filesystem/HadoopPinotFS.java:
##########
@@ -77,7 +77,9 @@ public void init(PinotConfiguration config) {
         UserGroupInformation.setLoginUser(ugi);
         LOGGER.info("Setting HDFS login user to: {}", globalHadoopUser);
       }
-      _hadoopFS = org.apache.hadoop.fs.FileSystem.get(_hadoopConf);
+      // Hadoop's FileSystem.get() returns a process-cached instance. 
HadoopPinotFS closes its filesystem, so it must
+      // own a distinct instance to avoid one PinotFS closing a client that is 
still in use elsewhere.
+      _hadoopFS = org.apache.hadoop.fs.FileSystem.newInstance(_hadoopConf);

Review Comment:
   This is the right ownership fix, but `newInstance()` also leaves the shared 
cache, so every instance must now be closed or it stays alive.
   
   `PinotFSFactory.register()` overwrites the map entry without closing the 
value it replaces, and two callers re-register in a loop inside a long-lived 
JVM:
   
   - `SparkSegmentGenerationJobRunner` registers all `pinotFSSpecs` once per 
input path inside `pathRDD.foreach`, on the executor.
   - `HadoopSegmentCreationMapper` does the same per mapper.
   
   With `get()` these all shared one cached client, so overwriting was 
harmless. Now each call builds a client that nothing closes. Hadoop's 
`newInstance()` goes through `Cache.getUnique()`, which keeps the instance in 
the static cache until it is closed, so this holds heap, RPC connections and 
lease renewer threads.
   
   Can `PinotFSFactory.register()` close the instance it replaces? That covers 
every implementation, not only this one. `S3PinotFS` has the same shape today.



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