Repository: relayrides/pushy
Pull request #165 (2015-04-25 18:06:37)

Schedule disconnection timeouts
Rather than adding a second idle state handler to the pipeline, we can just schedule an immediate disconnection after some delay. This seems a whole lot simpler to me.

Also, renamed "shutdown" to "disconnect" in the context of `ApnsConnections` for clarity.


Patch:
diff --git a/src/main/java/com/relayrides/pushy/apns/ApnsConnection.java b/src/main/java/com/relayrides/pushy/apns/ApnsConnection.java
index b149ddfa..1fcf69f8 100644
--- a/src/main/java/com/relayrides/pushy/apns/ApnsConnection.java
+++ b/src/main/java/com/relayrides/pushy/apns/ApnsConnection.java
@@ -46,7 +46,8 @@ import java.nio.charset.Charset;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
-import java.util.NoSuchElementException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.net.ssl.SSLContext;
@@ -89,15 +90,12 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 	private final AtomicInteger pendingWriteCount = new AtomicInteger(0);
 	private int sendAttempts = 0;
 
-	private SendableApnsPushNotification<KnownBadPushNotification> shutdownNotification;
+	private SendableApnsPushNotification<KnownBadPushNotification> disconnectNotification;
+	private ScheduledFuture<?> gracefulDisconnectionTimeoutFuture;
 
 	private boolean rejectionReceived = false;
 	private final SentNotificationBuffer<T> sentNotificationBuffer;
 
-	private static final String PIPELINE_MAIN_HANDLER = "handler";
-	private static final String PIPELINE_IDLE_STATE_HANDLER = "idleStateHandler";
-	private static final String PIPELINE_GRACEFUL_SHUTDOWN_TIMEOUT_HANDLER = "gracefulShutdownTimeoutHandler";
-
 	private static final Logger log = LoggerFactory.getLogger(ApnsConnection.class);
 
 	public static final int DEFAULT_SENT_NOTIFICATION_BUFFER_CAPACITY = 8192;
@@ -240,13 +238,13 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 			this.apnsConnection.rejectionReceived = true;
 			this.apnsConnection.sentNotificationBuffer.clearNotificationsBeforeSequenceNumber(rejectedNotification.getSequenceNumber());
 
-			final boolean isKnownBadRejection = this.apnsConnection.shutdownNotification != null &&
-					(rejectedNotification.getSequenceNumber() == this.apnsConnection.shutdownNotification.getSequenceNumber()
+			final boolean isKnownBadRejection = this.apnsConnection.disconnectNotification != null &&
+					(rejectedNotification.getSequenceNumber() == this.apnsConnection.disconnectNotification.getSequenceNumber()
 					|| (rejectedNotification.getSequenceNumber() == 0 && RejectedNotificationReason.MISSING_TOKEN.equals(rejectedNotification.getReason())));
 
 			// We only want to notify listeners of an actual rejection if something actually went wrong. We don't want
 			// to notify listeners if a known-bad notification was rejected because that's an expected case, and we
-			// don't want to notify listeners if the gateway is shutting down the connection, but still processed the
+			// don't want to notify listeners if the gateway is closing the connection, but still processed the
 			// named notification successfully.
 			if (!isKnownBadRejection && !RejectedNotificationReason.SHUTDOWN.equals(rejectedNotification.getReason())) {
 				final T notification = this.apnsConnection.sentNotificationBuffer.getNotificationWithSequenceNumber(
@@ -275,8 +273,8 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 			// Regardless of the cause, we ALWAYS want to notify listeners that some sent notifications were not
 			// processed by the gateway (assuming there are some such notifications). The exception here is an upstream
 			// bug where the sequence number will be incorrectly reported as zero when sending a zero-length token (i.e.
-			// a known-bad shutdown token). In that case we don't know the actual sequence number, and can't determine
-			// what was sent after the bad notification.
+			// a known-bad disconnection notification). In that case we don't know the actual sequence number, and can't
+			// determine what was sent after the bad notification.
 			if (rejectedNotification.getSequenceNumber() != 0) {
 				final Collection<T> unprocessedNotifications =
 						this.apnsConnection.sentNotificationBuffer.getAllNotificationsAfterSequenceNumber(
@@ -312,6 +310,10 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 					this.apnsConnection.listener.handleConnectionClosure(this.apnsConnection);
 				}
 			}
+
+			if (this.apnsConnection.gracefulDisconnectionTimeoutFuture != null) {
+				this.apnsConnection.gracefulDisconnectionTimeoutFuture.cancel(false);
+			}
 		}
 
 		@Override
@@ -327,17 +329,8 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 		@Override
 		public void userEventTriggered(final ChannelHandlerContext context, final Object event) throws Exception {
 			if (event instanceof IdleStateEvent) {
-				// The IdleStateHandler for connection inactivity is removed by shutdownGracefully, which also populates
-				// shutdownNotification. If we get an IdleStateEvent without a shutdownNotification, we know that the
-				// event came from the connection inactivity handler. Otherwise, we know it came from the graceful
-				// shutdown timeout handler.
-				if (this.apnsConnection.shutdownNotification == null) {
-					log.debug("{} will shut down gracefully due to inactivity.", this.apnsConnection.name);
-					this.apnsConnection.shutdownGracefully();
-				} else {
-					log.debug("Graceful shutdown attempt for {} timed out; shutting down immediately.", this.apnsConnection.name);
-					this.apnsConnection.shutdownImmediately();
-				}
+				log.debug("{} will disconnect gracefully due to inactivity.", this.apnsConnection.name);
+				this.apnsConnection.disconnectGracefully();
 			} else {
 				super.userEventTriggered(context, event);
 			}
@@ -433,7 +426,12 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 				pipeline.addLast("ssl", new SslHandler(sslEngine));
 				pipeline.addLast("decoder", new RejectedNotificationDecoder());
 				pipeline.addLast("encoder", new ApnsPushNotificationEncoder());
-				pipeline.addLast(ApnsConnection.PIPELINE_MAIN_HANDLER, new ApnsConnectionHandler(apnsConnection));
+
+				if (ApnsConnection.this.configuration.getCloseAfterInactivityTime() != null) {
+					pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 0, apnsConnection.configuration.getCloseAfterInactivityTime()));
+				}
+
+				pipeline.addLast("handler", new ApnsConnectionHandler(apnsConnection));
 			}
 		});
 
@@ -461,13 +459,6 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 									if (apnsConnection.listener != null) {
 										apnsConnection.listener.handleConnectionSuccess(apnsConnection);
 									}
-
-									if (apnsConnection.configuration.getCloseAfterInactivityTime() != null) {
-										connectFuture.channel().pipeline().addBefore(ApnsConnection.PIPELINE_MAIN_HANDLER,
-												ApnsConnection.PIPELINE_IDLE_STATE_HANDLER,
-												new IdleStateHandler(0, 0, apnsConnection.configuration.getCloseAfterInactivityTime()));
-									}
-
 								} else {
 									log.debug("{} failed to complete TLS handshake with APNs gateway.",
 											apnsConnection.name, handshakeFuture.cause());
@@ -516,7 +507,7 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 			throw new IllegalStateException(String.format("%s has not completed handshake.", this.name));
 		}
 
-		if (this.shutdownNotification == null) {
+		if (this.disconnectNotification == null) {
 			this.connectFuture.channel().eventLoop().execute(new Runnable() {
 
 				@Override
@@ -571,19 +562,19 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 			});
 		} else {
 			if (this.listener != null) {
-				this.listener.handleWriteFailure(this, notification, new IllegalStateException("Connection is shutting down."));
+				this.listener.handleWriteFailure(this, notification, new IllegalStateException("Connection is disconnecting."));
 			}
 		}
 
 		if (this.configuration.getSendAttemptLimit() != null && ++this.sendAttempts >= this.configuration.getSendAttemptLimit()) {
-			log.debug("{} reached send attempt limit and will shut down gracefully.", this.name);
-			this.shutdownGracefully();
+			log.debug("{} reached send attempt limit and will disconnect gracefully.", this.name);
+			this.disconnectGracefully();
 		}
 	}
 
 	/**
 	 * <p>Waits for all pending write operations to finish. When this method exits normally (i.e. when it does
-	 * not throw an {@code InterruptedException}), All pending writes will have either finished successfully or failed
+	 * not throw an {@code InterruptedException}), all pending writes will have either finished successfully or failed
 	 * and passed to this connection's listener via the
 	 * {@link ApnsConnectionListener#handleWriteFailure(ApnsConnection, ApnsPushNotification, Throwable)} method.</p>
 	 *
@@ -603,7 +594,7 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 	}
 
 	/**
-	 * <p>Gracefully and asynchronously shuts down this connection. Graceful disconnection is triggered by sending a
+	 * <p>Gracefully and asynchronously closes this connection. Graceful disconnection is triggered by sending a
 	 * known-bad notification to the APNs gateway; when the gateway rejects the notification, it is guaranteed that
 	 * preceding notifications were processed successfully and that all following notifications were not processed at
 	 * all. The gateway will close the connection after rejecting the notification, and this connection's listener will
@@ -612,21 +603,16 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 	 * <p>Note that if/when the known-bad notification is rejected by the APNs gateway, this connection's listener will
 	 * <em>not</em> be notified of the rejection.</p>
 	 *
-	 * <p>Calling this method before establishing a connection with the APNs gateway or while a graceful shutdown
+	 * <p>Calling this method before establishing a connection with the APNs gateway or while a graceful disconnection
 	 * attempt is already in progress has no effect.</p>
 	 *
 	 * @see ApnsConnectionListener#handleRejectedNotification(ApnsConnection, ApnsPushNotification, RejectedNotificationReason)
 	 * @see ApnsConnectionListener#handleConnectionClosure(ApnsConnection)
+	 *
+	 * @return {@code true} if this connection started a graceful disconnection attempt or {@code false} otherwise (i.e.
+	 * because no connection was ever established or the connection is already closed).
 	 */
-	public synchronized void shutdownGracefully() {
-
-		if (this.connectFuture != null && this.connectFuture.channel() != null) {
-			try {
-				this.connectFuture.channel().pipeline().remove(ApnsConnection.PIPELINE_IDLE_STATE_HANDLER);
-			} catch (NoSuchElementException ignored) {
-				log.debug("Failed to remove idle state handler from pipeline for {}.", this.name);
-			}
-		}
+	public synchronized boolean disconnectGracefully() {
 
 		final ApnsConnection<T> apnsConnection = this;
 
@@ -638,40 +624,39 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 
 				@Override
 				public void run() {
-					// Don't send a second shutdown notification if we've already started the graceful shutdown process.
-					if (apnsConnection.shutdownNotification == null) {
+					// Don't send a second disconnection notification if we've already started the graceful disconnection process.
+					if (apnsConnection.disconnectNotification == null) {
 
-						log.debug("{} sending known-bad notification to shut down.", apnsConnection.name);
+						log.debug("{} sending known-bad notification to disconnect.", apnsConnection.name);
 
-						apnsConnection.shutdownNotification = new SendableApnsPushNotification<KnownBadPushNotification>(
+						apnsConnection.disconnectNotification = new SendableApnsPushNotification<KnownBadPushNotification>(
 								new KnownBadPushNotification(), apnsConnection.sequenceNumber++);
 
-						if (apnsConnection.configuration.getGracefulShutdownTimeout() != null &&
-								apnsConnection.connectFuture.channel().pipeline().get(PIPELINE_GRACEFUL_SHUTDOWN_TIMEOUT_HANDLER) == null) {
-							// We should time out, but haven't added an idle state handler yet.
-							apnsConnection.connectFuture.channel().pipeline().addBefore(
-									PIPELINE_MAIN_HANDLER,
-									PIPELINE_GRACEFUL_SHUTDOWN_TIMEOUT_HANDLER,
-									new IdleStateHandler(apnsConnection.configuration.getGracefulShutdownTimeout(), 0, 0));
+						if (apnsConnection.configuration.getGracefulDisconnectionTimeout() != null) {
+							ApnsConnection.this.gracefulDisconnectionTimeoutFuture = ApnsConnection.this.connectFuture.channel().eventLoop().schedule(new Runnable() {
+								@Override
+								public void run() {
+									ApnsConnection.this.disconnectImmediately();
+								}
+							}, ApnsConnection.this.configuration.getGracefulDisconnectionTimeout(), TimeUnit.SECONDS);
 						}
 
-
 						apnsConnection.pendingWriteCount.incrementAndGet();
 
-						apnsConnection.connectFuture.channel().writeAndFlush(apnsConnection.shutdownNotification).addListener(new GenericFutureListener<ChannelFuture>() {
+						apnsConnection.connectFuture.channel().writeAndFlush(apnsConnection.disconnectNotification).addListener(new GenericFutureListener<ChannelFuture>() {
 
 							@Override
 							public void operationComplete(final ChannelFuture future) {
 								if (future.isSuccess()) {
 									log.trace("{} successfully wrote known-bad notification {}",
-											apnsConnection.name, apnsConnection.shutdownNotification.getSequenceNumber());
+											apnsConnection.name, apnsConnection.disconnectNotification.getSequenceNumber());
 								} else {
 									log.trace("{} failed to write known-bad notification {}",
-											apnsConnection.name, apnsConnection.shutdownNotification, future.cause());
+											apnsConnection.name, apnsConnection.disconnectNotification, future.cause());
 
 									// Try again!
-									apnsConnection.shutdownNotification = null;
-									apnsConnection.shutdownGracefully();
+									apnsConnection.disconnectNotification = null;
+									apnsConnection.disconnectGracefully();
 								}
 
 								final int currentPendingWriteCount = apnsConnection.pendingWriteCount.decrementAndGet();
@@ -687,16 +672,20 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 					}
 				}
 			});
+
+			return true;
 		} else {
 			// While we can't guarantee that the handshake won't complete in another thread, we CAN guarantee that no
-			// new notifications will be sent until shutdownImmediately happens because everything is synchronized.
-			this.shutdownImmediately();
+			// new notifications will be sent until disconnectImmediately happens because everything is synchronized.
+			this.disconnectImmediately();
+
+			return false;
 		}
 	}
 
 	/**
 	 * <p>Immediately closes this connection (assuming it was ever open). No guarantees are made with regard to the
-	 * state of sent notifications, and callers should generally prefer {@link ApnsConnection#shutdownGracefully} to
+	 * state of sent notifications, and callers should generally prefer {@link ApnsConnection#disconnectGracefully} to
 	 * this method. If the connection was previously open, the connection's listener will be notified of the
 	 * connection's closure. If a connection attempt was in progress, the listener will be notified of a connection
 	 * failure. If the connection was never open, this method has no effect.</p>
@@ -705,7 +694,7 @@ public class ApnsConnection<T extends ApnsPushNotification> {
 	 *
 	 * @see ApnsConnectionListener#handleConnectionClosure(ApnsConnection)
 	 */
-	public synchronized void shutdownImmediately() {
+	public synchronized void disconnectImmediately() {
 		if (this.connectFuture != null) {
 			this.connectFuture.channel().close();
 		}
diff --git a/src/main/java/com/relayrides/pushy/apns/ApnsConnectionConfiguration.java b/src/main/java/com/relayrides/pushy/apns/ApnsConnectionConfiguration.java
index 18ba59e7..ccbca745 100644
--- a/src/main/java/com/relayrides/pushy/apns/ApnsConnectionConfiguration.java
+++ b/src/main/java/com/relayrides/pushy/apns/ApnsConnectionConfiguration.java
@@ -95,25 +95,25 @@ public class ApnsConnectionConfiguration {
 	}
 
 	/**
-	 * Returns the time, in seconds, after which a graceful shutdown attempt should be abandoned and the connection
+	 * Returns the time, in seconds, after which a graceful disconnection attempt should be abandoned and the connection
 	 * should be closed immediately.
 	 *
-	 * @return the time, in seconds, after which a graceful shutdown attempt should be abandoned and the connection
+	 * @return the time, in seconds, after which a graceful disconnection attempt should be abandoned and the connection
 	 * should be closed immediately
 	 */
-	public Integer getGracefulShutdownTimeout() {
+	public Integer getGracefulDisconnectionTimeout() {
 		return this.gracefulShutdownTimeout;
 	}
 
 	/**
-	 * Sets the time, in seconds, after which a graceful shutdown attempt should be abandoned and the connection should
-	 * be closed immediately. If {@code null} (the default) graceful shutdown attempts will never time out. Note that,
-	 * if a graceful shutdown attempt times out, no guarantees are made as to the state of notifications sent by the
+	 * Sets the time, in seconds, after which a graceful disconnection attempt should be abandoned and the connection should
+	 * be closed immediately. If {@code null} (the default) graceful disconnection attempts will never time out. Note that,
+	 * if a graceful disconnection attempt times out, no guarantees are made as to the state of notifications sent by the
 	 * connection.
 	 *
-	 * @param gracefulShutdownTimeout the time, in seconds, after which a graceful shutdown attempt should be abandoned
+	 * @param gracefulShutdownTimeout the time, in seconds, after which a graceful disconnection attempt should be abandoned
 	 */
-	public void setGracefulShutdownTimeout(final Integer gracefulShutdownTimeout) {
+	public void setGracefulDisconnectionTimeout(final Integer gracefulShutdownTimeout) {
 		this.gracefulShutdownTimeout = gracefulShutdownTimeout;
 	}
 
diff --git a/src/main/java/com/relayrides/pushy/apns/PushManager.java b/src/main/java/com/relayrides/pushy/apns/PushManager.java
index 71bd217d..bcb748b4 100644
--- a/src/main/java/com/relayrides/pushy/apns/PushManager.java
+++ b/src/main/java/com/relayrides/pushy/apns/PushManager.java
@@ -284,7 +284,7 @@ public class PushManager<T extends ApnsPushNotification> implements ApnsConnecti
 							if (shutDownStarted) {
 								// We're trying to drain the retry queue before shutting down, and the retry queue is
 								// now empty. Close the connection and see if it stays closed.
-								connection.shutdownGracefully();
+								connection.disconnectGracefully();
 								writableConnections.remove(connection);
 							} else {
 								// We'll park here either until a new notification is available from the outside or until
@@ -412,7 +412,7 @@ public class PushManager<T extends ApnsPushNotification> implements ApnsConnecti
 
 		synchronized (this.activeConnections) {
 			for (final ApnsConnection<T> connection : this.activeConnections) {
-				connection.shutdownImmediately();
+				connection.disconnectImmediately();
 			}
 		}
 
@@ -701,7 +701,7 @@ public class PushManager<T extends ApnsPushNotification> implements ApnsConnecti
 			this.writableConnections.add(connection);
 		} else {
 			// There's no dispatch thread to use this connection, so shut it down immediately
-			connection.shutdownImmediately();
+			connection.disconnectImmediately();
 		}
 	}
 


Test patch:
diff --git a/src/test/java/com/relayrides/pushy/apns/ApnsConnectionConfigurationTest.java b/src/test/java/com/relayrides/pushy/apns/ApnsConnectionConfigurationTest.java
index adde24ee..bb466b0c 100644
--- a/src/test/java/com/relayrides/pushy/apns/ApnsConnectionConfigurationTest.java
+++ b/src/test/java/com/relayrides/pushy/apns/ApnsConnectionConfigurationTest.java
@@ -35,7 +35,7 @@ public class ApnsConnectionConfigurationTest {
 
 		assertTrue(configuration.getSentNotificationBufferCapacity() > 0);
 		assertNull(configuration.getCloseAfterInactivityTime());
-		assertNull(configuration.getGracefulShutdownTimeout());
+		assertNull(configuration.getGracefulDisconnectionTimeout());
 		assertNull(configuration.getSendAttemptLimit());
 	}
 
@@ -44,7 +44,7 @@ public class ApnsConnectionConfigurationTest {
 		final ApnsConnectionConfiguration configuration = new ApnsConnectionConfiguration();
 		configuration.setSentNotificationBufferCapacity(17);
 		configuration.setCloseAfterInactivityTime(19);
-		configuration.setGracefulShutdownTimeout(23);
+		configuration.setGracefulDisconnectionTimeout(23);
 		configuration.setSendAttemptLimit(29);
 
 		final ApnsConnectionConfiguration configurationCopy = new ApnsConnectionConfiguration(configuration);
diff --git a/src/test/java/com/relayrides/pushy/apns/ApnsConnectionTest.java b/src/test/java/com/relayrides/pushy/apns/ApnsConnectionTest.java
index 3c785b48..5221c3da 100644
--- a/src/test/java/com/relayrides/pushy/apns/ApnsConnectionTest.java
+++ b/src/test/java/com/relayrides/pushy/apns/ApnsConnectionTest.java
@@ -380,7 +380,7 @@ public class ApnsConnectionTest extends BasePushyTest {
 		assertTrue(listener.connectionSucceeded);
 
 		synchronized (mutex) {
-			apnsConnection.shutdownGracefully();
+			apnsConnection.disconnectGracefully();
 
 			while (!listener.connectionClosed) {
 				mutex.wait();
@@ -414,8 +414,8 @@ public class ApnsConnectionTest extends BasePushyTest {
 		assertTrue(listener.connectionSucceeded);
 
 		synchronized (mutex) {
-			apnsConnection.shutdownGracefully();
-			apnsConnection.shutdownGracefully();
+			apnsConnection.disconnectGracefully();
+			apnsConnection.disconnectGracefully();
 
 			while (!listener.connectionClosed) {
 				mutex.wait();
@@ -438,7 +438,7 @@ public class ApnsConnectionTest extends BasePushyTest {
 						TEST_ENVIRONMENT, SSLTestUtil.createSSLContextForTestClient(), this.getEventLoopGroup(),
 						new ApnsConnectionConfiguration(), listener, TEST_CONNECTION_NAME);
 
-		apnsConnection.shutdownGracefully();
+		apnsConnection.disconnectGracefully();
 	}
 
 	@Test
@@ -462,7 +462,7 @@ public class ApnsConnectionTest extends BasePushyTest {
 		assertTrue(listener.connectionSucceeded);
 
 		synchronized (mutex) {
-			apnsConnection.shutdownImmediately();
+			apnsConnection.disconnectImmediately();
 
 			while (!listener.connectionClosed) {
 				mutex.wait();
@@ -482,7 +482,7 @@ public class ApnsConnectionTest extends BasePushyTest {
 						TEST_ENVIRONMENT, SSLTestUtil.createSSLContextForTestClient(), this.getEventLoopGroup(),
 						new ApnsConnectionConfiguration(), listener, TEST_CONNECTION_NAME);
 
-		apnsConnection.shutdownImmediately();
+		apnsConnection.disconnectImmediately();
 	}
 
 	@Test
@@ -499,7 +499,7 @@ public class ApnsConnectionTest extends BasePushyTest {
 							new ApnsConnectionConfiguration(), listener, TEST_CONNECTION_NAME);
 
 			apnsConnection.waitForPendingWritesToFinish();
-			apnsConnection.shutdownImmediately();
+			apnsConnection.disconnectImmediately();
 		}
 
 		{
@@ -526,7 +526,7 @@ public class ApnsConnectionTest extends BasePushyTest {
 			}
 
 			apnsConnection.waitForPendingWritesToFinish();
-			apnsConnection.shutdownGracefully();
+			apnsConnection.disconnectGracefully();
 		}
 	}
 
@@ -563,7 +563,7 @@ public class ApnsConnectionTest extends BasePushyTest {
 		final TestListener listener = new TestListener(mutex);
 
 		final ApnsConnectionConfiguration gracefulShutdownTimeoutConfiguration = new ApnsConnectionConfiguration();
-		gracefulShutdownTimeoutConfiguration.setGracefulShutdownTimeout(1);
+		gracefulShutdownTimeoutConfiguration.setGracefulDisconnectionTimeout(1);
 
 		final ApnsConnection<SimpleApnsPushNotification> apnsConnection =
 				new ApnsConnection<SimpleApnsPushNotification>(
@@ -585,7 +585,7 @@ public class ApnsConnectionTest extends BasePushyTest {
 		assertTrue(listener.connectionSucceeded);
 
 		synchronized (mutex) {
-			apnsConnection.shutdownGracefully();
+			apnsConnection.disconnectGracefully();
 
 			while (!listener.connectionClosed) {
 				mutex.wait();
@@ -667,7 +667,7 @@ public class ApnsConnectionTest extends BasePushyTest {
 			this.waitForLatch(latch);
 
 			synchronized (mutex) {
-				apnsConnection.shutdownGracefully();
+				apnsConnection.disconnectGracefully();
 
 				while (!listener.connectionClosed) {
 					mutex.wait();