FTPConnection.disconnect() calls FTPConnection.sendCommand() which calls ReplyWorker.readReply(socketProvider) which throws IOException. The exception handler that catches this exception calls FTPConnection.disconnect() which results in infinite recursion/stack overflow.
In my specific case, I was attempting to upload a file that did not exist on my local system, so the initial problem occurs in FTPConnection.uploadFile(...) handles the resulting FileNotFoundException by calling FTPConnection.disconnect().
When the file does not exist, ReplyWorker.java:314 fails on a call to storeCommand.getStream(), which throws a FileNotFound exception. This is then stored and the worker thread terminates returning control back to the main thread.
The main thread checks the status of the worker and finds that an exception was thrown and re-throws the exception.
Somehow in this path, the connection is left if a bad state, so that there is an additional failure when an attempt is made to disconnect.
During the disconnect attempt, FTPConection.sendCommand(...) calls ReplyWorker.readReply(...) which calls socketProvider.read(...) which throws SocketTimeoutException.
Rather than calling FTPConnection.disconnect() in all (or at least a lot of the) "internal" exception hanlders, I think it would probably be best just to throw the exceptions all the way out to calling code and let the caller decide how to proceed.
I'm not sure if there are other paths to this same problem, but I imagine that there are.
Logged In: NO
I also discovered that if you call FTPConnection.disconnect() on a connection that has already been disconnected you get an exception. In general, I think it is good practice keep track of the state of resources one uses and avoid the unnecessary call. However, given that disconnect is called all over within the library, I think it would be good to make a disconnect call on an already disconnected connection a no-op.