Network I/O hangs on BlackBerry OS 5
I have some problems with my network I/O code on OS 5 of BlackBerry.
I keep getting sporadic hangs and eventually TCP timeout exceptions during my I/O operations.
I am using the 5.0 networking APIs for establishing the connection which works flawlessly every time.
The problem is when doing the actual I/O. I have a background worker thread that services I/O requests from a queue. There is only a single background thread, so all requests are serialized onto this thread.
Completion notification is done through a delegate interface that is passed in when the request is queued.
The completion delegate is called on the background worker thread, but clients are free to repost this to the event thread via invokeLater
to do UI updates, etc.
Notes:
HttpRequest is my own class that holds data about the request. MutableData is my own class that holds the data that is read. BUFFER_SIZE = 2048HttpConnection getConnectionForRequest(final HttpRequest inRequest) {
final String url = inRequest.getURL();
final int[] availableTransportTypes =
TransportInfo.getAvailableTransportTypes();
final ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setPreferredTransportTypes(availableTransportTypes);
connectionFactory.setConnectionMode(ConnectionFactory.ACCESS_READ);
final ConnectionDescriptor connectionDescriptor =
connectionFactory.getConnection(url);
HttpConnection connection = null;
if (connectionDescriptor != null) {
connection = (HttpConnection) connectionDescriptor.getConnection();
}
return connection;
}
public void run() {
while (isRunning()) {
// This blocks waiting on a request to appear in the queue.
final HttpRequest request = waitForRequest();
final HttpConnection connection = getConnectionForRequest(request);
final MutableData data = new MutableData();
final InputStream inputStream = connection.openInputStream();
final byte[] readBuffer = new byte[BUFFER_SIZE];
int chunkSize;
// *** The following read call sporadically hangs and eventually throws
// a TCP timeout exception.
while((chunkSize = inputStream.read(readBuff开发者_StackOverflow社区er, 0, BUFFER_SIZE)) != -1) {
data.appendData(readBuffer, 0, chunkSize);
}
mDelegate.receivedDataForRequest(request, data);
}
}
When it hangs it always eventually throws a TCP timeout error after about 30 seconds or so. If this occured occasionaly I would just chalk it up to normal network congestion, but it happens frequently enough to indicate a deeper problem.
Edit:
It happens on various simulators and the 2 physical devices I have. The simulators I have tried are...
- Storm 9550
- Tour 9630
- Bold 9000
- Pearl 9100
- Curve 8530
I have a Curve 8530 and Storm 9550 devices, and it happens on both of those as well.
Any help would be appreciated.
You might want to try the Available() method. Even though you are serializing data on one backround thread, it looks like the request is created in the main thread. You may be running into some weird race condition there.
Can you add some logging to display the transport type it is that the device is choosing to use for each connection? Perhaps it's a case of the transport-selection API picking a transport it thinks will work, when in fact it doesn't.
It was suggested elsewhere to put in a stall detector in my network I/O thread and when a stall is detected, interrupt the thread and restart the request. I do this by starting a timer before I begin the request and as I read each chunk of data I reset the timer. If the timer expires before I can read a chunk I assume the network has stalled, and I interrupt the thread and start over on that request.
I've done this and it does improve things by at least reducing the delay I have to wait before I can continue the request since I don't have to wait for the TCP timeout which can take a very long time.
Interrupting the current I/O operation and restarting seems to bring the network back to life for a while usually running fine for several minutes before stalling again. I log the stalls to the console when debugging, and I get quite a bit of them.
This is a very strange problem, and I'm not totally happy with the stall detection solution. It seems to be just masking the problem, but it does allow me to somewhat address the long delays that I got.
I think there is a bug in the implementation of read(byte[], int, int)
on all BlackBerry operating systems I've worked with - 4.5 to 6.0.
I wrote an adapter for InputStream that turns read(byte[], int, int)
into a single call to read()
and that solved the stream hanging issue in the app I am working on.
If you read the RIM specification for read(byte[], int, int)
it says:
The read(b, off, len) method for class InputStream simply calls the method read() repeatedly. If the first such call results in an IOException, that exception is returned from the call to the read(b, off, len) method. If any subsequent call to read() results in a IOException, the exception is caught and treated as if it were end of file; the bytes read up to that point are stored into b and the number of bytes read before the exception occurred is returned. Subclasses are encouraged to provide a more efficient implementation of this method.
I wrote my own version, following this spec, and ran into the same problem. I believe the issue is that the method needs to return without blocking once some data is available. The only way to do that is to make use of available()
to see how many bytes can be read without blocking. Since the RIM documentation doesn't mention the use of available()
, I think it just calls read()
until the buffer is full or read() returns -1. This could be a long time if your data comes in small bursts. And if that "long time" is beyond the connection timeout, the connection just dies.
Here's the code I used, which solved the hanging connection problem:
public int read(byte[] bts, int st, int len) throws IOException {
if(len == 0) {
return 0;
}
int readByte = this.read();
if(readByte == -1) {
return 0;
}
bts[st] = (byte)readByte;
return 1;
}
精彩评论