/* * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, version 2.0, as published by the * Free Software Foundation. * * This program is also distributed with certain software (including but not * limited to OpenSSL) that is licensed under separate terms, as designated in a * particular file or component or in included license documentation. The * authors of MySQL hereby grant you an additional permission to link the * program and your derivative works with the separately licensed software that * they have included with MySQL. * * Without limiting anything contained in the foregoing, this file, which is * part of MySQL Connector/J, is also subject to the Universal FOSS Exception, * version 1.0, a copy of which can be found at * http://oss.oracle.com/licenses/universal-foss-exception. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, * for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.mysql.cj; import java.util.TimerTask; import com.mysql.cj.Query.CancelStatus; import com.mysql.cj.conf.HostInfo; import com.mysql.cj.conf.PropertyDefinitions; import com.mysql.cj.exceptions.OperationCancelledException; import com.mysql.cj.protocol.a.NativeMessageBuilder; import com.mysql.cj.util.StringUtils; //TODO should not be protocol-specific /** * Thread used to implement query timeouts...Eventually we could be more * efficient and have one thread with timers, but this is a straightforward * and simple way to implement a feature that isn't used all that often. */ public class CancelQueryTaskImpl extends TimerTask implements CancelQueryTask { Query queryToCancel; Throwable caughtWhileCancelling = null; boolean queryTimeoutKillsConnection = false; public CancelQueryTaskImpl(Query cancellee) { this.queryToCancel = cancellee; NativeSession session = (NativeSession) cancellee.getSession(); this.queryTimeoutKillsConnection = session.getPropertySet().getBooleanReadableProperty(PropertyDefinitions.PNAME_queryTimeoutKillsConnection) .getValue(); } @Override public boolean cancel() { boolean res = super.cancel(); this.queryToCancel = null; return res; } @Override public void run() { Thread cancelThread = new Thread() { @Override public void run() { Query localQueryToCancel = CancelQueryTaskImpl.this.queryToCancel; if (localQueryToCancel == null) { return; } NativeSession session = (NativeSession) localQueryToCancel.getSession(); if (session == null) { return; } try { if (CancelQueryTaskImpl.this.queryTimeoutKillsConnection) { localQueryToCancel.setCancelStatus(CancelStatus.CANCELED_BY_TIMEOUT); session.invokeCleanupListeners(new OperationCancelledException(Messages.getString("Statement.ConnectionKilledDueToTimeout"))); } else { synchronized (localQueryToCancel.getCancelTimeoutMutex()) { long origConnId = session.getThreadId(); HostInfo hostInfo = session.getHostInfo(); String database = hostInfo.getDatabase(); String user = StringUtils.isNullOrEmpty(hostInfo.getUser()) ? "" : hostInfo.getUser(); String password = StringUtils.isNullOrEmpty(hostInfo.getPassword()) ? "" : hostInfo.getPassword(); NativeSession newSession = new NativeSession(hostInfo, session.getPropertySet()); newSession.connect(hostInfo, user, password, database, 30000, new TransactionEventHandler() { @Override public void transactionCompleted() { } public void transactionBegun() { } }); newSession.sendCommand(new NativeMessageBuilder().buildComQuery(newSession.getSharedSendPacket(), "KILL QUERY " + origConnId), false, 0); localQueryToCancel.setCancelStatus(CancelStatus.CANCELED_BY_TIMEOUT); } } // } catch (NullPointerException npe) { // Case when connection closed while starting to cancel. // We can't easily synchronise this, because then one thread can't cancel() a running query. // Ignore, we shouldn't re-throw this, because the connection's already closed, so the statement has been timed out. } catch (Throwable t) { CancelQueryTaskImpl.this.caughtWhileCancelling = t; } finally { setQueryToCancel(null); } } }; cancelThread.start(); } public Throwable getCaughtWhileCancelling() { return this.caughtWhileCancelling; } public void setCaughtWhileCancelling(Throwable caughtWhileCancelling) { this.caughtWhileCancelling = caughtWhileCancelling; } public Query getQueryToCancel() { return this.queryToCancel; } public void setQueryToCancel(Query queryToCancel) { this.queryToCancel = queryToCancel; } }