001    package org.apache.jcs.utils.threadpool;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.util.ArrayList;
023    import java.util.HashMap;
024    import java.util.Properties;
025    import java.util.concurrent.BlockingQueue;
026    import java.util.concurrent.LinkedBlockingQueue;
027    import java.util.concurrent.ThreadFactory;
028    import java.util.concurrent.ThreadPoolExecutor;
029    import java.util.concurrent.TimeUnit;
030    
031    import org.apache.commons.logging.Log;
032    import org.apache.commons.logging.LogFactory;
033    import org.apache.jcs.utils.props.PropertyLoader;
034    import org.apache.jcs.utils.threadpool.PoolConfiguration.WhenBlockedPolicy;
035    
036    /**
037     * This manages threadpools for an application using Doug Lea's Util Concurrent package.
038     * http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html
039     * <p>
040     * It is a singleton since threads need to be managed vm wide.
041     * <p>
042     * This manager forces you to use a bounded queue. By default it uses the current thread for
043     * execution when the buffer is full and no free threads can be created.
044     * <p>
045     * You can specify the props file to use or pass in a properties object prior to configuration. By
046     * default it looks for configuration information in thread_pool.properties.
047     * <p>
048     * If set, the Properties object will take precedence.
049     * <p>
050     * If a value is not set for a particular pool, the hard coded defaults will be used.
051     *
052     * <pre>
053     * int boundarySize_DEFAULT = 2000;
054     *
055     * int maximumPoolSize_DEFAULT = 150;
056     *
057     * int minimumPoolSize_DEFAULT = 4;
058     *
059     * int keepAliveTime_DEFAULT = 1000 * 60 * 5;
060     *
061     * boolean abortWhenBlocked = false;
062     *
063     * String whenBlockedPolicy_DEFAULT = IPoolConfiguration.POLICY_RUN;
064     *
065     * int startUpSize_DEFAULT = 4;
066     * </pre>
067     *
068     * You can configure default settings by specifying a default pool in the properties, ie "cache.ccf"
069     * <p>
070     * @author Aaron Smuts
071     */
072    public class ThreadPoolManager
073    {
074        /** The logger */
075        private static final Log log = LogFactory.getLog( ThreadPoolManager.class );
076    
077        /**
078         * DEFAULT SETTINGS, these are not final since they can be set via the properties file or object
079         */
080        private static boolean useBoundary_DEFAULT = true;
081    
082        /** Default queue size limit */
083        private static int boundarySize_DEFAULT = 2000;
084    
085        /** Default max size */
086        private static int maximumPoolSize_DEFAULT = 150;
087    
088        /** Default min */
089        private static int minimumPoolSize_DEFAULT = 4;
090    
091        /** Default keep alive */
092        private static int keepAliveTime_DEFAULT = 1000 * 60 * 5;
093    
094        /** Default when blocked */
095        private static WhenBlockedPolicy whenBlockedPolicy_DEFAULT = WhenBlockedPolicy.RUN;
096    
097        /** Default startup size */
098        private static int startUpSize_DEFAULT = 4;
099    
100        /** The default config, created using property defaults if present, else those above. */
101        private static PoolConfiguration defaultConfig;
102    
103        /** This is the default value. */
104        public static final String DEFAULT_PROPS_FILE_NAME = "cache.ccf";
105    
106        /** Setting this after initialization will have no effect.  */
107        private static String propsFileName = null;
108    
109        /** the root property name */
110        private static String PROP_NAME_ROOT = "thread_pool";
111    
112        /** default property file name */
113        private static String DEFAULT_PROP_NAME_ROOT = "thread_pool.default";
114    
115        /**
116         * You can specify the properties to be used to configure the thread pool. Setting this post
117         * initialization will have no effect.
118         */
119        private static volatile Properties props = null;
120    
121        /** Map of names to pools. */
122        private static HashMap<String, ThreadPoolExecutor> pools = new HashMap<String, ThreadPoolExecutor>();
123    
124        /** singleton instance */
125        private static ThreadPoolManager INSTANCE = null;
126    
127        /**
128         * No instances please. This is a singleton.
129         */
130        private ThreadPoolManager()
131        {
132            configure();
133        }
134    
135        /**
136         * Creates a pool based on the configuration info.
137         * <p>
138         * @param config
139         * @return A ThreadPoll wrapper
140         */
141        private ThreadPoolExecutor createPool( PoolConfiguration config )
142        {
143            ThreadPoolExecutor pool = null;
144            BlockingQueue<Runnable> queue = null;
145            if ( config.isUseBoundary() )
146            {
147                if ( log.isDebugEnabled() )
148                {
149                    log.debug( "Creating a Bounded Buffer to use for the pool" );
150                }
151    
152                queue = new LinkedBlockingQueue<Runnable>(config.getBoundarySize());
153            }
154            else
155            {
156                if ( log.isDebugEnabled() )
157                {
158                    log.debug( "Creating a non bounded Linked Queue to use for the pool" );
159                }
160                queue = new LinkedBlockingQueue<Runnable>();
161            }
162    
163            pool = new ThreadPoolExecutor(config.getStartUpSize(), config.getMaximumPoolSize(),
164                    config.getKeepAliveTime(), TimeUnit.MILLISECONDS,
165                    queue, new MyThreadFactory());
166    
167            // when blocked policy
168            switch (config.getWhenBlockedPolicy())
169            {
170                case ABORT:
171                    pool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
172                    break;
173    
174                case RUN:
175                    pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
176                    break;
177    
178                case WAIT:
179                    throw new RuntimeException("POLICY_WAIT no longer supported");
180    
181                case DISCARDOLDEST:
182                    pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
183                    break;
184    
185                default:
186                    break;
187            }
188    
189            pool.prestartAllCoreThreads();
190    
191            return pool;
192        }
193    
194        /**
195         * Returns a configured instance of the ThreadPoolManger To specify a configuration file or
196         * Properties object to use call the appropriate setter prior to calling getInstance.
197         * <p>
198         * @return The single instance of the ThreadPoolManager
199         */
200        public static synchronized ThreadPoolManager getInstance()
201        {
202            if ( INSTANCE == null )
203            {
204                INSTANCE = new ThreadPoolManager();
205            }
206            return INSTANCE;
207        }
208    
209        /**
210         * Returns a pool by name. If a pool by this name does not exist in the configuration file or
211         * properties, one will be created using the default values.
212         * <p>
213         * Pools are lazily created.
214         * <p>
215         * @param name
216         * @return The thread pool configured for the name.
217         */
218        public ThreadPoolExecutor getPool( String name )
219        {
220            ThreadPoolExecutor pool = null;
221    
222            synchronized ( pools )
223            {
224                pool = pools.get( name );
225                if ( pool == null )
226                {
227                    if ( log.isDebugEnabled() )
228                    {
229                        log.debug( "Creating pool for name [" + name + "]" );
230                    }
231                    PoolConfiguration config = this.loadConfig( PROP_NAME_ROOT + "." + name );
232                    pool = createPool( config );
233    
234                    if ( pool != null )
235                    {
236                        pools.put( name, pool );
237                    }
238    
239                    if ( log.isDebugEnabled() )
240                    {
241                        log.debug( "PoolName = " + getPoolNames() );
242                    }
243                }
244            }
245    
246            return pool;
247        }
248    
249        /**
250         * Returns the names of all configured pools.
251         * <p>
252         * @return ArrayList of string names
253         */
254        public ArrayList<String> getPoolNames()
255        {
256            ArrayList<String> poolNames = new ArrayList<String>();
257            synchronized ( pools )
258            {
259                poolNames.addAll(pools.keySet());
260            }
261            return poolNames;
262        }
263    
264        /**
265         * Setting this post initialization will have no effect.
266         * <p>
267         * @param propsFileName The propsFileName to set.
268         */
269        public static void setPropsFileName( String propsFileName )
270        {
271            ThreadPoolManager.propsFileName = propsFileName;
272        }
273    
274        /**
275         * Returns the name of the properties file that we used to initialize the pools. If the value
276         * was set post-initialization, then it may not be the file used.
277         * <p>
278         * @return Returns the propsFileName.
279         */
280        public static String getPropsFileName()
281        {
282            return propsFileName;
283        }
284    
285        /**
286         * This will be used if it is not null on initialization. Setting this post initialization will
287         * have no effect.
288         * <p>
289         * @param props The props to set.
290         */
291        public static void setProps( Properties props )
292        {
293            ThreadPoolManager.props = props;
294        }
295    
296        /**
297         * @return Returns the props.
298         */
299        public static Properties getProps()
300        {
301            return props;
302        }
303    
304        /**
305         * Initialize the ThreadPoolManager and create all the pools defined in the configuration.
306         */
307        protected void configure()
308        {
309            if ( log.isDebugEnabled() )
310            {
311                log.debug( "Initializing ThreadPoolManager" );
312            }
313    
314            if ( props == null )
315            {
316                try
317                {
318                    props = PropertyLoader.loadProperties( propsFileName );
319    
320                    if ( log.isDebugEnabled() )
321                    {
322                        log.debug( "File contained " + props.size() + " properties" );
323                    }
324                }
325                catch ( Exception e )
326                {
327                    log.error( "Problem loading properties. propsFileName [" + propsFileName + "]", e );
328                }
329            }
330    
331            if ( props == null )
332            {
333                log.warn( "No configuration settings found.  Using hardcoded default values for all pools." );
334                props = new Properties();
335            }
336    
337            // set intial default and then override if new
338            // settings are available
339            defaultConfig = new PoolConfiguration( useBoundary_DEFAULT, boundarySize_DEFAULT, maximumPoolSize_DEFAULT,
340                                                   minimumPoolSize_DEFAULT, keepAliveTime_DEFAULT,
341                                                   whenBlockedPolicy_DEFAULT, startUpSize_DEFAULT );
342    
343            defaultConfig = loadConfig( DEFAULT_PROP_NAME_ROOT );
344        }
345    
346        /**
347         * Configures the default PoolConfiguration settings.
348         * <p>
349         * @param root
350         * @return PoolConfiguration
351         */
352        protected PoolConfiguration loadConfig( String root )
353        {
354            PoolConfiguration config = (PoolConfiguration) defaultConfig.clone();
355    
356            if ( props.containsKey( root + ".useBoundary" ) )
357            {
358                try
359                {
360                    config.setUseBoundary( Boolean.valueOf( (String) props.get( root + ".useBoundary" ) ).booleanValue() );
361                }
362                catch ( NumberFormatException nfe )
363                {
364                    log.error( "useBoundary not a boolean.", nfe );
365                }
366            }
367    
368            // load default if they exist
369            if ( props.containsKey( root + ".boundarySize" ) )
370            {
371                try
372                {
373                    config.setBoundarySize( Integer.parseInt( (String) props.get( root + ".boundarySize" ) ) );
374                }
375                catch ( NumberFormatException nfe )
376                {
377                    log.error( "boundarySize not a number.", nfe );
378                }
379            }
380    
381            // maximum pool size
382            if ( props.containsKey( root + ".maximumPoolSize" ) )
383            {
384                try
385                {
386                    config.setMaximumPoolSize( Integer.parseInt( (String) props.get( root + ".maximumPoolSize" ) ) );
387                }
388                catch ( NumberFormatException nfe )
389                {
390                    log.error( "maximumPoolSize not a number.", nfe );
391                }
392            }
393    
394            // minimum pool size
395            if ( props.containsKey( root + ".minimumPoolSize" ) )
396            {
397                try
398                {
399                    config.setMinimumPoolSize( Integer.parseInt( (String) props.get( root + ".minimumPoolSize" ) ) );
400                }
401                catch ( NumberFormatException nfe )
402                {
403                    log.error( "minimumPoolSize not a number.", nfe );
404                }
405            }
406    
407            // keep alive
408            if ( props.containsKey( root + ".keepAliveTime" ) )
409            {
410                try
411                {
412                    config.setKeepAliveTime( Integer.parseInt( (String) props.get( root + ".keepAliveTime" ) ) );
413                }
414                catch ( NumberFormatException nfe )
415                {
416                    log.error( "keepAliveTime not a number.", nfe );
417                }
418            }
419    
420            // when blocked
421            if ( props.containsKey( root + ".whenBlockedPolicy" ) )
422            {
423                config.setWhenBlockedPolicy( (String) props.get( root + ".whenBlockedPolicy" ) );
424            }
425    
426            // startupsize
427            if ( props.containsKey( root + ".startUpSize" ) )
428            {
429                try
430                {
431                    config.setStartUpSize( Integer.parseInt( (String) props.get( root + ".startUpSize" ) ) );
432                }
433                catch ( NumberFormatException nfe )
434                {
435                    log.error( "startUpSize not a number.", nfe );
436                }
437            }
438    
439            if ( log.isInfoEnabled() )
440            {
441                log.info( root + " PoolConfiguration = " + config );
442            }
443    
444            return config;
445        }
446    
447        /**
448         * Allows us to set the daemon status on the threads.
449         * <p>
450         * @author aaronsm
451         */
452        protected static class MyThreadFactory
453            implements ThreadFactory
454        {
455            /**
456             * Sets the thread to daemon.
457             * <p>
458             * @param runner
459             * @return a daemon thread
460             */
461            public Thread newThread( Runnable runner )
462            {
463                Thread t = new Thread( runner );
464                String oldName = t.getName();
465                t.setName( "JCS-ThreadPoolManager-" + oldName );
466                t.setDaemon( true );
467                return t;
468            }
469        }
470    }