Skip to content

Commit 7f7caef

Browse files
authored
Publishing plus immediate flush (#1211)
* Publishing plus immediate flush * no casting * aroc naming
1 parent 8806fbf commit 7f7caef

File tree

12 files changed

+109
-102
lines changed

12 files changed

+109
-102
lines changed

src/main/java/io/nats/client/Connection.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,8 +539,8 @@ enum Status {
539539
String createInbox();
540540

541541
/**
542-
* Flushes the underlying connection buffer the next chance it gets if the connection is valid.
543-
* @throws IOException not applicable even though it's part of the signature due to implementation change
542+
* Immediately flushes the underlying connection buffer if the connection is valid.
543+
* @throws IOException if the connection flush fails
544544
*/
545545
void flushBuffer() throws IOException;
546546

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

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ void poisonTheQueue() {
206206

207207
NatsMessage poll(Duration timeout) throws InterruptedException {
208208
NatsMessage msg = null;
209-
209+
210210
if (timeout == null || this.isDraining()) { // try immediately
211211
msg = this.queue.poll();
212212
} else {
@@ -248,7 +248,7 @@ NatsMessage pop(Duration timeout) throws InterruptedException {
248248

249249
return msg;
250250
}
251-
251+
252252
// Waits up to the timeout to try to accumulate multiple messages
253253
// Use the next field to read the entire set accumulated.
254254
// maxSize and maxMessages are both checked and if either is exceeded
@@ -259,8 +259,8 @@ NatsMessage pop(Duration timeout) throws InterruptedException {
259259
// Only works in single reader mode, because we want to maintain order.
260260
// accumulate reads off the concurrent queue one at a time, so if multiple
261261
// readers are present, you could get out of order message delivery.
262-
NatsMessage accumulate(long maxSize, long maxMessages, Duration timeout)
263-
throws InterruptedException {
262+
NatsMessage accumulate(long maxBytesToAccumulate, long maxMessagesToAccumulate, Duration timeout)
263+
throws InterruptedException {
264264

265265
if (!this.singleReaderMode) {
266266
throw new IllegalStateException("Accumulate is only supported in single reader mode.");
@@ -278,7 +278,7 @@ NatsMessage accumulate(long maxSize, long maxMessages, Duration timeout)
278278

279279
long size = msg.getSizeInBytes();
280280

281-
if (maxMessages <= 1 || size >= maxSize) {
281+
if (maxMessagesToAccumulate <= 1 || size >= maxBytesToAccumulate) {
282282
this.sizeInBytes.addAndGet(-size);
283283
this.length.decrementAndGet();
284284
return msg;
@@ -287,21 +287,24 @@ NatsMessage accumulate(long maxSize, long maxMessages, Duration timeout)
287287
long count = 1;
288288
NatsMessage cursor = msg;
289289

290-
while (cursor != null) {
290+
while (true) {
291291
NatsMessage next = this.queue.peek();
292292
if (next != null && !isPoison(next)) {
293293
long s = next.getSizeInBytes();
294-
295-
if (maxSize<0 || (size + s) < maxSize) { // keep going
294+
if (maxBytesToAccumulate < 0 || (size + s) < maxBytesToAccumulate) { // keep going
296295
size += s;
297296
count++;
298-
299-
cursor.next = this.queue.poll();
300-
cursor = cursor.next;
301297

302-
if (count == maxMessages) {
298+
this.queue.poll(); // we need to get the message out of the queue b/c we only peeked
299+
cursor.next = next;
300+
if (next.flushImmediatelyAfterPublish) {
301+
// if we are going to flush, then don't accumulate more
303302
break;
304303
}
304+
if (count == maxMessagesToAccumulate) {
305+
break;
306+
}
307+
cursor = cursor.next;
305308
} else { // One more is too far
306309
break;
307310
}
@@ -348,7 +351,7 @@ void filter(Predicate<NatsMessage> p) {
348351
cursor = this.queue.poll();
349352
}
350353
this.queue.addAll(newQueue);
351-
} finally {
354+
} finally {
352355
editLock.unlock();
353356
}
354357
}

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

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -935,31 +935,31 @@ void cleanUpPongQueue() {
935935
*/
936936
@Override
937937
public void publish(String subject, byte[] body) {
938-
publishInternal(subject, null, null, body, true);
938+
publishInternal(subject, null, null, body, true, false);
939939
}
940940

941941
/**
942942
* {@inheritDoc}
943943
*/
944944
@Override
945945
public void publish(String subject, Headers headers, byte[] body) {
946-
publishInternal(subject, null, headers, body, true);
946+
publishInternal(subject, null, headers, body, true, false);
947947
}
948948

949949
/**
950950
* {@inheritDoc}
951951
*/
952952
@Override
953953
public void publish(String subject, String replyTo, byte[] body) {
954-
publishInternal(subject, replyTo, null, body, true);
954+
publishInternal(subject, replyTo, null, body, true, false);
955955
}
956956

957957
/**
958958
* {@inheritDoc}
959959
*/
960960
@Override
961961
public void publish(String subject, String replyTo, Headers headers, byte[] body) {
962-
publishInternal(subject, replyTo, headers, body, true);
962+
publishInternal(subject, replyTo, headers, body, true, false);
963963
}
964964

965965
/**
@@ -968,12 +968,12 @@ public void publish(String subject, String replyTo, Headers headers, byte[] body
968968
@Override
969969
public void publish(Message message) {
970970
validateNotNull(message, "Message");
971-
publishInternal(message.getSubject(), message.getReplyTo(), message.getHeaders(), message.getData(), false);
971+
publishInternal(message.getSubject(), message.getReplyTo(), message.getHeaders(), message.getData(), false, false);
972972
}
973973

974-
void publishInternal(String subject, String replyTo, Headers headers, byte[] data, boolean validateSubjectAndReplyTo) {
974+
void publishInternal(String subject, String replyTo, Headers headers, byte[] data, boolean validateSubjectAndReplyTo, boolean flushImmediatelyAfterPublish) {
975975
checkPayloadSize(data);
976-
NatsPublishableMessage npm = new NatsPublishableMessage(subject, replyTo, headers, data, validateSubjectAndReplyTo);
976+
NatsPublishableMessage npm = new NatsPublishableMessage(subject, replyTo, headers, data, validateSubjectAndReplyTo, flushImmediatelyAfterPublish);
977977
if (npm.hasHeaders && !serverInfo.get().isHeadersSupported()) {
978978
throw new IllegalArgumentException("Headers are not supported by the server, version: " + serverInfo.get().getVersion());
979979
}
@@ -1108,7 +1108,7 @@ void sendSubscriptionMessage(String sid, String subject, String queueName, boole
11081108
}
11091109
bab.append(SP).append(sid);
11101110

1111-
NatsMessage subMsg = new ProtocolMessage(bab);
1111+
ProtocolMessage subMsg = new ProtocolMessage(bab);
11121112

11131113
if (treatAsInternal) {
11141114
queueInternalOutgoing(subMsg);
@@ -1323,7 +1323,7 @@ CompletableFuture<Message> requestFutureInternal(String subject, Headers headers
13231323
responsesAwaiting.put(sub.getSID(), future);
13241324
}
13251325

1326-
publishInternal(subject, responseInbox, headers, data, validateSubjectAndReplyTo);
1326+
publishInternal(subject, responseInbox, headers, data, validateSubjectAndReplyTo, true);
13271327
statistics.incrementRequestsSent();
13281328

13291329
return future;
@@ -2256,15 +2256,6 @@ public void flushBuffer() throws IOException {
22562256
writer.flushBuffer();
22572257
}
22582258

2259-
void lenientFlushBuffer() {
2260-
try {
2261-
writer.flushBuffer();
2262-
}
2263-
catch (Exception e) {
2264-
// ignore
2265-
}
2266-
}
2267-
22682259
/**
22692260
* {@inheritDoc}
22702261
*/

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

Lines changed: 70 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class NatsConnectionWriter implements Runnable {
3737

3838
private final NatsConnection connection;
3939

40+
private final ReentrantLock writerLock;
4041
private Future<Boolean> stopped;
4142
private Future<DataPort> dataPortFuture;
4243
private DataPort dataPort;
@@ -50,10 +51,10 @@ class NatsConnectionWriter implements Runnable {
5051
private final MessageQueue outgoing;
5152
private final MessageQueue reconnectOutgoing;
5253
private final long reconnectBufferSize;
53-
private final AtomicBoolean flushBuffer;
5454

5555
NatsConnectionWriter(NatsConnection connection, NatsConnectionWriter sourceWriter) {
5656
this.connection = connection;
57+
writerLock = new ReentrantLock();
5758

5859
this.running = new AtomicBoolean(false);
5960
this.reconnectMode = new AtomicBoolean(sourceWriter != null);
@@ -76,8 +77,6 @@ class NatsConnectionWriter implements Runnable {
7677
reconnectOutgoing = new MessageQueue(true, options.getRequestCleanupInterval(),
7778
sourceWriter == null ? null : sourceWriter.reconnectOutgoing);
7879
reconnectBufferSize = options.getReconnectBufferSize();
79-
80-
flushBuffer = new AtomicBoolean(false);
8180
}
8281

8382
// Should only be called if the current thread has exited.
@@ -123,86 +122,88 @@ boolean isRunning() {
123122
}
124123

125124
void sendMessageBatch(NatsMessage msg, DataPort dataPort, StatisticsCollector stats) throws IOException {
126-
int sendPosition = 0;
127-
int sbl = sendBufferLength.get();
128-
129-
while (msg != null) {
130-
long size = msg.getSizeInBytes();
131-
132-
if (sendPosition + size > sbl) {
133-
if (sendPosition > 0) {
134-
dataPort.write(sendBuffer, sendPosition);
135-
connection.getNatsStatistics().registerWrite(sendPosition);
136-
sendPosition = 0;
137-
}
138-
if (size > sbl) { // have to resize b/c can't fit 1 message
139-
sbl = bufferAllocSize((int) size, BUFFER_BLOCK_SIZE);
140-
sendBufferLength.set(sbl);
141-
sendBuffer = new byte[sbl];
125+
writerLock.lock();
126+
try {
127+
int sendPosition = 0;
128+
int sbl = sendBufferLength.get();
129+
130+
while (msg != null) {
131+
long size = msg.getSizeInBytes();
132+
133+
if (sendPosition + size > sbl) {
134+
if (sendPosition > 0) {
135+
dataPort.write(sendBuffer, sendPosition);
136+
connection.getNatsStatistics().registerWrite(sendPosition);
137+
sendPosition = 0;
138+
}
139+
if (size > sbl) { // have to resize b/c can't fit 1 message
140+
sbl = bufferAllocSize((int) size, BUFFER_BLOCK_SIZE);
141+
sendBufferLength.set(sbl);
142+
sendBuffer = new byte[sbl];
143+
}
142144
}
143-
}
144145

145-
ByteArrayBuilder bab = msg.getProtocolBab();
146-
int babLen = bab.length();
147-
System.arraycopy(bab.internalArray(), 0, sendBuffer, sendPosition, babLen);
148-
sendPosition += babLen;
146+
ByteArrayBuilder bab = msg.getProtocolBab();
147+
int babLen = bab.length();
148+
System.arraycopy(bab.internalArray(), 0, sendBuffer, sendPosition, babLen);
149+
sendPosition += babLen;
150+
151+
sendBuffer[sendPosition++] = CR;
152+
sendBuffer[sendPosition++] = LF;
149153

150-
sendBuffer[sendPosition++] = CR;
151-
sendBuffer[sendPosition++] = LF;
154+
if (!msg.isProtocol()) {
155+
sendPosition += msg.copyNotEmptyHeaders(sendPosition, sendBuffer);
152156

153-
if (!msg.isProtocol()) {
154-
sendPosition += msg.copyNotEmptyHeaders(sendPosition, sendBuffer);
157+
byte[] bytes = msg.getData(); // guaranteed to not be null
158+
if (bytes.length > 0) {
159+
System.arraycopy(bytes, 0, sendBuffer, sendPosition, bytes.length);
160+
sendPosition += bytes.length;
161+
}
155162

156-
byte[] bytes = msg.getData(); // guaranteed to not be null
157-
if (bytes.length > 0) {
158-
System.arraycopy(bytes, 0, sendBuffer, sendPosition, bytes.length);
159-
sendPosition += bytes.length;
163+
sendBuffer[sendPosition++] = CR;
164+
sendBuffer[sendPosition++] = LF;
160165
}
161166

162-
sendBuffer[sendPosition++] = CR;
163-
sendBuffer[sendPosition++] = LF;
164-
}
167+
stats.incrementOutMsgs();
168+
stats.incrementOutBytes(size);
165169

166-
stats.incrementOutMsgs();
167-
stats.incrementOutBytes(size);
170+
if (msg.flushImmediatelyAfterPublish) {
171+
dataPort.flush();
172+
}
173+
msg = msg.next;
174+
}
168175

169-
msg = msg.next;
176+
// no need to write if there are no bytes
177+
if (sendPosition > 0) {
178+
dataPort.write(sendBuffer, sendPosition);
179+
connection.getNatsStatistics().registerWrite(sendPosition);
180+
}
170181
}
171-
172-
// no need to write if there are no bytes
173-
if (sendPosition > 0) {
174-
dataPort.write(sendBuffer, sendPosition);
182+
finally {
183+
writerLock.unlock();
175184
}
176-
177-
connection.getNatsStatistics().registerWrite(sendPosition);
178185
}
179186

180187
@Override
181188
public void run() {
182-
Duration waitForMessage = Duration.ofMinutes(2); // This can be long since no one is sending
183-
Duration reconnectWait = Duration.ofMillis(1); // This should be short, since we are trying to get the reconnect through
189+
Duration outgoingTimeout = Duration.ofMinutes(2); // This can be long since no one is sending
190+
Duration reconnectTimeout = Duration.ofMillis(1); // This should be short, since we are trying to get the reconnect through
184191

185192
try {
186193
dataPort = this.dataPortFuture.get(); // Will wait for the future to complete
187194
StatisticsCollector stats = this.connection.getNatsStatistics();
188-
int maxAccumulate = Options.MAX_MESSAGES_IN_NETWORK_BUFFER;
189195

190196
while (this.running.get()) {
191-
NatsMessage msg = null;
192-
197+
NatsMessage msg;
193198
if (this.reconnectMode.get()) {
194-
msg = this.reconnectOutgoing.accumulate(sendBufferLength.get(), maxAccumulate, reconnectWait);
195-
} else {
196-
msg = this.outgoing.accumulate(sendBufferLength.get(), maxAccumulate, waitForMessage);
199+
msg = this.reconnectOutgoing.accumulate(sendBufferLength.get(), Options.MAX_MESSAGES_IN_NETWORK_BUFFER, reconnectTimeout);
200+
}
201+
else {
202+
msg = this.outgoing.accumulate(sendBufferLength.get(), Options.MAX_MESSAGES_IN_NETWORK_BUFFER, outgoingTimeout);
197203
}
198-
199204
if (msg != null) {
200205
sendMessageBatch(msg, dataPort, stats);
201206
}
202-
203-
if (flushBuffer.getAndSet(false)) {
204-
dataPort.flush();
205-
}
206207
}
207208
} catch (IOException | BufferOverflowException io) {
208209
// if already not running, an IOE is not unreasonable in a transition state
@@ -241,8 +242,18 @@ void queueInternalMessage(NatsMessage msg) {
241242
}
242243

243244
void flushBuffer() {
244-
if (running.get()) {
245-
flushBuffer.set(true);
245+
// Since there is no connection level locking, we rely on synchronization
246+
// of the APIs here.
247+
writerLock.lock();
248+
try {
249+
if (this.running.get()) {
250+
dataPort.flush();
251+
}
252+
} catch (Exception e) {
253+
// NOOP;
254+
}
255+
finally {
256+
writerLock.unlock();
246257
}
247258
}
248259
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ private PublishAck publishSyncInternal(String subject, Headers headers, byte[] d
146146
Headers merged = mergePublishOptions(headers, options);
147147

148148
if (jso.isPublishNoAck()) {
149-
conn.publishInternal(subject, null, merged, data, validateSubjectAndReplyTo);
149+
conn.publishInternal(subject, null, merged, data, validateSubjectAndReplyTo, false);
150150
return null;
151151
}
152152

@@ -160,7 +160,7 @@ private CompletableFuture<PublishAck> publishAsyncInternal(String subject, Heade
160160
Headers merged = mergePublishOptions(headers, options);
161161

162162
if (jso.isPublishNoAck()) {
163-
conn.publishInternal(subject, null, merged, data, validateSubjectAndReplyTo);
163+
conn.publishInternal(subject, null, merged, data, validateSubjectAndReplyTo, false);
164164
return null;
165165
}
166166

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ protected String _pull(PullRequestOptions pullRequestOptions, boolean raiseStatu
6262
String publishSubject = js.prependPrefix(String.format(JSAPI_CONSUMER_MSG_NEXT, stream, consumerName));
6363
String pullSubject = getSubject().replace("*", Long.toString(this.pullSubjectIdHolder.incrementAndGet()));
6464
manager.startPullRequest(pullSubject, pullRequestOptions, raiseStatusWarnings, pullManagerObserver);
65-
connection.publish(publishSubject, pullSubject, pullRequestOptions.serialize());
66-
connection.lenientFlushBuffer();
65+
connection.publishInternal(publishSubject, pullSubject, null, pullRequestOptions.serialize(), true, true);
6766
return pullSubject;
6867
}
6968

0 commit comments

Comments
 (0)