Skip to content

Commit e321055

Browse files
authored
Fix Batch bulk task retry race (#50881)
* Fix Batch bulk task retry race * Updated versions and changelog
1 parent cc81e63 commit e321055

3 files changed

Lines changed: 68 additions & 10 deletions

File tree

‎sdk/batch/azure-compute-batch/CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
- Replaced the public `BatchFileProperties(HttpHeaders)` constructor with a static factory
1717
`BatchFileProperties.fromHeaders(HttpHeaders)`.
1818

19+
### Bugs Fixed
20+
21+
- Fixed a race condition that could cause bulk task creation to return before retrying tasks with server errors.
22+
1923
### Other Changes
2024

2125
This is the first generally available release of the `azure-compute-batch` library. It includes all features and breaking changes introduced in `1.0.0-beta.6`.

‎sdk/batch/azure-compute-batch/src/main/java/com/azure/compute/batch/implementation/task/TaskManager.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,8 @@ public static void createTasks(TaskSubmitter taskSubmitter, String jobId, List<B
220220
List<ThreadPairInfo> semaphoreThreads = new ArrayList<>();
221221
Exception innerException = null;
222222

223-
// Continue looping while there are still tasks left to submit OR while any active threads are still processing tasks.
224-
while (!pendingList.isEmpty() || taskSemaphore.availablePermits() < threadNumber) {
223+
// Check workers first so tasks requeued before their permits are released cannot be missed.
224+
while (taskSemaphore.availablePermits() < threadNumber || !pendingList.isEmpty()) {
225225
try {
226226
if (taskSemaphore.tryAcquire(30, TimeUnit.SECONDS)) {
227227
if (!pendingList.isEmpty()) {

‎sdk/batch/azure-compute-batch/src/test/java/com/azure/compute/batch/TaskManagerTests.java‎

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,35 @@
44
package com.azure.compute.batch;
55

66
import com.azure.compute.batch.implementation.task.TaskManager;
7+
import com.azure.compute.batch.implementation.task.TaskSubmitter;
78
import com.azure.compute.batch.models.BatchTaskBulkCreateOptions;
89
import com.azure.compute.batch.models.BatchTaskCreateParameters;
910
import com.azure.compute.batch.models.CreateTasksErrorException;
10-
import org.junit.jupiter.api.*;
11+
import org.junit.jupiter.api.RepeatedTest;
12+
import org.junit.jupiter.api.Test;
1113
import org.junit.jupiter.api.parallel.Execution;
1214
import org.junit.jupiter.api.parallel.ExecutionMode;
1315
import org.junit.jupiter.api.parallel.Isolated;
14-
15-
import java.util.*;
16+
import org.junit.jupiter.params.ParameterizedTest;
17+
import org.junit.jupiter.params.provider.ValueSource;
18+
19+
import java.io.IOException;
20+
import java.util.Arrays;
21+
import java.util.Collections;
22+
import java.util.HashMap;
23+
import java.util.HashSet;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Set;
1627
import java.util.stream.Collectors;
1728
import java.util.stream.IntStream;
1829

19-
import static org.junit.jupiter.api.Assertions.*;
30+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
31+
import static org.junit.jupiter.api.Assertions.assertEquals;
32+
import static org.junit.jupiter.api.Assertions.assertFalse;
33+
import static org.junit.jupiter.api.Assertions.assertSame;
34+
import static org.junit.jupiter.api.Assertions.assertThrows;
35+
import static org.junit.jupiter.api.Assertions.assertTrue;
2036

2137
@Execution(ExecutionMode.SAME_THREAD)
2238
@Isolated
@@ -28,7 +44,7 @@ private static List<BatchTaskCreateParameters> makeTasks(String prefix, int coun
2844
.collect(Collectors.toList());
2945
}
3046

31-
private static void runCreateTasks(FakeTaskSubmitter submitter, List<BatchTaskCreateParameters> tasks) {
47+
private static void runCreateTasks(TaskSubmitter submitter, List<BatchTaskCreateParameters> tasks) {
3248
TaskManager.createTasks(submitter, "job-1", tasks, new BatchTaskBulkCreateOptions().setMaxConcurrency(4));
3349
}
3450

@@ -44,11 +60,11 @@ public void serverErrorsAreRetriedUntilSuccessNoCap() {
4460

4561
assertDoesNotThrow(() -> runCreateTasks(fake, tasks));
4662

47-
assertTrue(fake.getSubmissionCount("A") > 1, "A should have been retried at least once");
63+
assertEquals(11, fake.getSubmissionCount("A"), "A should succeed after ten retries");
4864
assertEquals(1, fake.getSubmissionCount("B"), "B should submit once");
4965
}
5066

51-
@Test
67+
@RepeatedTest(1000)
5268
public void successAfterOneServerErrorDidRetry() {
5369
Map<String, Integer> serverErrors = new HashMap<>();
5470
serverErrors.put("X", 1); // one failure then success
@@ -57,7 +73,45 @@ public void successAfterOneServerErrorDidRetry() {
5773
List<BatchTaskCreateParameters> tasks = Collections.singletonList(new BatchTaskCreateParameters("X", "echo X"));
5874

5975
assertDoesNotThrow(() -> runCreateTasks(fake, tasks));
60-
assertTrue(fake.getSubmissionCount("X") >= 2, "X should have been retried at least once");
76+
assertEquals(2, fake.getSubmissionCount("X"), "X should succeed after one retry");
77+
}
78+
79+
@ParameterizedTest
80+
@ValueSource(ints = { 1, 4 })
81+
public void retriesAcrossMultipleChunks(int maxConcurrency) {
82+
List<BatchTaskCreateParameters> tasks = makeTasks("T", 201);
83+
Map<String, Integer> serverErrors = new HashMap<>();
84+
tasks.forEach(task -> serverErrors.put(task.getId(), 1));
85+
FakeTaskSubmitter fake = new FakeTaskSubmitter(Collections.emptySet(), serverErrors, 0);
86+
87+
assertDoesNotThrow(() -> TaskManager.createTasks(fake, "job-1", tasks,
88+
new BatchTaskBulkCreateOptions().setMaxConcurrency(maxConcurrency)));
89+
90+
tasks.forEach(task -> assertEquals(2, fake.getSubmissionCount(task.getId()),
91+
task.getId() + " should succeed after one retry"));
92+
}
93+
94+
@RepeatedTest(100)
95+
public void submissionExceptionIsPropagated() {
96+
RuntimeException expected = new IllegalStateException("Submission failed");
97+
TaskSubmitter submitter = (jobId, taskCollection) -> {
98+
throw expected;
99+
};
100+
101+
assertSame(expected, assertThrows(IllegalStateException.class,
102+
() -> runCreateTasks(submitter, Collections.singletonList(new BatchTaskCreateParameters("X", "echo X")))));
103+
}
104+
105+
@Test
106+
public void checkedSubmissionExceptionIsPropagated() {
107+
IOException expected = new IOException("Submission failed");
108+
TaskSubmitter submitter = (jobId, taskCollection) -> {
109+
throw expected;
110+
};
111+
112+
RuntimeException actual = assertThrows(RuntimeException.class,
113+
() -> runCreateTasks(submitter, Collections.singletonList(new BatchTaskCreateParameters("X", "echo X"))));
114+
assertSame(expected, actual.getCause());
61115
}
62116

63117
@Test

0 commit comments

Comments
 (0)