Project: javamail
Repository: mercurial
Revision: 715
Author: shannon
Date: 2015-05-08 23:07:14 UTC
Link:
Log Message:
------------
Clarify that get/setDisposition only operates on the disposition field,
not the entire header.
Fix more IdleManager deadlocks related to connection failures - bug 6817
Revisions:
----------
714
715
Modified Paths:
---------------
mail/src/main/java/javax/mail/internet/MimeBodyPart.java
mail/src/main/java/javax/mail/internet/MimeMessage.java
doc/release/CHANGES.txt
mail/src/main/java/com/sun/mail/iap/Response.java
mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
mail/src/main/java/com/sun/mail/imap/IdleManager.java
mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java
mail/src/test/java/com/sun/mail/imap/IMAPIdleManagerTest.java
Diffs:
------
diff -r 9711ace03942 -r 9d2e180e859e mail/src/main/java/javax/mail/internet/MimeBodyPart.java
--- a/mail/src/main/java/javax/mail/internet/MimeBodyPart.java Mon Apr 27 14:34:17 2015 -0700
+++ b/mail/src/main/java/javax/mail/internet/MimeBodyPart.java Fri May 08 15:38:53 2015 -0700
@@ -298,7 +298,7 @@
}
/**
- * Returns the value of the "Content-Disposition" header field.
+ * Returns the disposition from the "Content-Disposition" header field.
* This represents the disposition of this part. The disposition
* describes how the part should be presented to the user. <p>
*
@@ -316,9 +316,9 @@
}
/**
- * Set the "Content-Disposition" header field of this body part.
- * If the disposition is null, any existing "Content-Disposition"
- * header field is removed.
+ * Set the disposition in the "Content-Disposition" header field
+ * of this body part. If the disposition is null, any existing
+ * "Content-Disposition" header field is removed.
*
* @exception IllegalWriteException if the underlying
* implementation does not support modification
diff -r 9711ace03942 -r 9d2e180e859e mail/src/main/java/javax/mail/internet/MimeMessage.java
--- a/mail/src/main/java/javax/mail/internet/MimeMessage.java Mon Apr 27 14:34:17 2015 -0700
+++ b/mail/src/main/java/javax/mail/internet/MimeMessage.java Fri May 08 15:38:53 2015 -0700
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
- * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997-2015 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
@@ -1016,7 +1016,7 @@
}
/**
- * Returns the value of the "Content-Disposition" header field.
+ * Returns the disposition from the "Content-Disposition" header field.
* This represents the disposition of this part. The disposition
* describes how the part should be presented to the user. <p>
*
@@ -1034,9 +1034,9 @@
}
/**
- * Set the "Content-Disposition" header field of this Message.
- * If <code>disposition</code> is null, any existing "Content-Disposition"
- * header field is removed.
+ * Set the disposition in the "Content-Disposition" header field
+ * of this body part. If the disposition is null, any existing
+ * "Content-Disposition" header field is removed.
*
* @exception IllegalWriteException if the underlying
* implementation does not support modification
diff -r 9d2e180e859e -r 109444121a8a doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Fri May 08 15:38:53 2015 -0700
+++ b/doc/release/CHANGES.txt Fri May 08 16:07:14 2015 -0700
@@ -19,6 +19,7 @@
The following bugs have been fixed in the 1.5.4 release.
K 6804 IdleManager can deadlock with frequent notifications
+K 6817 IdleManager can deadlock when connection fails
CHANGES IN THE 1.5.3 RELEASE
diff -r 9d2e180e859e -r 109444121a8a mail/src/main/java/com/sun/mail/iap/Response.java
--- a/mail/src/main/java/com/sun/mail/iap/Response.java Fri May 08 15:38:53 2015 -0700
+++ b/mail/src/main/java/com/sun/mail/iap/Response.java Fri May 08 16:07:14 2015 -0700
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
- * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997-2015 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
@@ -58,6 +58,8 @@
protected byte[] buffer = null;
protected int type = 0;
protected String tag = null;
+ /** @since JavaMail 1.5.4 */
+ protected Exception ex;
private static final int increment = 100;
@@ -133,6 +135,7 @@
err = err.replace('\r', ' ').replace('\n', ' ');
Response r = new Response(err);
r.type |= SYNTHETIC;
+ r.ex = ex;
return r;
}
@@ -525,6 +528,15 @@
}
/**
+ * Return the exception for a synthetic BYE response.
+ *
+ * @since JavaMail 1.5.4
+ */
+ public Exception getException() {
+ return ex;
+ }
+
+ /**
* Reset pointer to beginning of response.
*/
public void reset() {
diff -r 9d2e180e859e -r 109444121a8a mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Fri May 08 15:38:53 2015 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Fri May 08 16:07:14 2015 -0700
@@ -50,6 +50,7 @@
import java.util.Locale;
import java.util.logging.Level;
import java.io.*;
+import java.net.SocketTimeoutException;
import java.nio.channels.SocketChannel;
import javax.mail.*;
@@ -2965,10 +2966,44 @@
* @since JavaMail 1.5.2
*/
boolean handleIdle(boolean once) throws MessagingException {
+ Response r = null;
do {
- Response r = protocol.readIdleResponse();
+ r = protocol.readIdleResponse();
try {
synchronized (messageCacheLock) {
+ if (r.isBYE() && r.isSynthetic() && idleState == IDLE) {
+ /*
+ * If it was a timeout and no bytes were transferred
+ * we ignore it and go back and read again.
+ * If the I/O was otherwise interrupted, and no
+ * bytes were transferred, we take it as a request
+ * to abort the IDLE.
+ */
+ Exception ex = r.getException();
+ if (ex instanceof InterruptedIOException &&
+ ((InterruptedIOException)ex).
+ bytesTransferred == 0) {
+ if (ex instanceof SocketTimeoutException) {
+ logger.finest(
+ "handleIdle: ignoring socket timeout");
+ r = null; // repeat do/while loop
+ } else {
+ logger.finest("handleIdle: interrupting IDLE");
+ IdleManager im = idleManager;
+ if (im != null) {
+ logger.finest(
+ "handleIdle: request IdleManager to abort");
+ im.requestAbort(this);
+ } else {
+ logger.finest("handleIdle: abort IDLE");
+ protocol.idleAbort();
+ idleState = ABORTING;
+ }
+ // normally will exit the do/while loop
+ }
+ continue;
+ }
+ }
boolean done = true;
try {
if (r == null || protocol == null ||
@@ -2997,13 +3032,13 @@
}
}
} catch (ConnectionException cex) {
- // Oops, the store or folder died on us.
- throwClosedException(cex);
+ // Oops, the folder died on us.
+ throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
// keep processing responses already in our buffer
- } while (protocol.hasResponse());
+ } while (r == null || protocol.hasResponse());
return true;
}
@@ -3079,7 +3114,9 @@
}
} catch (Exception ex) {
// assume it's a connection failure; nothing more to do
+ logger.log(Level.FINEST, "Exception in idleAbortWait", ex);
}
+ logger.finest("IDLE aborted");
}
}
}
@@ -3410,6 +3447,11 @@
protected IMAPProtocol getProtocol() throws ProtocolException {
assert Thread.holdsLock(messageCacheLock);
waitIfIdle();
+ // if we no longer have a protocol object after waiting, it probably
+ // means the connection has been closed due to a communnication error,
+ // or possibly because the folder has been closed
+ if (protocol == null)
+ throw new ConnectionException("Connection closed");
return protocol;
}
diff -r 9d2e180e859e -r 109444121a8a mail/src/main/java/com/sun/mail/imap/IdleManager.java
--- a/mail/src/main/java/com/sun/mail/imap/IdleManager.java Fri May 08 15:38:53 2015 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/IdleManager.java Fri May 08 16:07:14 2015 -0700
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
- * Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014-2015 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
@@ -42,6 +42,7 @@
import java.io.IOException;
import java.io.InterruptedIOException;
+import java.net.Socket;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;
@@ -130,6 +131,7 @@
* @since JavaMail 1.5.2
*/
public class IdleManager {
+ private Executor es;
private Selector selector;
private MailLogger logger;
private volatile boolean die = false;
@@ -146,6 +148,7 @@
* @exception IOException for Selector failures
*/
public IdleManager(Session session, Executor es) throws IOException {
+ this.es = es;
logger = new MailLogger(this.getClass(), "DEBUG IMAP", session);
selector = Selector.open();
es.execute(new Runnable() {
@@ -193,7 +196,7 @@
* blocking I/O mode when not selecting, so wake up the selector,
* which will process this request when it wakes up.
*/
- synchronized void requestAbort(IMAPFolder folder) {
+ void requestAbort(IMAPFolder folder) {
toAbort.add(folder);
selector.wakeup();
}
@@ -283,34 +286,25 @@
*/
private boolean processKeys() throws IOException {
boolean more = false;
- /*
- * First, process any folders that we need to abort.
- */
IMAPFolder folder;
- while ((folder = toAbort.poll()) != null) {
- logger.log(Level.FINE,
- "IdleManager aborting IDLE for folder: {0}", folder);
- SocketChannel sc = folder.getChannel();
- if (sc == null)
- continue;
- SelectionKey sk = sc.keyFor(selector);
- // have to cancel so we can switch back to blocking I/O mode
- if (sk != null)
- sk.cancel();
- // switch back to blocking to allow normal I/O
- sc.configureBlocking(true);
- folder.idleAbort(); // send the DONE message
- // watch for OK response to DONE
- toWatch.add(folder);
- more = true;
- }
/*
- * Now, process any channels with data to read.
+ * First, process any channels with data to read.
*/
Set<SelectionKey> selectedKeys = selector.selectedKeys();
+ /*
+ * XXX - this is simpler, but it can fail with
+ * ConncurentModificationException
+ *
for (SelectionKey sk : selectedKeys) {
selectedKeys.remove(sk); // only process each key once
+ ...
+ }
+ */
+ Iterator<SelectionKey> it = selectedKeys.iterator();
+ while (it.hasNext()) {
+ SelectionKey sk = it.next();
+ it.remove(); // only process each key once
// have to cancel so we can switch back to blocking I/O mode
sk.cancel();
folder = (IMAPFolder)sk.attachment();
@@ -339,6 +333,44 @@
ex);
}
}
+
+ /*
+ * Now, process any folders that we need to abort.
+ */
+ while ((folder = toAbort.poll()) != null) {
+ logger.log(Level.FINE,
+ "IdleManager aborting IDLE for folder: {0}", folder);
+ SocketChannel sc = folder.getChannel();
+ if (sc == null)
+ continue;
+ SelectionKey sk = sc.keyFor(selector);
+ // have to cancel so we can switch back to blocking I/O mode
+ if (sk != null)
+ sk.cancel();
+ // switch back to blocking to allow normal I/O
+ sc.configureBlocking(true);
+
+ // if there's a read timeout, have to do the abort in a new thread
+ Socket sock = sc.socket();
+ if (sock != null && sock.getSoTimeout() > 0) {
+ logger.fine("IdleManager requesting DONE with timeout");
+ toWatch.remove(folder);
+ final IMAPFolder folder0 = folder;
+ es.execute(new Runnable() {
+ @Override
+ public void run() {
+ // send the DONE and wait for the response
+ folder0.idleAbortWait();
+ }
+ });
+ } else {
+ folder.idleAbort(); // send the DONE message
+ // watch for OK response to DONE
+ toWatch.add(folder);
+ more = true;
+ }
+ }
+
return more;
}
diff -r 9d2e180e859e -r 109444121a8a mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java
--- a/mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java Fri May 08 15:38:53 2015 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java Fri May 08 16:07:14 2015 -0700
@@ -284,8 +284,10 @@
if (ir.keyEquals("PREAUTH")) {
authenticated = true;
setCapabilities(r);
- } else
+ } else {
+ disconnect();
throw new ConnectionException(this, r);
+ }
}
/**
@@ -2634,28 +2636,14 @@
if (idleTag == null)
return null; // IDLE not in progress
Response r = null;
- while (r == null) {
- try {
- r = readResponse();
- } catch (InterruptedIOException iioex) {
- /*
- * If a socket timeout was set, the read will timeout
- * before the IDLE times out. In that case, just go
- * back and read some more. After all, the point of
- * IDLE is to sit here and wait until something happens.
- */
- if (iioex.bytesTransferred == 0)
- r = null; // keep trying
- else
- // convert this into a BYE response
- r = Response.byeResponse(iioex);
- } catch (IOException ioex) {
- // convert this into a BYE response
- r = Response.byeResponse(ioex);
- } catch (ProtocolException pex) {
- // convert this into a BYE response
- r = Response.byeResponse(pex);
- }
+ try {
+ r = readResponse();
+ } catch (IOException ioex) {
+ // convert this into a BYE response
+ r = Response.byeResponse(ioex);
+ } catch (ProtocolException pex) {
+ // convert this into a BYE response
+ r = Response.byeResponse(pex);
}
return r;
}
diff -r 9d2e180e859e -r 109444121a8a mail/src/test/java/com/sun/mail/imap/IMAPIdleManagerTest.java
--- a/mail/src/test/java/com/sun/mail/imap/IMAPIdleManagerTest.java Fri May 08 15:38:53 2015 -0700
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPIdleManagerTest.java Fri May 08 16:07:14 2015 -0700
@@ -45,11 +45,15 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Folder;
import javax.mail.FetchProfile;
+import javax.mail.MessagingException;
+import javax.mail.event.ConnectionAdapter;
+import javax.mail.event.ConnectionEvent;
import com.sun.mail.test.TestServer;
@@ -58,29 +62,35 @@
import org.junit.rules.Timeout;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
/**
- * Test that IdleManager handles multiple responses in a single
- * network packet correctly.
+ * Test IdleManager.
*/
public final class IMAPIdleManagerTest {
+ private static final int TIMEOUT = 1000; // 1 second
+
// timeout the test in case of deadlock
@Rule
- public Timeout deadlockTimeout = new Timeout(5000);
+ public Timeout deadlockTimeout = new Timeout(5 * TIMEOUT);
+ /**
+ * Test that IdleManager handles multiple responses in a single packet.
+ */
@Test
public void testDone() {
- test(new IMAPHandlerIdleDone());
+ testSuccess(new IMAPHandlerIdleDone());
}
@Test
public void testExists() {
- test(new IMAPHandlerIdleExists());
+ testSuccess(new IMAPHandlerIdleExists());
}
- private void test(IMAPHandlerIdle handler) {
+ private void testSuccess(IMAPHandlerIdle handler) {
TestServer server = null;
+ IdleManager idleManager = null;
try {
server = new TestServer(handler);
server.start();
@@ -93,7 +103,7 @@
//session.setDebug(true);
ExecutorService executor = Executors.newCachedThreadPool();
- IdleManager idleManager = new IdleManager(session, executor);
+ idleManager = new IdleManager(session, executor);
final IMAPStore store = (IMAPStore)session.getStore("imap");
try {
@@ -124,6 +134,8 @@
e.printStackTrace();
fail(e.getMessage());
} finally {
+ if (idleManager != null)
+ idleManager.stop();
if (server != null) {
server.quit();
}
@@ -131,22 +143,167 @@
}
/**
- * Custom handler.
+ * Test that IdleManager handles timeouts.
*/
- private static class IMAPHandlerIdle extends IMAPHandler {
- // must be static because handler is cloned for each connection
- protected static CountDownLatch latch = new CountDownLatch(1);
+ @Test
+ public void testBeforeIdleTimeout() {
+ testFailure(new IMAPHandlerBeforeIdleTimeout(), true);
+ }
+ @Test
+ public void testIdleTimeout() {
+ testFailure(new IMAPHandlerIdleTimeout(), true);
+ }
+
+ @Test
+ public void testDoneTimeout() {
+ testFailure(new IMAPHandlerDoneTimeout(), true);
+ }
+
+ /**
+ * Test that IdleManager handles connection failures.
+ */
+ @Test
+ public void testBeforeIdleDrop() {
+ testFailure(new IMAPHandlerBeforeIdleDrop(), false);
+ }
+
+ @Test
+ public void testIdleDrop() {
+ testFailure(new IMAPHandlerIdleDrop(), false);
+ }
+
+ @Test
+ public void testDoneDrop() {
+ testFailure(new IMAPHandlerDoneDrop(), false);
+ }
+
+ private void testFailure(IMAPHandlerIdle handler, boolean setTimeout) {
+ TestServer server = null;
+ IdleManager idleManager = null;
+ final CountDownLatch closedLatch = new CountDownLatch(1);
+ try {
+ server = new TestServer(handler);
+ server.start();
+
+ final Properties properties = new Properties();
+ properties.setProperty("mail.imap.host", "localhost");
+ properties.setProperty("mail.imap.port", "" + server.getPort());
+ if (setTimeout)
+ properties.setProperty("mail.imap.timeout", "" + TIMEOUT);
+ properties.setProperty("mail.imap.usesocketchannels", "true");
+ final Session session = Session.getInstance(properties);
+ //session.setDebug(true);
+
+ ExecutorService executor = Executors.newCachedThreadPool();
+ idleManager = new IdleManager(session, executor);
+
+ final IMAPStore store = (IMAPStore)session.getStore("imap");
+ try {
+ store.connect("test", "test");
+ Folder folder = store.getFolder("INBOX");
+ folder.open(Folder.READ_WRITE);
+ idleManager.watch(folder);
+ handler.waitForIdle();
+
+ // now do something that is sure to touch the server
+ FetchProfile fp = new FetchProfile();
+ fp.add(FetchProfile.Item.ENVELOPE);
+ folder.fetch(folder.getMessages(), fp);
+
+ fail("No exception");
+ } catch (MessagingException mex) {
+ // success!
+ } catch (Exception ex) {
+ System.out.println(ex);
+ //ex.printStackTrace();
+ fail(ex.toString());
+ } finally {
+ store.close();
+ }
+ } catch (final Exception e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ } finally {
+ if (idleManager != null)
+ idleManager.stop();
+ if (server != null) {
+ server.quit();
+ }
+ }
+ }
+
+ /**
+ * Base class for custom handler.
+ */
+ private static abstract class IMAPHandlerIdle extends IMAPHandler {
@Override
public void select() throws IOException {
numberOfMessages = 1;
super.select();
}
+ public abstract void waitForIdle() throws InterruptedException;
+ }
+
+ /**
+ * Custom handler. Respond to DONE with a single packet containing
+ * EXISTS and OK.
+ */
+ private static final class IMAPHandlerIdleDone extends IMAPHandlerIdle {
+ // must be static because handler is cloned for each connection
+ private static CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
public void idle() throws IOException {
cont();
latch.countDown();
idleWait();
+ println("* 3 EXISTS\r\n" + tag + " OK");
+ }
+
+ public void waitForIdle() throws InterruptedException {
+ latch.await();
+ }
+ }
+
+ /**
+ * Custom handler. Send two EXISTS responses in a single packet.
+ */
+ private static final class IMAPHandlerIdleExists extends IMAPHandlerIdle {
+ // must be static because handler is cloned for each connection
+ private static CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
+ public void idle() throws IOException {
+ cont();
+ latch.countDown();
+ idleWait();
+ println("* 2 EXISTS\r\n* 3 EXISTS");
+ ok();
+ }
+
+ public void waitForIdle() throws InterruptedException {
+ latch.await();
+ }
+ }
+
+ /**
+ * Custom handler. Delay long enough before IDLE starts to force a timeout.
+ */
+ private static final class IMAPHandlerBeforeIdleTimeout
+ extends IMAPHandlerIdle {
+ // must be static because handler is cloned for each connection
+ private static CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
+ public void idle() throws IOException {
+ try {
+ Thread.sleep(2 * TIMEOUT);
+ } catch (InterruptedException ex) { }
+ cont();
+ latch.countDown();
+ idleWait();
ok();
}
@@ -156,30 +313,107 @@
}
/**
- * Custom handler. Respond to DONE with a single packet containing
- * EXISTS and OK.
+ * Custom handler. Delay long enough after IDLE starts to force a timeout.
*/
- private static final class IMAPHandlerIdleDone extends IMAPHandlerIdle {
+ private static final class IMAPHandlerIdleTimeout extends IMAPHandlerIdle {
+ // must be static because handler is cloned for each connection
+ private static CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
+ public void idle() throws IOException {
+ cont();
+ latch.countDown();
+ try {
+ Thread.sleep(2 * TIMEOUT);
+ } catch (InterruptedException ex) { }
+ idleWait();
+ ok();
+ }
+
+ public void waitForIdle() throws InterruptedException {
+ latch.await();
+ }
+ }
+
+ /**
+ * Custom handler. Delay long enough after DONE received to force a
+ * timeout.
+ */
+ private static final class IMAPHandlerDoneTimeout extends IMAPHandlerIdle {
+ // must be static because handler is cloned for each connection
+ private static CountDownLatch latch = new CountDownLatch(1);
+
@Override
public void idle() throws IOException {
cont();
latch.countDown();
idleWait();
- println("* 3 EXISTS\r\n" + tag + " OK");
+ try {
+ Thread.sleep(2 * TIMEOUT);
+ } catch (InterruptedException ex) { }
+ ok();
}
+
+ public void waitForIdle() throws InterruptedException {
+ latch.await();
+ }
}
/**
- * Custom handler. Send two EXISTS responses in a single packet.
+ * Custom handler. Drop the connection before IDLE started.
*/
- private static final class IMAPHandlerIdleExists extends IMAPHandlerIdle {
+ private static final class IMAPHandlerBeforeIdleDrop extends
+ IMAPHandlerIdle {
+ // must be static because handler is cloned for each connection
+ private static CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
+ public void idle() throws IOException {
+ latch.countDown();
+ exit();
+ }
+
+ public void waitForIdle() throws InterruptedException {
+ latch.await();
+ }
+ }
+
+ /**
+ * Custom handler. Drop the connection after IDLE started.
+ */
+ private static final class IMAPHandlerIdleDrop extends IMAPHandlerIdle {
+ // must be static because handler is cloned for each connection
+ private static CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
+ public void idle() throws IOException {
+ cont();
+ latch.countDown();
+ exit();
+ }
+
+ public void waitForIdle() throws InterruptedException {
+ latch.await();
+ }
+ }
+
+ /**
+ * Custom handler. Drop the connection after DONE received.
+ */
+ private static final class IMAPHandlerDoneDrop extends IMAPHandlerIdle {
+ // must be static because handler is cloned for each connection
+ private static CountDownLatch latch = new CountDownLatch(1);
+
@Override
public void idle() throws IOException {
cont();
latch.countDown();
idleWait();
- println("* 2 EXISTS\r\n* 3 EXISTS");
- ok();
+ exit();
}
+
+ public void waitForIdle() throws InterruptedException {
+ latch.await();
+ }
}
}