summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNick Hudson <nick.hudson@isode.com>2014-10-24 15:28:28 (GMT)
committerNick Hudson <nick.hudson@isode.com>2014-10-24 16:02:48 (GMT)
commit244aff320257d178bbe35d87b0e09d939bd2f893 (patch)
tree6175d827be9bf72629016fb5ae166432ee65f94f
parentdabadc693220858ecb34af7e8df5f4e0b32461fc (diff)
downloadstroke-244aff320257d178bbe35d87b0e09d939bd2f893.zip
stroke-244aff320257d178bbe35d87b0e09d939bd2f893.tar.bz2
Don't disregard data that arrives on network just prior to socket closing
The JavaConnection code which reads from a socket detects a socket closure and emits a disconnected signal. It was noticed that on some occasions, data was arriving on the socket just before it was closed, and this data was never passed to the application. This happens when the server writes e.g. a "BYE" message and closes the socket straight away: when JavaConnection is woken to read the message, it does so and then goes on to notice that the connection has been closed and throws an IOException without passing the message back to the application. This patch fixes the problem by making sure that any data read prior to the close being noticed is sent to the application before the closed signal is emitted Test-information: It was possible to provoke the problem by deliberately breaking socket connections - if you do this often enough you see cases where data read from the socket is lost. After this patch, such cases do not result in data loss. Also tested with email client and verified that connections to icloud.com which previously had provoked this problem when authentication failed now seem to return all data reliably. Change-Id: Ieba0f4186b7c91e55f5f1a4b3b64bc923006b933
-rw-r--r--src/com/isode/stroke/network/JavaConnection.java72
1 files changed, 55 insertions, 17 deletions
diff --git a/src/com/isode/stroke/network/JavaConnection.java b/src/com/isode/stroke/network/JavaConnection.java
index 807d971..46f43ab 100644
--- a/src/com/isode/stroke/network/JavaConnection.java
+++ b/src/com/isode/stroke/network/JavaConnection.java
@@ -27,6 +27,26 @@ import com.isode.stroke.eventloop.EventLoop;
import com.isode.stroke.eventloop.EventOwner;
public class JavaConnection extends Connection implements EventOwner {
+
+ /**
+ * Wrapper class that is used by the "doRead" method so that it can
+ * return both a ByteArray and an indication of whether the socket
+ * got closed.
+ */
+ private static class ReadResult {
+ public ByteArray dataRead_;
+ public boolean socketClosed_;
+
+ ReadResult(boolean socketClosed) {
+ dataRead_ = new ByteArray();
+ socketClosed_ = socketClosed;
+ }
+
+ ReadResult(ByteArrayOutputStream byteArrayOutputStream, boolean socketClosed) {
+ dataRead_ = new ByteArray(byteArrayOutputStream.toByteArray());
+ socketClosed_ = socketClosed;
+ }
+ }
private class Worker implements Runnable {
@@ -96,12 +116,12 @@ public class JavaConnection extends Connection implements EventOwner {
ByteArray dataRead;
if (readNeeded) {
- try {
- dataRead = doRead();
- if (!dataRead.isEmpty()) {
- handleDataRead(dataRead);
- }
- } catch (IOException ex) {
+ ReadResult rr = doRead();
+ dataRead = rr.dataRead_;
+ if (!dataRead.isEmpty()) {
+ handleDataRead(dataRead);
+ }
+ if (rr.socketClosed_) {
handleDisconnected(Error.ReadError);
return;
}
@@ -210,19 +230,31 @@ public class JavaConnection extends Connection implements EventOwner {
}
/**
- * Called when there's something that's come in on the socket
- * @return a ByteBuffer containing bytes read (may be empty, won't be null)
- * @throws IOException if the socket got closed
+ * Called when there's something that's come in on the socket. The ReadResult
+ * returned will contain any data that was read from the socket and a
+ * flag to say whether the socket has been closed.
+ * <p>If the socket has been closed, it may still be the case that data
+ * was read before the close happened.
+ * @return a ReadResult containing bytes read (may be empty, won't be null),
+ * and an indication of whether the was closed.
*/
- private ByteArray doRead() throws IOException {
+ private ReadResult doRead() {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
- int count = socketChannel_.read(byteBuffer);
+ int count;
+ try {
+ count = socketChannel_.read(byteBuffer);
+ }
+ catch (IOException e) {
+ // Nothing read and the socket's closed
+ return new ReadResult(true);
+ }
if (count == 0) {
- return new ByteArray();
+ // Nothing read, but socket's open
+ return new ReadResult(false);
}
-
+ boolean isClosed = false;
ByteArrayOutputStream byteArrayOutputStream =
new ByteArrayOutputStream(1024);
@@ -231,18 +263,24 @@ public class JavaConnection extends Connection implements EventOwner {
byte[] result = new byte[byteBuffer.remaining()];
byteBuffer.get(result);
byteBuffer.compact();
- byteArrayOutputStream.write(result);
- count = socketChannel_.read(byteBuffer);
+ try {
+ byteArrayOutputStream.write(result);
+ count = socketChannel_.read(byteBuffer);
+ }
+ catch (IOException e) {
+ // Force exit from loop and indicate socket closed
+ count = -1;
+ }
}
if (count == -1) {
/* socketChannel input has reached "end-of-stream", which
* we regard as meaning that the socket has been closed
*/
- throw new IOException("socketChannel_.read returned -1");
+ isClosed = true;
}
/* There is no need to close the ByteArrayOutputStream */
- return new ByteArray(byteArrayOutputStream.toByteArray());
+ return new ReadResult(byteArrayOutputStream, isClosed);
}