ARTEMIS-1898 Proper fix on AtomicRunnables and avoiding leaks
GlobalDiskFullTest was broken before this fix. Basically when using multiple addresses over a session you would miss flow credits on all your producers except to the first one that ran out of credit.
This commit is contained in:
parent
2b3819bc19
commit
fceb9ea718
|
@ -22,9 +22,9 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||||
public abstract class AtomicRunnable implements Runnable {
|
public abstract class AtomicRunnable implements Runnable {
|
||||||
|
|
||||||
|
|
||||||
public static Runnable checkAtomic(Runnable run) {
|
public static AtomicRunnable checkAtomic(Runnable run) {
|
||||||
if (run instanceof AtomicRunnable) {
|
if (run instanceof AtomicRunnable) {
|
||||||
return run;
|
return (AtomicRunnable)run;
|
||||||
} else {
|
} else {
|
||||||
return new AtomicRunnableWithDelegate(run);
|
return new AtomicRunnableWithDelegate(run);
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,20 @@ public abstract class AtomicRunnable implements Runnable {
|
||||||
private static final AtomicIntegerFieldUpdater<AtomicRunnable> RAN_UPDATE =
|
private static final AtomicIntegerFieldUpdater<AtomicRunnable> RAN_UPDATE =
|
||||||
AtomicIntegerFieldUpdater.newUpdater(AtomicRunnable.class, "ran");
|
AtomicIntegerFieldUpdater.newUpdater(AtomicRunnable.class, "ran");
|
||||||
|
|
||||||
|
public AtomicRunnable reset() {
|
||||||
|
RAN_UPDATE.set(this, 0);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AtomicRunnable setRan() {
|
||||||
|
RAN_UPDATE.set(this, 1);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRun() {
|
||||||
|
return RAN_UPDATE.get(this) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (RAN_UPDATE.compareAndSet(this, 0, 1)) {
|
if (RAN_UPDATE.compareAndSet(this, 0, 1)) {
|
||||||
|
|
|
@ -109,8 +109,6 @@ public class AMQPSessionCallback implements SessionCallback {
|
||||||
|
|
||||||
private final AddressQueryCache<AddressQueryResult> addressQueryCache = new AddressQueryCache<>();
|
private final AddressQueryCache<AddressQueryResult> addressQueryCache = new AddressQueryCache<>();
|
||||||
|
|
||||||
private CreditRunnable creditRunnable;
|
|
||||||
|
|
||||||
public AMQPSessionCallback(AMQPConnectionCallback protonSPI,
|
public AMQPSessionCallback(AMQPConnectionCallback protonSPI,
|
||||||
ProtonProtocolManager manager,
|
ProtonProtocolManager manager,
|
||||||
AMQPConnectionContext connection,
|
AMQPConnectionContext connection,
|
||||||
|
@ -577,49 +575,17 @@ public class AMQPSessionCallback implements SessionCallback {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void offerProducerCredit(final SimpleString address,
|
/** Will execute a Runnable on an Address when there's space in memory*/
|
||||||
final int credits,
|
public void flow(final SimpleString address,
|
||||||
final int threshold,
|
Runnable runnable) {
|
||||||
final Receiver receiver) {
|
|
||||||
try {
|
try {
|
||||||
/*
|
|
||||||
* The credit runnable will always be run in this thread unless the address or disc is full. If this is the case the
|
|
||||||
* runnable is run once the memory or disc is free, if this happens we don't want to keep adding runnables as this
|
|
||||||
* may cause a memory leak, one is enough.
|
|
||||||
* */
|
|
||||||
if (creditRunnable != null && !creditRunnable.isRun())
|
|
||||||
return;
|
|
||||||
PagingManager pagingManager = manager.getServer().getPagingManager();
|
PagingManager pagingManager = manager.getServer().getPagingManager();
|
||||||
creditRunnable = new CreditRunnable() {
|
|
||||||
boolean isRun = false;
|
|
||||||
@Override
|
|
||||||
public boolean isRun() {
|
|
||||||
return isRun;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
connection.lock();
|
|
||||||
try {
|
|
||||||
if (receiver.getCredit() <= threshold) {
|
|
||||||
int topUp = credits - receiver.getCredit();
|
|
||||||
if (topUp > 0) {
|
|
||||||
receiver.flow(topUp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
isRun = true;
|
|
||||||
connection.unlock();
|
|
||||||
}
|
|
||||||
connection.flush();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (address == null) {
|
if (address == null) {
|
||||||
pagingManager.checkMemory(creditRunnable);
|
pagingManager.checkMemory(runnable);
|
||||||
} else {
|
} else {
|
||||||
final PagingStore store = manager.getServer().getPagingManager().getPageStore(address);
|
final PagingStore store = manager.getServer().getPagingManager().getPageStore(address);
|
||||||
store.checkMemory(creditRunnable);
|
store.checkMemory(runnable);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
@ -791,7 +757,4 @@ public class AMQPSessionCallback implements SessionCallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
interface CreditRunnable extends Runnable {
|
|
||||||
boolean isRun();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.apache.activemq.artemis.protocol.amqp.logger.ActiveMQAMQPProtocolMess
|
||||||
import org.apache.activemq.artemis.protocol.amqp.sasl.PlainSASLResult;
|
import org.apache.activemq.artemis.protocol.amqp.sasl.PlainSASLResult;
|
||||||
import org.apache.activemq.artemis.protocol.amqp.sasl.SASLResult;
|
import org.apache.activemq.artemis.protocol.amqp.sasl.SASLResult;
|
||||||
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
|
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
|
||||||
|
import org.apache.activemq.artemis.utils.runnables.AtomicRunnable;
|
||||||
import org.apache.qpid.proton.amqp.Symbol;
|
import org.apache.qpid.proton.amqp.Symbol;
|
||||||
import org.apache.qpid.proton.amqp.messaging.Rejected;
|
import org.apache.qpid.proton.amqp.messaging.Rejected;
|
||||||
import org.apache.qpid.proton.amqp.messaging.TerminusExpiryPolicy;
|
import org.apache.qpid.proton.amqp.messaging.TerminusExpiryPolicy;
|
||||||
|
@ -60,6 +61,35 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
|
||||||
|
|
||||||
protected final AMQPSessionCallback sessionSPI;
|
protected final AMQPSessionCallback sessionSPI;
|
||||||
|
|
||||||
|
/** We create this AtomicRunnable with setRan.
|
||||||
|
* This is because we always reuse the same instance.
|
||||||
|
* In case the creditRunnable was run, we reset and send it over.
|
||||||
|
* We set it as ran as the first one should always go through */
|
||||||
|
protected final AtomicRunnable creditRunnable;
|
||||||
|
|
||||||
|
|
||||||
|
/** This Credit Runnable may be used in Mock tests to simulate the credit semantic here */
|
||||||
|
public static AtomicRunnable createCreditRunnable(int refill, int threshold, Receiver receiver, AMQPConnectionContext connection) {
|
||||||
|
return new AtomicRunnable() {
|
||||||
|
@Override
|
||||||
|
public void atomicRun() {
|
||||||
|
connection.lock();
|
||||||
|
try {
|
||||||
|
if (receiver.getCredit() <= threshold) {
|
||||||
|
int topUp = refill - receiver.getCredit();
|
||||||
|
if (topUp > 0) {
|
||||||
|
receiver.flow(topUp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
connection.unlock();
|
||||||
|
}
|
||||||
|
connection.flush();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The maximum number of credits we will allocate to clients.
|
The maximum number of credits we will allocate to clients.
|
||||||
This number is also used by the broker when refresh client credits.
|
This number is also used by the broker when refresh client credits.
|
||||||
|
@ -68,7 +98,6 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
|
||||||
|
|
||||||
// Used by the broker to decide when to refresh clients credit. This is not used when client requests credit.
|
// Used by the broker to decide when to refresh clients credit. This is not used when client requests credit.
|
||||||
private final int minCreditRefresh;
|
private final int minCreditRefresh;
|
||||||
private TerminusExpiryPolicy expiryPolicy;
|
|
||||||
|
|
||||||
public ProtonServerReceiverContext(AMQPSessionCallback sessionSPI,
|
public ProtonServerReceiverContext(AMQPSessionCallback sessionSPI,
|
||||||
AMQPConnectionContext connection,
|
AMQPConnectionContext connection,
|
||||||
|
@ -80,11 +109,12 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
|
||||||
this.sessionSPI = sessionSPI;
|
this.sessionSPI = sessionSPI;
|
||||||
this.amqpCredits = connection.getAmqpCredits();
|
this.amqpCredits = connection.getAmqpCredits();
|
||||||
this.minCreditRefresh = connection.getAmqpLowCredits();
|
this.minCreditRefresh = connection.getAmqpLowCredits();
|
||||||
|
this.creditRunnable = createCreditRunnable(amqpCredits, minCreditRefresh, receiver, connection).setRan();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFlow(int credits, boolean drain) {
|
public void onFlow(int credits, boolean drain) {
|
||||||
flow(Math.min(credits, amqpCredits), amqpCredits);
|
flow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -116,7 +146,6 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
|
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
expiryPolicy = target.getExpiryPolicy() != null ? target.getExpiryPolicy() : TerminusExpiryPolicy.LINK_DETACH;
|
|
||||||
target.setAddress(address.toString());
|
target.setAddress(address.toString());
|
||||||
} else {
|
} else {
|
||||||
// the target will have an address unless the remote is requesting an anonymous
|
// the target will have an address unless the remote is requesting an anonymous
|
||||||
|
@ -182,7 +211,7 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flow(amqpCredits, minCreditRefresh);
|
flow();
|
||||||
}
|
}
|
||||||
|
|
||||||
public RoutingType getRoutingType(Receiver receiver, SimpleString address) {
|
public RoutingType getRoutingType(Receiver receiver, SimpleString address) {
|
||||||
|
@ -245,7 +274,7 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
|
||||||
|
|
||||||
sessionSPI.serverSend(this, tx, receiver, delivery, address, delivery.getMessageFormat(), data);
|
sessionSPI.serverSend(this, tx, receiver, delivery, address, delivery.getMessageFormat(), data);
|
||||||
|
|
||||||
flow(amqpCredits, minCreditRefresh);
|
flow();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn(e.getMessage(), e);
|
log.warn(e.getMessage(), e);
|
||||||
Rejected rejected = new Rejected();
|
Rejected rejected = new Rejected();
|
||||||
|
@ -262,7 +291,7 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
|
||||||
|
|
||||||
delivery.disposition(rejected);
|
delivery.disposition(rejected);
|
||||||
delivery.settle();
|
delivery.settle();
|
||||||
flow(amqpCredits, minCreditRefresh);
|
flow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,20 +314,18 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
|
||||||
close(false);
|
close(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flow(int credits, int threshold) {
|
public void flow() {
|
||||||
|
if (!creditRunnable.isRun()) {
|
||||||
|
return; // nothing to be done as the previous one did not run yet
|
||||||
|
}
|
||||||
|
|
||||||
|
creditRunnable.reset();
|
||||||
|
|
||||||
// Use the SessionSPI to allocate producer credits, or default, always allocate credit.
|
// Use the SessionSPI to allocate producer credits, or default, always allocate credit.
|
||||||
if (sessionSPI != null) {
|
if (sessionSPI != null) {
|
||||||
if (receiver.getCredit() <= threshold) {
|
sessionSPI.flow(address, creditRunnable);
|
||||||
sessionSPI.offerProducerCredit(address, credits, threshold, receiver);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
connection.lock();
|
creditRunnable.run();
|
||||||
try {
|
|
||||||
receiver.flow(credits);
|
|
||||||
} finally {
|
|
||||||
connection.unlock();
|
|
||||||
}
|
|
||||||
connection.flush();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.activemq.artemis.core.paging.PagingStore;
|
||||||
import org.apache.activemq.artemis.core.persistence.OperationContext;
|
import org.apache.activemq.artemis.core.persistence.OperationContext;
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
||||||
import org.apache.activemq.artemis.protocol.amqp.proton.AMQPConnectionContext;
|
import org.apache.activemq.artemis.protocol.amqp.proton.AMQPConnectionContext;
|
||||||
|
import org.apache.activemq.artemis.protocol.amqp.proton.ProtonServerReceiverContext;
|
||||||
import org.apache.activemq.artemis.spi.core.remoting.Connection;
|
import org.apache.activemq.artemis.spi.core.remoting.Connection;
|
||||||
import org.apache.qpid.proton.engine.Receiver;
|
import org.apache.qpid.proton.engine.Receiver;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
@ -74,7 +75,7 @@ public class AMQPSessionCallbackTest {
|
||||||
// Credit is above threshold
|
// Credit is above threshold
|
||||||
Mockito.when(receiver.getCredit()).thenReturn(AMQP_LOW_CREDITS_DEFAULT + 1);
|
Mockito.when(receiver.getCredit()).thenReturn(AMQP_LOW_CREDITS_DEFAULT + 1);
|
||||||
|
|
||||||
session.offerProducerCredit(null, AMQP_CREDITS_DEFAULT, AMQP_LOW_CREDITS_DEFAULT, receiver);
|
session.flow(null, ProtonServerReceiverContext.createCreditRunnable(AMQP_CREDITS_DEFAULT, AMQP_LOW_CREDITS_DEFAULT, receiver, connection));
|
||||||
|
|
||||||
// Run the credit refill code.
|
// Run the credit refill code.
|
||||||
Mockito.verify(pagingManager).checkMemory(argument.capture());
|
Mockito.verify(pagingManager).checkMemory(argument.capture());
|
||||||
|
@ -105,7 +106,7 @@ public class AMQPSessionCallbackTest {
|
||||||
// Credit is at threshold
|
// Credit is at threshold
|
||||||
Mockito.when(receiver.getCredit()).thenReturn(AMQP_LOW_CREDITS_DEFAULT);
|
Mockito.when(receiver.getCredit()).thenReturn(AMQP_LOW_CREDITS_DEFAULT);
|
||||||
|
|
||||||
session.offerProducerCredit(null, AMQP_CREDITS_DEFAULT, AMQP_LOW_CREDITS_DEFAULT, receiver);
|
session.flow(null, ProtonServerReceiverContext.createCreditRunnable(AMQP_CREDITS_DEFAULT, AMQP_LOW_CREDITS_DEFAULT, receiver, connection));
|
||||||
|
|
||||||
// Run the credit refill code.
|
// Run the credit refill code.
|
||||||
Mockito.verify(pagingManager).checkMemory(argument.capture());
|
Mockito.verify(pagingManager).checkMemory(argument.capture());
|
||||||
|
@ -137,7 +138,7 @@ public class AMQPSessionCallbackTest {
|
||||||
// Credit is above threshold
|
// Credit is above threshold
|
||||||
Mockito.when(receiver.getCredit()).thenReturn(AMQP_LOW_CREDITS_DEFAULT + 1);
|
Mockito.when(receiver.getCredit()).thenReturn(AMQP_LOW_CREDITS_DEFAULT + 1);
|
||||||
|
|
||||||
session.offerProducerCredit(new SimpleString("test"), AMQP_CREDITS_DEFAULT, AMQP_LOW_CREDITS_DEFAULT, receiver);
|
session.flow(new SimpleString("test"), ProtonServerReceiverContext.createCreditRunnable(AMQP_CREDITS_DEFAULT, AMQP_LOW_CREDITS_DEFAULT, receiver, connection));
|
||||||
|
|
||||||
// Run the credit refill code.
|
// Run the credit refill code.
|
||||||
Mockito.verify(pagingStore).checkMemory(argument.capture());
|
Mockito.verify(pagingStore).checkMemory(argument.capture());
|
||||||
|
@ -169,7 +170,7 @@ public class AMQPSessionCallbackTest {
|
||||||
// Credit is at threshold
|
// Credit is at threshold
|
||||||
Mockito.when(receiver.getCredit()).thenReturn(AMQP_LOW_CREDITS_DEFAULT);
|
Mockito.when(receiver.getCredit()).thenReturn(AMQP_LOW_CREDITS_DEFAULT);
|
||||||
|
|
||||||
session.offerProducerCredit(new SimpleString("test"), AMQP_CREDITS_DEFAULT, AMQP_LOW_CREDITS_DEFAULT, receiver);
|
session.flow(new SimpleString("test"), ProtonServerReceiverContext.createCreditRunnable(AMQP_CREDITS_DEFAULT, AMQP_LOW_CREDITS_DEFAULT, receiver, connection));
|
||||||
|
|
||||||
// Run the credit refill code.
|
// Run the credit refill code.
|
||||||
Mockito.verify(pagingStore).checkMemory(argument.capture());
|
Mockito.verify(pagingStore).checkMemory(argument.capture());
|
||||||
|
@ -200,7 +201,7 @@ public class AMQPSessionCallbackTest {
|
||||||
// Credit is at threshold
|
// Credit is at threshold
|
||||||
Mockito.when(receiver.getCredit()).thenReturn(AMQP_LOW_CREDITS_DEFAULT);
|
Mockito.when(receiver.getCredit()).thenReturn(AMQP_LOW_CREDITS_DEFAULT);
|
||||||
|
|
||||||
session.offerProducerCredit(null, 1, AMQP_LOW_CREDITS_DEFAULT, receiver);
|
session.flow(null, ProtonServerReceiverContext.createCreditRunnable(1, AMQP_LOW_CREDITS_DEFAULT, receiver, connection));
|
||||||
|
|
||||||
// Run the credit refill code.
|
// Run the credit refill code.
|
||||||
Mockito.verify(pagingManager).checkMemory(argument.capture());
|
Mockito.verify(pagingManager).checkMemory(argument.capture());
|
||||||
|
@ -232,7 +233,7 @@ public class AMQPSessionCallbackTest {
|
||||||
// Credit is at threshold
|
// Credit is at threshold
|
||||||
Mockito.when(receiver.getCredit()).thenReturn(AMQP_LOW_CREDITS_DEFAULT);
|
Mockito.when(receiver.getCredit()).thenReturn(AMQP_LOW_CREDITS_DEFAULT);
|
||||||
|
|
||||||
session.offerProducerCredit(new SimpleString("test"), 1, AMQP_LOW_CREDITS_DEFAULT, receiver);
|
session.flow(new SimpleString("test"), ProtonServerReceiverContext.createCreditRunnable(1, AMQP_LOW_CREDITS_DEFAULT, receiver, connection));
|
||||||
|
|
||||||
// Run the credit refill code.
|
// Run the credit refill code.
|
||||||
Mockito.verify(pagingStore).checkMemory(argument.capture());
|
Mockito.verify(pagingStore).checkMemory(argument.capture());
|
||||||
|
|
Loading…
Reference in New Issue