@@ -223,6 +223,12 @@ public final class BatchContext<PropertiesT> implements Closeable {
223223 */
224224 private volatile CompletableFuture <?> recv ;
225225
226+ /**
227+ * Retry policy controls if and how many times
228+ * a {@link RetriableTask} can be retried.
229+ */
230+ private final RetryPolicy retryPolicy ;
231+
226232 /**
227233 * Maximum number of times the client will attempt to re-open the stream
228234 * before terminating the context.
@@ -243,12 +249,14 @@ public final class BatchContext<PropertiesT> implements Closeable {
243249 int maxSizeBytes ,
244250 CollectionDescriptor <PropertiesT > collectionDescriptor ,
245251 CollectionHandleDefaults collectionHandleDefaults ,
252+ RetryPolicy retryPolicy ,
246253 int batchSize ,
247254 int queueSize ,
248255 int maxReconnectRetries ) {
249- this .streamFactory = requireNonNull (streamFactory , "streamFactory is null" );
250256 this .collectionDescriptor = requireNonNull (collectionDescriptor , "collectionDescriptor is null" );
251257 this .collectionHandleDefaults = requireNonNull (collectionHandleDefaults , "collectionHandleDefaults is null" );
258+ this .retryPolicy = requireNonNull (retryPolicy , "retryPolicy is null" );
259+ this .streamFactory = requireNonNull (streamFactory , "streamFactory is null" );
252260
253261 this .queue = new ArrayBlockingQueue <>(queueSize );
254262 this .batch = new Batch (batchSize , maxSizeBytes );
@@ -263,6 +271,7 @@ private BatchContext(Builder<PropertiesT> builder) {
263271 builder .maxSizeBytes ,
264272 builder .collectionDescriptor ,
265273 builder .collectionHandleDefaults ,
274+ builder .retryPolicy ,
266275 builder .batchSize ,
267276 builder .queueSize ,
268277 builder .maxReconnectRetries );
@@ -272,18 +281,35 @@ private BatchContext(Builder<PropertiesT> builder) {
272281 public TaskHandle add (WeaviateObject <PropertiesT > object ) throws InterruptedException {
273282 TaskHandle handle = new TaskHandle (
274283 object ,
275- InsertManyRequest .buildObject (object , collectionDescriptor , collectionHandleDefaults ));
284+ InsertManyRequest .buildObject (object , collectionDescriptor , collectionHandleDefaults ),
285+ retryPolicy , this ::retry );
276286 return add (handle );
277287 }
278288
279289 /** Add {@link BatchReference} to the batch. */
280290 public TaskHandle add (BatchReference reference ) throws InterruptedException {
281291 TaskHandle handle = new TaskHandle (
282292 reference ,
283- InsertManyRequest .buildReference (reference , collectionHandleDefaults .tenant ()));
293+ InsertManyRequest .buildReference (reference , collectionHandleDefaults .tenant ()),
294+ retryPolicy , this ::retry );
284295 return add (handle );
285296 }
286297
298+ private TaskHandle add (final TaskHandle taskHandle ) throws InterruptedException {
299+ if (closed ) {
300+ throw new IllegalStateException ("context is closed" );
301+ }
302+ requireNonNull (taskHandle , "taskHandle is null" );
303+
304+ TaskHandle existing = wip .get (taskHandle .id ());
305+ if (existing != null ) {
306+ throw new DuplicateTaskException (taskHandle , existing );
307+ }
308+
309+ queue .put (taskHandle );
310+ return taskHandle ;
311+ }
312+
287313 void start () {
288314 if (closed ) {
289315 throw new IllegalStateException ("context is closed" );
@@ -316,12 +342,28 @@ void reconnect() throws InterruptedException, ExecutionException {
316342 *
317343 * <p>
318344 * BatchContext does not impose any limit on the number of times a task can
319- * be retried -- it is up to the user to implement an appropriate retry policy.
345+ * be retried -- it is up to the user to select an appropriate retry policy.
320346 *
321347 * @see TaskHandle#timesRetried
348+ * @see RetryPolicy
322349 */
323- public TaskHandle retry (TaskHandle taskHandle ) throws InterruptedException {
324- return add (taskHandle .retry ());
350+ private void retry (String id ) {
351+ try {
352+ requireNonNull (id , "id is null" );
353+
354+ TaskHandle taskHandle = wip .get (id );
355+ assert taskHandle != null : taskHandle + " is not wip" ;
356+
357+ // Put the handle back on the queue directly, circumventing
358+ // the checks closed- and duplicate items checks we do for
359+ // public methods. The retried task is guaranteed to be present
360+ // in the WIP list and may be retried well after the context
361+ // is closed to the user.
362+ queue .put (taskHandle );
363+ } catch (InterruptedException e ) {
364+ // Preserve interrupted state without throwing the exception.
365+ Thread .currentThread ().interrupt ();
366+ }
325367 }
326368
327369 /**
@@ -562,21 +604,6 @@ private void onEvent(Event event) {
562604 }
563605 }
564606
565- private TaskHandle add (final TaskHandle taskHandle ) throws InterruptedException {
566- if (closed ) {
567- throw new IllegalStateException ("context is closed" );
568- }
569- requireNonNull (taskHandle , "taskHandle is null" );
570-
571- TaskHandle existing = wip .get (taskHandle .id ());
572- if (existing != null ) {
573- throw new DuplicateTaskException (taskHandle , existing );
574- }
575-
576- queue .put (taskHandle );
577- return taskHandle ;
578- }
579-
580607 private final class Send implements Runnable {
581608
582609 @ Override
@@ -856,10 +883,6 @@ private void onAcks(Event.Acks acks) {
856883 if (!acks .acked ().containsAll (removed )) {
857884 throwInternal (ProtocolViolationException .incompleteAcks (List .copyOf (removed )));
858885 }
859- acks .acked ().stream ()
860- .map (wip ::get ).filter (Objects ::nonNull )
861- .forEach (TaskHandle ::setAcked );
862-
863886 setState (ACTIVE );
864887 }
865888
@@ -870,13 +893,18 @@ private void onResults(Event.Results results) {
870893 .addKeyValue ("wip_tasks" , wip ::size )
871894 .log ("Received Results" );
872895
896+ // Remove successfully completed tasks from the WIP list and mark them done.
873897 results .successful ().stream ()
874898 .map (wip ::remove ).filter (Objects ::nonNull )
875899 .forEach (TaskHandle ::setSuccess );
876900
901+ // Report errors for failed tasks. Do NOT remove them from the WIP list.
877902 results .errors ().keySet ().stream ()
878- .map (wip ::remove ).filter (Objects ::nonNull )
879- .forEach (taskHandle -> taskHandle .setError (results .errors ().get (taskHandle .id ())));
903+ .map (wip ::get ).filter (Objects ::nonNull )
904+ .forEach (taskHandle -> taskHandle .setError (
905+ new ServerException (results .errors ().get (taskHandle .id ()))));
906+
907+ // TODO(dyma): notify receivedResults
880908 }
881909
882910 private void onBackoff (Event .Backoff backoff ) {
@@ -1194,10 +1222,16 @@ public static class Builder<PropertiesT> implements ObjectBuilder<BatchContext<P
11941222 this .collectionHandleDefaults = collectionHandleDefaults ;
11951223 }
11961224
1225+ private RetryPolicy retryPolicy = RetryPolicy .never ();
11971226 private int batchSize = 1_000 ;
11981227 private int queueSize = 1_000 ;
11991228 private int maxReconnectRetries = 5 ;
12001229
1230+ public Builder <PropertiesT > retryPolicy (RetryPolicy retryPolicy ) {
1231+ this .retryPolicy = retryPolicy ;
1232+ return this ;
1233+ }
1234+
12011235 public Builder <PropertiesT > batchSize (int batchSize ) {
12021236 this .batchSize = batchSize ;
12031237 return this ;
0 commit comments