{"record":{"id":"c1507e0aa10fb092","repo":"apache/hadoop","slug":"failed-to-disconnect","errorCode":null,"errorMessage":"Failed to disconnect","messagePattern":"Failed to disconnect","errorType":"exception","errorClass":"FTPException","httpStatus":null,"severity":"warning","filePath":"hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/ftp/FTPFileSystem.java","lineNumber":726,"sourceCode":"  public Path getWorkingDirectory() {\n    // Return home directory always since we do not maintain state.\n    return getHomeDirectory();\n  }\n\n  @Override\n  public Path getHomeDirectory() {\n    FTPClient client = null;\n    try {\n      client = connect();\n      Path homeDir = new Path(client.printWorkingDirectory());\n      return homeDir;\n    } catch (IOException ioe) {\n      throw new FTPException(\"Failed to get home directory\", ioe);\n    } finally {\n      try {\n        disconnect(client);\n      } catch (IOException ioe) {\n        throw new FTPException(\"Failed to disconnect\", ioe);\n      }\n    }\n  }\n\n  @Override\n  public void setWorkingDirectory(Path newDir) {\n    // we do not maintain the working directory state\n  }\n}\n","sourceCodeStart":708,"sourceCodeEnd":736,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/ftp/FTPFileSystem.java#L708-L736","documentation":"getHomeDirectory() disconnects its short-lived client inside a finally block; any IOException from disconnect() is wrapped as FTPException(\"Failed to disconnect\"). Because it is thrown from finally, it replaces both the computed home directory and any primary \"Failed to get home directory\" exception — the cleanup failure masks what actually happened.","triggerScenarios":"The server closes the control connection before the client can send QUIT (idle timeout, aggressive firewall); the socket is reset during logout; or the initial connect/PWD already failed and the disconnect of the half-open client also fails.","commonSituations":"Servers with very short idle timeouts killing sessions between operations; NAT dropping connection state; per-call connect/disconnect patterns (getWorkingDirectory() is invoked liberally by higher-level code) multiplying exposure to close-time failures.","solutions":["Look at the cause chain first: if a \"Failed to get home directory\" cause is attached, fix that — the disconnect error is secondary noise","Treat the disconnect failure as non-fatal when you can recompute the value: catch, log, and continue with a retry","Cache/reuse the FileSystem instance instead of triggering per-call connections; fewer connect/disconnect cycles means fewer close failures","Align client keepalive with server-side idle limits (e.g. vsftpd idle_session_timeout) so sessions survive until QUIT"],"exampleFix":"// before\nPath home = fs.getHomeDirectory();  // may throw FTPException(\"Failed to disconnect\"), masking the real error\n\n// after\nPath home;\ntry {\n  home = fs.getHomeDirectory();\n} catch (FTPException e) {\n  LOG.warn(\"getHomeDirectory failed; cause chain tells whether it was connect or disconnect: {}\", e.getCause(), e);\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"wrap getHomeDirectory()/getWorkingDirectory() calls; on FTPException inspect the cause chain — if the failure is only the disconnect (connect and PWD succeeded per logs), log and retry the call instead of propagating the masked cleanup error.","preventionTips":["Cache the home directory instead of calling getHomeDirectory() repeatedly","Reuse FileSystem instances to avoid per-call connect/disconnect cycles","Align client keepalive with the server's idle timeout so sessions survive until QUIT"],"tags":["ftp","hadoop","connection-cleanup","masked-exception"],"backgroundTag":"connection-close-failure","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}