Responsive Remote Connections
We've had a couple of contributions for fetching contents directly from a URL:
- Add a URL to Apply Patch Wizard (bug 73683)
- Add a URL to Project Set Import: (bug 162608)
We encountered these issues several years ago with the CVS client and came up with solutions that allowed the remote connections to remain responsive. I thought it would be worthwhile to summarize the issues and what we came up with to solve the problems. I would be curious to know if others have encountered similar problems and, if so, how they solved them. Also, I'd be curious to know if the situation has improved in later versions of Java (our coding was done against 1.4 or earlier).
Problem 1: Connecting to a Socket may hang indefinitely
When initially connecting to a socket in Java, there is no way to specify a timeout. In most cases, this is not a problem as the connection will either succeed in a fairly short time or fail fairly quickly. However, we found that, under some circumstances, the connection attempt would hang indefinitely.
To work around this, we wrote a createSocket method that performed the open in a separate thread while the caller thread blocked until the socket was opened. However, the caller thread would only sleep for a second at a time and would also only loop for a predefined number of times (default of 60). That way, the connection would only block for a maximum of 60 seconds (or whatever value the user specified as the CVS timeout preference) and would respond to cancellation with a second. For those that are curious, the implementation of the create socket is in the CVS/Core Util class.
Problem 2: Users expect cancellation to happen immediately
Once you have a socket open, you can specify the read and write timeouts for the socket streams. Thus, you can ensure that the connection will be dropped if it has been blocked for too long. However, after waiting for a while, the user may decide to explicitly cancel the operation and would expect this to occur immediately. To make this work, we created a wrapper stream that would only block on the read for a second. If no content arrived in that time, the stream would poll to see if the user canceled. If a cancel did occur, the read was terminated. Otherwise, the read was reattempted until either content became available or the number of attempts exceeded the timeout (default 60).
The other benefit of this stream wrapper is that appropriate progress messages could be shown to the user (e.g. 350 or 2000 bytes read). The stream implementation is internal to the Team/Core plug-in in the PollingInputStream class.
Problem 3: Some streams do not have a timeout
The final problem we found was that some streams do not have a timeout. Specifically, when reading the output from a system process (for the EXT connection method), under some circumstances, the read could block indefinitely. To solve this, we used a similar approach to problem 1. All the reading was performed in a separate thread and any read content was passed between the reading thread and the caller thread using a shared buffer. The implementation for this is found in the TimeoutInputStream class in the Team/Core plug-in.
Thoughts?
As I mentioned earlier, I would be curious to know how others have addressed these issues. Is there facilities available from the JDK that make this easier or are there better approaches that should be considered? Let me know what you think.
6 Comments:
Well, throwing in Jini as a solution is a bit oversized for opening a URL connection. But yes, the only way (if you don't want to introduce other frameworks or dependencies) is to open a separate thread. That's how I (still) do it.
These seem to be very useful features. You also could mention the proxy stuff, which I don't believe that you mentioned -- is that at a higher or lower level than where you are at the moment?
Also, why not consider contributing them upwards, or at least, outside of the team API? It sounds like these would be excellent Equinox services.
PS posted to Equinox forum at http://www.eclipsezone.com/forums/thread.jspa?threadID=88490 for those that are interested in that idea.
The java.nio can provide asynchronous I/O. The problem is that the API is quite different from stream I/O...and rather complex compared with blocking I/O. This complexity is one of the reasons that the ECF project is trying defining higher level APIs based upon asynch models (e.g. queued sending and listeners). For example, we have an API called datashare that exposes channels to asynchronously distribute byte arrays...and a filetransfer API based upon similar approaches.
Seems like I'm not the only person who thinks this might be useful:
bug 173607
When a thread is blocked in a socket call, it can be unblocked when the socket is closed by another thread. It will then throw an exception. If I recollect right this was typically a SocketException 'socket closed'.
This can be used to have another thread monitor the socket communication. This thread can then check for cancel without affecting the network thread. Once cancel is pressed the thread calls socket.close() which causes the communication thread to get an exception and it can then check for cancel as well.
The thread which checks for cancel can also monitor if the socket becomes unresponsive and provide appropriate feedback to the user.
This seems to work with Java 1.5. I have not tried this on other versions.
I have not found Java documentation that would explicitly describe that blocked socket calls will become unblocked if the connection is closed. There remains some risk that it will not always work.
But it appears to me that if a server closes a connection, blocked socked calls should become unblocked. If this is the case then it is a smaller leap to assume that this should also happen when a socket is closed by a separate thread.
Post a Comment
<< Home