Skip to content

Commit 2ff8314

Browse files
committed
fix(batch): fine-tuning reconnect flow after OOM and stream hangup
1 parent 40ca5de commit 2ff8314

4 files changed

Lines changed: 253 additions & 88 deletions

File tree

src/main/java/io/weaviate/client6/v1/api/collections/batch/Batch.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ final class Batch {
7373
* Must be greater that zero.
7474
*
7575
* <p>
76-
* This is determined by the {@link GrpcChannelOptions#maxMessageSize}.
76+
* This is determined by the {@link GrpcChannelOptions#maxMessageSize()}.
7777
*/
7878
@GuardedBy("this")
7979
private final int maxSizeBytes;
@@ -142,7 +142,7 @@ synchronized Message prepare() {
142142
* Set the new {@link #maxSize} for this buffer.
143143
*
144144
* <p>
145-
* How the size is applied depends of the buffer's current state:
145+
* How the size is applied depends on the buffer's current state:
146146
* <ul>
147147
* <li>When the batch is in-flight, the new limit is stored in
148148
* {@link #pendingMaxSize} and will be applied once the batch is cleared.
@@ -240,7 +240,7 @@ synchronized void add(Data data) throws IllegalStateException, DataTooBigExcepti
240240
/**
241241
* Add a data item to the batch.
242242
*
243-
* This method does not check {@link Data#sizeBytes}, so the caller
243+
* This method does not check {@link Data#sizeBytes()}, so the caller
244244
* must ensure that this item will not overflow the batch.
245245
*/
246246
private synchronized void addSafe(Data data) {
@@ -258,7 +258,7 @@ private synchronized void addBacklog(Data data) {
258258
*
259259
* <p>
260260
* Once the buffer is pruned, it is re-populated from the backlog
261-
* until the former is full or the latter is exhaused.
261+
* until the former is full or the latter is exhausted.
262262
* If {@link #pendingMaxSize} is not empty, it is applied
263263
* before re-populating the buffer.
264264
*

src/main/java/io/weaviate/client6/v1/api/collections/batch/BatchContext.java

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public final class BatchContext<PropertiesT> implements Closeable {
5959
private final CollectionHandleDefaults collectionHandleDefaults;
6060

6161
/**
62-
* Internal execution service. It's lifecycle is bound to that of the
62+
* Internal execution service. Its lifecycle is bound to that of the
6363
* BatchContext: it's started when the context is initialized
6464
* and shutdown on {@link #close}.
6565
*
@@ -87,20 +87,19 @@ public final class BatchContext<PropertiesT> implements Closeable {
8787

8888
/**
8989
* Queue publishes insert tasks from the main thread to the "sender".
90-
* It has a maximum capacity of {@link #queueSize}.
9190
*
92-
* Send {@link TaskHandle#POISON} to gracefully shutdown the "sender"
91+
* Send {@link TaskHandle#POISON} to gracefully shut down the "sender"
9392
* thread. The same queue may be re-used with a different "sender",
9493
* e.g. after {@link #reconnect}, but only when the new thread is known
95-
* to have started. Otherwise the thread trying to put an item on
94+
* to have started. Otherwise, the thread trying to put an item on
9695
* the queue will block indefinitely.
9796
*/
9897
private final BlockingQueue<TaskHandle> queue;
9998

10099
/**
101100
* Work-in-progress items.
102101
*
103-
* An item is added to the {@link #wip} map after the Sender successfully
102+
* An item is added to the wip map after the Sender successfully
104103
* adds it to the {@link #batch} and is removed once the server reports
105104
* back the result (whether success of failure).
106105
*/
@@ -110,14 +109,14 @@ public final class BatchContext<PropertiesT> implements Closeable {
110109
* Current batch.
111110
*
112111
* <p>
113-
* An item is added to the {@link #batch} after the Sender pulls it
112+
* An item is added to the batch after the Sender pulls it
114113
* from the queue and remains there until it's Ack'ed.
115114
*/
116115
private final Batch batch;
117116

118117
/**
119118
* State encapsulates state-dependent behavior of the {@link BatchContext}.
120-
* Before reading {@link #state}, a thread MUST acquire {@link #lock}.
119+
* Before reading state, a thread MUST acquire {@link #lock}.
121120
*/
122121
@GuardedBy("lock")
123122
private State state;
@@ -137,7 +136,7 @@ public final class BatchContext<PropertiesT> implements Closeable {
137136

138137
/**
139138
* Latch reaches zero once both "send" (client side) and "recv" (server side)
140-
* parts of the stream have closed. After a {@link reconnect}, the latch is
139+
* parts of the stream have closed. After a {@link #reconnect}, the latch is
141140
* reset.
142141
*/
143142
private volatile CountDownLatch workers;
@@ -195,37 +194,38 @@ public TaskHandle add(BatchReference reference) throws InterruptedException {
195194
}
196195

197196
void start() {
198-
start(AWAIT_STARTED);
199-
}
200-
201-
void start(State nextState) {
197+
System.out.println("RESET COUNTDOWN LATCH to [2]");
202198
workers = new CountDownLatch(2);
203199

204200
messages = streamFactory.createStream(new Recv());
205-
206-
// Start the stream and await Started message.
207201
messages.onNext(Message.start(collectionHandleDefaults.consistencyLevel()));
208-
setState(nextState);
209202

210203
// "send" routine must start after the nextState has been set.
204+
setState(AWAIT_STARTED);
211205
send = sendExec.submit(new Send());
212206
}
213207

214208
/**
215-
* Reconnect waits for "send" and "recv" streams to exit
216-
* and restarts the process with a new stream.
209+
* Reconnect resets {@link #workers} latch and re-creates the stream.
217210
*
218-
* @param reconnecting Reconnecting instance that called reconnect.
211+
* <p>
212+
* Unlike {@link #start} it does not trigger a state transition, and
213+
* {@link Reconnecting} will should continue to handle events until
214+
* the stream is renewed successfully or {@link #maxReconnectRetries}
215+
* is reached.
219216
*/
220-
void reconnect(Reconnecting reconnecting) throws InterruptedException, ExecutionException {
221-
workers.await();
222-
send.get();
223-
start(reconnecting);
217+
void reconnect() throws InterruptedException, ExecutionException {
218+
System.out.println("RESET COUNTDOWN LATCH to [1]");
219+
workers = new CountDownLatch(2);
220+
221+
messages = streamFactory.createStream(new Recv());
222+
messages.onNext(Message.start(collectionHandleDefaults.consistencyLevel()));
224223
}
225224

226225
/**
227226
* Retry a task.
228227
*
228+
* <p>
229229
* BatchContext does not impose any limit on the number of times a task can
230230
* be retried -- it is up to the user to implement an appropriate retry policy.
231231
*
@@ -249,6 +249,7 @@ public void close() throws IOException {
249249
closed = true;
250250

251251
if (!closedBefore) {
252+
System.out.println("CLOSE CONTEXT");
252253
shutdown();
253254
}
254255

@@ -268,18 +269,21 @@ public void close() throws IOException {
268269
private void shutdown() {
269270
CompletableFuture.runAsync(() -> {
270271
try {
271-
// Poison the queue -- this will signal "send" to drain the remaing
272+
// Poison the queue -- this will signal "send" to drain the remaining
272273
// items in the batch and in the backlog and exit.
273274
//
274275
// If shutdownNow has been called previously and the "send" routine
275-
// has been interrupted, this may block indefinitely.
276-
// However, shutdownNow ensures that `closing` future is resolved.
276+
// has been interrupted, this would block indefinitely.
277+
// Luckily, shutdownNow resolves the `closing` future as well.
278+
System.out.println("POISON THE QUEUE");
277279
queue.put(TaskHandle.POISON);
278280

279281
// Wait for the send to exit before closing our end of the stream.
280-
send.get();
281-
messages.onNext(Message.stop());
282-
messages.onCompleted();
282+
try {
283+
send.get();
284+
} catch (CancellationException ignored) {
285+
// Send task can be cancelled due to a reconnect or an internal error.
286+
}
283287

284288
// Wait for both "send" and "recv" to exit.
285289
workers.await();
@@ -405,6 +409,7 @@ public void run() {
405409
try {
406410
trySend();
407411
} finally {
412+
System.out.println("sender countDown");
408413
workers.countDown();
409414
}
410415
}
@@ -428,7 +433,7 @@ private void trySend() {
428433
if (task == TaskHandle.POISON) {
429434
System.out.println("took POISON");
430435
drain();
431-
return;
436+
break;
432437
}
433438

434439
Data data = task.data();
@@ -443,6 +448,9 @@ private void trySend() {
443448
onEvent(new Event.ClientError(e));
444449
return;
445450
}
451+
452+
messages.onNext(Message.stop());
453+
messages.onCompleted();
446454
}
447455

448456
/**
@@ -535,6 +543,7 @@ public void onCompleted() {
535543
try {
536544
onEvent(Event.EOF);
537545
} finally {
546+
System.out.println("recv countDown (onCompleted)");
538547
workers.countDown();
539548
}
540549
}
@@ -545,6 +554,7 @@ public void onError(Throwable t) {
545554
try {
546555
onEvent(Event.StreamHangup.fromThrowable(t));
547556
} finally {
557+
System.out.println("recv countDown (onError)");
548558
workers.countDown();
549559
}
550560
}
@@ -662,20 +672,20 @@ public void onEvent(Event event) {
662672
}
663673
}
664674

665-
private final void onResults(Event.Results results) {
675+
private void onResults(Event.Results results) {
666676
results.successful().forEach(id -> wip.remove(id).setSuccess());
667677
results.errors().forEach((id, error) -> wip.remove(id).setError(error));
668678
}
669679

670-
private final void onBackoff(Event.Backoff backoff) {
680+
private void onBackoff(Event.Backoff backoff) {
671681
batch.setMaxSize(backoff.maxSize());
672682
}
673683

674-
private final void onShuttingDown() {
684+
private void onShuttingDown() {
675685
setState(new ServerShuttingDown(this));
676686
}
677687

678-
private final void onStreamClosed(Event event) {
688+
private void onStreamClosed(Event event) {
679689
if (event instanceof Event.StreamHangup hangup) {
680690
hangup.exception().printStackTrace();
681691
}
@@ -684,7 +694,7 @@ private final void onStreamClosed(Event event) {
684694
}
685695
}
686696

687-
private final void onClientError(Event.ClientError error) {
697+
private void onClientError(Event.ClientError error) {
688698
shutdownNow(error.exception());
689699
}
690700

@@ -696,7 +706,7 @@ public String toString() {
696706

697707
/**
698708
* Oom waits for {@link Event#SHUTTING_DOWN} up to a specified amount of time,
699-
* after which it will force stream termiation by imitating server shutdown.
709+
* after which it will force stream termination by imitating server shutdown.
700710
*/
701711
private final class Oom extends BaseState {
702712
private final long delaySeconds;
@@ -719,11 +729,11 @@ private void initiateShutdown() {
719729
// receive an Event.SHUTTING_DOWN, it would cancel this execution of this
720730
// very sequence. Instead, we delegate to our parent BaseState which normally
721731
// handles these events.
722-
if (Thread.currentThread().isInterrupted()) {
723-
super.onEvent(Event.SHUTTING_DOWN);
732+
if (!Thread.currentThread().isInterrupted()) {
733+
BatchContext.this.onEvent(Event.SHUTTING_DOWN);
724734
}
725-
if (Thread.currentThread().isInterrupted()) {
726-
super.onEvent(Event.EOF);
735+
if (!Thread.currentThread().isInterrupted()) {
736+
BatchContext.this.onEvent(Event.EOF);
727737
}
728738
}
729739

@@ -761,7 +771,7 @@ private final class ServerShuttingDown extends BaseState {
761771

762772
private ServerShuttingDown(State previous) {
763773
super("SERVER_SHUTTING_DOWN");
764-
this.canPrepareNext = requireNonNull(previous, "previous is null").getClass() != Oom.class;
774+
this.canPrepareNext = previous == null || !Oom.class.isAssignableFrom(previous.getClass());
765775
}
766776

767777
@Override
@@ -776,13 +786,14 @@ public boolean canSend() {
776786

777787
@Override
778788
public void onEnter(State prev) {
779-
send.cancel(true);
789+
messages.onNext(Message.stop());
790+
messages.onCompleted();
780791
}
781792
}
782793

783794
/**
784-
* Reconnecting state is entererd either by the server finishing a shutdown
785-
* and closing it's end of the stream or an unexpected stream hangup.
795+
* Reconnecting state is entered either by the server finishing a shutdown
796+
* and closing its end of the stream or an unexpected stream hangup.
786797
*
787798
* @see Recv#onCompleted graceful server shutdown
788799
* @see Recv#onError stream hangup
@@ -805,9 +816,9 @@ public void onEnter(State prev) {
805816
return;
806817
}
807818

808-
send.cancel(true);
819+
// send.cancel(true);
809820

810-
if (prev.getClass() != ServerShuttingDown.class) {
821+
if (!ServerShuttingDown.class.isAssignableFrom(prev.getClass())) {
811822
// This is NOT an orderly shutdown, we're reconnecting after a stream hangup.
812823
// Assume all WIP items have been lost and re-submit everything.
813824
// All items in the batch are contained in WIP, so it is safe to discard the
@@ -836,8 +847,10 @@ public void onEvent(Event event) {
836847
if (retries == maxRetries) {
837848
onEvent(new Event.ClientError(new IOException("Server unavailable")));
838849
} else {
839-
reconnectAfter(1 * 2 ^ retries);
850+
reconnectAfter(2 ^ retries);
840851
}
852+
} else {
853+
super.onEvent(event);
841854
}
842855

843856
assert retries <= maxRetries : "maxRetries exceeded";
@@ -864,7 +877,7 @@ private void reconnectAfter(long delaySeconds) {
864877

865878
scheduledExec.schedule(() -> {
866879
try {
867-
reconnect(this);
880+
reconnect();
868881
} catch (InterruptedException e) {
869882
Thread.currentThread().interrupt();
870883
} catch (ExecutionException e) {
@@ -888,7 +901,7 @@ void scheduleReconnect(int reconnectIntervalSeconds) {
888901
}
889902

890903
// We want to count down from the moment we re-opened the stream,
891-
// not from the moment we initialited the sequence.
904+
// not from the moment we initialized the sequence.
892905
lock.lock();
893906
try {
894907
while (state != ACTIVE) {

src/main/java/io/weaviate/client6/v1/api/collections/batch/TaskHandle.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ public final class TaskHandle {
2323
* Input value as passed by the user.
2424
*
2525
* <p>
26-
* Changes in the {@link #raw}'s underlying value will not be reflected
26+
* Changes in the {@link Data}'s underlying value will not be reflected
2727
* in the {@link TaskHandle} (e.g. the serialized version is not updated),
2828
* so users SHOULD treat items passed to and retrieved from {@link TaskHandle}
29-
* as effectively ummodifiable.
29+
* as effectively unmodifiable.
3030
*/
3131
private final Data data;
3232

33-
/** Flag indicatig the task has been ack'ed. */
33+
/** Flag indicating the task has been ack'ed. */
3434
private final CompletableFuture<Void> acked = new CompletableFuture<>();
3535

3636
public final record Result(Optional<String> error) {
@@ -119,7 +119,7 @@ void setSuccess() {
119119
* {@link #setSuccess} afterwards will have no effect.
120120
*
121121
* @param error Error message. Null values are tolerated, but are only expected
122-
* to occurr due to a server's mistake.
122+
* to occur due to a server's mistake.
123123
* Do not use {@code setError(null)} if the server reports success
124124
* status for the task; prefer {@link #setSuccess} in that case.
125125
*/

0 commit comments

Comments
 (0)