Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix MySQL high availability not working in native mode #8052

Merged
merged 2 commits into from
Mar 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,18 +1,41 @@
package io.quarkus.jdbc.mysql.deployment;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import com.mysql.cj.MysqlConnection;
import com.mysql.cj.WarningListener;
import com.mysql.cj.conf.PropertySet;
import com.mysql.cj.jdbc.JdbcConnection;
import com.mysql.cj.jdbc.JdbcPreparedStatement;
import com.mysql.cj.jdbc.JdbcPropertySet;
import com.mysql.cj.jdbc.JdbcStatement;
import com.mysql.cj.jdbc.ha.LoadBalancedConnection;
import com.mysql.cj.jdbc.ha.ReplicationConnection;
import com.mysql.cj.jdbc.result.ResultSetInternalMethods;
import com.mysql.cj.protocol.Resultset;

import io.quarkus.agroal.deployment.JdbcDriverBuildItem;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.processor.BuiltinScope;
import io.quarkus.datasource.common.runtime.DatabaseKind;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem;
import io.quarkus.deployment.builditem.NativeImageEnableAllTimeZonesBuildItem;
import io.quarkus.deployment.builditem.SslNativeConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.jdbc.mysql.runtime.MySQLAgroalConnectionConfigurer;
import io.quarkus.jdbc.mysql.runtime.MySQLRecorder;

public class JDBCMySQLProcessor {

Expand All @@ -39,6 +62,12 @@ void configureAgroalConnection(BuildProducer<AdditionalBeanBuildItem> additional
}
}

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void abandonedConnectionCleanUp(MySQLRecorder recorder) {
recorder.startAbandonedConnectionCleanup();
}

@BuildStep
NativeImageResourceBuildItem resource() {
return new NativeImageResourceBuildItem("com/mysql/cj/util/TimeZoneMapping.properties");
Expand All @@ -53,4 +82,30 @@ NativeImageEnableAllCharsetsBuildItem enableAllCharsets() {
NativeImageEnableAllTimeZonesBuildItem enableAllTimeZones() {
return new NativeImageEnableAllTimeZonesBuildItem();
}

@BuildStep
List<NativeImageProxyDefinitionBuildItem> registerProxies() {
List<NativeImageProxyDefinitionBuildItem> proxies = new ArrayList<>();
proxies.add(new NativeImageProxyDefinitionBuildItem(JdbcConnection.class.getName()));
proxies.add(new NativeImageProxyDefinitionBuildItem(MysqlConnection.class.getName()));
proxies.add(new NativeImageProxyDefinitionBuildItem(Statement.class.getName()));
proxies.add(new NativeImageProxyDefinitionBuildItem(AutoCloseable.class.getName()));
proxies.add(new NativeImageProxyDefinitionBuildItem(JdbcStatement.class.getName()));
proxies.add(new NativeImageProxyDefinitionBuildItem(Connection.class.getName()));
proxies.add(new NativeImageProxyDefinitionBuildItem(ResultSet.class.getName()));
proxies.add(
new NativeImageProxyDefinitionBuildItem(JdbcPreparedStatement.class.getName(), JdbcStatement.class.getName()));
proxies.add(new NativeImageProxyDefinitionBuildItem(JdbcPropertySet.class.getName(), PropertySet.class.getName(),
Serializable.class.getName()));
proxies.add(
new NativeImageProxyDefinitionBuildItem(Resultset.class.getName(), ResultSetInternalMethods.class.getName()));
proxies.add(new NativeImageProxyDefinitionBuildItem(LoadBalancedConnection.class.getName(),
JdbcConnection.class.getName()));
proxies.add(
new NativeImageProxyDefinitionBuildItem(ReplicationConnection.class.getName(), JdbcConnection.class.getName()));
proxies.add(
new NativeImageProxyDefinitionBuildItem(ResultSetInternalMethods.class.getName(),
WarningListener.class.getName(), Resultset.class.getName()));
return proxies;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
package io.quarkus.jdbc.mysql.deployment;

import java.sql.Wrapper;

import com.mysql.cj.conf.url.FailoverConnectionUrl;
import com.mysql.cj.conf.url.FailoverDnsSrvConnectionUrl;
import com.mysql.cj.conf.url.LoadBalanceConnectionUrl;
import com.mysql.cj.conf.url.LoadBalanceDnsSrvConnectionUrl;
import com.mysql.cj.conf.url.ReplicationConnectionUrl;
import com.mysql.cj.conf.url.ReplicationDnsSrvConnectionUrl;
import com.mysql.cj.conf.url.SingleConnectionUrl;
import com.mysql.cj.conf.url.XDevApiConnectionUrl;
import com.mysql.cj.conf.url.XDevApiDnsSrvConnectionUrl;
import com.mysql.cj.exceptions.AssertionFailedException;
import com.mysql.cj.exceptions.CJCommunicationsException;
import com.mysql.cj.exceptions.CJConnectionFeatureNotAvailableException;
Expand All @@ -26,6 +37,10 @@
import com.mysql.cj.exceptions.UnableToConnectException;
import com.mysql.cj.exceptions.UnsupportedConnectionStringException;
import com.mysql.cj.exceptions.WrongArgumentException;
import com.mysql.cj.jdbc.Driver;
import com.mysql.cj.jdbc.ha.NdbLoadBalanceExceptionChecker;
import com.mysql.cj.jdbc.ha.StandardLoadBalanceExceptionChecker;
import com.mysql.cj.log.StandardLogger;
import com.mysql.cj.protocol.AsyncSocketFactory;
import com.mysql.cj.protocol.NamedPipeSocketFactory;
import com.mysql.cj.protocol.SocksProxySocketFactory;
Expand All @@ -39,28 +54,30 @@ public final class MySQLJDBCReflections {

@BuildStep
void registerDriverForReflection(BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, com.mysql.cj.jdbc.Driver.class.getName()));

reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, Driver.class.getName()));
reflectiveClass.produce(
new ReflectiveClassBuildItem(false, false, com.mysql.cj.conf.url.FailoverDnsSrvConnectionUrl.class.getName()));
new ReflectiveClassBuildItem(false, false, FailoverDnsSrvConnectionUrl.class.getName()));
reflectiveClass.produce(
new ReflectiveClassBuildItem(false, false, com.mysql.cj.conf.url.FailoverConnectionUrl.class.getName()));
new ReflectiveClassBuildItem(false, false, FailoverConnectionUrl.class.getName()));
reflectiveClass
.produce(new ReflectiveClassBuildItem(false, false, com.mysql.cj.conf.url.SingleConnectionUrl.class.getName()));
.produce(new ReflectiveClassBuildItem(false, false, SingleConnectionUrl.class.getName()));
reflectiveClass.produce(
new ReflectiveClassBuildItem(false, false, com.mysql.cj.conf.url.LoadBalanceConnectionUrl.class.getName()));
new ReflectiveClassBuildItem(false, false, LoadBalanceConnectionUrl.class.getName()));
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false,
com.mysql.cj.conf.url.LoadBalanceDnsSrvConnectionUrl.class.getName()));
LoadBalanceDnsSrvConnectionUrl.class.getName()));
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false,
com.mysql.cj.conf.url.ReplicationDnsSrvConnectionUrl.class.getName()));
ReplicationDnsSrvConnectionUrl.class.getName()));
reflectiveClass.produce(
new ReflectiveClassBuildItem(false, false, com.mysql.cj.conf.url.ReplicationConnectionUrl.class.getName()));
new ReflectiveClassBuildItem(false, false, ReplicationConnectionUrl.class.getName()));
reflectiveClass.produce(
new ReflectiveClassBuildItem(false, false, com.mysql.cj.conf.url.XDevApiConnectionUrl.class.getName()));
new ReflectiveClassBuildItem(false, false, XDevApiConnectionUrl.class.getName()));
reflectiveClass.produce(
new ReflectiveClassBuildItem(false, false, com.mysql.cj.conf.url.XDevApiDnsSrvConnectionUrl.class.getName()));
new ReflectiveClassBuildItem(false, false, XDevApiDnsSrvConnectionUrl.class.getName()));
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false,
com.mysql.cj.jdbc.ha.LoadBalancedAutoCommitInterceptor.class.getName()));
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, com.mysql.cj.log.StandardLogger.class.getName()));
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, StandardLogger.class.getName()));
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, Wrapper.class.getName()));
}

@BuildStep
Expand Down Expand Up @@ -103,5 +120,9 @@ void registerExceptionsForReflection(BuildProducer<ReflectiveClassBuildItem> ref
reflectiveClass
.produce(new ReflectiveClassBuildItem(false, false, UnsupportedConnectionStringException.class.getName()));
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, WrongArgumentException.class.getName()));
reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, "com.mysql.cj.jdbc.MysqlXAException"));
reflectiveClass
.produce(new ReflectiveClassBuildItem(false, false, StandardLoadBalanceExceptionChecker.class.getName()));
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, NdbLoadBalanceExceptionChecker.class.getName()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.quarkus.jdbc.mysql.runtime;

import org.graalvm.nativeimage.ImageInfo;

import io.quarkus.jdbc.mysql.runtime.graal.AbandonedConnectionCleanupThreadSubstitutions;
import io.quarkus.runtime.annotations.Recorder;

@Recorder
public class MySQLRecorder {

public void startAbandonedConnectionCleanup() {
if (ImageInfo.inImageRuntimeCode()) {
AbandonedConnectionCleanupThreadSubstitutions.startCleanUp();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package io.quarkus.jdbc.mysql.runtime.graal;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.mysql.cj.MysqlConnection;
import com.mysql.cj.jdbc.AbandonedConnectionCleanupThread;
import com.mysql.cj.protocol.NetworkResources;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;

/**
* This is to disable connection clean up thread {@link AbandonedConnectionCleanupThread} which launches a thread on a static
* block.
* GraalVM is not happy about that. The issue might have been fixed with https://github.com/oracle/graal/pull/1542 but we have
* to wait for a proper GraalVM release, so we substitute the class and start the clean up thread manually when running in
* native image.
*/
@Substitute
@TargetClass(AbandonedConnectionCleanupThread.class)
final public class AbandonedConnectionCleanupThreadSubstitutions implements Runnable {

private static Set<ConnectionFinalizerPhantomReference> connectionFinalizerPhantomReferences;
private static ReferenceQueue<MysqlConnection> mysqlConnectionReferenceQueue;

private static ExecutorService executorService;

@Substitute
private AbandonedConnectionCleanupThreadSubstitutions() {
}

public static void startCleanUp() {
connectionFinalizerPhantomReferences = ConcurrentHashMap.newKeySet();
mysqlConnectionReferenceQueue = new ReferenceQueue<>();
executorService = Executors.newSingleThreadScheduledExecutor();
executorService.execute(new AbandonedConnectionCleanupThreadSubstitutions());
}

@Substitute
public void run() {
for (;;) {
try {
Reference<? extends MysqlConnection> reference = mysqlConnectionReferenceQueue.remove(5000);
if (reference != null) {
finalizeResourceAndRemoveReference((ConnectionFinalizerPhantomReference) reference);
}
} catch (InterruptedException e) {
synchronized (mysqlConnectionReferenceQueue) {
Reference<? extends MysqlConnection> reference;
while ((reference = mysqlConnectionReferenceQueue.poll()) != null) {
finalizeResourceAndRemoveReference((ConnectionFinalizerPhantomReference) reference);
}
connectionFinalizerPhantomReferences.clear();
}

return;
} catch (Exception ex) {
}
}
}

@Substitute
protected static void trackConnection(MysqlConnection conn, NetworkResources io) {
synchronized (mysqlConnectionReferenceQueue) {
ConnectionFinalizerPhantomReference reference = new ConnectionFinalizerPhantomReference(conn, io,
mysqlConnectionReferenceQueue);
connectionFinalizerPhantomReferences.add(reference);
}
}

private static void finalizeResourceAndRemoveReference(ConnectionFinalizerPhantomReference reference) {
try {
reference.finalizeResources();
reference.clear();
} finally {
connectionFinalizerPhantomReferences.remove(reference);
}
}

private static class ConnectionFinalizerPhantomReference extends PhantomReference<MysqlConnection> {
private NetworkResources networkResources;

ConnectionFinalizerPhantomReference(MysqlConnection conn, NetworkResources networkResources,
ReferenceQueue<? super MysqlConnection> refQueue) {
super(conn, refQueue);
this.networkResources = networkResources;
}

void finalizeResources() {
if (this.networkResources != null) {
try {
this.networkResources.forceClose();
} finally {
this.networkResources = null;
}
}
}
}
}

This file was deleted.