ARTEMIS-398 - AMQP protocol idle timeout issue

added functionality to tick every n seconds where n is 1/2 the idle timeout

https://issues.apache.org/jira/browse/ARTEMIS-398
This commit is contained in:
Andy Taylor 2016-02-15 10:06:49 +00:00
parent 7d9cafe4b0
commit 7702a0a1f9
14 changed files with 93 additions and 28 deletions

View File

@ -100,7 +100,7 @@ public class ProtonProtocolManager implements ProtocolManager<Interceptor>, Noti
} }
AMQPServerConnectionContext amqpConnection = ProtonServerConnectionContextFactory.getFactory(). AMQPServerConnectionContext amqpConnection = ProtonServerConnectionContextFactory.getFactory().
createConnection(connectionCallback, (int) ttl, DEFAULT_MAX_FRAME_SIZE, DEFAULT_CHANNEL_MAX); createConnection(connectionCallback, (int) ttl, DEFAULT_MAX_FRAME_SIZE, DEFAULT_CHANNEL_MAX, server.getScheduledPool());
Executor executor = server.getExecutorFactory().getExecutor(); Executor executor = server.getExecutorFactory().getExecutor();

View File

@ -16,6 +16,8 @@
*/ */
package org.proton.plug; package org.proton.plug;
import java.util.concurrent.ScheduledExecutorService;
public abstract class AMQPConnectionContextFactory { public abstract class AMQPConnectionContextFactory {
/** /**
@ -24,10 +26,11 @@ public abstract class AMQPConnectionContextFactory {
public abstract AMQPConnectionContext createConnection(AMQPConnectionCallback connectionCallback, public abstract AMQPConnectionContext createConnection(AMQPConnectionCallback connectionCallback,
int idleTimeout, int idleTimeout,
int maxFrameSize, int maxFrameSize,
int channelMax); int channelMax,
ScheduledExecutorService scheduledPool);
/** /**
* @return * @return
*/ */
public abstract AMQPConnectionContext createConnection(AMQPConnectionCallback connectionCallback); public abstract AMQPConnectionContext createConnection(AMQPConnectionCallback connectionCallback, ScheduledExecutorService scheduledPool);
} }

View File

@ -18,8 +18,11 @@ package org.proton.plug.context;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.engine.Connection; import org.apache.qpid.proton.engine.Connection;
import org.apache.qpid.proton.engine.Delivery; import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.Link; import org.apache.qpid.proton.engine.Link;
@ -40,28 +43,32 @@ import static org.proton.plug.context.AMQPConstants.Connection.DEFAULT_MAX_FRAME
public abstract class AbstractConnectionContext extends ProtonInitializable implements AMQPConnectionContext { public abstract class AbstractConnectionContext extends ProtonInitializable implements AMQPConnectionContext {
public static final Symbol CONNECTION_OPEN_FAILED = Symbol.valueOf("amqp:connection-establishment-failed");
protected ProtonHandler handler = ProtonHandler.Factory.create(); protected ProtonHandler handler = ProtonHandler.Factory.create();
protected AMQPConnectionCallback connectionCallback; protected AMQPConnectionCallback connectionCallback;
private final ScheduledExecutorService scheduledPool;
private final Map<Session, AbstractProtonSessionContext> sessions = new ConcurrentHashMap<>(); private final Map<Session, AbstractProtonSessionContext> sessions = new ConcurrentHashMap<>();
protected LocalListener listener = new LocalListener(); protected LocalListener listener = new LocalListener();
public AbstractConnectionContext(AMQPConnectionCallback connectionCallback) { public AbstractConnectionContext(AMQPConnectionCallback connectionCallback, ScheduledExecutorService scheduledPool) {
this(connectionCallback, DEFAULT_IDLE_TIMEOUT, DEFAULT_MAX_FRAME_SIZE, DEFAULT_CHANNEL_MAX); this(connectionCallback, DEFAULT_IDLE_TIMEOUT, DEFAULT_MAX_FRAME_SIZE, DEFAULT_CHANNEL_MAX, scheduledPool);
} }
public AbstractConnectionContext(AMQPConnectionCallback connectionCallback, public AbstractConnectionContext(AMQPConnectionCallback connectionCallback,
int idleTimeout, int idleTimeout,
int maxFrameSize, int maxFrameSize,
int channelMax) { int channelMax,
ScheduledExecutorService scheduledPool) {
this.connectionCallback = connectionCallback; this.connectionCallback = connectionCallback;
this.scheduledPool = scheduledPool;
connectionCallback.setConnection(this); connectionCallback.setConnection(this);
Transport transport = handler.getTransport(); Transport transport = handler.getTransport();
if (idleTimeout > 0) { if (idleTimeout > 0) {
transport.setIdleTimeout(idleTimeout); transport.setIdleTimeout(idleTimeout);
transport.tick(idleTimeout / 2);
} }
transport.setChannelMax(channelMax); transport.setChannelMax(channelMax);
transport.setMaxFrameSize(maxFrameSize); transport.setMaxFrameSize(maxFrameSize);
@ -172,6 +179,22 @@ public abstract class AbstractConnectionContext extends ProtonInitializable impl
connection.open(); connection.open();
} }
initialise(); initialise();
if (!connection.getRemoteProperties().containsKey(CONNECTION_OPEN_FAILED)) {
long nextKeepAliveTime = handler.tick(true);
flushBytes();
if (nextKeepAliveTime > 0) {
scheduledPool.schedule(new Runnable() {
@Override
public void run() {
long rescheduleAt = (handler.tick(false) - TimeUnit.NANOSECONDS.toMillis(System.nanoTime()));
flushBytes();
if (rescheduleAt > 0) {
scheduledPool.schedule(this, rescheduleAt, TimeUnit.MILLISECONDS);
}
}
}, (nextKeepAliveTime - TimeUnit.NANOSECONDS.toMillis(System.nanoTime())), TimeUnit.MILLISECONDS);
}
}
} }
@Override @Override

View File

@ -29,17 +29,20 @@ import org.proton.plug.exceptions.ActiveMQAMQPException;
import org.proton.plug.context.ProtonInitializable; import org.proton.plug.context.ProtonInitializable;
import org.proton.plug.util.FutureRunnable; import org.proton.plug.util.FutureRunnable;
import java.util.concurrent.ScheduledExecutorService;
public class ProtonClientConnectionContext extends AbstractConnectionContext implements AMQPClientConnectionContext { public class ProtonClientConnectionContext extends AbstractConnectionContext implements AMQPClientConnectionContext {
public ProtonClientConnectionContext(AMQPConnectionCallback connectionCallback) { public ProtonClientConnectionContext(AMQPConnectionCallback connectionCallback, ScheduledExecutorService scheduledPool) {
super(connectionCallback); super(connectionCallback, scheduledPool);
} }
public ProtonClientConnectionContext(AMQPConnectionCallback connectionCallback, public ProtonClientConnectionContext(AMQPConnectionCallback connectionCallback,
int idleTimeout, int idleTimeout,
int maxFrameSize, int maxFrameSize,
int channelMax) { int channelMax,
super(connectionCallback, idleTimeout, maxFrameSize, channelMax); ScheduledExecutorService scheduledPool) {
super(connectionCallback, idleTimeout, maxFrameSize, channelMax, scheduledPool);
} }
// Maybe a client interface? // Maybe a client interface?

View File

@ -20,6 +20,8 @@ import org.proton.plug.AMQPConnectionContext;
import org.proton.plug.AMQPConnectionContextFactory; import org.proton.plug.AMQPConnectionContextFactory;
import org.proton.plug.AMQPConnectionCallback; import org.proton.plug.AMQPConnectionCallback;
import java.util.concurrent.ScheduledExecutorService;
public class ProtonClientConnectionContextFactory extends AMQPConnectionContextFactory { public class ProtonClientConnectionContextFactory extends AMQPConnectionContextFactory {
private static final AMQPConnectionContextFactory theInstance = new ProtonClientConnectionContextFactory(); private static final AMQPConnectionContextFactory theInstance = new ProtonClientConnectionContextFactory();
@ -29,15 +31,17 @@ public class ProtonClientConnectionContextFactory extends AMQPConnectionContextF
} }
@Override @Override
public AMQPConnectionContext createConnection(AMQPConnectionCallback connectionCallback) { public AMQPConnectionContext createConnection(AMQPConnectionCallback connectionCallback, ScheduledExecutorService scheduledPool) {
return new ProtonClientConnectionContext(connectionCallback); return new ProtonClientConnectionContext(connectionCallback, scheduledPool);
} }
@Override @Override
public AMQPConnectionContext createConnection(AMQPConnectionCallback connectionCallback, public AMQPConnectionContext createConnection(AMQPConnectionCallback connectionCallback,
int idleTimeout, int idleTimeout,
int maxFrameSize, int maxFrameSize,
int channelMax) { int channelMax,
return new ProtonClientConnectionContext(connectionCallback, idleTimeout, maxFrameSize, channelMax); ScheduledExecutorService scheduledPool) {
return new ProtonClientConnectionContext(connectionCallback, idleTimeout, maxFrameSize, channelMax, scheduledPool);
} }
} }

View File

@ -28,17 +28,20 @@ import org.proton.plug.context.AbstractConnectionContext;
import org.proton.plug.context.AbstractProtonSessionContext; import org.proton.plug.context.AbstractProtonSessionContext;
import org.proton.plug.exceptions.ActiveMQAMQPException; import org.proton.plug.exceptions.ActiveMQAMQPException;
import java.util.concurrent.ScheduledExecutorService;
public class ProtonServerConnectionContext extends AbstractConnectionContext implements AMQPServerConnectionContext { public class ProtonServerConnectionContext extends AbstractConnectionContext implements AMQPServerConnectionContext {
public ProtonServerConnectionContext(AMQPConnectionCallback connectionSP) { public ProtonServerConnectionContext(AMQPConnectionCallback connectionSP, ScheduledExecutorService scheduledPool) {
super(connectionSP); super(connectionSP, scheduledPool);
} }
public ProtonServerConnectionContext(AMQPConnectionCallback connectionSP, public ProtonServerConnectionContext(AMQPConnectionCallback connectionSP,
int idleTimeout, int idleTimeout,
int maxFrameSize, int maxFrameSize,
int channelMax) { int channelMax,
super(connectionSP, idleTimeout, maxFrameSize, channelMax); ScheduledExecutorService scheduledPool) {
super(connectionSP, idleTimeout, maxFrameSize, channelMax, scheduledPool);
} }
@Override @Override

View File

@ -20,6 +20,8 @@ import org.proton.plug.AMQPConnectionContextFactory;
import org.proton.plug.AMQPConnectionCallback; import org.proton.plug.AMQPConnectionCallback;
import org.proton.plug.AMQPServerConnectionContext; import org.proton.plug.AMQPServerConnectionContext;
import java.util.concurrent.ScheduledExecutorService;
import static org.proton.plug.context.AMQPConstants.Connection.DEFAULT_IDLE_TIMEOUT; import static org.proton.plug.context.AMQPConstants.Connection.DEFAULT_IDLE_TIMEOUT;
import static org.proton.plug.context.AMQPConstants.Connection.DEFAULT_CHANNEL_MAX; import static org.proton.plug.context.AMQPConstants.Connection.DEFAULT_CHANNEL_MAX;
import static org.proton.plug.context.AMQPConstants.Connection.DEFAULT_MAX_FRAME_SIZE; import static org.proton.plug.context.AMQPConstants.Connection.DEFAULT_MAX_FRAME_SIZE;
@ -33,15 +35,16 @@ public class ProtonServerConnectionContextFactory extends AMQPConnectionContextF
} }
@Override @Override
public AMQPServerConnectionContext createConnection(AMQPConnectionCallback connectionCallback) { public AMQPServerConnectionContext createConnection(AMQPConnectionCallback connectionCallback, ScheduledExecutorService scheduledPool) {
return createConnection(connectionCallback, DEFAULT_IDLE_TIMEOUT, DEFAULT_MAX_FRAME_SIZE, DEFAULT_CHANNEL_MAX); return createConnection(connectionCallback, DEFAULT_IDLE_TIMEOUT, DEFAULT_MAX_FRAME_SIZE, DEFAULT_CHANNEL_MAX, scheduledPool);
} }
@Override @Override
public AMQPServerConnectionContext createConnection(AMQPConnectionCallback connectionCallback, public AMQPServerConnectionContext createConnection(AMQPConnectionCallback connectionCallback,
int idleTimeout, int idleTimeout,
int maxFrameSize, int maxFrameSize,
int channelMax) { int channelMax,
return new ProtonServerConnectionContext(connectionCallback, idleTimeout, maxFrameSize, channelMax); ScheduledExecutorService scheduledPool) {
return new ProtonServerConnectionContext(connectionCallback, idleTimeout, maxFrameSize, channelMax, scheduledPool);
} }
} }

View File

@ -29,6 +29,8 @@ import org.proton.plug.handler.impl.ProtonHandlerImpl;
*/ */
public interface ProtonHandler { public interface ProtonHandler {
long tick(boolean firstTick);
public static final class Factory { public static final class Factory {
public static ProtonHandler create() { public static ProtonHandler create() {

View File

@ -20,6 +20,7 @@ import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator; import io.netty.buffer.PooledByteBufAllocator;
@ -27,6 +28,7 @@ import org.apache.qpid.proton.Proton;
import org.apache.qpid.proton.amqp.transport.ErrorCondition; import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.engine.Collector; import org.apache.qpid.proton.engine.Collector;
import org.apache.qpid.proton.engine.Connection; import org.apache.qpid.proton.engine.Connection;
import org.apache.qpid.proton.engine.EndpointState;
import org.apache.qpid.proton.engine.Event; import org.apache.qpid.proton.engine.Event;
import org.apache.qpid.proton.engine.Sasl; import org.apache.qpid.proton.engine.Sasl;
import org.apache.qpid.proton.engine.Transport; import org.apache.qpid.proton.engine.Transport;
@ -45,6 +47,7 @@ import org.proton.plug.util.DebugInfo;
*/ */
public class ProtonHandlerImpl extends ProtonInitializable implements ProtonHandler { public class ProtonHandlerImpl extends ProtonInitializable implements ProtonHandler {
private final Transport transport = Proton.transport(); private final Transport transport = Proton.transport();
private final Connection connection = Proton.connection(); private final Connection connection = Proton.connection();
@ -82,6 +85,27 @@ public class ProtonHandlerImpl extends ProtonInitializable implements ProtonHand
connection.collect(collector); connection.collect(collector);
} }
@Override
public long tick(boolean firstTick) {
if (!firstTick) {
try {
if (connection.getLocalState() != EndpointState.CLOSED) {
long rescheduleAt = transport.tick(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()));
if (transport.isClosed()) {
throw new IllegalStateException("Channel was inactive for to long");
}
return rescheduleAt;
}
}
catch (Exception e) {
transport.close();
connection.setCondition(new ErrorCondition());
}
return 0;
}
return transport.tick(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()));
}
@Override @Override
public int capacity() { public int capacity() {
synchronized (lock) { synchronized (lock) {

View File

@ -48,7 +48,7 @@ public class AbstractConnectionContextTest {
private class TestConnectionContext extends AbstractConnectionContext { private class TestConnectionContext extends AbstractConnectionContext {
public TestConnectionContext(AMQPConnectionCallback connectionCallback) { public TestConnectionContext(AMQPConnectionCallback connectionCallback) {
super(connectionCallback); super(connectionCallback, null);
} }
@Override @Override

View File

@ -32,6 +32,6 @@ public class InVMTestConnector implements Connector {
@Override @Override
public AMQPClientConnectionContext connect(String host, int port) throws Exception { public AMQPClientConnectionContext connect(String host, int port) throws Exception {
return new ProtonClientConnectionContext(new ProtonINVMSPI()); return new ProtonClientConnectionContext(new ProtonINVMSPI(), null);
} }
} }

View File

@ -35,7 +35,7 @@ public class ProtonINVMSPI implements AMQPConnectionCallback {
AMQPConnectionContext returningConnection; AMQPConnectionContext returningConnection;
ProtonServerConnectionContext serverConnection = new ProtonServerConnectionContext(new ReturnSPI()); ProtonServerConnectionContext serverConnection = new ProtonServerConnectionContext(new ReturnSPI(), null);
final ExecutorService mainExecutor = Executors.newSingleThreadExecutor(); final ExecutorService mainExecutor = Executors.newSingleThreadExecutor();

View File

@ -59,7 +59,7 @@ public class SimpleAMQPConnector implements Connector {
AMQPClientSPI clientConnectionSPI = new AMQPClientSPI(future.channel()); AMQPClientSPI clientConnectionSPI = new AMQPClientSPI(future.channel());
final AMQPClientConnectionContext connection = (AMQPClientConnectionContext) ProtonClientConnectionContextFactory.getFactory().createConnection(clientConnectionSPI); final AMQPClientConnectionContext connection = (AMQPClientConnectionContext) ProtonClientConnectionContextFactory.getFactory().createConnection(clientConnectionSPI, null);
future.channel().pipeline().addLast(new ChannelDuplexHandler() { future.channel().pipeline().addLast(new ChannelDuplexHandler() {
@Override @Override

View File

@ -124,7 +124,7 @@ public class MinimalServer {
@Override @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx); super.channelActive(ctx);
connection = ProtonServerConnectionContextFactory.getFactory().createConnection(new MinimalConnectionSPI(ctx.channel())); connection = ProtonServerConnectionContextFactory.getFactory().createConnection(new MinimalConnectionSPI(ctx.channel()), null);
//ctx.read(); //ctx.read();
} }