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

org.tentackle.dbms.DbPoolTimeoutThread Maven / Gradle / Ivy

/*
 * Tentackle - https://tentackle.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.tentackle.dbms;

import org.tentackle.common.Constants;
import org.tentackle.common.Timestamp;
import org.tentackle.daemon.Scavenger;
import org.tentackle.log.Logger;

import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Timeout thread to supervise {@link DbPool}s.
 * There is only one such thread for all pools.
 * 

* Closes pooled db instances when not used for maxMinutes time. * This is to release resources, if any, and improves memory consumption * for long-running servers. */ public class DbPoolTimeoutThread extends Thread implements Scavenger { private static final Logger LOGGER = Logger.get(DbPoolTimeoutThread.class); private static final long SLEEP_INTERVAL = Constants.SECOND_MS * 10; // sleep interval for timeout thread (10secs) private static final Map> poolMap = new ConcurrentHashMap<>(); // poolname -> pool ref private static final AtomicInteger instanceCount = new AtomicInteger(); // number of created threads so far private static DbPoolTimeoutThread timeoutThread; // the currently running or dead thread, null if no thread started yet /** * Registers a new pool for supervision.
* There is no unregister. Pools are automatically unregistered when GC'd or shut down. * * @param pool the pool */ public static void register(DbPool pool) { poolMap.put(pool.getName(), new WeakReference<>(pool)); LOGGER.info("session pool {0} registered", pool); checkAlive(); } private static synchronized void checkAlive() { if (timeoutThread == null || !timeoutThread.isAlive()) { timeoutThread = new DbPoolTimeoutThread(); timeoutThread.start(); } } private DbPoolTimeoutThread() { super("Sessionpool Timeout Thread(" + instanceCount.incrementAndGet() + ")"); setDaemon(true); } @Override public void run() { LOGGER.info(this + " started"); for (;;) { try { try { sleep(SLEEP_INTERVAL); // wait for a few seconds } catch (InterruptedException ex) { // check termination condition below (pool == null) } if (poolMap.isEmpty()) { break; // no more pools -> terminate } for (Iterator> iter = poolMap.values().iterator(); iter.hasNext(); ) { DbPool pool = iter.next().get(); if (pool == null) { iter.remove(); } else { long curtime = System.currentTimeMillis(); synchronized (pool) { if (pool.isShutdown()) { iter.remove(); } else { // bring down timed out unused Db instances int i = 0; // start with the oldest unused while (i < pool.getUnusedCount()) { int index = pool.getUnusedSessionIndex(i); PooledDb pooledDb = pool.getPooledSessions()[index]; // pooledDb.db != null because unused boolean idleTimedOut = false; long maxIdleMinutes = pool.getMaxIdleMinutes(); if (maxIdleMinutes > 0) { long idleMinutes = pooledDb.idleMinutes(curtime); idleTimedOut = idleMinutes > maxIdleMinutes; if (idleTimedOut) { LOGGER.info("{0} idle for {1} (max={2}) -> closed", pooledDb, idleMinutes, maxIdleMinutes); } } boolean usageTimedOut = false; long maxUsageMinutes = pool.getMaxUsageMinutes(); if (maxUsageMinutes > 0) { long usedMinutes = pooledDb.usedMinutes(curtime); usageTimedOut = usedMinutes > maxUsageMinutes; if (usageTimedOut) { LOGGER.info("{0} used for {1} (max={2}) -> closed", pooledDb, usedMinutes, maxUsageMinutes); } } // if used at all and unused interval elapsed if (idleTimedOut || usageTimedOut) { pool.removeDbInstance(pooledDb.getSession(), index); i--; // start over at same slot } else if (pooledDb.getSession().isRemote() && pooledDb.getSession().getSessionGroupId() == 0) { // remote connections must be kept alive in order not to be closed by remote server. // But only if they do not belong to session group! It is assumed that the root session // of the session group is kept alive by the application (modtracker, for example). try { pooledDb.getSession().setAlive(true); } catch (RuntimeException ex) { LOGGER.severe("remote keep alive failed", ex); pool.removeDbInstance(pooledDb.getSession(), index); i--; // start over at same slot } } i++; } // remove unreferenced sessions i = 0; for (PooledDb pooledDb : pool.getPooledSessions()) { if (pooledDb != null && pooledDb.isUnreferenced()) { LOGGER.warning("unreferenced " + pooledDb + " last used by " + pooledDb.getUsingThreadStr() + " in MDC{" + pooledDb.getMdcStr() + "} since " + new Timestamp(pooledDb.getUsedSince()) + " -> removed from pool"); pool.removeDbIndex(i); // the cleaner already closed or will close the session physically } i++; } // check if we need to bring up some sessions for minSize int size = pool.getSize(); if (pool.getMinSize() > 0 && size < pool.getMinSize()) { pool.createDbInstances(pool.getMinSize() - size); } else if (size == 0 && pool.getMinSize() < 0) { // minsize < 0 and all sessions closed -> close pool and remove it pool.shutdown(); iter.remove(); } } } } } } catch(RuntimeException ex){ LOGGER.severe("cleaning up unused session instance(s) failed", ex); } } LOGGER.info(this + " terminated"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy