package com.tesora.dve.sql.schema.mt; /* * #%L * Tesora Inc. * Database Virtualization Engine * %% * Copyright (C) 2011 - 2014 Tesora Inc. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import java.sql.ResultSet; import java.util.LinkedHashSet; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.apache.log4j.Logger; import com.tesora.dve.common.PEConstants; import com.tesora.dve.common.catalog.CatalogDAO; import com.tesora.dve.common.catalog.User; import com.tesora.dve.common.catalog.CatalogDAO.CatalogDAOFactory; import com.tesora.dve.dbc.ServerDBConnection; import com.tesora.dve.sql.util.Pair; public class TableGarbageCollector implements Runnable { private static final Logger logger = Logger.getLogger( TableGarbageCollector.class ); private AtomicReference<Thread> execThread = new AtomicReference<>(null); private AtomicInteger interval; private AtomicInteger request; private ServerDBConnection persistentConnection; public TableGarbageCollector(int cleanupInterval) { persistentConnection = null; request = new AtomicInteger(1); interval = new AtomicInteger(cleanupInterval); } public void setCollectionInterval(int value) { interval.set(value); } public void onGarbageEvent() { request.incrementAndGet(); } private ServerDBConnection getConnection() throws Throwable { if (persistentConnection == null) { CatalogDAO c = null; String rootName, rootPass; try { c = CatalogDAOFactory.newInstance(); User rootUser = c.findDefaultProject().getRootUser(); rootName = rootUser.getName(); rootPass = rootUser.getPlaintextPassword(); } finally { c.close(); c = null; } persistentConnection = new ServerDBConnection(rootName,rootPass); persistentConnection.execute("set foreign_key_checks=0"); } return persistentConnection; } private void closeConnection() throws Throwable { if (persistentConnection != null) persistentConnection.closeComms(); persistentConnection = null; } private Pair<Integer,LinkedHashSet<String>> findCandidates(ServerDBConnection conn) throws Throwable { LinkedHashSet<String> matching = new LinkedHashSet<String>(); ResultSet rs = null; try { rs = conn.executeQuery("select table_name from information_schema.scopes where scope_name is null"); while(rs.next()) { matching.add(rs.getString(1)); } } finally { if (rs != null) rs.close(); } if (matching.isEmpty()) { return new Pair<Integer,LinkedHashSet<String>>(0,matching); } int raw = matching.size(); StringBuilder buf = new StringBuilder(); buf.append("select referenced_table_name from information_schema.referential_constraints where referenced_table_name in ("); boolean first = true; for(String tn : matching) { if (first) first = false; else buf.append(","); buf.append("'").append(tn).append("'"); } buf.append(")"); try { rs = conn.executeQuery(buf.toString()); while(rs.next()) { matching.remove(rs.getString(1)); } } finally { if (rs != null) rs.close(); } return new Pair<Integer,LinkedHashSet<String>>(raw,matching); } private void doCleanup() throws Throwable { if (request.get() < 1) return; String dropSQL = "DROP TABLE IF EXISTS %s"; // get all the table names Pair<Integer,LinkedHashSet<String>> matching = findCandidates(getConnection()); if (matching.getFirst() == 0) { // truly nothing to do request.decrementAndGet(); return; } if (matching.getSecond().isEmpty()) { // nothing to do at this time (existing garbage is referred to, cannot be dropped) return; } try { getConnection().execute("use " + PEConstants.LANDLORD_TENANT); } catch (Throwable t) { // landlord doesn't exist - we're done here return; } for(String tn : matching.getSecond()) { try { // verify that the table is not the target of any fks String sql = String.format(dropSQL,tn); getConnection().execute(sql); logger.warn("Garbage collected table " + tn); } catch (Throwable t) { logger.warn("Unable to garbage collect table " + tn,t); // start fresh closeConnection(); } } request.decrementAndGet(); } @Override public void run() { logger.info("Starting TableCleanup thread with interval=" + interval.get()); while ( isActiveThread(Thread.currentThread()) ) { try { Thread.sleep(interval.get()); doCleanup(); } catch (InterruptedException e) { //ignore. } catch (Error td) { logger.error("Caught severe error in TableCleanup, stopping table GC.",td); throw td; } catch (Throwable t) { logger.error("Exception returned from cleanup",t); } } logger.info("Stopping TableCleanup thread"); // we're leaving, close the connection if need be. try { closeConnection(); } catch (Throwable t) { logger.error("Unable to shut down table garbage collector connection",t); } } public void startTableCleanup() { Thread newThread = new Thread(this,"AdaptiveMTGarbageCollector"); newThread.setDaemon(true); if (execThread.compareAndSet(null,newThread)) newThread.start(); } public boolean isActive(){ return execThread.get() != null; } public boolean isActiveThread(Thread thread){ return execThread.get() == thread; } public void stopTableCleanup() { Thread existingThread = execThread.getAndSet(null); if (existingThread != null) try { existingThread.interrupt(); existingThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }