All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.datarouter.client.mysql.execution.SessionExecutor Maven / Gradle / Ivy

There is a newer version: 0.0.125
Show newest version
/*
 * Copyright © 2009 HotPads ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.datarouter.client.mysql.execution;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException;

import io.datarouter.client.mysql.MysqlConnectionClientManager;
import io.datarouter.client.mysql.TxnClientManager;
import io.datarouter.client.mysql.op.BaseMysqlOp;
import io.datarouter.instrumentation.trace.TraceSpanGroupType;
import io.datarouter.instrumentation.trace.TracerTool;
import io.datarouter.model.exception.DataAccessException;
import io.datarouter.storage.client.ClientId;
import io.datarouter.storage.client.ConnectionHandle;
import io.datarouter.storage.client.DatarouterClients;
import io.datarouter.storage.op.executor.impl.SessionExecutorPleaseRetryException;
import io.datarouter.util.string.StringTool;

@Singleton
public class SessionExecutor{
	private static final Logger logger = LoggerFactory.getLogger(SessionExecutor.class);

	private static final String READ_ONLY_ERROR_MESSAGE = "The MySQL server is running with the --read-only option"
			+ " so it cannot execute this statement";

	public static final Set> ROLLED_BACK_EXCEPTIONS = Set.of(
			MySQLTransactionRollbackException.class);

	@Inject
	private DatarouterClients datarouterClients;

	public  SessionExecutorCallable makeCallable(BaseMysqlOp parallelTxnOp, String traceName){
		return () -> run(parallelTxnOp, traceName);
	}

	public  T runWithoutRetries(BaseMysqlOp parallelTxnOp){
		return runWithoutRetries(parallelTxnOp, null);
	}

	public  T runWithoutRetries(BaseMysqlOp parallelTxnOp, String traceName){
		try{
			return run(parallelTxnOp, traceName);
		}catch(SessionExecutorPleaseRetryException e){
			logger.warn("no retrying operation", e);
			throw new RuntimeException(e);
		}
	}

	public  T run(BaseMysqlOp parallelTxnOp) throws SessionExecutorPleaseRetryException{
		return run(parallelTxnOp, null);
	}

	public  T run(BaseMysqlOp parallelTxnOp, String traceName) throws SessionExecutorPleaseRetryException{
		ClientId clientId = parallelTxnOp.getClientId();
		TxnClientManager clientManager = (TxnClientManager)datarouterClients.getClientManager(clientId);
		try{
			startTrace(traceName);
			clientManager.reserveConnection(clientId);
			return innerRun(clientId, clientManager, parallelTxnOp);
		}finally{
			finishTrace(traceName);
		}
	}

	private  T innerRun(ClientId clientId, TxnClientManager clientManager, BaseMysqlOp parallelTxnOp)
	throws SessionExecutorPleaseRetryException{
		try{
			ConnectionHandle connectionHandle = clientManager.getExistingHandle(clientId);
			if(clientManager.getExistingHandle(clientId).isOutermostHandle()){
				clientManager.beginTxn(clientId, parallelTxnOp.getIsolation(), parallelTxnOp.isAutoCommit());
			}

			//begin user code
			T result = parallelTxnOp.runOnce();
			//end user code

			if(connectionHandle.isOutermostHandle()){
				String spanName = "commit " + clientId.getName();
				try(var $ = TracerTool.startSpan(spanName, TraceSpanGroupType.DATABASE)){
					clientManager.commitTxn(clientId);
				}
			}
			return result;
		}catch(Exception e){
			if(e instanceof DataAccessException){
				Throwable cause = e.getCause();
				if(cause instanceof SQLException && cause.getMessage().equals(READ_ONLY_ERROR_MESSAGE)){
					List badConnections = new ArrayList<>();
					if(clientManager instanceof MysqlConnectionClientManager){
						badConnections.add(((MysqlConnectionClientManager)clientManager).getExistingConnection(
								clientId));
					}
					logger.warn("read only mode detected, need to discard the connection(s) {}", badConnections);
				}
			}
			if(wasRolledBackAndShouldRetry(e)){
				//make sure MysqlRollbackRetryingCallable catches this particular exception
				throw new SessionExecutorPleaseRetryException("", e);
			}
			try{
				clientManager.rollbackTxn(clientId);
			}catch(RuntimeException exceptionDuringRollback){
				logger.warn("EXCEPTION THROWN DURING TXN ROLL-BACK", exceptionDuringRollback);
				throw e;
			}
			throw e;
		}finally{
			try{
				clientManager.releaseConnection(clientId);
			}catch(Exception e){
				// This is an unexpected exception because each individual release is done in a try/catch block
				logger.warn("EXCEPTION THROWN DURING RELEASE OF CONNECTIONS", e);
			}
		}
	}

	/*------------------------------ helper  --------------------------------*/

	private boolean shouldTrace(String traceName){
		return StringTool.notEmpty(traceName);
	}

	private void startTrace(String traceName){
		if(shouldTrace(traceName)){
			TracerTool.startSpan(traceName, TraceSpanGroupType.DATABASE);
		}
	}

	private void finishTrace(String traceName){
		if(shouldTrace(traceName)){
			TracerTool.finishSpan();
		}
	}

	private boolean wasRolledBackAndShouldRetry(Exception exception){
		if(exception == null){
			return false;
		}
		if(ROLLED_BACK_EXCEPTIONS.contains(exception.getClass())){
			return true;
		}
		Throwable cause = exception.getCause();
		return cause != null && ROLLED_BACK_EXCEPTIONS.contains(cause.getClass());
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy