Skip to content

Commit 02c3771

Browse files
fix: ordering keys publishing of last batch (#9)
* google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/FakePublisherServiceImpl.java * Ensure that if a batch is started and the timeout completes before the currently outstanding message has finished publishing with an ordering key that the last batch does in fact get published. * add back in unit test
1 parent ec9cbce commit 02c3771

File tree

4 files changed

+76
-2
lines changed

4 files changed

+76
-2
lines changed

google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java

+24
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,25 @@ private void publishAllWithoutInflight() {
371371
}
372372
}
373373

374+
/**
375+
* Publish any outstanding batches if non-empty and there are no other batches in flight for
376+
* orderingKey. This method sends buffered messages, but does not wait for the send operations to
377+
* complete. To wait for messages to send, call {@code get} on the futures returned from {@code
378+
* publish}.
379+
*/
380+
private void publishAllWithoutInflightForKey(final String orderingKey) {
381+
messagesBatchLock.lock();
382+
try {
383+
MessagesBatch batch = messagesBatches.get(orderingKey);
384+
if (batch != null && !sequentialExecutor.hasTasksInflight(orderingKey)) {
385+
publishOutstandingBatch(batch.popOutstandingBatch());
386+
messagesBatches.remove(orderingKey);
387+
}
388+
} finally {
389+
messagesBatchLock.unlock();
390+
}
391+
}
392+
374393
private ApiFuture<PublishResponse> publishCall(OutstandingBatch outstandingBatch) {
375394
return publisherStub
376395
.publishCallable()
@@ -397,6 +416,11 @@ public void onSuccess(PublishResponse result) {
397416
result.getMessageIdsCount(), outstandingBatch.size())));
398417
} else {
399418
outstandingBatch.onSuccess(result.getMessageIdsList());
419+
if (!activeAlarm.get()
420+
&& outstandingBatch.orderingKey != null
421+
&& !outstandingBatch.orderingKey.isEmpty()) {
422+
publishAllWithoutInflightForKey(outstandingBatch.orderingKey);
423+
}
400424
}
401425
} finally {
402426
messagesWaiter.incrementPendingMessages(-outstandingBatch.size());

google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/SequentialExecutorService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ public void run() {
216216
// Step 5.1: on success
217217
@Override
218218
public void onSuccess(T msg) {
219-
future.set(msg);
220219
callNextTaskAsync(key);
220+
future.set(msg);
221221
}
222222

223223
// Step 5.2: on failure

google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/FakePublisherServiceImpl.java

+36-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
import java.util.ArrayList;
2525
import java.util.List;
2626
import java.util.concurrent.LinkedBlockingQueue;
27+
import java.util.concurrent.ScheduledExecutorService;
28+
import java.util.concurrent.TimeUnit;
2729
import java.util.concurrent.atomic.AtomicInteger;
30+
import org.threeten.bp.Duration;
2831

2932
/**
3033
* A fake implementation of {@link PublisherImplBase}, that can be used to test clients of a Cloud
@@ -36,6 +39,8 @@ class FakePublisherServiceImpl extends PublisherImplBase {
3639
private final LinkedBlockingQueue<Response> publishResponses = new LinkedBlockingQueue<>();
3740
private final AtomicInteger nextMessageId = new AtomicInteger(1);
3841
private boolean autoPublishResponse;
42+
private ScheduledExecutorService executor = null;
43+
private Duration responseDelay = Duration.ZERO;
3944

4045
/** Class used to save the state of a possible response. */
4146
private static class Response {
@@ -74,7 +79,8 @@ public String toString() {
7479
}
7580

7681
@Override
77-
public void publish(PublishRequest request, StreamObserver<PublishResponse> responseObserver) {
82+
public void publish(
83+
PublishRequest request, final StreamObserver<PublishResponse> responseObserver) {
7884
requests.add(request);
7985
Response response;
8086
try {
@@ -90,6 +96,23 @@ public void publish(PublishRequest request, StreamObserver<PublishResponse> resp
9096
} catch (InterruptedException e) {
9197
throw new IllegalArgumentException(e);
9298
}
99+
if (responseDelay == Duration.ZERO) {
100+
sendResponse(response, responseObserver);
101+
} else {
102+
final Response responseToSend = response;
103+
executor.schedule(
104+
new Runnable() {
105+
@Override
106+
public void run() {
107+
sendResponse(responseToSend, responseObserver);
108+
}
109+
},
110+
responseDelay.toMillis(),
111+
TimeUnit.MILLISECONDS);
112+
}
113+
}
114+
115+
private void sendResponse(Response response, StreamObserver<PublishResponse> responseObserver) {
93116
if (response.isError()) {
94117
responseObserver.onError(response.getError());
95118
} else {
@@ -107,6 +130,18 @@ public FakePublisherServiceImpl setAutoPublishResponse(boolean autoPublishRespon
107130
return this;
108131
}
109132

133+
/** Set an executor to use to delay publish responses. */
134+
public FakePublisherServiceImpl setExecutor(ScheduledExecutorService executor) {
135+
this.executor = executor;
136+
return this;
137+
}
138+
139+
/** Set an amount of time by which to delay publish responses. */
140+
public FakePublisherServiceImpl setPublishResponseDelay(Duration responseDelay) {
141+
this.responseDelay = responseDelay;
142+
return this;
143+
}
144+
110145
public FakePublisherServiceImpl addPublishResponse(PublishResponse publishResponse) {
111146
publishResponses.add(new Response(publishResponse));
112147
return this;

google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java

+15
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ public void testBatchedMessagesWithOrderingKeyByDuration() throws Exception {
308308
.setEnableMessageOrdering(true)
309309
.build();
310310
testPublisherServiceImpl.setAutoPublishResponse(true);
311+
testPublisherServiceImpl.setExecutor(fakeExecutor);
312+
testPublisherServiceImpl.setPublishResponseDelay(Duration.ofSeconds(300));
311313

312314
// Publish two messages with ordering key, "OrderA", and other two messages with "OrderB".
313315
ApiFuture<String> publishFuture1 = sendTestMessageWithOrderingKey(publisher, "m1", "OrderA");
@@ -325,10 +327,23 @@ public void testBatchedMessagesWithOrderingKeyByDuration() throws Exception {
325327
// The timeout expires.
326328
fakeExecutor.advanceTime(Duration.ofSeconds(100));
327329

330+
// Publish one more message on "OrderA" while publishes are outstanding.
331+
testPublisherServiceImpl.setPublishResponseDelay(Duration.ZERO);
332+
ApiFuture<String> publishFuture5 = sendTestMessageWithOrderingKey(publisher, "m5", "OrderA");
333+
334+
// The second timeout expires.
335+
fakeExecutor.advanceTime(Duration.ofSeconds(100));
336+
337+
// Publishing completes on the first four messages.
338+
fakeExecutor.advanceTime(Duration.ofSeconds(200));
339+
328340
// Verify that they were delivered in order per ordering key.
329341
assertTrue(Integer.parseInt(publishFuture1.get()) < Integer.parseInt(publishFuture3.get()));
330342
assertTrue(Integer.parseInt(publishFuture2.get()) < Integer.parseInt(publishFuture4.get()));
331343

344+
// Verify that they were delivered in order per ordering key.
345+
assertTrue(Integer.parseInt(publishFuture3.get()) < Integer.parseInt(publishFuture5.get()));
346+
332347
// Verify that every message within the same batch has the same ordering key.
333348
List<PublishRequest> requests = testPublisherServiceImpl.getCapturedRequests();
334349
for (PublishRequest request : requests) {

0 commit comments

Comments
 (0)