001package org.apache.commons.jcs.auxiliary.disk.jdbc;
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
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025import java.util.Collections;
026import java.util.HashSet;
027import java.util.Set;
028
029/**
030 * Calls delete expired on the disk caches. The shrinker is run by a clock daemon. The shrinker
031 * calls delete on each region. It pauses between calls.
032 * <p>
033 * @author Aaron Smuts
034 */
035public class ShrinkerThread
036    implements Runnable
037{
038    /** The logger. */
039    private static final Log log = LogFactory.getLog( ShrinkerThread.class );
040
041    /** A set of JDBCDiskCache objects to call deleteExpired on. */
042    private final Set<JDBCDiskCache<?, ?>> shrinkSet =
043        Collections.synchronizedSet( new HashSet<JDBCDiskCache<?, ?>>() );
044
045    /** Default time period to use. */
046    private static final long DEFAULT_PAUSE_BETWEEN_REGION_CALLS_MILLIS = 5000;
047
048    /**
049     * How long should we wait between calls to deleteExpired when we are iterating through the list
050     * of regions. Delete can lock the table. We want to give clients a chance to get some work
051     * done.
052     */
053    private long pauseBetweenRegionCallsMillis = DEFAULT_PAUSE_BETWEEN_REGION_CALLS_MILLIS;
054
055    /**
056     * Does nothing special.
057     */
058    protected ShrinkerThread()
059    {
060        super();
061    }
062
063    /**
064     * Adds a JDBC disk cache to the set of disk cache to shrink.
065     * <p>
066     * @param diskCache
067     */
068    public void addDiskCacheToShrinkList( JDBCDiskCache<?, ?> diskCache )
069    {
070        // the set will prevent dupes.
071        // we could also just add these to a hashmap by region name
072        // but that might cause a problem if you wanted to use two different
073        // jbdc disk caches for the same region.
074        shrinkSet.add( diskCache );
075    }
076
077    /**
078     * Calls deleteExpired on each item in the set. It pauses between each call.
079     */
080    @Override
081    public void run()
082    {
083        try
084        {
085            deleteExpiredFromAllRegisteredRegions();
086        }
087        catch ( Throwable e )
088        {
089            log.error( "Caught an expcetion while trying to delete expired items.", e );
090        }
091    }
092
093    /**
094     * Deletes the expired items from all the registered regions.
095     */
096    private void deleteExpiredFromAllRegisteredRegions()
097    {
098        if ( log.isInfoEnabled() )
099        {
100            log.info( "Running JDBC disk cache shrinker.  Number of regions [" + shrinkSet.size() + "]" );
101        }
102
103        Object[] caches = null;
104
105        synchronized ( shrinkSet )
106        {
107            caches = this.shrinkSet.toArray();
108        }
109
110        if ( caches != null )
111        {
112            for ( int i = 0; i < caches.length; i++ )
113            {
114                JDBCDiskCache<?, ?> cache = (JDBCDiskCache<?, ?>) caches[i];
115
116                long start = System.currentTimeMillis();
117                int deleted = cache.deleteExpired();
118                long end = System.currentTimeMillis();
119
120                if ( log.isInfoEnabled() )
121                {
122                    log.info( "Deleted [" + deleted + "] expired for region [" + cache.getCacheName() + "] for table ["
123                        + cache.getTableName() + "] in " + ( end - start ) + " ms." );
124                }
125
126                // don't pause after the last call to delete expired.
127                if ( i < caches.length - 1 )
128                {
129                    if ( log.isInfoEnabled() )
130                    {
131                        log.info( "Pausing for [" + this.getPauseBetweenRegionCallsMillis()
132                            + "] ms. before shrinking the next region." );
133                    }
134
135                    try
136                    {
137                        Thread.sleep( this.getPauseBetweenRegionCallsMillis() );
138                    }
139                    catch ( InterruptedException e )
140                    {
141                        log.warn( "Interrupted while waiting to delete expired for the next region." );
142                    }
143                }
144            }
145        }
146    }
147
148    /**
149     * How long should we wait between calls to deleteExpired when we are iterating through the list
150     * of regions.
151     * <p>
152     * @param pauseBetweenRegionCallsMillis The pauseBetweenRegionCallsMillis to set.
153     */
154    public void setPauseBetweenRegionCallsMillis( long pauseBetweenRegionCallsMillis )
155    {
156        this.pauseBetweenRegionCallsMillis = pauseBetweenRegionCallsMillis;
157    }
158
159    /**
160     * How long should we wait between calls to deleteExpired when we are iterating through the list
161     * of regions.
162     * <p>
163     * @return Returns the pauseBetweenRegionCallsMillis.
164     */
165    public long getPauseBetweenRegionCallsMillis()
166    {
167        return pauseBetweenRegionCallsMillis;
168    }
169}