-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13977 from Sanne/ReactiveHealth
Reactive Connection Pools: always use TLOCAL, HealthCheck refactoring
- Loading branch information
Showing
10 changed files
with
189 additions
and
205 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
120 changes: 120 additions & 0 deletions
120
...e/src/main/java/io/quarkus/reactive/datasource/runtime/ReactiveDatasourceHealthCheck.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package io.quarkus.reactive.datasource.runtime; | ||
|
||
import java.util.Map; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.TimeoutException; | ||
|
||
import javax.enterprise.inject.spi.Bean; | ||
|
||
import org.eclipse.microprofile.health.HealthCheck; | ||
import org.eclipse.microprofile.health.HealthCheckResponse; | ||
import org.eclipse.microprofile.health.HealthCheckResponseBuilder; | ||
import org.jboss.logging.Logger; | ||
|
||
import io.quarkus.datasource.common.runtime.DataSourceUtil; | ||
import io.quarkus.reactive.datasource.ReactiveDataSource; | ||
import io.vertx.core.AsyncResult; | ||
import io.vertx.core.Context; | ||
import io.vertx.core.Vertx; | ||
import io.vertx.sqlclient.Pool; | ||
import io.vertx.sqlclient.Row; | ||
import io.vertx.sqlclient.RowSet; | ||
|
||
public abstract class ReactiveDatasourceHealthCheck implements HealthCheck { | ||
|
||
private static final Logger log = Logger.getLogger(ReactiveDatasourceHealthCheck.class); | ||
|
||
private final Map<String, Pool> pools = new ConcurrentHashMap<>(); | ||
private final String healthCheckResponseName; | ||
private final String healthCheckSQL; | ||
|
||
protected ReactiveDatasourceHealthCheck(String healthCheckResponseName, String healthCheckSQL) { | ||
this.healthCheckResponseName = healthCheckResponseName; | ||
this.healthCheckSQL = healthCheckSQL; | ||
} | ||
|
||
protected void addPool(String name, Pool p) { | ||
final Pool previous = pools.put(name, p); | ||
if (previous != null) { | ||
throw new IllegalStateException("Duplicate pool name: " + name); | ||
} | ||
} | ||
|
||
@Override | ||
public HealthCheckResponse call() { | ||
HealthCheckResponseBuilder builder = HealthCheckResponse.named(healthCheckResponseName); | ||
builder.up(); | ||
|
||
for (Map.Entry<String, Pool> pgPoolEntry : pools.entrySet()) { | ||
final String dataSourceName = pgPoolEntry.getKey(); | ||
final Pool pgPool = pgPoolEntry.getValue(); | ||
try { | ||
CompletableFuture<Void> databaseConnectionAttempt = new CompletableFuture<>(); | ||
Context context = Vertx.currentContext(); | ||
if (context != null) { | ||
log.debug("Run health check on the current Vert.x context"); | ||
context.runOnContext(v -> { | ||
pgPool.query(healthCheckSQL) | ||
.execute(ar -> { | ||
checkFailure(ar, builder, dataSourceName); | ||
databaseConnectionAttempt.complete(null); | ||
}); | ||
}); | ||
} else { | ||
log.warn("Vert.x context unavailable to perform healthcheck of reactive datasource `" + dataSourceName | ||
+ "`. This is unlikely to work correctly."); | ||
pgPool.query(healthCheckSQL) | ||
.execute(ar -> { | ||
checkFailure(ar, builder, dataSourceName); | ||
databaseConnectionAttempt.complete(null); | ||
}); | ||
} | ||
|
||
//20 seconds is rather high, but using just 10 is often not enough on slow CI | ||
//systems, especially if the connections have to be established for the first time. | ||
databaseConnectionAttempt.get(20, TimeUnit.SECONDS); | ||
builder.withData(dataSourceName, "up"); | ||
} catch (RuntimeException | ExecutionException exception) { | ||
operationsError(dataSourceName, exception); | ||
builder.down(); | ||
builder.withData(dataSourceName, "down - connection failed: " + exception.getMessage()); | ||
} catch (InterruptedException e) { | ||
log.warn("Interrupted while obtaining database connection for healthcheck of datasource " + dataSourceName); | ||
Thread.currentThread().interrupt(); | ||
return builder.build(); | ||
} catch (TimeoutException e) { | ||
log.warn("Timed out while waiting for an available connection to perform healthcheck of datasource " | ||
+ dataSourceName); | ||
builder.down(); | ||
builder.withData(dataSourceName, "timed out, unable to obtain connection to perform healthcheck of datasource"); | ||
} | ||
} | ||
|
||
return builder.build(); | ||
} | ||
|
||
private void operationsError(final String datasourceName, final Throwable cause) { | ||
log.warn("Error obtaining database connection for healthcheck of datasource '" + datasourceName + '\'', cause); | ||
} | ||
|
||
private void checkFailure(AsyncResult<RowSet<Row>> ar, HealthCheckResponseBuilder builder, String dataSourceName) { | ||
if (ar.failed()) { | ||
operationsError(dataSourceName, ar.cause()); | ||
builder.down(); | ||
builder.withData(dataSourceName, "down - connection failed: " + ar.cause().getMessage()); | ||
} | ||
} | ||
|
||
protected String getPoolName(Bean<?> bean) { | ||
for (Object qualifier : bean.getQualifiers()) { | ||
if (qualifier instanceof ReactiveDataSource) { | ||
return ((ReactiveDataSource) qualifier).value(); | ||
} | ||
} | ||
return DataSourceUtil.DEFAULT_DATASOURCE_NAME; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.