/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF 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 org.apache.sling.commons.threads.impl; import java.util.Dictionary; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.UUID; import java.util.concurrent.ThreadPoolExecutor; import org.apache.sling.commons.threads.ModifiableThreadPoolConfig; import org.apache.sling.commons.threads.ThreadPool; import org.apache.sling.commons.threads.ThreadPoolConfig; import org.apache.sling.commons.threads.ThreadPoolConfig.ThreadPoolPolicy; import org.apache.sling.commons.threads.ThreadPoolConfig.ThreadPriority; import org.apache.sling.commons.threads.ThreadPoolManager; import org.apache.sling.commons.threads.jmx.ThreadPoolMBean; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceRegistration; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedServiceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The DefaultThreadPoolManager implements the {@link ThreadPoolManager} interface * and is responsible for managing {@link ThreadPool}s. */ public class DefaultThreadPoolManager implements ThreadPoolManager, ManagedServiceFactory { /** By default we use the logger for this class. */ protected final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); /** The managed thread pools */ protected final Map<String, Entry> pools = new HashMap<String, Entry>(); /** The properties. */ protected final Dictionary<String, Object> properties; /** The bundle context. */ protected final BundleContext bundleContext; /** * Constructor and activate this component. */ public DefaultThreadPoolManager(final BundleContext bc, final Dictionary<String, Object> props) { this.properties = props; this.bundleContext = bc; this.logger.info("Started Apache Sling Thread Pool Manager"); } /** * Deactivate this component. */ public void destroy() { this.logger.debug("Disposing all thread pools"); final Map<String, Entry> localCopy = new HashMap<String, Entry>(this.pools.size()); synchronized ( this.pools ) { localCopy.putAll(this.pools); this.pools.clear(); } for (final Entry entry : localCopy.values()) { entry.unregisterMBean(); entry.shutdown(); } this.logger.info("Stopped Apache Sling Thread Pool Manager"); } /** * Create a thread pool configuration from a config admin configuration */ private ThreadPoolConfig createConfig(final Dictionary<String, Object> props) { final ModifiableThreadPoolConfig config = new ModifiableThreadPoolConfig(); if ( props.get(ModifiableThreadPoolConfig.PROPERTY_MIN_POOL_SIZE) != null ) { config.setMinPoolSize((Integer)props.get(ModifiableThreadPoolConfig.PROPERTY_MIN_POOL_SIZE)); } if ( props.get(ModifiableThreadPoolConfig.PROPERTY_MAX_POOL_SIZE) != null ) { config.setMaxPoolSize((Integer)props.get(ModifiableThreadPoolConfig.PROPERTY_MAX_POOL_SIZE)); } if ( props.get(ModifiableThreadPoolConfig.PROPERTY_QUEUE_SIZE) != null ) { config.setQueueSize((Integer)props.get(ModifiableThreadPoolConfig.PROPERTY_QUEUE_SIZE)); } if ( props.get(ModifiableThreadPoolConfig.PROPERTY_KEEP_ALIVE_TIME) != null ) { config.setKeepAliveTime((Long)props.get(ModifiableThreadPoolConfig.PROPERTY_KEEP_ALIVE_TIME)); } if ( props.get(ModifiableThreadPoolConfig.PROPERTY_BLOCK_POLICY) != null ) { config.setBlockPolicy(ThreadPoolPolicy.valueOf(props.get(ModifiableThreadPoolConfig.PROPERTY_BLOCK_POLICY).toString())); } if ( props.get(ModifiableThreadPoolConfig.PROPERTY_SHUTDOWN_GRACEFUL) != null ) { config.setShutdownGraceful((Boolean)props.get(ModifiableThreadPoolConfig.PROPERTY_SHUTDOWN_GRACEFUL)); } if ( props.get(ModifiableThreadPoolConfig.PROPERTY_SHUTDOWN_WAIT_TIME) != null ) { config.setShutdownWaitTimeMs((Integer)props.get(ModifiableThreadPoolConfig.PROPERTY_SHUTDOWN_WAIT_TIME)); } if ( props.get(ModifiableThreadPoolConfig.PROPERTY_PRIORITY) != null ) { config.setPriority(ThreadPriority.valueOf(props.get(ModifiableThreadPoolConfig.PROPERTY_PRIORITY).toString())); } if ( props.get(ModifiableThreadPoolConfig.PROPERTY_DAEMON) != null ) { config.setDaemon((Boolean)props.get(ModifiableThreadPoolConfig.PROPERTY_DAEMON)); } return config; } /** * @see org.apache.sling.commons.threads.ThreadPoolManager#get(java.lang.String) */ public ThreadPool get(final String name) { final String poolName = (name == null ? DEFAULT_THREADPOOL_NAME : name); Entry entry = null; boolean created = false; ThreadPool threadPool = null; synchronized (this.pools) { entry = this.pools.get(poolName); if ( entry == null ) { this.logger.debug("Creating new pool with name {}", poolName); final ModifiableThreadPoolConfig config = new ModifiableThreadPoolConfig(); entry = new Entry(null, config, poolName, bundleContext); created = true; this.pools.put(poolName, entry); } threadPool = entry.incUsage(); } if (created) { entry.registerMBean(); } return threadPool; } /** * @see org.apache.sling.commons.threads.ThreadPoolManager#release(org.apache.sling.commons.threads.ThreadPool) */ public void release(ThreadPool pool) { if ( pool instanceof ThreadPoolFacade ) { Entry removedEntry = null; synchronized ( this.pools ) { final Entry entry = this.pools.get(pool.getName()); if ( entry != null ) { entry.decUsage(); if ( !entry.isUsed() ) { removedEntry = entry; this.pools.remove(pool.getName()); } } } if ( removedEntry != null ) { removedEntry.unregisterMBean(); } } } /** * @see org.apache.sling.commons.threads.ThreadPoolManager#create(org.apache.sling.commons.threads.ThreadPoolConfig) */ public ThreadPool create(ThreadPoolConfig config) { return this.create(config, null); } public ThreadPool create(ThreadPoolConfig config, String label) { if ( config == null ) { throw new IllegalArgumentException("Config must not be null."); } if ( label == null ) { // generate the label by taking the first external frame off the stack trace final StackTraceElement[] stackTrace = new Exception().getStackTrace(); if ( stackTrace != null && stackTrace.length > 1 ) { if ( stackTrace[1].getClassName().equals( this.getClass().getName() ) ) { label = stackTrace[2].getClassName(); } else { label = stackTrace[1].getClassName(); } } } final String name = "ThreadPool-" + UUID.randomUUID().toString() + (label == null ? "" : " (" + label + ")"); final Entry entry = new Entry(null, config, name, bundleContext); ThreadPool threadPool = null; synchronized ( this.pools ) { this.pools.put(name, entry); threadPool = entry.incUsage(); } entry.registerMBean(); return threadPool; } /** * @see org.osgi.service.cm.ManagedServiceFactory#getName() */ public String getName() { return this.properties.get(Constants.SERVICE_DESCRIPTION).toString(); } /** * Return all configurations for the web console printer */ public Entry[] getConfigurations() { synchronized ( this.pools ) { return this.pools.values().toArray(new Entry[this.pools.size()]); } } /** * @see org.osgi.service.cm.ManagedServiceFactory#updated(java.lang.String, java.util.Dictionary) */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void updated(String pid, Dictionary properties) throws ConfigurationException { final String name = (String) properties.get(ModifiableThreadPoolConfig.PROPERTY_NAME); if ( name == null || name.length() == 0 ) { throw new ConfigurationException(ModifiableThreadPoolConfig.PROPERTY_NAME, "Property is missing or empty."); } this.logger.debug("Updating {} with {}", pid, properties); Entry createdEntry = null; synchronized ( this.pools ) { final ThreadPoolConfig config = this.createConfig(properties); Entry foundEntry = null; // we have to search the config by using the pid first! for (final Entry entry : this.pools.values()) { if ( pid.equals(entry.getPid()) ) { foundEntry = entry; break; } } // if we haven't found it by pid we search by name if ( foundEntry == null ) { for (final Entry entry : this.pools.values()) { if ( name.equals(entry.getName()) ) { foundEntry = entry; break; } } } if ( foundEntry != null ) { // if the name changed - we have to reregister(!) if ( !name.equals(foundEntry.getName()) ) { this.pools.remove(foundEntry.getName()); this.pools.put(name, foundEntry); } // update foundEntry.update(config, name, pid); } else { // create createdEntry = new Entry(pid, config, name, bundleContext); this.pools.put(name, createdEntry); } } if ( createdEntry != null ) { createdEntry.registerMBean(); } } /** * @see org.osgi.service.cm.ManagedServiceFactory#deleted(java.lang.String) */ public void deleted(String pid) { this.logger.debug("Deleting " + pid); // we just remove the thread pool from our list of pools and readd it // as an anonymous pool with default config(!) if it is used synchronized ( this.pools ) { Entry foundEntry = null; // we have to search the config by using the pid! for (final Entry entry : this.pools.values()) { if ( pid.equals(entry.getPid()) ) { foundEntry = entry; break; } } if ( foundEntry != null ) { this.pools.remove(foundEntry.getName()); if ( foundEntry.isUsed() ) { // we register this with a new name final String name = "ThreadPool-" + UUID.randomUUID().toString(); foundEntry.update(new ModifiableThreadPoolConfig(), name, null); this.pools.put(name, foundEntry); } } } } protected static final class Entry { private static final Logger logger = LoggerFactory.getLogger(Entry.class); /** The configuration pid. (might be null for anonymous pools.*/ private volatile String pid; /** Usage count. */ private volatile int count; /** The configuration for the pool. */ private volatile ThreadPoolConfig config; /** The name of the pool. */ private volatile String name; /** The corresponding pool - might be null if unused. */ private volatile ThreadPoolFacade pool; private ServiceRegistration mbeanRegistration; private BundleContext bundleContext; /** * This lock protects the counter which is volatile so must be * protected. */ private Object usagelock = new Object(); public Entry(final String pid, final ThreadPoolConfig config, final String name, final BundleContext bundleContext) { this.pid = pid; this.config = config; this.name = name; this.bundleContext = bundleContext; } public String getPid() { return this.pid; } public void shutdown() { if ( this.pool != null ) { this.pool.shutdown(); this.pool = null; } } /** * Increments a usage counter and gets the ThreadPoolFacade inside the Entry. * Note this method is not thread safe and must at all time be protected from * multiple threads accessing it at the same time. The counter is volatile and * hence not atomic in updates. * @return the thread pool Facade instance after reference counting the usage. */ public ThreadPoolFacade incUsage() { if ( pool == null ) { pool = new ThreadPoolFacade(new DefaultThreadPool(name, this.config)); } this.count++; return pool; } /** * Decrement the usage counter, and if its zero initiate a shutdown the entry (and the thread pool). * Note: this method is not thread safe and must at all time be protected from multiple threads * accessing it at the same time. The counter is volatile and hence not atomic in updates. */ public void decUsage() { this.count--; if ( this.count == 0 ) { this.shutdown(); } } public void update(final ThreadPoolConfig config, final String name, final String pid) { if ( this.pool != null ) { this.pool.setName(name); if ( !this.config.equals(config) ) { this.pool.setPool(new DefaultThreadPool(name, config)); } } this.config = config; this.name = name; this.pid = pid; } public String getName() { return this.name; } public boolean isUsed() { return this.count > 0; } public ThreadPoolConfig getConfig() { return this.config; } public ThreadPoolExecutor getExecutor() { if ( this.pool != null ) { return this.pool.getExecutor(); } return null; } protected void unregisterMBean() { if ( this.mbeanRegistration != null ) { this.mbeanRegistration.unregister(); this.mbeanRegistration = null; } } protected void registerMBean() { try { final Dictionary<String, String> mbeanProps = new Hashtable<String, String>(); mbeanProps.put("jmx.objectname", "org.apache.sling:type=threads,service=ThreadPool,name=" + this.name); final ThreadPoolMBeanImpl mbean = new ThreadPoolMBeanImpl(this); this.mbeanRegistration = bundleContext.registerService(ThreadPoolMBean.class.getName(), mbean, mbeanProps); } catch (Throwable t) { logger.warn("Unable to register Thread Pool MBean", t); } } } }