Skip to content

Commit 2fd2d56

Browse files
committed
Implement Iterator pull methods, add javadoc and tests (#1041)
1 parent e3a6d2e commit 2fd2d56

File tree

6 files changed

+591
-167
lines changed

6 files changed

+591
-167
lines changed

gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/PubSub.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,56 @@ interface MessageConsumer extends AutoCloseable {
381381
*/
382382
Future<AsyncPage<SubscriptionId>> listSubscriptionsAsync(String topic, ListOption... options);
383383

384+
/**
385+
* Pulls messages from the provided subscription. This method possibly returns no messages if no
386+
* message was available at the time the request was processed by the Pub/Sub service (i.e. the
387+
* system is not allowed to wait until at least one message is available). Pulled messages have
388+
* their acknowledge deadline automatically renewed until they are explicitly consumed using
389+
* {@link Iterator#next()}.
390+
*
391+
* <p>Example usage of synchronous message pulling:
392+
* <pre> {@code
393+
* Iterator<ReceivedMessage> messageIterator = pubsub.pull("subscription", 100);
394+
* while (messageIterator.hasNext()) {
395+
* ReceivedMessage message = messageIterator.next();
396+
* // message's acknowledge deadline is no longer automatically renewed. If processing takes
397+
* // long pubsub.modifyAckDeadline(String, String, long, TimeUnit) can be used to extend it.
398+
* doSomething(message);
399+
* message.ack(); // or message.nack()
400+
* }}</pre>
401+
*
402+
* @param subscription the subscription from which to pull messages
403+
* @param maxMessages the maximum number of messages pulled by this method. This method can
404+
* possibly return fewer messages.
405+
* @throws PubSubException upon failure
406+
*/
384407
Iterator<ReceivedMessage> pull(String subscription, int maxMessages);
385408

409+
/**
410+
* Sends a request for pulling messages from the provided subscription. This method returns a
411+
* {@code Future} object to consume the result. {@link Future#get()} returns a message iterator.
412+
* This method possibly returns no messages if no message was available at the time the request
413+
* was processed by the Pub/Sub service (i.e. the system is not allowed to wait until at least one
414+
* message is available).
415+
*
416+
* <p>Example usage of asynchronous message pulling:
417+
* <pre> {@code
418+
* Future<Iterator<ReceivedMessage>> future = pubsub.pull("subscription", 100);
419+
* // do something while the request gets processed
420+
* Iterator<ReceivedMessage> messageIterator = future.get();
421+
* while (messageIterator.hasNext()) {
422+
* ReceivedMessage message = messageIterator.next();
423+
* // message's acknowledge deadline is no longer automatically renewed. If processing takes
424+
* // long pubsub.modifyAckDeadline(String, String, long, TimeUnit) can be used to extend it.
425+
* doSomething(message);
426+
* message.ack(); // or message.nack()
427+
* }}</pre>
428+
*
429+
* @param subscription the subscription from which to pull messages
430+
* @param maxMessages the maximum number of messages pulled by this method. This method can
431+
* possibly return fewer messages.
432+
* @throws PubSubException upon failure
433+
*/
386434
Future<Iterator<ReceivedMessage>> pullAsync(String subscription, int maxMessages);
387435

388436
MessageConsumer pullAsync(String subscription, MessageProcessor callback, PullOption... options);

gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/PubSubImpl.java

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616

1717
package com.google.cloud.pubsub;
1818

19+
import static com.google.api.client.util.Preconditions.checkArgument;
1920
import static com.google.cloud.pubsub.PubSub.ListOption.OptionType.PAGE_SIZE;
2021
import static com.google.cloud.pubsub.PubSub.ListOption.OptionType.PAGE_TOKEN;
21-
import static com.google.common.base.Preconditions.checkArgument;
2222
import static com.google.common.util.concurrent.Futures.lazyTransform;
2323

2424
import com.google.cloud.AsyncPage;
@@ -29,10 +29,12 @@
2929
import com.google.cloud.pubsub.spi.PubSubRpc;
3030
import com.google.cloud.pubsub.spi.v1.PublisherApi;
3131
import com.google.cloud.pubsub.spi.v1.SubscriberApi;
32+
import com.google.common.annotations.VisibleForTesting;
3233
import com.google.common.base.Function;
3334
import com.google.common.base.Throwables;
3435
import com.google.common.collect.ImmutableList;
3536
import com.google.common.collect.Iterables;
37+
import com.google.common.collect.Iterators;
3638
import com.google.common.collect.Lists;
3739
import com.google.common.collect.Maps;
3840
import com.google.common.util.concurrent.Uninterruptibles;
@@ -52,6 +54,8 @@
5254
import com.google.pubsub.v1.ModifyPushConfigRequest;
5355
import com.google.pubsub.v1.PublishRequest;
5456
import com.google.pubsub.v1.PublishResponse;
57+
import com.google.pubsub.v1.PullRequest;
58+
import com.google.pubsub.v1.PullResponse;
5559

5660
import java.util.Collections;
5761
import java.util.Iterator;
@@ -64,6 +68,8 @@
6468
class PubSubImpl extends BaseService<PubSubOptions> implements PubSub {
6569

6670
private final PubSubRpc rpc;
71+
private final AckDeadlineRenewer ackDeadlineRenewer;
72+
private boolean closed;
6773

6874
private static final Function<Empty, Void> EMPTY_TO_VOID_FUNCTION = new Function<Empty, Void>() {
6975
@Override
@@ -78,10 +84,25 @@ public Boolean apply(Empty input) {
7884
return input != null;
7985
}
8086
};
87+
private static final Function<com.google.pubsub.v1.ReceivedMessage, String>
88+
MESSAGE_TO_ACK_ID_FUNCTION = new Function<com.google.pubsub.v1.ReceivedMessage, String>() {
89+
@Override
90+
public String apply(com.google.pubsub.v1.ReceivedMessage message) {
91+
return message.getAckId();
92+
}
93+
};
8194

8295
PubSubImpl(PubSubOptions options) {
8396
super(options);
8497
rpc = options.rpc();
98+
ackDeadlineRenewer = new AckDeadlineRenewer(this);
99+
}
100+
101+
@VisibleForTesting
102+
PubSubImpl(PubSubOptions options, AckDeadlineRenewer ackDeadlineRenewer) {
103+
super(options);
104+
rpc = options.rpc();
105+
this.ackDeadlineRenewer = ackDeadlineRenewer;
85106
}
86107

87108
private abstract static class BasePageFetcher<T> implements AsyncPageImpl.NextPageFetcher<T> {
@@ -445,17 +466,35 @@ public Future<AsyncPage<SubscriptionId>> listSubscriptionsAsync(String topic,
445466

446467
@Override
447468
public Iterator<ReceivedMessage> pull(String subscription, int maxMessages) {
448-
// this should set return_immediately to true
449-
return null;
469+
return get(pullAsync(subscription, maxMessages));
450470
}
451471

452472
@Override
453-
public Future<Iterator<ReceivedMessage>> pullAsync(String subscription, int maxMessages) {
454-
// though this method can set return_immediately to false (as future can be canceled) I
455-
// suggest to keep it false so sync could delegate to asyc and use the same options
456-
// this method also should use the VTKIT thread-pool to renew ack deadline for non consumed
457-
// messages
458-
return null;
473+
public Future<Iterator<ReceivedMessage>> pullAsync(final String subscription, int maxMessages) {
474+
PullRequest request = PullRequest.newBuilder().setReturnImmediately(true)
475+
.setSubscription(SubscriberApi.formatSubscriptionName(options().projectId(), subscription))
476+
.setMaxMessages(maxMessages)
477+
.setReturnImmediately(true)
478+
.build();
479+
Future<PullResponse> response = rpc.pull(request);
480+
return lazyTransform(response, new Function<PullResponse, Iterator<ReceivedMessage>>() {
481+
@Override
482+
public Iterator<ReceivedMessage> apply(PullResponse pullResponse) {
483+
// Add all received messages to the automatic ack deadline renewer
484+
List<String> ackIds = Lists.transform(pullResponse.getReceivedMessagesList(),
485+
MESSAGE_TO_ACK_ID_FUNCTION);
486+
ackDeadlineRenewer.add(subscription, ackIds);
487+
return Iterators.transform(pullResponse.getReceivedMessagesList().iterator(),
488+
new Function<com.google.pubsub.v1.ReceivedMessage, ReceivedMessage>() {
489+
@Override
490+
public ReceivedMessage apply(com.google.pubsub.v1.ReceivedMessage receivedMessage) {
491+
// Remove consumed message from automatic ack deadline renewer
492+
ackDeadlineRenewer.remove(subscription, receivedMessage.getAckId());
493+
return ReceivedMessage.fromPb(PubSubImpl.this, subscription, receivedMessage);
494+
}
495+
});
496+
}
497+
});
459498
}
460499

461500
@Override
@@ -549,6 +588,13 @@ public Future<Void> modifyAckDeadlineAsync(String subscription, int deadline, Ti
549588

550589
@Override
551590
public void close() throws Exception {
591+
if (closed) {
592+
return;
593+
}
594+
closed = true;
552595
rpc.close();
596+
if (ackDeadlineRenewer != null) {
597+
ackDeadlineRenewer.close();
598+
}
553599
}
554600
}

gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/spi/DefaultPubSubRpc.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ public class DefaultPubSubRpc implements PubSubRpc {
7373
private final ScheduledExecutorService executor;
7474
private final ExecutorFactory executorFactory;
7575

76+
private boolean closed;
77+
7678
private static final class InternalPubSubOptions extends PubSubOptions {
7779

7880
private static final long serialVersionUID = -7997372049256706185L;
@@ -233,6 +235,10 @@ public Future<Empty> modify(ModifyPushConfigRequest request) {
233235

234236
@Override
235237
public void close() throws Exception {
238+
if (closed) {
239+
return;
240+
}
241+
closed = true;
236242
subscriberApi.close();
237243
publisherApi.close();
238244
executorFactory.release(executor);

gcloud-java-pubsub/src/test/java/com/google/cloud/pubsub/AckDeadlineRenewerTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
import org.easymock.IAnswer;
2626
import org.junit.After;
2727
import org.junit.Before;
28+
import org.junit.Rule;
2829
import org.junit.Test;
30+
import org.junit.rules.Timeout;
2931

3032
import java.util.concurrent.CountDownLatch;
3133
import java.util.concurrent.Future;
@@ -47,6 +49,9 @@ public class AckDeadlineRenewerTest {
4749
private PubSub pubsub;
4850
private AckDeadlineRenewer ackDeadlineRenewer;
4951

52+
@Rule
53+
public Timeout globalTimeout = Timeout.seconds(60);
54+
5055
@Before
5156
public void setUp() {
5257
pubsub = EasyMock.createStrictMock(PubSub.class);

0 commit comments

Comments
 (0)