Skip to content

Commit 720a9cb

Browse files
Only rely on next page token for pagination (#1886)
1 parent b083a70 commit 720a9cb

File tree

2 files changed

+43
-21
lines changed

2 files changed

+43
-21
lines changed

temporal-sdk/src/main/java/io/temporal/internal/replay/ServiceWorkflowHistoryIterator.java

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,24 +72,27 @@ class ServiceWorkflowHistoryIterator implements WorkflowHistoryIterator {
7272
}
7373

7474
// Returns true if more history events are available.
75-
// Server can return page tokens that point to empty pages.
76-
// We need to verify that page is valid before returning true.
77-
// Otherwise next() method would throw NoSuchElementException after hasNext() returning true.
7875
@Override
7976
public boolean hasNext() {
8077
if (current.hasNext()) {
8178
return true;
8279
}
83-
if (nextPageToken.isEmpty()) {
84-
return false;
85-
}
86-
87-
GetWorkflowExecutionHistoryResponse response = queryWorkflowExecutionHistory();
88-
89-
current = response.getHistory().getEventsList().iterator();
90-
nextPageToken = response.getNextPageToken();
80+
while (!nextPageToken.isEmpty()) {
81+
// Server can return page tokens that point to empty pages.
82+
// We need to verify that page is valid before returning true.
83+
// Otherwise, next() method would throw NoSuchElementException after hasNext() returning
84+
// true.
85+
GetWorkflowExecutionHistoryResponse response = queryWorkflowExecutionHistory();
9186

92-
return current.hasNext();
87+
current = response.getHistory().getEventsList().iterator();
88+
nextPageToken = response.getNextPageToken();
89+
// Server can return an empty page, but a valid nextPageToken that contains
90+
// more events.
91+
if (current.hasNext()) {
92+
return true;
93+
}
94+
}
95+
return false;
9396
}
9497

9598
@Override

temporal-sdk/src/test/java/io/temporal/internal/replay/ServiceWorkflowHistoryIteratorTest.java

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,23 @@
3232
import org.junit.Test;
3333

3434
public class ServiceWorkflowHistoryIteratorTest {
35-
36-
public static final ByteString EMPTY_PAGE_TOKEN =
37-
ByteString.copyFrom("empty page token", Charset.defaultCharset());
3835
public static final ByteString NEXT_PAGE_TOKEN =
3936
ByteString.copyFrom("next token", Charset.defaultCharset());
37+
public static final ByteString EMPTY_HISTORY_PAGE =
38+
ByteString.copyFrom("empty history page token", Charset.defaultCharset());
39+
public static final ByteString NEXT_NEXT_PAGE_TOKEN =
40+
ByteString.copyFrom("next next token", Charset.defaultCharset());
41+
public static final ByteString EMPTY_PAGE_TOKEN =
42+
ByteString.copyFrom("empty page token", Charset.defaultCharset());
4043

4144
/*
4245
This test Scenario verifies following things:
4346
1. hasNext() method makes a call to the server to retrieve workflow history when current
4447
history is empty and history token is available and cached the result.
4548
2. next() method reuses cached history when possible.
46-
3. hasNext() fetches an empty page and return false.
47-
4. next() throws NoSuchElementException when neither history no history token is available.
49+
3. hasNext() keeps fetching as long as the server returns a next page token.
50+
4. hasNext() fetches an empty page and return false.
51+
5. next() throws NoSuchElementException when neither history no history token is available.
4852
*/
4953
@Test
5054
public void verifyHasNextIsFalseWhenHistoryIsEmpty() {
@@ -58,13 +62,22 @@ public void verifyHasNextIsFalseWhenHistoryIsEmpty() {
5862
GetWorkflowExecutionHistoryResponse queryWorkflowExecutionHistory() {
5963
timesCalledServer.incrementAndGet();
6064
try {
65+
History history = HistoryUtils.generateWorkflowTaskWithInitialHistory().getHistory();
6166
if (EMPTY_PAGE_TOKEN.equals(nextPageToken)) {
6267
return GetWorkflowExecutionHistoryResponse.newBuilder().build();
68+
} else if (EMPTY_HISTORY_PAGE.equals(nextPageToken)) {
69+
return GetWorkflowExecutionHistoryResponse.newBuilder()
70+
.setNextPageToken(NEXT_NEXT_PAGE_TOKEN)
71+
.build();
72+
} else if (NEXT_NEXT_PAGE_TOKEN.equals(nextPageToken)) {
73+
return GetWorkflowExecutionHistoryResponse.newBuilder()
74+
.setHistory(history)
75+
.setNextPageToken(EMPTY_PAGE_TOKEN)
76+
.build();
6377
}
64-
History history = HistoryUtils.generateWorkflowTaskWithInitialHistory().getHistory();
6578
return GetWorkflowExecutionHistoryResponse.newBuilder()
6679
.setHistory(history)
67-
.setNextPageToken(EMPTY_PAGE_TOKEN)
80+
.setNextPageToken(EMPTY_HISTORY_PAGE)
6881
.build();
6982
} catch (Exception e) {
7083
throw new RuntimeException(e);
@@ -80,9 +93,15 @@ GetWorkflowExecutionHistoryResponse queryWorkflowExecutionHistory() {
8093
Assert.assertTrue(iterator.hasNext());
8194
Assert.assertNotNull(iterator.next());
8295
Assert.assertEquals(1, timesCalledServer.get());
96+
Assert.assertTrue(iterator.hasNext());
97+
Assert.assertEquals(3, timesCalledServer.get());
98+
Assert.assertNotNull(iterator.next());
99+
Assert.assertTrue(iterator.hasNext());
100+
Assert.assertNotNull(iterator.next());
101+
Assert.assertTrue(iterator.hasNext());
102+
Assert.assertNotNull(iterator.next());
83103
Assert.assertFalse(iterator.hasNext());
84-
Assert.assertEquals(2, timesCalledServer.get());
85104
Assert.assertThrows(NoSuchElementException.class, iterator::next);
86-
Assert.assertEquals(2, timesCalledServer.get());
105+
Assert.assertEquals(4, timesCalledServer.get());
87106
}
88107
}

0 commit comments

Comments
 (0)