Package org.openquark.cal.services

Source Code of org.openquark.cal.services.EntryPointCache

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

/*
* Created: Feb 23, 2007
* By: Robin Salkeld
* --!>
*/
package org.openquark.cal.services;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.openquark.cal.compiler.CompilerMessage;
import org.openquark.cal.compiler.CompilerMessageLogger;
import org.openquark.cal.compiler.MessageLogger;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.io.EntryPoint;
import org.openquark.cal.compiler.io.EntryPointSpec;
import org.openquark.cal.compiler.io.InputPolicy;
import org.openquark.cal.compiler.io.OutputPolicy;
import org.openquark.cal.machine.CALExecutor;
import org.openquark.cal.runtime.CALExecutorException;
import org.openquark.cal.runtime.ExecutionContext;
import org.openquark.util.General;


/**
* This is a helper class that manages a cache of {@link EntryPoint}s for a
* particular workspace. It provides runFunction() methods that accept an
* {@link EntryPointSpec} along with a set of arguments and execute the
* application function of the specified function to those arguments, using the
* {@link InputPolicy InputPolicies} and {@link OutputPolicy OutputPolicies}
* specified. This is implemented by dynamically obtaining and recreating
* {@code EntryPoints} as they are needed and holding them in a cache for future
* function calls to use. This will often be more efficient than
* BasicCalServices.runFunction() since repeated calls to the same functions
* with different arguments is a common usage pattern.
*
* @author Robin Salkeld, Magnus Byne
*/
final class EntryPointCache {
   
    /** The program model manager this class caches entry points for. */
    final ProgramModelManager programModelManager;
       
    /** this is the size of the module cache - it limits the number of entry
     * points that will be cached for any one module.
     */
    private final int numEntryPointsToCachePerModule;
   
    /**
     * This is used to map from ModuleName to a module caches.
     * A LRU cache is used, so that we only cache entry points in a fixed
     * number of modules. Once this number is exceeded, all the entry points for
     * the first module referenced are removed.
     */
    final private LRUCache<ModuleName, ModuleCache> cache;
   
    /**
     * this lock is used to protect the cache
     */
    final private ReadWriteLock cacheLock = new ReentrantReadWriteLock();

    /**
     * this is used for the caching  -
     * the least recently added items are removed from the cache
     * when the max size is exceeded
     *
     * @author Magnus Byne
     */
    private static class LRUCache<K,V> extends java.util.LinkedHashMap<K,V> {
        private static final long serialVersionUID = -2300095914974540766L;

        /** this is the maximum number of items the map will hold*/
        protected int cacheSize;
       
        /**
         * Create the cache
         * @param cacheSize the number of items to store
         */
        LRUCache(int cacheSize) {
            this.cacheSize = cacheSize;
        }
       
        @Override
        protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return size() >= cacheSize; }
    }
   
    /**
     * This class is used to cache entry points for a single module
     * @author mbyne
     */
    private final class ModuleCache {
        /** the cache of entrypointspec -> entrypoint*/
        final private LRUCache<EntryPointSpec, EntryPoint> cache;
       
        /** the name of the module that entry points are cached for*/
        final private ModuleName moduleName;
       
        /**lock used to gate access to the cache*/
        final private ReadWriteLock cacheLock = new ReentrantReadWriteLock();
       
        ModuleCache(ModuleName name, int size) {
            moduleName = name;
            cache = new LRUCache<EntryPointSpec, EntryPoint>(size);
        }
       
        /**
         * runs a function. If the entryPointSpec is not already in the cache it is
         * first added.
         * @param entryPointSpec describes the function to run
         * @param arguments the arguments for running the function
         * @param executionContext the execution context to use
         * @return the result of running the function
         * @throws CALExecutorException
         * @throws GemCompilationException
         */
        Object runFunction(EntryPointSpec entryPointSpec, Object[] arguments, ExecutionContext executionContext) throws CALExecutorException, GemCompilationException {           
            cacheLock.readLock().lock();
            try {
                EntryPoint ep = cache.get(entryPointSpec);
                if (ep == null) {
                    addEntryPoints(Collections.singletonList(entryPointSpec), true);
                    ep = cache.get(entryPointSpec);
                }      
                CALExecutor executor = programModelManager.makeExecutor(executionContext);

                return executor.exec(ep, arguments);

            } finally {
                cacheLock.readLock().unlock();
            }
        }
       
        /**
         * Add a list of entrypointspecs to the module cache
         * assumes that a read lock is already held on the cache
         * @param entryPointSpecs
         */
        void addEntryPoints(Collection<EntryPointSpec> entryPointSpecs) throws GemCompilationException {
            addEntryPoints(entryPointSpecs, false);
        }
       
        /**
         * Add a list of entrypointspecs to the module cache
         * assumes that a read lock is already held on the cache
         * @param entryPointSpecs
         * @param haveRead - this param indicates if we already have a read lock on the cache
         */
        private void addEntryPoints(Collection<EntryPointSpec> entryPointSpecs, boolean haveRead) throws GemCompilationException {
            //upgrade to a write lock
            if (haveRead) {
                cacheLock.readLock().unlock();
            }
            cacheLock.writeLock().lock();
            try {

                HashSet<EntryPointSpec> required = new HashSet<EntryPointSpec>();

                required.addAll(cache.keySet());
                required.addAll(entryPointSpecs);

                CompilerMessageLogger compilerLogger = new MessageLogger();
                      
                List<EntryPointSpec> requiredList = new ArrayList<EntryPointSpec>(required);
                //compile the entry points
                List<EntryPoint> entryPoints
                = programModelManager.getCompiler().getEntryPoints(requiredList, moduleName, compilerLogger);

                if (compilerLogger.getMaxSeverity().compareTo(CompilerMessage.Severity.ERROR) >= 0) {
                    StringBuffer sb = new StringBuffer();
                    sb.append("Failed to generate entry points for module '");
                    sb.append(moduleName);
                    sb.append(General.SYSTEM_EOL);
                    for(CompilerMessage msg : compilerLogger.getCompilerMessages()) {
                        sb.append(msg);
                        sb.append(General.SYSTEM_EOL);
                    }

                    //all of the cached entry points are invalid now.
                    cache.clear();
                    throw new GemCompilationException(sb.toString());
                }

                //update the cache with the new entry points
                // entry point spec
                for (int i = 0, n = entryPoints.size(); i < n; i++) {
                    EntryPointSpec entryPointSpec = requiredList.get(i);
                    EntryPoint entryPoint = entryPoints.get(i);
                    if (entryPoint != null) {
                        cache.put(entryPointSpec, entryPoint);
                    } else {
                        cache.remove(entryPointSpec);
                    }
                }
            } finally {
                if (haveRead) {
                    cacheLock.readLock().lock();
                }
                cacheLock.writeLock().unlock();
            }
        }
    }
   
    Object runFunction(EntryPointSpec entryPointSpec, Object[] arguments, ExecutionContext executionContext) throws CALExecutorException, GemCompilationException {
        cacheLock.readLock().lock();
        try {
            ModuleName moduleName = entryPointSpec.getFunctionalAgentName().getModuleName();
            ModuleCache moduleCache = getModuleCache(moduleName);
           
            return moduleCache.runFunction(entryPointSpec, arguments, executionContext);
        } finally {
            cacheLock.readLock().unlock();
        }
    }
  
    /**
     * gets the module cache for the specified module. It assumes there is already
     * a read lock on the module cache. If there is no entry for the module
     * it is created.
     * @param moduleName
     * @return the module cache
     */
    private ModuleCache getModuleCache(ModuleName moduleName)
    {
        ModuleCache moduleCache = cache.get(moduleName);

        if (moduleCache != null) {
            return moduleCache;
        }
       
        cacheLock.readLock().unlock();
        cacheLock.writeLock().lock();
        try {
            //make sure it hasn't been inserted by someone while the lock was being upgraded
            moduleCache = cache.get(moduleName);
            if (moduleCache != null) {
                return moduleCache;
            }
           
            moduleCache = new ModuleCache(moduleName, numEntryPointsToCachePerModule);
            cache.put(moduleName, moduleCache);
           
            return moduleCache;
        } finally {
            cacheLock.readLock().lock();
            cacheLock.writeLock().unlock();
        }
    }
    /**
     * Pre-caches entry points - can be used to cut down initial time taken to run functions.
     * @param entryPointSpecs
     * @throws GemCompilationException
     */
    void cacheEntryPoints(Collection<EntryPointSpec> entryPointSpecs) throws GemCompilationException {

        //split the entry points into sets of each module 
        HashMap<ModuleName, HashSet<EntryPointSpec> > specs = new HashMap<ModuleName, HashSet<EntryPointSpec> >();
       
        for(EntryPointSpec spec : entryPointSpecs) {
            ModuleName module = spec.getFunctionalAgentName().getModuleName();
            Set<EntryPointSpec> moduleSet = specs.get(module);
           
            if (moduleSet == null) {
                HashSet<EntryPointSpec> set = new HashSet<EntryPointSpec>();
                set.add(spec);
                specs.put(module, set);
            } else {
                moduleSet.add(spec);
            }
        }

        //update each of the module caches
        cacheLock.readLock().lock();
        try {
            for(ModuleName moduleName : specs.keySet()) {
                ModuleCache moduleCache = getModuleCache(moduleName);
                moduleCache.addEntryPoints(specs.get(moduleName));
            }
        } finally {
            cacheLock.readLock().unlock();
        }      
       
    }
   
    /**
     * EntryPointCache constructor.
     * @param programModelManager
     */
    EntryPointCache(ProgramModelManager programModelManager, int numEntryPointsToCachePerModule, int numModulesToCache) {
        this.programModelManager = programModelManager;
        this.numEntryPointsToCachePerModule = numEntryPointsToCachePerModule;
        cache = new LRUCache<ModuleName, ModuleCache>(numModulesToCache);
    }
   
   
    /** lock the module cache */
    void lockCache() {
        cacheLock.writeLock().lock();
    }

    /** unlock the module cache */
    void unlockCache() {
        cacheLock.writeLock().unlock();
    }
   
    /**
     * remove everything from the cache
     * must do this whenever any modules are recompiled, as they could affect any other module.
     * Must have acquired the module lock first.
     */
    void flush() {
        cache.clear();
    }
}
TOP

Related Classes of org.openquark.cal.services.EntryPointCache

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.