57
57
import java .util .concurrent .ScheduledThreadPoolExecutor ;
58
58
import java .util .concurrent .TimeUnit ;
59
59
import javax .annotation .Nonnull ;
60
+ import org .junit .After ;
60
61
import org .junit .Assert ;
61
62
import org .junit .Before ;
62
63
import org .junit .Rule ;
@@ -121,6 +122,8 @@ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
121
122
private DocumentReference doc1 ;
122
123
private DocumentReference doc2 ;
123
124
125
+ private ScheduledExecutorService timeoutExecutor ;
126
+
124
127
public static ApiFuture <BatchWriteResponse > successResponse (int updateTimeSeconds ) {
125
128
BatchWriteResponse .Builder response = BatchWriteResponse .newBuilder ();
126
129
response .addWriteResultsBuilder ().getUpdateTimeBuilder ().setSeconds (updateTimeSeconds ).build ();
@@ -155,7 +158,7 @@ public void before() {
155
158
lenient ().doReturn (immediateExecutor ).when (firestoreRpc ).getExecutor ();
156
159
testExecutor = Executors .newSingleThreadScheduledExecutor ();
157
160
158
- final ScheduledExecutorService timeoutExecutor =
161
+ timeoutExecutor =
159
162
new ScheduledThreadPoolExecutor (1 ) {
160
163
@ Override
161
164
@ Nonnull
@@ -170,6 +173,21 @@ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
170
173
doc2 = firestoreMock .document ("coll/doc2" );
171
174
}
172
175
176
+ @ After
177
+ public void after () throws InterruptedException {
178
+ shutdownScheduledExecutorService (timeoutExecutor );
179
+ }
180
+
181
+ void shutdownScheduledExecutorService (ScheduledExecutorService executorService )
182
+ throws InterruptedException {
183
+ // Wait for the executor to finish after each test.
184
+ //
185
+ // This ensures the executor service is shut down properly within the given timeout, and thereby
186
+ // avoids potential hangs caused by lingering threads. Note that if a given thread is terminated
187
+ // because of the timeout, the associated test will fail, which is what we want.
188
+ executorService .awaitTermination (100 , TimeUnit .MILLISECONDS );
189
+ }
190
+
173
191
@ Test
174
192
public void hasSetMethod () throws Exception {
175
193
ResponseStubber responseStubber =
@@ -968,7 +986,7 @@ public void doesNotSendBatchesIfDoingSoExceedsRateLimit() throws Exception {
968
986
// future at the end of the test to ensure that the timeout was called.
969
987
final SettableApiFuture <Void > timeoutCalledFuture = SettableApiFuture .create ();
970
988
971
- final ScheduledExecutorService timeoutExecutor =
989
+ final ScheduledExecutorService customExecutor =
972
990
new ScheduledThreadPoolExecutor (1 ) {
973
991
@ Override
974
992
@ Nonnull
@@ -994,14 +1012,15 @@ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
994
1012
firestoreMock .bulkWriter (
995
1013
BulkWriterOptions .builder ()
996
1014
.setInitialOpsPerSecond (5 )
997
- .setExecutor (timeoutExecutor )
1015
+ .setExecutor (customExecutor )
998
1016
.build ());
999
1017
1000
1018
for (int i = 0 ; i < 600 ; ++i ) {
1001
1019
bulkWriter .set (firestoreMock .document ("coll/doc" ), LocalFirestoreHelper .SINGLE_FIELD_MAP );
1002
1020
}
1003
1021
bulkWriter .flush ();
1004
1022
timeoutCalledFuture .get ();
1023
+ shutdownScheduledExecutorService (customExecutor );
1005
1024
}
1006
1025
1007
1026
@ Test
@@ -1097,7 +1116,7 @@ public void retriesWritesWhenBatchWriteFailsWithRetryableError() throws Exceptio
1097
1116
public void failsWritesAfterAllRetryAttemptsFail () throws Exception {
1098
1117
final int [] retryAttempts = {0 };
1099
1118
final int [] scheduleWithDelayCount = {0 };
1100
- final ScheduledExecutorService timeoutExecutor =
1119
+ final ScheduledExecutorService customExecutor =
1101
1120
new ScheduledThreadPoolExecutor (1 ) {
1102
1121
@ Override
1103
1122
@ Nonnull
@@ -1127,7 +1146,7 @@ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
1127
1146
ArgumentMatchers .<UnaryCallable <BatchWriteRequest , BatchWriteResponse >>any ());
1128
1147
1129
1148
bulkWriter =
1130
- firestoreMock .bulkWriter (BulkWriterOptions .builder ().setExecutor (timeoutExecutor ).build ());
1149
+ firestoreMock .bulkWriter (BulkWriterOptions .builder ().setExecutor (customExecutor ).build ());
1131
1150
ApiFuture <WriteResult > result = bulkWriter .set (doc1 , LocalFirestoreHelper .SINGLE_FIELD_MAP );
1132
1151
bulkWriter .flush ().get ();
1133
1152
@@ -1139,14 +1158,16 @@ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
1139
1158
assertEquals (BulkWriter .MAX_RETRY_ATTEMPTS + 1 , retryAttempts [0 ]);
1140
1159
// The first attempt should not have a delay.
1141
1160
assertEquals (BulkWriter .MAX_RETRY_ATTEMPTS , scheduleWithDelayCount [0 ]);
1161
+ } finally {
1162
+ shutdownScheduledExecutorService (customExecutor );
1142
1163
}
1143
1164
}
1144
1165
1145
1166
@ Test
1146
1167
public void appliesMaxBackoffOnRetriesForResourceExhausted () throws Exception {
1147
1168
final int [] retryAttempts = {0 };
1148
1169
final int [] scheduleWithDelayCount = {0 };
1149
- final ScheduledExecutorService timeoutExecutor =
1170
+ final ScheduledExecutorService customExecutor =
1150
1171
new ScheduledThreadPoolExecutor (1 ) {
1151
1172
@ Override
1152
1173
@ Nonnull
@@ -1177,7 +1198,7 @@ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
1177
1198
ArgumentMatchers .<UnaryCallable <BatchWriteRequest , BatchWriteResponse >>any ());
1178
1199
1179
1200
bulkWriter =
1180
- firestoreMock .bulkWriter (BulkWriterOptions .builder ().setExecutor (timeoutExecutor ).build ());
1201
+ firestoreMock .bulkWriter (BulkWriterOptions .builder ().setExecutor (customExecutor ).build ());
1181
1202
bulkWriter .addWriteErrorListener (error -> error .getFailedAttempts () < 5 );
1182
1203
1183
1204
ApiFuture <WriteResult > result = bulkWriter .create (doc1 , LocalFirestoreHelper .SINGLE_FIELD_MAP );
@@ -1191,6 +1212,8 @@ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
1191
1212
assertEquals (5 , retryAttempts [0 ]);
1192
1213
// The first attempt should not have a delay.
1193
1214
assertEquals (4 , scheduleWithDelayCount [0 ]);
1215
+ } finally {
1216
+ shutdownScheduledExecutorService (customExecutor );
1194
1217
}
1195
1218
}
1196
1219
@@ -1203,7 +1226,7 @@ public void usesHighestBackoffFoundInBatch() throws Exception {
1203
1226
* BulkWriterOperation .DEFAULT_BACKOFF_FACTOR )
1204
1227
};
1205
1228
final int [] retryAttempts = {0 };
1206
- final ScheduledExecutorService timeoutExecutor =
1229
+ final ScheduledExecutorService customExecutor =
1207
1230
new ScheduledThreadPoolExecutor (1 ) {
1208
1231
@ Override
1209
1232
@ Nonnull
@@ -1244,14 +1267,15 @@ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
1244
1267
responseStubber .initializeStub (batchWriteCapture , firestoreMock );
1245
1268
1246
1269
bulkWriter =
1247
- firestoreMock .bulkWriter (BulkWriterOptions .builder ().setExecutor (timeoutExecutor ).build ());
1270
+ firestoreMock .bulkWriter (BulkWriterOptions .builder ().setExecutor (customExecutor ).build ());
1248
1271
bulkWriter .addWriteErrorListener (error -> error .getFailedAttempts () < 5 );
1249
1272
1250
1273
bulkWriter .create (doc1 , LocalFirestoreHelper .SINGLE_FIELD_MAP );
1251
1274
bulkWriter .set (doc2 , LocalFirestoreHelper .SINGLE_FIELD_MAP );
1252
1275
bulkWriter .close ();
1253
1276
responseStubber .verifyAllRequestsSent ();
1254
1277
assertEquals (2 , retryAttempts [0 ]);
1278
+ shutdownScheduledExecutorService (customExecutor );
1255
1279
}
1256
1280
1257
1281
@ Test
0 commit comments