Skip to content

Commit

Permalink
Patch CloudSqlInstance
Browse files Browse the repository at this point in the history
  • Loading branch information
johanblumenberg committed Sep 15, 2022
1 parent 8b54240 commit bf94b6b
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 18 deletions.
52 changes: 52 additions & 0 deletions gcp-cloud-sql-serverless-patch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Google Cloud SQL Connector patch

This is a patch for CloudSqlInstance.java that fixes a problem in
`com.google.cloud.sql:jdbc-socket-factory-core:1.6.3` when running in a serverless environment.

Original file copied from https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory/blob/v1.6.3/core/src/main/java/com/google/cloud/sql/core/CloudSqlInstance.java

## Background

The `CloudSqlInstance` class is used in the Cloud SQL connector. It is responsible for fetching and
refreshing certificates that are used in the communication with the Cloud SQL instance.

This class is creating a background job that periodically fetches a new certificate, when the current
certificate is about to expire.

This class is used both in the `jdbc` and the `r2dbc` connectors.

## Problem

When running serverless containers such as Cloud Functions or Cloud Run with CPU throttling, you are
not allowed to keep background processes.

This causes the background process to fail to refresh the certificate now and then.

```
java.lang.RuntimeException: Failed to update metadata for Cloud SQL instance.
at com.google.cloud.sql.core.CloudSqlInstance.addExceptionContext(CloudSqlInstance.java:598)
at com.google.cloud.sql.core.CloudSqlInstance.fetchMetadata(CloudSqlInstance.java:505)
at com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleTask.runInterruptibly(TrustedListenableFutureTask.java:131)
at com.google.common.util.concurrent.InterruptibleTask.run(InterruptibleTask.java:74)
at com.google.common.util.concurrent.TrustedListenableFutureTask.run(TrustedListenableFutureTask.java:82)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.net.SocketException: Unexpected end of file from server
at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:866)
```

## Solution

This patch changes the `CloudSqlInstance` class to refresh the certificate when it is needed instead of
as a background job.

## Usage

To use the patch, place this jar file on the class path, before the `jdbc-socket-factory-core` jar file.

This patch is based on version `1.6.3` of `jdbc-socket-factory-core`, and it might not work with any other
version.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -133,6 +132,8 @@ class CloudSqlInstance {
ListeningScheduledExecutorService executor,
ListenableFuture<KeyPair> keyPair) {

logger.log(Level.INFO, "Using patched CloudSqlInstance");

Matcher matcher = CONNECTION_NAME.matcher(connectionName);
checkArgument(
matcher.matches(),
Expand Down Expand Up @@ -269,6 +270,9 @@ private InstanceData getInstanceData() {
* block until required instance data is available.
*/
SSLSocket createSslSocket() throws IOException {
if (needRefresh()) {
forceRefresh();
}
return (SSLSocket) getInstanceData().getSslContext().getSocketFactory().createSocket();
}

Expand Down Expand Up @@ -304,13 +308,13 @@ String getPreferredIp(List<String> preferredTypes) {
* @return {@code true} if successfully scheduled, or {@code false} otherwise.
*/
boolean forceRefresh() {
logger.fine("forceRefresh()");

synchronized (instanceDataGuard) {
// If a scheduled refresh hasn't started, perform one immediately
if (nextInstanceData.cancel(false)) {
if (nextInstanceData.isDone()) {
currentInstanceData = performRefresh();
nextInstanceData = Futures.immediateFuture(currentInstanceData);
} else {
// Otherwise it's already running, so just block on the results
currentInstanceData = blockOnNestedFuture(nextInstanceData, executor);
}
return true;
Expand Down Expand Up @@ -372,11 +376,6 @@ public void onSuccess(InstanceData instanceData) {
synchronized (instanceDataGuard) {
// update currentInstanceData with the most recent results
currentInstanceData = refreshFuture;
// schedule a replacement before the SSLContext expires;
nextInstanceData = executor
.schedule(() -> performRefresh(),
secondsUntilRefresh(),
TimeUnit.SECONDS);
}
}

Expand Down Expand Up @@ -564,21 +563,25 @@ private Date getTokenExpirationTime() {
return credentials.get().getAccessToken().getExpirationTime();
}

private long secondsUntilRefresh() {
Duration refreshBuffer = enableIamAuth ? IAM_AUTH_REFRESH_BUFFER : DEFAULT_REFRESH_BUFFER;

Date expiration = getInstanceData().getExpiration();
private boolean needRefresh() {
InstanceData instanceData = null;
try {
instanceData = getInstanceData();
} catch (Exception e) {
// this means the result was invalid
return true;
}

Duration refreshBuffer = enableIamAuth ? IAM_AUTH_REFRESH_BUFFER : DEFAULT_REFRESH_BUFFER;
Date expiration = instanceData.getExpiration();
Duration timeUntilRefresh = Duration.between(Instant.now(), expiration.toInstant())
.minus(refreshBuffer);

if (timeUntilRefresh.isNegative()) {
// If the time until the certificate expires is less than the buffer, schedule the refresh
// closer to the expiration time
timeUntilRefresh = Duration.between(Instant.now(), expiration.toInstant())
.minus(Duration.ofSeconds(5));
return true;
} else {
return false;
}
return timeUntilRefresh.getSeconds();
}

/**
Expand Down Expand Up @@ -627,6 +630,9 @@ private RuntimeException addExceptionContext(IOException ex, String fallbackDesc
}

SslData getSslData() {
if (needRefresh()) {
forceRefresh();
}
return getInstanceData().getSslData();
}

Expand Down

0 comments on commit bf94b6b

Please sign in to comment.