Java tutorial
/** * Licensed to Jasig under one or more contributor license * agreements. See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Jasig licenses this file to you 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 edu.wisc.commons.httpclient; import java.lang.Thread.State; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.http.conn.DnsResolver; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.impl.conn.PoolingClientConnectionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Extension of PoolingClientConnectionManager that spawns a monitoring thread to facilitate timely shutdown. * * @author Eric Dalquist * @version $Revision: 1.1 $ */ public class CleanShutdownPoolingClientConnectionManager extends PoolingClientConnectionManager { private final Lock shutdownLock = new ReentrantLock(); private final AtomicBoolean shutdownComplete = new AtomicBoolean(false); protected final Logger logger = LoggerFactory.getLogger(getClass()); private long shutdownThreadKillTime = TimeUnit.SECONDS.toMillis(45); private long shutdownThreadMaxTime = TimeUnit.SECONDS.toMillis(30); private long shutdownThreadMaxWaitTime = TimeUnit.SECONDS.toMillis(1); private long shutdownThreadPollRate = 5; public CleanShutdownPoolingClientConnectionManager() { super(); } public CleanShutdownPoolingClientConnectionManager(SchemeRegistry schreg, DnsResolver dnsResolver) { super(schreg, dnsResolver); } public CleanShutdownPoolingClientConnectionManager(SchemeRegistry schemeRegistry, long timeToLive, TimeUnit tunit, DnsResolver dnsResolver) { super(schemeRegistry, timeToLive, tunit, dnsResolver); } public CleanShutdownPoolingClientConnectionManager(SchemeRegistry schemeRegistry, long timeToLive, TimeUnit tunit) { super(schemeRegistry, timeToLive, tunit); } public CleanShutdownPoolingClientConnectionManager(SchemeRegistry schreg) { super(schreg); } /** * Hard limit time for shutting down the connection manager. * Once hit the shutdown thread is killed via {@link Thread#stop()}. * Defaults to 45s (45000ms) */ public void setShutdownThreadKillTime(int shutdownThreadKillTime) { this.shutdownThreadKillTime = shutdownThreadKillTime; } /** * Limit after which the shutdown thread is repeatedly interrupted. * Defaults to 30s (30000ms) */ public void setShutdownThreadMaxTime(int shutdownThreadMaxTime) { this.shutdownThreadMaxTime = shutdownThreadMaxTime; } /** * Limit of time the shutdown thread is allowed to stay in {@link State#BLOCKED}, {@link State#WAITING}, or * {@link State#TIMED_WAITING}. Once hit the thread is interrupted and the timer is reset. * Defaults to 1s (1000ms) */ public void setShutdownThreadMaxWaitTime(int shutdownThreadMaxWaitTime) { this.shutdownThreadMaxWaitTime = shutdownThreadMaxWaitTime; } /** * Rate at which the state of the shutdown thread is polled and/or interrupted. * Defaults to 5ms */ public void setShutdownThreadPollRate(int shutdownThreadPollRate) { this.shutdownThreadPollRate = shutdownThreadPollRate; } @Override public void shutdown() { if (shutdownComplete.get() || !this.shutdownLock.tryLock()) { //Already shutdown or shutdown in progress return; } try { //Create Thread to call shutdown final Thread shutdownThread = new Thread(new Runnable() { @Override public void run() { try { logger.info("PoolingClientConnectionManager shutdown started"); CleanShutdownPoolingClientConnectionManager.super.shutdown(); } finally { shutdownComplete.set(true); logger.info("PoolingClientConnectionManager shutdown complete"); } } }); shutdownThread.setName("PoolingClientConnectionManager Shutdown Monitor"); shutdownThread.setDaemon(true); //start shutdown thread shutdownThread.start(); //track initial shutdown start time and time spent by the shutdown thread waiting or blocked final long shutdownStart = System.nanoTime(); long waitStart = shutdownStart; //Monitor the shutdown thread while (!shutdownComplete.get()) { final long now = System.nanoTime(); final long shutdownTime = TimeUnit.NANOSECONDS.toMillis(now - shutdownStart); //if time spent shutting down is greater than kill time forcibly stop the shutdown thread if (shutdownTime > this.shutdownThreadKillTime) { final String stackTrace = getStackTrace(shutdownThread); logger.error("Shutdown thread " + shutdownThread.getName() + " has been stopping for " + shutdownTime + "ms, killing it. THIS IS BAD. \n" + stackTrace); shutdownThread.stop(); //break out of the monitoring loop break; } //if time spent shutting down is greater than max time immediately interrupt the thread else if (shutdownTime > this.shutdownThreadMaxTime) { logger.warn("Shutdown thread " + shutdownThread.getName() + " has been stopping for " + shutdownTime + "ms, interrupting immediately"); shutdownThread.interrupt(); } //otherwise check the state of the thread else { //If the thread is blocked or waiting and has been for longer than the max wait time //interrupt the thread. If not in blocked or waiting state update the wait-start time final State state = shutdownThread.getState(); switch (state) { case BLOCKED: case TIMED_WAITING: case WAITING: { final long waitTime = TimeUnit.NANOSECONDS.toMillis(now - waitStart); if (waitTime > shutdownThreadMaxWaitTime) { logger.info("Shutdown thread " + shutdownThread.getName() + " has been waiting for " + waitTime + "ms, interrupting"); shutdownThread.interrupt(); } else { break; } } default: { waitStart = now; break; } } } //Sleep between state checks, don't want to overload anything try { Thread.sleep(shutdownThreadPollRate); } catch (InterruptedException e) { //ignore } } } finally { this.shutdownLock.unlock(); } } private static String getStackTrace(Thread t) { final StringBuilder traceBuilder = new StringBuilder(); final StackTraceElement[] trace = t.getStackTrace(); for (final StackTraceElement element : trace) { traceBuilder.append("\tat ").append(element).append("\n"); } return traceBuilder.toString(); } }