Package org.apache.jcs.engine.memory.shrinking

Source Code of org.apache.jcs.engine.memory.shrinking.ShrinkerThread

package org.apache.jcs.engine.memory.shrinking;

/*
* 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.
*/

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jcs.engine.behavior.ICacheElement;
import org.apache.jcs.engine.behavior.IElementAttributes;
import org.apache.jcs.engine.control.event.ElementEvent;
import org.apache.jcs.engine.control.event.behavior.IElementEvent;
import org.apache.jcs.engine.control.event.behavior.IElementEventConstants;
import org.apache.jcs.engine.control.event.behavior.IElementEventHandler;
import org.apache.jcs.engine.memory.MemoryCache;

/**
* A background memory shrinker. Memory problems and concurrent modification
* exception caused by acting directly on an iterator of the underlying memory
* cache should have been solved.
*
* @version $Id: ShrinkerThread.java 536904 2007-05-10 16:03:42Z tv $
*/
public class ShrinkerThread
    implements Runnable
{
    private final static Log log = LogFactory.getLog( ShrinkerThread.class );

    /** The MemoryCache instance which this shrinker is watching */
    private final MemoryCache cache;

    /** Maximum memory idle time for the whole cache */
    private final long maxMemoryIdleTime;

    /** Maximum number of items to spool per run. Default is -1, or no limit. */
    private int maxSpoolPerRun;

    private boolean spoolLimit = false;

    /**
     * Constructor for the ShrinkerThread object.
     *
     * @param cache
     *            The MemoryCache which the new shrinker should watch.
     */
    public ShrinkerThread( MemoryCache cache )
    {
        super();

        this.cache = cache;

        long maxMemoryIdleTimeSeconds = cache.getCacheAttributes().getMaxMemoryIdleTimeSeconds();

        if ( maxMemoryIdleTimeSeconds < 0 )
        {
            this.maxMemoryIdleTime = -1;
        }
        else
        {
            this.maxMemoryIdleTime = maxMemoryIdleTimeSeconds * 1000;
        }

        this.maxSpoolPerRun = cache.getCacheAttributes().getMaxSpoolPerRun();
        if ( this.maxSpoolPerRun != -1 )
        {
            this.spoolLimit = true;
        }

    }

    /**
     * Main processing method for the ShrinkerThread object
     */
    public void run()
    {
        shrink();
    }

    /**
     * This method is called when the thread wakes up. Frist the method obtains
     * an array of keys for the cache region. It iterates through the keys and
     * tries to get the item from the cache without affecting the last access or
     * position of the item. The item is checked for expiration, the expiration
     * check has 3 parts:
     * <ol>
     * <li>Has the cacheattributes.MaxMemoryIdleTimeSeconds defined for the
     * region been exceeded? If so, the item should be move to disk.</li>
     * <li>Has the item exceeded MaxLifeSeconds defined in the element
     * attributes? If so, remove it.</li>
     * <li>Has the item exceeded IdleTime defined in the element atributes? If
     * so, remove it. If there are event listeners registered for the cache
     * element, they will be called.</li>
     * </ol>
     *
     * @todo Change element event handling to use the queue, then move the queue
     *       to the region and access via the Cache.
     */
    protected void shrink()
    {
        if ( log.isDebugEnabled() )
        {
            if ( this.cache.getCompositeCache() != null )
            {
                log.debug( "Shrinking memory cache for: " + this.cache.getCompositeCache().getCacheName() );
            }
        }

        try
        {
            Object[] keys = cache.getKeyArray();
            int size = keys.length;
            if ( log.isDebugEnabled() )
            {
                log.debug( "Keys size: " + size );
            }

            Serializable key;
            ICacheElement cacheElement;
            IElementAttributes attributes;

            int spoolCount = 0;

            for ( int i = 0; i < size; i++ )
            {
                key = (Serializable) keys[i];
                cacheElement = cache.getQuiet( key );

                if ( cacheElement == null )
                {
                    continue;
                }

                attributes = cacheElement.getElementAttributes();

                boolean remove = false;

                long now = System.currentTimeMillis();

                // Useful, but overkill even for DEBUG since it is written for
                // every element in memory
                //
                // if ( log.isDebugEnabled() )
                // {
                // log.debug( "IsEternal: " + attributes.getIsEternal() );
                // log.debug( "MaxLifeSeconds: "
                // + attributes.getMaxLifeSeconds() );
                // log.debug( "CreateTime:" + attributes.getCreateTime() );
                // }

                // If the element is not eternal, check if it should be
                // removed and remove it if so.

                if ( !cacheElement.getElementAttributes().getIsEternal() )
                {
                    remove = checkForRemoval( cacheElement, now );

                    if ( remove )
                    {
                        cache.remove( cacheElement.getKey() );
                    }
                }

                // If the item is not removed, check is it has been idle
                // long enough to be spooled.

                if ( !remove && ( maxMemoryIdleTime != -1 ) )
                {
                    if ( !spoolLimit || ( spoolCount < this.maxSpoolPerRun ) )
                    {

                        final long lastAccessTime = attributes.getLastAccessTime();

                        if ( lastAccessTime + maxMemoryIdleTime < now )
                        {
                            if ( log.isDebugEnabled() )
                            {
                                log.debug( "Exceeded memory idle time: " + cacheElement.getKey() );
                            }

                            // Shouldn't we ensure that the element is
                            // spooled before removing it from memory?
                            // No the disk caches have a purgatory. If it fails
                            // to spool that does not affect the
                            // responsibilities of the memory cache.

                            spoolCount++;

                            cache.remove( cacheElement.getKey() );

                            cache.waterfal( cacheElement );

                            key = null;
                            cacheElement = null;
                        }
                    }
                    else
                    {
                        if ( log.isDebugEnabled() )
                        {
                            log.debug( "spoolCount = '" + spoolCount + "'; " + "maxSpoolPerRun = '" + maxSpoolPerRun
                                + "'" );
                        }

                        // stop processing if limit has been reached.
                        if ( spoolLimit && ( spoolCount >= this.maxSpoolPerRun ) )
                        {
                            keys = null;
                            return;
                        }
                    }
                }
            }

            keys = null;
        }
        catch ( Throwable t )
        {
            log.info( "Unexpected trouble in shrink cycle", t );

            // concurrent modifications should no longer be a problem
            // It is up to the IMemoryCache to return an array of keys

            // stop for now
            return;
        }

    }

    /**
     * Check if either lifetime or idletime has expired for the provided event,
     * and remove it from the cache if so.
     *
     * @param cacheElement
     *            Element to check for expiration
     * @param now
     *            Time to consider expirations relative to
     * @return true if the element should be removed, or false.
     * @throws IOException
     */
    private boolean checkForRemoval( ICacheElement cacheElement, long now )
        throws IOException
    {
        IElementAttributes attributes = cacheElement.getElementAttributes();

        final long maxLifeSeconds = attributes.getMaxLifeSeconds();
        final long createTime = attributes.getCreateTime();

        // Check if maxLifeSeconds has been exceeded
        if ( maxLifeSeconds != -1 && now - createTime > maxLifeSeconds * 1000 )
        {
            if ( log.isInfoEnabled() )
            {
                log.info( "Exceeded maxLifeSeconds: " + cacheElement.getKey() );
            }

            handleElementEvents( cacheElement, IElementEventConstants.ELEMENT_EVENT_EXCEEDED_MAXLIFE_BACKGROUND );

            return true;
        }

        final long idleTime = attributes.getIdleTime();
        final long lastAccessTime = attributes.getLastAccessTime();

        // Check maxIdleTime has been exceeded
        if ( idleTime != -1 && now - lastAccessTime > idleTime * 1000 )
        {
            if ( log.isInfoEnabled() )
            {
                log.info( "Exceeded maxIdleTime " + cacheElement.getKey() );
            }

            handleElementEvents( cacheElement, IElementEventConstants.ELEMENT_EVENT_EXCEEDED_IDLETIME_BACKGROUND );

            return true;
        }

        return false;
    }

    /**
     * Handle any events registered for the given element of the given event
     * type.
     *
     * @param cacheElement
     *            Element to handle events for
     * @param eventType
     *            Type of event to handle
     * @throws IOException
     *             If an error occurs
     */
    private void handleElementEvents( ICacheElement cacheElement, int eventType )
        throws IOException
    {
        IElementAttributes attributes = cacheElement.getElementAttributes();

        ArrayList eventHandlers = attributes.getElementEventHandlers();

        if ( eventHandlers != null )
        {
            if ( log.isDebugEnabled() )
            {
                log.debug( "Handlers are registered, type: " + eventType );
            }

            IElementEvent event = new ElementEvent( cacheElement, eventType );

            Iterator handlerIter = eventHandlers.iterator();

            while ( handlerIter.hasNext() )
            {
                IElementEventHandler hand = (IElementEventHandler) handlerIter.next();

                // extra safety
                // TODO we shouldn't be operating on a variable of another class.
                // we did this to get away from the singleton composite cache.
                // we will need to create an event manager and pass it around instead.
                if ( cache.getCompositeCache() != null )
                {
                    cache.getCompositeCache().addElementEvent( hand, event );
                }
            }
        }
    }
}
TOP

Related Classes of org.apache.jcs.engine.memory.shrinking.ShrinkerThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.