Skip to content
This repository was archived by the owner on Feb 24, 2026. It is now read-only.

Commit b803863

Browse files
authored
fix: several StreamWriter issues (#213)
* fix: several StreamWriter issues - Make messages wait in flow controll to be delivered in order - Avoid recreating the BigQueryWriteClient stub during reconnection, which is not necessary. - Allow user to pass in BigQueryWriteClient stub so that it can be shared with other API calls. modified: google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1alpha2/StreamWriter.java modified: google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1alpha2/Waiter.java modified: google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1alpha2/StreamWriterTest.java * Wait doesn't need to be synchronized * fix: unlock is not called in exception case modified: google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1alpha2/StreamWriter.java modified: google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1alpha2/Waiter.java
1 parent e3314f4 commit b803863

File tree

3 files changed

+184
-139
lines changed

3 files changed

+184
-139
lines changed

google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1alpha2/StreamWriter.java

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public class StreamWriter implements AutoCloseable {
8585

8686
private final BatchingSettings batchingSettings;
8787
private final RetrySettings retrySettings;
88-
private final BigQueryWriteSettings stubSettings;
88+
private BigQueryWriteSettings stubSettings;
8989

9090
private final Lock messagesBatchLock;
9191
private final MessagesBatch messagesBatch;
@@ -142,13 +142,21 @@ private StreamWriter(Builder builder)
142142
messagesWaiter = new Waiter(this.batchingSettings.getFlowControlSettings());
143143
responseObserver = new AppendResponseObserver(this);
144144

145-
stubSettings =
146-
BigQueryWriteSettings.newBuilder()
147-
.setCredentialsProvider(builder.credentialsProvider)
148-
.setExecutorProvider(builder.executorProvider)
149-
.setTransportChannelProvider(builder.channelProvider)
150-
.setEndpoint(builder.endpoint)
151-
.build();
145+
if (builder.client == null) {
146+
stubSettings =
147+
BigQueryWriteSettings.newBuilder()
148+
.setCredentialsProvider(builder.credentialsProvider)
149+
.setExecutorProvider(builder.executorProvider)
150+
.setTransportChannelProvider(builder.channelProvider)
151+
.setEndpoint(builder.endpoint)
152+
.build();
153+
stub = BigQueryWriteClient.create(stubSettings);
154+
backgroundResourceList.add(stub);
155+
} else {
156+
stub = builder.client;
157+
}
158+
backgroundResources = new BackgroundResourceAggregation(backgroundResourceList);
159+
152160
shutdown = new AtomicBoolean(false);
153161
refreshAppend();
154162
Stream.WriteStream stream =
@@ -240,15 +248,10 @@ public ApiFuture<AppendRowsResponse> append(AppendRowsRequest message) {
240248
public void refreshAppend() throws IOException, InterruptedException {
241249
synchronized (this) {
242250
Preconditions.checkState(!shutdown.get(), "Cannot append on a shut-down writer.");
243-
if (stub != null) {
251+
// There could be a moment, stub is not yet initialized.
252+
if (clientStream != null) {
244253
clientStream.closeSend();
245-
stub.shutdown();
246-
stub.awaitTermination(1, TimeUnit.MINUTES);
247254
}
248-
backgroundResourceList.remove(stub);
249-
stub = BigQueryWriteClient.create(stubSettings);
250-
backgroundResourceList.add(stub);
251-
backgroundResources = new BackgroundResourceAggregation(backgroundResourceList);
252255
messagesBatch.resetAttachSchema();
253256
bidiStreamingCallable = stub.appendRowsCallable();
254257
clientStream = bidiStreamingCallable.splitCall(responseObserver);
@@ -314,14 +317,12 @@ public void writeAllOutstanding() {
314317
private void writeBatch(final InflightBatch inflightBatch) {
315318
if (inflightBatch != null) {
316319
AppendRowsRequest request = inflightBatch.getMergedRequest();
317-
messagesWaiter.waitOnElementCount();
318-
messagesWaiter.waitOnSizeLimit(inflightBatch.getByteSize());
319-
responseObserver.addInflightBatch(inflightBatch);
320-
clientStream.send(request);
321-
322-
synchronized (messagesWaiter) {
323-
messagesWaiter.incrementPendingCount(1);
324-
messagesWaiter.incrementPendingSize(inflightBatch.getByteSize());
320+
try {
321+
messagesWaiter.acquire(inflightBatch.getByteSize());
322+
responseObserver.addInflightBatch(inflightBatch);
323+
clientStream.send(request);
324+
} catch (FlowController.FlowControlException ex) {
325+
inflightBatch.onFailure(ex);
325326
}
326327
}
327328
}
@@ -346,14 +347,14 @@ private static final class InflightBatch {
346347
final ArrayList<Long> offsetList;
347348
final long creationTime;
348349
int attempt;
349-
int batchSizeBytes;
350+
long batchSizeBytes;
350351
long expectedOffset;
351352
Boolean attachSchema;
352353
String streamName;
353354

354355
InflightBatch(
355356
List<AppendRequestAndFutureResponse> inflightRequests,
356-
int batchSizeBytes,
357+
long batchSizeBytes,
357358
String streamName,
358359
Boolean attachSchema) {
359360
this.inflightRequests = inflightRequests;
@@ -377,7 +378,7 @@ int count() {
377378
return inflightRequests.size();
378379
}
379380

380-
int getByteSize() {
381+
long getByteSize() {
381382
return this.batchSizeBytes;
382383
}
383384

@@ -478,7 +479,9 @@ public void shutdown() {
478479
currentAlarmFuture.cancel(false);
479480
}
480481
writeAllOutstanding();
481-
messagesWaiter.waitComplete();
482+
synchronized (messagesWaiter) {
483+
messagesWaiter.waitComplete();
484+
}
482485
if (clientStream.isSendReady()) {
483486
clientStream.closeSend();
484487
}
@@ -496,7 +499,7 @@ public boolean awaitTermination(long duration, TimeUnit unit) throws Interrupted
496499
}
497500

498501
/**
499-
* Constructs a new {@link Builder} using the given topic.
502+
* Constructs a new {@link Builder} using the given stream.
500503
*
501504
* <p>Example of creating a {@code WriteStream}.
502505
*
@@ -514,7 +517,15 @@ public boolean awaitTermination(long duration, TimeUnit unit) throws Interrupted
514517
* }</pre>
515518
*/
516519
public static Builder newBuilder(String streamName) {
517-
return new Builder(streamName);
520+
return new Builder(streamName, null);
521+
}
522+
523+
/**
524+
* Constructs a new {@link Builder} using the given stream and an existing BigQueryWriteClient.
525+
*/
526+
public static Builder newBuilder(String streamName, BigQueryWriteClient client) {
527+
Preconditions.checkArgument(client != null);
528+
return new Builder(streamName, client);
518529
}
519530

520531
/** A builder of {@link StreamWriter}s. */
@@ -523,9 +534,6 @@ public static final class Builder {
523534
static final Duration MIN_RPC_TIMEOUT = Duration.ofMillis(10);
524535

525536
// Meaningful defaults.
526-
static final long DEFAULT_ELEMENT_COUNT_THRESHOLD = 100L;
527-
static final long DEFAULT_REQUEST_BYTES_THRESHOLD = 100 * 1024L; // 100 kB
528-
static final Duration DEFAULT_DELAY_THRESHOLD = Duration.ofMillis(10);
529537
static final FlowControlSettings DEFAULT_FLOW_CONTROL_SETTINGS =
530538
FlowControlSettings.newBuilder()
531539
.setLimitExceededBehavior(FlowController.LimitExceededBehavior.Block)
@@ -534,9 +542,9 @@ public static final class Builder {
534542
.build();
535543
public static final BatchingSettings DEFAULT_BATCHING_SETTINGS =
536544
BatchingSettings.newBuilder()
537-
.setDelayThreshold(DEFAULT_DELAY_THRESHOLD)
538-
.setRequestByteThreshold(DEFAULT_REQUEST_BYTES_THRESHOLD)
539-
.setElementCountThreshold(DEFAULT_ELEMENT_COUNT_THRESHOLD)
545+
.setDelayThreshold(Duration.ofMillis(10))
546+
.setRequestByteThreshold(100 * 1024L) // 100 kb
547+
.setElementCountThreshold(100L)
540548
.setFlowControlSettings(DEFAULT_FLOW_CONTROL_SETTINGS)
541549
.build();
542550
public static final RetrySettings DEFAULT_RETRY_SETTINGS =
@@ -555,6 +563,8 @@ public static final class Builder {
555563
private String streamName;
556564
private String endpoint = BigQueryWriteSettings.getDefaultEndpoint();
557565

566+
private BigQueryWriteClient client = null;
567+
558568
// Batching options
559569
BatchingSettings batchingSettings = DEFAULT_BATCHING_SETTINGS;
560570

@@ -569,8 +579,9 @@ public static final class Builder {
569579
private CredentialsProvider credentialsProvider =
570580
BigQueryWriteSettings.defaultCredentialsProviderBuilder().build();
571581

572-
private Builder(String stream) {
582+
private Builder(String stream, BigQueryWriteClient client) {
573583
this.streamName = Preconditions.checkNotNull(stream);
584+
this.client = client;
574585
}
575586

576587
/**
@@ -771,11 +782,7 @@ public void onResponse(AppendRowsResponse response) {
771782
inflightBatch.onSuccess(response);
772783
}
773784
} finally {
774-
synchronized (streamWriter.messagesWaiter) {
775-
streamWriter.messagesWaiter.incrementPendingCount(-1);
776-
streamWriter.messagesWaiter.incrementPendingSize(0 - inflightBatch.getByteSize());
777-
streamWriter.messagesWaiter.notifyAll();
778-
}
785+
streamWriter.messagesWaiter.release(inflightBatch.getByteSize());
779786
}
780787
}
781788

@@ -805,11 +812,11 @@ public void onError(Throwable t) {
805812
&& !streamWriter.shutdown.get()) {
806813
streamWriter.refreshAppend();
807814
// Currently there is a bug that it took reconnected stream 5 seconds to pick up
808-
// stream count. So wait at least 5 seconds before sending a new request.
815+
// stream count. So wait at least 7 seconds before sending a new request.
809816
Thread.sleep(
810817
Math.min(
811818
streamWriter.getRetrySettings().getInitialRetryDelay().toMillis(),
812-
Duration.ofSeconds(5).toMillis()));
819+
Duration.ofSeconds(7).toMillis()));
813820
streamWriter.writeBatch(inflightBatch);
814821
synchronized (streamWriter.currentRetries) {
815822
streamWriter.currentRetries++;
@@ -837,19 +844,15 @@ public void onError(Throwable t) {
837844
}
838845
}
839846
} finally {
840-
synchronized (streamWriter.messagesWaiter) {
841-
streamWriter.messagesWaiter.incrementPendingCount(-1);
842-
streamWriter.messagesWaiter.incrementPendingSize(0 - inflightBatch.getByteSize());
843-
streamWriter.messagesWaiter.notifyAll();
844-
}
847+
streamWriter.messagesWaiter.release(inflightBatch.getByteSize());
845848
}
846849
}
847850
};
848851

849852
// This class controls how many messages are going to be sent out in a batch.
850853
private static class MessagesBatch {
851854
private List<AppendRequestAndFutureResponse> messages;
852-
private int batchedBytes;
855+
private long batchedBytes;
853856
private final BatchingSettings batchingSettings;
854857
private Boolean attachSchema = true;
855858
private final String streamName;
@@ -882,7 +885,7 @@ private boolean isEmpty() {
882885
return messages.isEmpty();
883886
}
884887

885-
private int getBatchedBytes() {
888+
private long getBatchedBytes() {
886889
return batchedBytes;
887890
}
888891

0 commit comments

Comments
 (0)