Skip to content

Commit 1a69192

Browse files
feat: fix some todos and reject stream writer if it's created with mixed behavior of passed in client or not (#1803)
* feat: Split writer into connection worker and wrapper, this is a prerequisite for multiplexing client * feat: add connection worker pool skeleton, used for multiplexing client * feat: add Load api for connection worker for multiplexing client * feat: add multiplexing support to connection worker. We will treat every new stream name as a switch of destinationt * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * feat: port the multiplexing client core algorithm and basic tests also fixed a tiny bug inside fake bigquery write impl for getting thre response from offset * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * feat: wire multiplexing connection pool to stream writer * feat: some fixes for multiplexing client * feat: fix some todos, and reject the mixed behavior of passed in client or not Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 6380f71 commit 1a69192

File tree

3 files changed

+78
-46
lines changed

3 files changed

+78
-46
lines changed

google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1/ConnectionWorkerPool.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.io.IOException;
2626
import java.util.Collections;
2727
import java.util.Comparator;
28+
import java.util.HashMap;
2829
import java.util.HashSet;
2930
import java.util.List;
3031
import java.util.Map;
@@ -324,18 +325,19 @@ private ConnectionWorker createConnectionWorker(String streamName, ProtoSchema w
324325
// Though atomic integer is super lightweight, add extra if check in case adding future logic.
325326
testValueCreateConnectionCount.getAndIncrement();
326327
}
327-
// TODO(gaole): figure out a better way to handle header / request body mismatch
328-
// currently we use different header for the client in each connection worker to be different
328+
// currently we use different header for the client in each connection worker to be different
329329
// as the backend require the header to have the same write_stream field as request body.
330330
BigQueryWriteClient clientAfterModification = client;
331331
if (ownsBigQueryWriteClient) {
332332
BigQueryWriteSettings settings = client.getSettings();
333+
334+
// Every header to write api is required to set write_stream in the header to help routing
335+
// the request to correct region.
336+
HashMap<String, String> newHeaders = new HashMap<>();
337+
newHeaders.putAll(settings.toBuilder().getHeaderProvider().getHeaders());
338+
newHeaders.put("x-goog-request-params", "write_stream=" + streamName);
333339
BigQueryWriteSettings stubSettings =
334-
settings
335-
.toBuilder()
336-
.setHeaderProvider(
337-
FixedHeaderProvider.create("x-goog-request-params", "write_stream=" + streamName))
338-
.build();
340+
settings.toBuilder().setHeaderProvider(FixedHeaderProvider.create(newHeaders)).build();
339341
clientAfterModification = BigQueryWriteClient.create(stubSettings);
340342
}
341343
ConnectionWorker connectionWorker =

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

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import java.util.Objects;
3434
import java.util.UUID;
3535
import java.util.concurrent.ConcurrentHashMap;
36-
import java.util.concurrent.TimeUnit;
3736
import java.util.logging.Logger;
3837

3938
/**
@@ -68,6 +67,9 @@ public class StreamWriter implements AutoCloseable {
6867
*/
6968
private final SingleConnectionOrConnectionPool singleConnectionOrConnectionPool;
7069

70+
/** Test only param to tell how many times a client is created. */
71+
private static int testOnlyClientCreatedTimes = 0;
72+
7173
/**
7274
* Static map from {@link ConnectionPoolKey} to connection pool. Note this map is static to be
7375
* shared by every stream writer in the same process.
@@ -169,25 +171,7 @@ private StreamWriter(Builder builder) throws IOException {
169171
this.streamName = builder.streamName;
170172
this.writerSchema = builder.writerSchema;
171173
this.location = builder.location;
172-
boolean ownsBigQueryWriteClient;
173-
if (builder.client == null) {
174-
BigQueryWriteSettings stubSettings =
175-
BigQueryWriteSettings.newBuilder()
176-
.setCredentialsProvider(builder.credentialsProvider)
177-
.setTransportChannelProvider(builder.channelProvider)
178-
.setBackgroundExecutorProvider(builder.executorProvider)
179-
.setEndpoint(builder.endpoint)
180-
// (b/185842996): Temporily fix this by explicitly providing the header.
181-
.setHeaderProvider(
182-
FixedHeaderProvider.create(
183-
"x-goog-request-params", "write_stream=" + this.streamName))
184-
.build();
185-
client = BigQueryWriteClient.create(stubSettings);
186-
ownsBigQueryWriteClient = true;
187-
} else {
188-
client = builder.client;
189-
ownsBigQueryWriteClient = false;
190-
}
174+
boolean ownsBigQueryWriteClient = builder.client == null;
191175
if (!builder.enableConnectionPool) {
192176
this.singleConnectionOrConnectionPool =
193177
SingleConnectionOrConnectionPool.ofSingleConnection(
@@ -198,7 +182,7 @@ private StreamWriter(Builder builder) throws IOException {
198182
builder.maxInflightBytes,
199183
builder.limitExceededBehavior,
200184
builder.traceId,
201-
client,
185+
getBigQueryWriteClient(builder),
202186
ownsBigQueryWriteClient));
203187
} else {
204188
if (builder.location == null || builder.location.isEmpty()) {
@@ -212,29 +196,39 @@ private StreamWriter(Builder builder) throws IOException {
212196
SingleConnectionOrConnectionPool.ofConnectionPool(
213197
connectionPoolMap.computeIfAbsent(
214198
ConnectionPoolKey.create(builder.location),
215-
(key) ->
216-
new ConnectionWorkerPool(
199+
(key) -> {
200+
try {
201+
return new ConnectionWorkerPool(
217202
builder.maxInflightRequest,
218203
builder.maxInflightBytes,
219204
builder.limitExceededBehavior,
220205
builder.traceId,
221-
client,
222-
ownsBigQueryWriteClient)));
206+
getBigQueryWriteClient(builder),
207+
ownsBigQueryWriteClient);
208+
} catch (IOException e) {
209+
throw new RuntimeException(e);
210+
}
211+
}));
223212
validateFetchedConnectonPool(builder);
224-
// Shut down the passed in client. Internally we will create another client inside connection
225-
// pool for every new connection worker.
226-
// TODO(gaole): instead of perform close outside of pool approach, change to always create
227-
// new client in connection.
228-
if (client != singleConnectionOrConnectionPool.connectionWorkerPool().bigQueryWriteClient()
229-
&& ownsBigQueryWriteClient) {
230-
client.shutdown();
231-
try {
232-
client.awaitTermination(150, TimeUnit.SECONDS);
233-
} catch (InterruptedException unused) {
234-
// Ignore interruption as this client is not used.
235-
}
236-
client.close();
237-
}
213+
}
214+
}
215+
216+
private BigQueryWriteClient getBigQueryWriteClient(Builder builder) throws IOException {
217+
if (builder.client == null) {
218+
BigQueryWriteSettings stubSettings =
219+
BigQueryWriteSettings.newBuilder()
220+
.setCredentialsProvider(builder.credentialsProvider)
221+
.setTransportChannelProvider(builder.channelProvider)
222+
.setEndpoint(builder.endpoint)
223+
// (b/185842996): Temporily fix this by explicitly providing the header.
224+
.setHeaderProvider(
225+
FixedHeaderProvider.create(
226+
"x-goog-request-params", "write_stream=" + this.streamName))
227+
.build();
228+
testOnlyClientCreatedTimes++;
229+
return BigQueryWriteClient.create(stubSettings);
230+
} else {
231+
return builder.client;
238232
}
239233
}
240234

@@ -245,6 +239,10 @@ private void validateFetchedConnectonPool(StreamWriter.Builder builder) {
245239
this.singleConnectionOrConnectionPool.connectionWorkerPool().getTraceId(),
246240
builder.traceId)) {
247241
paramsValidatedFailed = "Trace id";
242+
} else if (!Objects.equals(
243+
this.singleConnectionOrConnectionPool.connectionWorkerPool().ownsBigQueryWriteClient(),
244+
builder.client == null)) {
245+
paramsValidatedFailed = "Whether using passed in clients";
248246
} else if (!Objects.equals(
249247
this.singleConnectionOrConnectionPool.connectionWorkerPool().limitExceededBehavior(),
250248
builder.limitExceededBehavior)) {
@@ -361,6 +359,17 @@ SingleConnectionOrConnectionPool.Kind getConnectionOperationType() {
361359
return singleConnectionOrConnectionPool.getKind();
362360
}
363361

362+
@VisibleForTesting
363+
static int getTestOnlyClientCreatedTimes() {
364+
return testOnlyClientCreatedTimes;
365+
}
366+
367+
@VisibleForTesting
368+
static void cleanUp() {
369+
testOnlyClientCreatedTimes = 0;
370+
connectionPoolMap.clear();
371+
}
372+
364373
/** A builder of {@link StreamWriter}s. */
365374
public static final class Builder {
366375
private static final long DEFAULT_MAX_INFLIGHT_REQUESTS = 1000L;

google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1/StreamWriterTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,15 @@ public void setUp() throws Exception {
8282
.setCredentialsProvider(NoCredentialsProvider.create())
8383
.setTransportChannelProvider(serviceHelper.createChannelProvider())
8484
.build());
85+
StreamWriter.cleanUp();
8586
}
8687

8788
@After
8889
public void tearDown() throws Exception {
8990
log.info("tearDown called");
9091
client.close();
9192
serviceHelper.stop();
93+
StreamWriter.cleanUp();
9294
}
9395

9496
private StreamWriter getMultiplexingTestStreamWriter() throws IOException {
@@ -722,6 +724,25 @@ public void testInitialization_operationKind() throws Exception {
722724
}
723725
}
724726

727+
@Test
728+
public void createStreamWithDifferentWhetherOwnsClient() throws Exception {
729+
StreamWriter streamWriter1 = getMultiplexingTestStreamWriter();
730+
731+
assertThrows(
732+
IllegalArgumentException.class,
733+
new ThrowingRunnable() {
734+
@Override
735+
public void run() throws Throwable {
736+
StreamWriter.newBuilder(TEST_STREAM)
737+
.setWriterSchema(createProtoSchema())
738+
.setTraceId(TEST_TRACE_ID)
739+
.setLocation("US")
740+
.setEnableConnectionPool(true)
741+
.build();
742+
}
743+
});
744+
}
745+
725746
// Timeout to ensure close() doesn't wait for done callback timeout.
726747
@Test(timeout = 10000)
727748
public void testCloseDisconnectedStream() throws Exception {

0 commit comments

Comments
 (0)