Skip to content

Commit b452b42

Browse files
authored
handle better cases where a pull status message comes from a previous pull (#899)
1 parent 56fbf47 commit b452b42

File tree

4 files changed

+63
-43
lines changed

4 files changed

+63
-43
lines changed

src/examples/java/io/nats/examples/jetstream/NatsJsUtils.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,8 @@ public static long extractId(Message m) {
268268
return extractId(m.getData());
269269
}
270270

271-
public static void publishInBackground(JetStream js, String subject, String prefix, int count) {
272-
new Thread(() -> {
271+
public static Thread publishInBackground(JetStream js, String subject, String prefix, int count) {
272+
Thread t = new Thread(() -> {
273273
try {
274274
for (int x = 1; x <= count; x++) {
275275
String data = prefix + "-" + x;
@@ -283,7 +283,9 @@ public static void publishInBackground(JetStream js, String subject, String pref
283283
e.printStackTrace();
284284
System.exit(-1);
285285
}
286-
}).start();
286+
});
287+
t.start();
288+
return t;
287289
}
288290

289291
// ----------------------------------------------------------------------------------------------------

src/main/java/io/nats/client/impl/NatsJetStream.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,16 @@ else if (so.isBind()) {
370370
}
371371

372372
// 4. If no deliver subject (inbox) provided or found, make an inbox.
373-
final String fnlInboxDeliver = inboxDeliver == null ? conn.createInbox() : inboxDeliver;
373+
final String fnlInboxDeliver;
374+
if (isPullMode) {
375+
fnlInboxDeliver = conn.createInbox() + ".*";
376+
}
377+
else if (inboxDeliver == null) {
378+
fnlInboxDeliver = conn.createInbox();
379+
}
380+
else {
381+
fnlInboxDeliver = inboxDeliver;
382+
}
374383

375384
// 5. If consumer does not exist, create and settle on the config. Name will have to wait
376385
// If the consumer exists, I know what the settled info is

src/main/java/io/nats/client/impl/NatsJetStreamPullSubscription.java

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
import java.util.ArrayList;
2222
import java.util.Iterator;
2323
import java.util.List;
24+
import java.util.concurrent.atomic.AtomicLong;
2425

2526
public class NatsJetStreamPullSubscription extends NatsJetStreamSubscription {
2627

28+
AtomicLong pullId = new AtomicLong();
29+
2730
NatsJetStreamPullSubscription(String sid, String subject,
2831
NatsConnection connection, NatsDispatcher dispatcher,
2932
NatsJetStream js,
@@ -42,26 +45,32 @@ boolean isPullMode() {
4245
*/
4346
@Override
4447
public void pull(int batchSize) {
45-
pull(PullRequestOptions.builder(batchSize).build());
48+
_pull(PullRequestOptions.builder(batchSize).build());
4649
}
4750

4851
/**
4952
* {@inheritDoc}
5053
*/
5154
@Override
5255
public void pull(PullRequestOptions pullRequestOptions) {
56+
_pull(pullRequestOptions);
57+
}
58+
59+
private String _pull(PullRequestOptions pullRequestOptions) {
5360
String publishSubject = js.prependPrefix(String.format(JSAPI_CONSUMER_MSG_NEXT, stream, consumerName));
5461
manager.startPullRequest(pullRequestOptions);
55-
connection.publish(publishSubject, getSubject(), pullRequestOptions.serialize());
62+
String pullId = getSubject().replace("*", Long.toString(this.pullId.incrementAndGet()));
63+
connection.publish(publishSubject, pullId, pullRequestOptions.serialize());
5664
connection.lenientFlushBuffer();
65+
return pullId;
5766
}
5867

5968
/**
6069
* {@inheritDoc}
6170
*/
6271
@Override
6372
public void pullNoWait(int batchSize) {
64-
pull(PullRequestOptions.noWait(batchSize).build());
73+
_pull(PullRequestOptions.noWait(batchSize).build());
6574
}
6675

6776
/**
@@ -70,7 +79,7 @@ public void pullNoWait(int batchSize) {
7079
@Override
7180
public void pullNoWait(int batchSize, Duration expiresIn) {
7281
durationGtZeroRequired(expiresIn, "NoWait Expires In");
73-
pull(PullRequestOptions.noWait(batchSize).expiresIn(expiresIn).build());
82+
_pull(PullRequestOptions.noWait(batchSize).expiresIn(expiresIn).build());
7483
}
7584

7685
/**
@@ -79,7 +88,7 @@ public void pullNoWait(int batchSize, Duration expiresIn) {
7988
@Override
8089
public void pullNoWait(int batchSize, long expiresInMillis) {
8190
durationGtZeroRequired(expiresInMillis, "NoWait Expires In");
82-
pull(PullRequestOptions.noWait(batchSize).expiresIn(expiresInMillis).build());
91+
_pull(PullRequestOptions.noWait(batchSize).expiresIn(expiresInMillis).build());
8392
}
8493

8594
/**
@@ -88,7 +97,7 @@ public void pullNoWait(int batchSize, long expiresInMillis) {
8897
@Override
8998
public void pullExpiresIn(int batchSize, Duration expiresIn) {
9099
durationGtZeroRequired(expiresIn, "Expires In");
91-
pull(PullRequestOptions.builder(batchSize).expiresIn(expiresIn).build());
100+
_pull(PullRequestOptions.builder(batchSize).expiresIn(expiresIn).build());
92101
}
93102

94103
/**
@@ -97,7 +106,7 @@ public void pullExpiresIn(int batchSize, Duration expiresIn) {
97106
@Override
98107
public void pullExpiresIn(int batchSize, long expiresInMillis) {
99108
durationGtZeroRequired(expiresInMillis, "Expires In");
100-
pull(PullRequestOptions.builder(batchSize).expiresIn(expiresInMillis).build());
109+
_pull(PullRequestOptions.builder(batchSize).expiresIn(expiresInMillis).build());
101110
}
102111

103112
/**
@@ -133,7 +142,7 @@ private List<Message> _fetch(int batchSize, long maxWaitMillis) {
133142
maxWaitMillis > MIN_MILLIS
134143
? maxWaitMillis - EXPIRE_LESS_MILLIS
135144
: maxWaitMillis);
136-
pull(PullRequestOptions.builder(batchLeft).expiresIn(expires).build());
145+
String pullId = _pull(PullRequestOptions.builder(batchLeft).expiresIn(expires).build());
137146

138147
// timeout > 0 process as many messages we can in that time period
139148
// If we get a message that either manager handles, we try again, but
@@ -152,9 +161,13 @@ private List<Message> _fetch(int batchSize, long maxWaitMillis) {
152161
break;
153162
case TERMINUS:
154163
case ERROR:
155-
return messages;
164+
// reply match will be null on pushes, all status are "managed" anyway, so ignored in this loop
165+
// otherwise (pull) if there is a match, the status applies
166+
if (pullId.equals(msg.getSubject())) {
167+
return messages;
168+
}
156169
}
157-
// case STATUS, try again while we have time
170+
// case pull not match / other ManageResult (i.e. STATUS), try again while we have time
158171
timeLeftNanos = maxWaitNanos - (System.nanoTime() - start);
159172
}
160173
}
@@ -172,18 +185,13 @@ private List<Message> drainAlreadyBuffered(int batchSize) {
172185
if (msg == null) {
173186
return messages; // no more message currently queued
174187
}
175-
switch (manager.manage(msg)) {
176-
case MESSAGE:
177-
messages.add(msg);
178-
if (messages.size() == batchSize) {
179-
return messages;
180-
}
181-
break;
182-
case TERMINUS:
183-
case ERROR:
188+
if (manager.manage(msg) == MessageManager.ManageResult.MESSAGE) {
189+
messages.add(msg);
190+
if (messages.size() == batchSize) {
184191
return messages;
192+
}
185193
}
186-
// case STATUS, we need to try again
194+
// since this is buffered, no non-message applies, try again
187195
}
188196
}
189197
catch (InterruptedException ignore) {
@@ -242,7 +250,7 @@ public Message next() {
242250
}
243251

244252
// if there were some messages buffered, reduce the raw pull batch size
245-
pull(PullRequestOptions.builder(batchLeft).expiresIn(maxWaitMillis).build());
253+
String pullId = _pull(PullRequestOptions.builder(batchLeft).expiresIn(maxWaitMillis).build());
246254

247255
final long timeout = maxWaitMillis;
248256

@@ -264,7 +272,7 @@ public boolean hasNext() {
264272
}
265273

266274
if (buffered.size() == 0) {
267-
msg = _nextUnmanaged(timeout);
275+
msg = _nextUnmanaged(timeout, pullId);
268276
if (msg == null) {
269277
done = true;
270278
return false;

src/main/java/io/nats/client/impl/NatsJetStreamSubscription.java

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import io.nats.client.*;
1717
import io.nats.client.api.ConsumerInfo;
18+
import io.nats.client.impl.MessageManager.ManageResult;
1819
import io.nats.client.support.NatsJetStreamConstants;
1920

2021
import java.io.IOException;
@@ -83,7 +84,7 @@ public Message nextMessage(Duration timeout) throws InterruptedException, Illega
8384
return _nextUnmanagedNullOrLteZero(timeout);
8485
}
8586

86-
return _nextUnmanaged(timeout.toMillis());
87+
return _nextUnmanaged(timeout.toMillis(), null);
8788
}
8889

8990
@Override
@@ -92,7 +93,7 @@ public Message nextMessage(long timeoutMillis) throws InterruptedException, Ille
9293
return _nextUnmanagedNullOrLteZero(Duration.ZERO);
9394
}
9495

95-
return _nextUnmanaged(timeoutMillis);
96+
return _nextUnmanaged(timeoutMillis, null);
9697
}
9798

9899
protected Message _nextUnmanagedNullOrLteZero(Duration timeout) throws InterruptedException {
@@ -104,29 +105,25 @@ protected Message _nextUnmanagedNullOrLteZero(Duration timeout) throws Interrupt
104105
if (msg == null) {
105106
return null; // no message currently queued
106107
}
107-
switch (manager.manage(msg)) {
108-
case MESSAGE:
108+
if (manager.manage(msg) == ManageResult.MESSAGE) {
109109
return msg;
110-
case TERMINUS:
111-
case ERROR:
112-
return null;
113110
}
114-
// case STATUS, we need to try again
111+
// since this is strictly called from user calls of nextMessage, non-messages are considered managed
115112
}
116113
}
117114

118115
protected static final long MIN_MILLIS = 20;
119116
protected static final long EXPIRE_LESS_MILLIS = 10;
120117

121-
protected Message _nextUnmanaged(long timeout) throws InterruptedException {
122-
118+
protected Message _nextUnmanaged(long timeout, String replyMatch) throws InterruptedException {
123119
// timeout > 0 process as many messages we can in that time period
124120
// If we get a message that either manager handles, we try again, but
125121
// with a shorter timeout based on what we already used up
126-
long elapsed = 0;
127-
long start = System.currentTimeMillis();
128-
while (elapsed < timeout) {
129-
Message msg = nextMessageInternal( Duration.ofMillis(Math.max(MIN_MILLIS, timeout - elapsed)) );
122+
long start = System.nanoTime();
123+
long timeoutNanos = timeout * 1_000_000;
124+
long timeLeftNanos = timeoutNanos;
125+
while (timeLeftNanos > 0) {
126+
Message msg = nextMessageInternal( Duration.ofNanos(timeLeftNanos) );
130127
if (msg == null) {
131128
return null; // normal timeout
132129
}
@@ -135,10 +132,14 @@ protected Message _nextUnmanaged(long timeout) throws InterruptedException {
135132
return msg;
136133
case TERMINUS:
137134
case ERROR:
138-
return null;
135+
// reply match will be null on pushes and all status are "managed" so ignored in this loop
136+
// otherwise (pull) if there is a match, the status applies
137+
if (replyMatch != null && replyMatch.equals(msg.getSubject())) {
138+
return null;
139+
}
139140
}
140-
// case STATUS, try again while we have time
141-
elapsed = System.currentTimeMillis() - start;
141+
// case push managed / pull not match / other ManageResult (i.e. STATUS), try again while we have time
142+
timeLeftNanos = timeoutNanos - (System.nanoTime() - start);
142143
}
143144
return null;
144145
}

0 commit comments

Comments
 (0)