Skip to content

Commit 39776f0

Browse files
committed
Force replica WIP
Conflicts: src/test/java/com/rabbitmq/stream/impl/ConsumersCoordinatorTest.java
1 parent 1c6c819 commit 39776f0

File tree

5 files changed

+166
-32
lines changed

5 files changed

+166
-32
lines changed

src/main/java/com/rabbitmq/stream/EnvironmentBuilder.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,8 @@ public interface EnvironmentBuilder {
6969
* <p><i>The default implementation is overridden automatically if the following conditions are
7070
* met: the host to connect to is <code>localhost</code>, the user is <code>guest</code>, and no
7171
* address resolver has been provided. The client will then always tries to connect to <code>
72-
* localhost</code> to facilitate local development.
73-
* Just provide a pass-through address resolver to avoid this
74-
* behavior, e.g.:</i>
72+
* localhost</code> to facilitate local development. Just provide a pass-through address resolver
73+
* to avoid this behavior, e.g.:</i>
7574
*
7675
* <pre>
7776
* Environment.builder()

src/main/java/com/rabbitmq/stream/impl/ConsumersCoordinator.java

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static com.rabbitmq.stream.impl.Utils.namedFunction;
2121
import static com.rabbitmq.stream.impl.Utils.namedRunnable;
2222
import static com.rabbitmq.stream.impl.Utils.quote;
23+
import static java.lang.String.format;
2324

2425
import com.rabbitmq.stream.*;
2526
import com.rabbitmq.stream.Consumer;
@@ -41,10 +42,12 @@
4142
import java.util.Objects;
4243
import java.util.Random;
4344
import java.util.Set;
45+
import java.util.concurrent.Callable;
4446
import java.util.concurrent.ConcurrentHashMap;
4547
import java.util.concurrent.ConcurrentSkipListSet;
4648
import java.util.concurrent.CopyOnWriteArrayList;
4749
import java.util.concurrent.atomic.AtomicBoolean;
50+
import java.util.concurrent.atomic.AtomicInteger;
4851
import java.util.concurrent.atomic.AtomicLong;
4952
import java.util.concurrent.atomic.AtomicReference;
5053
import java.util.function.*;
@@ -56,6 +59,7 @@
5659
class ConsumersCoordinator {
5760

5861
static final int MAX_SUBSCRIPTIONS_PER_CLIENT = 256;
62+
static final int MAX_ATTEMPT_BEFORE_FALLING_BACK_TO_LEADER = 5;
5963

6064
static final OffsetSpecification DEFAULT_OFFSET_SPECIFICATION = OffsetSpecification.next();
6165

@@ -74,16 +78,19 @@ class ConsumersCoordinator {
7478
private final ExecutorServiceFactory executorServiceFactory =
7579
new DefaultExecutorServiceFactory(
7680
Runtime.getRuntime().availableProcessors(), 10, "rabbitmq-stream-consumer-connection-");
81+
private final boolean forceReplica;
7782

7883
ConsumersCoordinator(
7984
StreamEnvironment environment,
8085
int maxConsumersByConnection,
8186
Function<ClientConnectionType, String> connectionNamingStrategy,
82-
ClientFactory clientFactory) {
87+
ClientFactory clientFactory,
88+
boolean forceReplica) {
8389
this.environment = environment;
8490
this.clientFactory = clientFactory;
8591
this.maxConsumersByConnection = maxConsumersByConnection;
8692
this.connectionNamingStrategy = connectionNamingStrategy;
93+
this.forceReplica = forceReplica;
8794
}
8895

8996
private static String keyForClientSubscription(Client.Broker broker) {
@@ -108,7 +115,7 @@ Runnable subscribe(
108115
MessageHandler messageHandler,
109116
Map<String, String> subscriptionProperties,
110117
ConsumerFlowStrategy flowStrategy) {
111-
List<Client.Broker> candidates = findBrokersForStream(stream);
118+
List<Client.Broker> candidates = findBrokersForStream(stream, forceReplica);
112119
Client.Broker newNode = pickBroker(candidates);
113120
if (newNode == null) {
114121
throw new IllegalStateException("No available node to subscribe to");
@@ -201,11 +208,8 @@ private void addToManager(
201208
// manager connection is dead or stream not available
202209
// scheduling manager closing if necessary in another thread to avoid blocking this one
203210
if (pickedManager.isEmpty()) {
204-
ClientSubscriptionsManager manager = pickedManager;
205211
ConsumersCoordinator.this.environment.execute(
206-
() -> {
207-
manager.closeIfEmpty();
208-
},
212+
pickedManager::closeIfEmpty,
209213
"Consumer manager closing after timeout, consumer %d on stream '%s'",
210214
tracker.consumer.id(),
211215
tracker.stream);
@@ -225,12 +229,14 @@ int managerCount() {
225229
}
226230

227231
// package protected for testing
228-
List<Client.Broker> findBrokersForStream(String stream) {
232+
List<Client.Broker> findBrokersForStream(String stream, boolean forceReplica) {
233+
LOGGER.debug(
234+
"Candidate lookup to consumer from '{}', forcing replica? {}", stream, forceReplica);
229235
Map<String, Client.StreamMetadata> metadata =
230236
this.environment.locatorOperation(
231237
namedFunction(
232238
c -> c.metadata(stream), "Candidate lookup to consume from '%s'", stream));
233-
if (metadata.size() == 0 || metadata.get(stream) == null) {
239+
if (metadata.isEmpty() || metadata.get(stream) == null) {
234240
// this is not supposed to happen
235241
throw new StreamDoesNotExistException(stream);
236242
}
@@ -253,8 +259,17 @@ List<Client.Broker> findBrokersForStream(String stream) {
253259

254260
List<Client.Broker> brokers;
255261
if (replicas == null || replicas.isEmpty()) {
256-
brokers = Collections.singletonList(streamMetadata.getLeader());
257-
LOGGER.debug("Only leader node {} for consuming from {}", streamMetadata.getLeader(), stream);
262+
if (forceReplica) {
263+
throw new IllegalStateException(
264+
format(
265+
"Only the leader node is available for consuming from %s and "
266+
+ "consuming from leader has been deactivated for this consumer",
267+
stream));
268+
} else {
269+
brokers = Collections.singletonList(streamMetadata.getLeader());
270+
LOGGER.debug(
271+
"Only leader node {} for consuming from {}", streamMetadata.getLeader(), stream);
272+
}
258273
} else {
259274
LOGGER.debug("Replicas for consuming from {}: {}", stream, replicas);
260275
brokers = new ArrayList<>(replicas);
@@ -265,6 +280,20 @@ List<Client.Broker> findBrokersForStream(String stream) {
265280
return brokers;
266281
}
267282

283+
private Callable<List<Broker>> findBrokersForStream(String stream) {
284+
AtomicInteger attemptNumber = new AtomicInteger();
285+
return () -> {
286+
boolean mustUseReplica;
287+
if (forceReplica) {
288+
mustUseReplica =
289+
attemptNumber.incrementAndGet() <= MAX_ATTEMPT_BEFORE_FALLING_BACK_TO_LEADER;
290+
} else {
291+
mustUseReplica = false;
292+
}
293+
return findBrokersForStream(stream, mustUseReplica);
294+
};
295+
}
296+
268297
private Client.Broker pickBroker(List<Client.Broker> brokers) {
269298
if (brokers.isEmpty()) {
270299
return null;
@@ -792,7 +821,7 @@ private void assignConsumersToStream(
792821
}
793822
};
794823

795-
AsyncRetry.asyncRetry(() -> findBrokersForStream(stream))
824+
AsyncRetry.asyncRetry(findBrokersForStream(stream))
796825
.description("Candidate lookup to consume from '%s'", stream)
797826
.scheduler(environment.scheduledExecutorService())
798827
.retry(ex -> !(ex instanceof StreamDoesNotExistException))
@@ -885,9 +914,9 @@ private void recoverSubscription(List<Broker> candidates, SubscriptionTracker tr
885914
// maybe not a good candidate, let's refresh and retry for this one
886915
candidates =
887916
Utils.callAndMaybeRetry(
888-
() -> findBrokersForStream(tracker.stream),
917+
findBrokersForStream(tracker.stream),
889918
ex -> !(ex instanceof StreamDoesNotExistException),
890-
environment.recoveryBackOffDelayPolicy(),
919+
recoveryBackOffDelayPolicy(),
891920
"Candidate lookup to consume from '%s' (subscription recovery)",
892921
tracker.stream);
893922
} catch (Exception e) {

src/main/java/com/rabbitmq/stream/impl/StreamEnvironment.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ class StreamEnvironment implements Environment {
208208
this,
209209
maxConsumersByConnection,
210210
connectionNamingStrategy,
211-
Utils.coordinatorClientFactory(this));
211+
Utils.coordinatorClientFactory(this),
212+
false);
212213
this.offsetTrackingCoordinator = new OffsetTrackingCoordinator(this);
213214
ClientParameters clientParametersForInit = locatorParametersCopy();
214215
Runnable locatorInitSequence =

src/main/java/com/rabbitmq/stream/impl/Utils.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ static <T, R> Function<T, R> namedFunction(Function<T, R> task, String format, O
207207
}
208208

209209
static <T> T callAndMaybeRetry(
210-
Supplier<T> operation, Predicate<Exception> retryCondition, String format, Object... args) {
210+
Callable<T> operation, Predicate<Exception> retryCondition, String format, Object... args) {
211211
return callAndMaybeRetry(
212212
operation,
213213
retryCondition,
@@ -217,7 +217,7 @@ static <T> T callAndMaybeRetry(
217217
}
218218

219219
static <T> T callAndMaybeRetry(
220-
Supplier<T> operation,
220+
Callable<T> operation,
221221
Predicate<Exception> retryCondition,
222222
BackOffDelayPolicy delayPolicy,
223223
String format,
@@ -230,7 +230,7 @@ static <T> T callAndMaybeRetry(
230230
while (keepTrying) {
231231
try {
232232
attempt++;
233-
T result = operation.get();
233+
T result = operation.call();
234234
Duration operationDuration = Duration.ofNanos(System.nanoTime() - startTime);
235235
LOGGER.debug(
236236
"Operation '{}' completed in {} ms after {} attempt(s)",

0 commit comments

Comments
 (0)