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