FileDocCategorySizeDatePackage
LruSessionCache.javaAPI DocGlassfish v2 API30407Fri May 04 22:33:00 BST 2007com.sun.ejb.containers.util.cache

LruSessionCache

public class LruSessionCache extends com.sun.ejb.containers.util.cache.LruCache implements com.sun.ejb.spi.stats.EJBCacheStatsProvider

Fields Summary
protected int
numActivated
protected int
cacheIdleTimeoutInSeconds
protected int
removalTimeoutInSeconds
protected Object
loadCountLock
protected int
loadFromBackupCount
protected boolean
removeIfIdle
public int
passivationCount
protected Object
passivationCountLock
private Object
numPassLock
private int
numVictimsAccessed
protected com.sun.ejb.spi.container.SFSBContainerCallback
container
protected com.sun.ejb.spi.sfsb.store.SFSBStoreManager
storeManager
private static final byte
CACHE_ITEM_VALID
private static final byte
CACHE_ITEM_LOADING
private static final byte
CACHE_ITEM_REMOVED
protected String
configData
private static final int
STATE_RUNNING
private static final int
STATE_SHUTTING_DOWN
private static final int
STATE_UNDEPLOYING
private static final int
STATE_DESTROYED
private int
currentCacheState
protected int
confMaxCacheSize
private com.sun.ejb.base.stats.StatefulSessionStoreMonitor
sfsbStoreMonitor
Constructors Summary
public LruSessionCache(String cacheName, com.sun.ejb.spi.container.SFSBContainerCallback container, int cacheIdleTime, int removalTime)

        super();
        super.setCacheName(cacheName);

        this.container = container;
        
        this.cacheIdleTimeoutInSeconds = 
            (cacheIdleTime <= 0) ? 0 : cacheIdleTime;
        this.removalTimeoutInSeconds = 
            (removalTime <= 0) ? 0 : removalTime;
        
        if (cacheIdleTimeoutInSeconds > 0) {
            super.timeout = cacheIdleTimeoutInSeconds*1000;
        }

        removeIfIdle = (removalTimeoutInSeconds > 0)
	    && (removalTimeoutInSeconds <= cacheIdleTimeoutInSeconds);
    
Methods Summary
public voidappendStats(java.lang.StringBuffer sbuf)

	sbuf.append("[Cache: ")
	    .append("Size=").append(entryCount).append("; ")
	    .append("HitCount=").append(hitCount).append("; ")
	    .append("MissCount=").append(missCount).append("; ")
	    .append("Passivations=").append(getNumPassivations()).append("; ");
	if (configData != null) {
	    sbuf.append(configData);
	}
	sbuf.append("]");
    
protected CacheItemcreateItem(int hashCode, java.lang.Object sessionKey, java.lang.Object value, int size)

        return new LruSessionCacheItem(hashCode, sessionKey, value, size);
    
public voiddestroy()
Destroys all references. This is the last method call of this object's life cycle. This method is called during undeploy of ejb container.


                                  
       
        this.currentCacheState = STATE_DESTROYED;
        this.container = null;

        super.destroy();
    
public intgetCacheHits()

	return hitCount;
    
public intgetCacheMisses()

	return missCount;
    
public intgetLoadFromBackupCount()

        return loadFromBackupCount;
    
public intgetMaxCacheSize()

	return confMaxCacheSize;
    
public intgetNumBeansInCache()

	return entryCount;
    
public intgetNumExpiredSessionsRemoved()

	return (sfsbStoreMonitor == null)
        ? 0 : sfsbStoreMonitor.getNumExpiredSessionsRemoved();
    
public intgetNumPassivationErrors()

	return (sfsbStoreMonitor == null)
        ? 0 : sfsbStoreMonitor.getNumPassivationErrors();
    
public intgetNumPassivationSuccess()

	return (sfsbStoreMonitor == null)
        ? 0 : sfsbStoreMonitor.getNumPassivationSuccess();
    
public intgetNumPassivations()

	return (sfsbStoreMonitor == null)
        ? 0 : sfsbStoreMonitor.getNumPassivations();
    
public intgetNumVictimsAccessed()

        return numVictimsAccessed;
    
private java.lang.ObjectgetStateFromStore(java.lang.Object sessionKey, com.sun.ejb.spi.container.SFSBContainerCallback container)


        Object object = null;

        try {
            SFSBBeanState beanState = storeManager.getState(sessionKey);
            byte[] data = (beanState != null)
                ? beanState.getState()
                : null;
            if ( data == null ) {
                if(_logger.isLoggable(Level.SEVERE)) {
                    _logger.log(Level.SEVERE, cacheName + ": Cannot load from "
                                + " BACKUPSTORE FOR Key: <" + sessionKey + ">");
                }
            }  else {
		sfsbStoreMonitor.setActivationSize(data.length);
                incrementLoadFromBackupCount();
                object = IOUtils.deserializeObject(data, true,
                        container.getClassLoader());
            }
        } catch ( Exception ex ) {
            _logger.log(Level.SEVERE, cacheName + ": Exception while "
                        + " loading from backup session: <" + sessionKey + ">", ex);
        } catch ( Error ex ) {
            _logger.log(Level.SEVERE, cacheName + ": Error while "
                        + " loading from backup session: <" + sessionKey + ">", ex);
        }

        return object;
    
protected voidincrementLoadFromBackupCount()

        synchronized (loadCountLock) {
            loadFromBackupCount++;
        }
    
protected voiditemAccessed(CacheItem item)

        LruCacheItem lc = (LruCacheItem) item;
        synchronized (this) {
	    if (lc.isTrimmed) {
		lc.isTrimmed = false;
		numVictimsAccessed += 1;
		CacheItem overflow = super.itemAdded(item);
		if (overflow != null) {
		    trimItem(overflow);
		}
	    } else {
		super.itemAccessed(item);
	    }
	}
    
public com.sun.ejb.spi.container.StatefulEJBContextlookupEJB(java.lang.Object sessionKey, com.sun.ejb.spi.container.SFSBContainerCallback container, java.lang.Object cookie)

        int hashCode = hash(sessionKey);
        int index = getIndex(hashCode);
        CacheItem item = null;
        LruSessionCacheItem newItem = null;
        Object value = null;

        synchronized (bucketLocks[index]) {
            item = buckets[index];
            for (; item != null; item = item.next) {
                if ( (hashCode == item.hashCode) && 
                     (item.key.equals(sessionKey)) )
                {
                    value = item.value;
                    break;
                }
            }
            
            // update the stats in line
            if (value != null) {
                itemAccessed(item);
            } else if (item == null) {
                newItem = new LruSessionCacheItem(hashCode, sessionKey,
                        null, -1, CACHE_ITEM_LOADING); 
                newItem.next = buckets[index];
                buckets[index] = newItem;
            }
        }

        
        if (value != null) {
            incrementHitCount();
            return (StatefulEJBContext) value;
        } 
        
        incrementMissCount();
        if (item != null) {
            synchronized (item) {
                LruSessionCacheItem lruItem = (LruSessionCacheItem) item;
                if ((lruItem.value == null) && (lruItem.cacheItemState == CACHE_ITEM_LOADING)) {
                    lruItem.waitCount++;
                    try { item.wait(); } catch (InterruptedException inEx) {}
                }
                return (StatefulEJBContext) item.value;
            }
        }

        //This is the thread that actually does the I/O
	long activationStartTime = -1;
	if (sfsbStoreMonitor.isMonitoringOn()) {
	    activationStartTime = System.currentTimeMillis();
	}
        try {
            newItem.value = value = getStateFromStore(sessionKey, container);
            synchronized (buckets[index]) {
                if (value == null) {
                    //Remove the temp cacheItem that we created.
                    CacheItem prev = null;
                    for (CacheItem current = buckets[index]; current != null;
                            current = current.next)
                    {
                        if (current == newItem) {
                            if (prev == null) {
                                buckets[index] = current.next;
                            } else {
                                prev.next = current.next;
                            }
                            current.next = null;
                            break;
                        }
                        prev = current;
                    }
                } else {
                    container.activateEJB(sessionKey,
                        (StatefulEJBContext) value, cookie);
		    sfsbStoreMonitor.incrementActivationCount(true);

                    CacheItem overflow = itemAdded(newItem);
                    incrementEntryCount();
                    // make sure we are are not crossing the threshold
                    if (overflow != null) {
                        trimItem(overflow);
                    }
                }
            } //end of sync
        } catch (javax.ejb.EJBException ejbEx) {
	    sfsbStoreMonitor.incrementActivationCount(false);
            remove(sessionKey);
            value = null;
        } finally {
            synchronized (newItem) {
                newItem.cacheItemState = CACHE_ITEM_VALID;
                if (newItem.waitCount > 0) {
                    newItem.notifyAll();
                }
            }
	    if (activationStartTime != -1) {
		long timeSpent = System.currentTimeMillis()
		    - activationStartTime;
		sfsbStoreMonitor.setActivationTime(timeSpent);
	    }
        }

        return (StatefulEJBContext) value;
    
public booleanpassivateEJB(com.sun.ejb.spi.container.StatefulEJBContext ctx, java.lang.Object sessionKey)


        try {
            int hashCode = hash(sessionKey);
            int index = getIndex(hashCode);
            
            boolean itemRemoved = false;
            CacheItem prev = null, item = null;
            synchronized (bucketLocks[index]) {
                for (item = buckets[index]; item != null; item = item.next) {
                    if (item.value == ctx) {
                        LruCacheItem lruSCItem = (LruCacheItem) item;
                        if (lruSCItem.isTrimmed == false) {
                            //Was accessed just after marked for passivation
                            if(_logger.isLoggable(Level.FINE)) {
                                _logger.log(Level.FINE, cacheName +  ": session accessed after marked for passivation: " + sessionKey);
                            }
                            return false;
                        }
                        break;
                    }
                    prev = item;
                }

                if (item == null) {
                    //Could have been removed
                    return true; //???????
                    //return (storeManager.contains(sessionKey));
                }

                if (removeIfIdle) {
                    long idleThreshold = System.currentTimeMillis() - 
                        removalTimeoutInSeconds*1000;
                    //XXX: Avoid currentTimeMillis
                    if (ctx.getLastAccessTime() <= idleThreshold) {
                        if(_logger.isLoggable(Level.FINE)) {
                            _logger.log(Level.FINE, cacheName + 
                                ": Removing session "
                                + " instead of passivating for key: " + sessionKey);
                        }

                    	if (prev == null) {
                            buckets[index] = item.next;
                    	} else {
                            prev.next = item.next;
                    	}
                    	item.next = null;
                        itemRemoved = true;
                        //TODO::store.incrementExpiredSessionsRemoved();
                    }
                }

            }

            if (itemRemoved) {
                decrementEntryCount();
                incrementRemovalCount();
                return true;
            }

            if (saveStateToStore(sessionKey, ctx) == false) {
                return false;
            }

            synchronized (bucketLocks[index]) {
                prev = null;
                for (item = buckets[index]; item != null; item = item.next) {
                    if (item.value == ctx) {
                        LruCacheItem lruSCItem = (LruCacheItem) item;
                        if (lruSCItem.isTrimmed == false) {
                            //Was accessed just after marked for passivation
                            return false;
                        }
                        
                    	if (prev == null) {
                            buckets[index] = item.next;
                    	} else  {
                            prev.next = item.next;
                    	}
                    	item.next = null;
                        break;
                    }
                    prev = item;
                }
            }
            
            if (item != null) {
            	decrementEntryCount();
                incrementRemovalCount();
            }
            
            return true;
        } catch (java.io.NotSerializableException notSerEx) {
	    throw notSerEx;
        } catch (Exception ex) {
            _logger.log(Level.WARNING, 
               "[" + cacheName + "]: passivateEJB(), Exception caught -> ",ex);
        }
        return false;
    
public java.lang.Objectremove(java.lang.Object sessionKey)

	return remove(sessionKey, true);
    
public java.lang.Objectremove(java.lang.Object sessionKey, boolean removeFromStore)

        int hashCode = hash(sessionKey);
        int index = getIndex(hashCode);

        CacheItem prev = null, item = null;

        synchronized (bucketLocks[index]) {
            for (item = buckets[index]; item != null; item = item.next) {
                if ((hashCode == item.hashCode) && sessionKey.equals(item.key)) {
                    if (prev == null) {
                        buckets[index] = item.next;
                    } else  {
                        prev.next = item.next;
                    }
                    item.next = null;
                    
                    itemRemoved(item);
                    ((LruSessionCacheItem) item).cacheItemState = CACHE_ITEM_REMOVED;
                    break;
                }
                prev = item;
            }

            //remove it from the storeManager also
            //In case it had been checkpointed

            //  remove it from storeManager outside sync block
	    if (removeFromStore) {
		try {
		    storeManager.remove(sessionKey);
		} catch (SFSBStoreManagerException sfsbEx) {
		    _logger.log(Level.WARNING, "[" + cacheName + "]: Exception in "
			+ "storeManager.remove(" + sessionKey + ")", sfsbEx);
		}
	    }
	}
        
        if (item != null) {
            decrementEntryCount();
            incrementRemovalCount();
            
            incrementHitCount();
        } else {
            incrementMissCount();
        }
        
        return null;
    
private booleansaveStateToStore(java.lang.Object sessionKey, com.sun.ejb.spi.container.StatefulEJBContext ctx)

        byte[] data = IOUtils.serializeObject(ctx.getSessionContext(), true);

	//If we are here then we were able to serialize the object successfully
        boolean status = false;
	
	if (data != null) {
	    SFSBBeanState beanState = storeManager.createSFSBBeanState(
		sessionKey, ctx.getLastAccessTime(),
		!ctx.existsInStore(), data);
        
        //Note: Don't increment the version here because
        //  this is called on an async thread and the client
        //  already has the correct version
        beanState.setVersion(ctx.getVersion());
	    try {
		storeManager.passivateSave(beanState);
		sfsbStoreMonitor.setPassivationSize(data.length);
		status = true;
	    } catch (SFSBStoreManagerException sfsbEx) {
		_logger.log(Level.WARNING, "[" + cacheName + "]: Exception during "
		    + "storeManager.passivateSave(" + sessionKey + ")", sfsbEx);
	    }
	}

	return status;
    
public voidsetConfigData(java.lang.String configData)

	this.configData = configData;
    
public voidsetMaxCacheSize(int val)

	this.confMaxCacheSize = val;
    
public voidsetSessionStore(com.sun.ejb.spi.sfsb.store.SFSBStoreManager storeManager)

        this.storeManager = storeManager;
	//this.monitorableSFSBStore = storeManager.getMonitorableSFSBStore();
    
public voidsetShutdownState()

	currentCacheState = STATE_SHUTTING_DOWN;
    
public voidsetStatefulSessionStoreMonitor(com.sun.ejb.base.stats.StatefulSessionStoreMonitor storeMonitor)

	this.sfsbStoreMonitor = storeMonitor;
    
public voidsetUndeployedState()

	currentCacheState = STATE_UNDEPLOYING;
    
public voidshutdown()

        ArrayList<StatefulEJBContext> valueList = new ArrayList<StatefulEJBContext>();

        synchronized (this) {
            LruCacheItem item = tail;
            while (item != null) {
                StatefulEJBContext ctx = (StatefulEJBContext) item.value;
                if (ctx != null) {
                    item.isTrimmed = true;
                    valueList.add(ctx);
                }

                // Ensure that for head the lPrev is null
                if ((item == head) && (item.lPrev != null)) {
                    _logger.log(Level.WARNING, "[" + cacheName
                            + "]: Iterator(), resetting head.lPrev");
                    item.lPrev = null;
                }
                // traverse to the previous one
                item = item.lPrev;
            }
        }

        for (StatefulEJBContext ctx : valueList) {
            container.passivateEJB(ctx);
        }
    
protected voidtrimItem(CacheItem item)
trim the item from the cache and notify listeners

param
item to be trimmed

        LruCacheItem removed = (LruCacheItem) item;

        if (removeIfIdle) {
            StatefulEJBContext ctx = (StatefulEJBContext) item.value;
            
            long idleThreshold = 
                System.currentTimeMillis() - removalTimeoutInSeconds*1000;
            if (ctx.getLastAccessTime() <= idleThreshold) {
                container.passivateEJB(ctx);
                return;
            }
        }

        for (int i = 0; i < listeners.size(); i++) {
            CacheListener listener = (CacheListener) listeners.get(i);
            listener.trimEvent(removed.key, removed.value);
        }
    
private voidtrimSelectedVictims(java.util.ArrayList victims)

        int sz = victims.size();
        
        synchronized (this) {
            trimCount += sz;
        }
        CacheItem item = null;
        for (int i=0; i<sz; i++) {
            item = (CacheItem) victims.get(i);
            trimItem(item);
    	}
    
public voidtrimTimedoutItems(int maxTrimCount)
trim the timedOut entries from the cache. This call is to be scheduled by a thread managed by the container. In this case a sorted LRU list exists based on access time and this list is scanned

        
        int count = 0;
        LruCacheItem item;
        long currentTime = System.currentTimeMillis();
        long idleThresholdTime = currentTime - cacheIdleTimeoutInSeconds*1000;
        ArrayList victimList = new ArrayList();

        synchronized (this) {
            if(_logger.isLoggable(Level.FINE)) {
            	_logger.log(Level.FINE, 
                    "[" + cacheName + "]: TrimTimedoutBeans started...");
            }
           
            if (tail == null) {	// No LRU list exists
                if(_logger.isLoggable(Level.FINE)) {
                    _logger.log(Level.FINE, 
                                "[" + cacheName + "]: TrimTimedoutBeans "
                                + " finished after removing 0 idle beans");
                }
                head = null;
                return;
            }
            // traverse LRU list and collect victims into the victimList
            item = tail;
            while (true) {
		if (currentCacheState != STATE_RUNNING) {
                    _logger.log(Level.WARNING, 
                        "[" + cacheName + "]: Exiting TrimTimedoutBeans() because "
			+ "current cache state: " + currentCacheState);
		    break;
		}

                StatefulEJBContext ctx = (StatefulEJBContext) item.value;
                if (ctx != null) {
                    // if we found a valid item, add it to the list
                    if ((ctx.getLastAccessTime() <= idleThresholdTime) &&
                        ctx.canBePassivated()) {
                        item.isTrimmed = true;
                        victimList.add(item);
                    } else {
                        break;
                    }
                }
                //Ensure that for head the lPrev is null
                if( (item == head) && (item.lPrev != null) ) {
                    _logger.log(Level.WARNING, 
                        "[" + cacheName + "]: TrimTimedoutBeans(), resetting head.lPrev");
                    item.lPrev = null;
                }
                // traverse to the previous one
                item = item.lPrev;
                if (item == null) {
                    break;
                }
                //for the last item that was picked up as a victim disconnect
                //it from the list
                item.lNext.lPrev = null;
                item.lNext.lNext = null;

                item.lNext = null;
            }
            if (item == tail) {			
                // no items were selected for trimming
                if(_logger.isLoggable(Level.FINE)) {
                    _logger.log(Level.FINE, 
                                "[" + cacheName + "]: TrimTimedoutBeans "
                                + " finished after removing 0 idle beans");
                }
                return;
            }

            // there is at least one item selected for trimming
            if (item == null)
                head = null;

            tail = item;
            count = victimList.size();
            listSize -= count;
            trimCount += count;
        }
        
        // trim the items from the BaseCache
        for (int idx = 0;idx < count; idx++) {
            trimItem((LruCacheItem) victimList.get(idx));
        }

        if(_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE, 
                        "[" + cacheName + "]: TrimTimedoutBeans "
                        + " finished after removing " + count + " idle beans");
        }
    
public voidtrimUnSortedTimedoutItems(int maxCount)
This method picks idle items from a cache which does not have a sorted LRU list NRU cache at light loads and FIFO caches do not maintain a LRU list and hence they have to scan the entire cache and select victims

        int maxIndex = buckets.length;
        long idleThreshold = System.currentTimeMillis() - timeout;
        ArrayList victims = new ArrayList();
        int sz = 0;
        int totalSize = 0;
        
        if(_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE, 
                        "[" + cacheName + "]: TrimUnsortedTimedoutBeans started...");
        }
        // Go through each bucket in the cache and if there are entries in that
        // bucket scan them and select victims
        for (int index = 0; index < maxIndex ; index++) {

            if (buckets[index] != null) {
                synchronized (bucketLocks[index]) {
                    for (CacheItem item = buckets[index]; item != null; 
                         item = item.next) {
                        StatefulEJBContext ctx = 
                            (StatefulEJBContext) item.value;
                        //Note ctx can be null if bean is in BEING_REFRESHED state
                        if ((ctx != null) && 
                            (ctx.getLastAccessTime() <= idleThreshold) &&
                            ctx.canBePassivated()) {
                            LruCacheItem litem = (LruCacheItem)item;
                            synchronized (this) {
				if (currentCacheState != STATE_RUNNING) {
				    _logger.log(Level.WARNING, 
					"[" + cacheName + "]: Exiting TrimUnSortedTimedoutBeans() "
					+ "because current cache state: " + currentCacheState);
				    break;
				}
                                if (litem.isTrimmed == false) {
                                    itemRemoved(litem);
                                    litem.isTrimmed = true;
                                    victims.add(litem);
                                }
                            }
                        }
                    }
                }
                // Check and see if we have collected enough victims 
                // to start a cleaner task
                sz = victims.size();
                if (sz >= container.getPassivationBatchCount()) {
                    trimSelectedVictims(victims);
                    totalSize += sz;
                    victims.clear();
                }
            }
        }

        sz = victims.size();
        if (sz > 0) {
            trimSelectedVictims(victims);
            totalSize += sz;
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE, "[" + cacheName + "]: TrimUnsortedTimedoutBeans "
                + " finished after removing " + totalSize + " idle beans");
        }
    
public java.util.Iteratorvalues()
get an Iterator for the values stored in the cache

returns
an Iterator

        ArrayList valueList = new ArrayList();

	synchronized (this) {
            LruCacheItem item = tail;
            while (item != null) {
                StatefulEJBContext ctx = (StatefulEJBContext) item.value;
                if (ctx != null) {
		    valueList.add(ctx);
		}

                //Ensure that for head the lPrev is null
                if( (item == head) && (item.lPrev != null) ) {
                    _logger.log(Level.WARNING, 
                        "[" + cacheName + "]: Iterator(), resetting head.lPrev");
                    item.lPrev = null;
                }
                // traverse to the previous one
                item = item.lPrev;
	    }
	}

        return valueList.iterator();