directory-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From pamarce...@apache.org
Subject svn commit: r1161559 - in /directory/apacheds/branches/apacheds-jdbm/jdbm/src: main/java/jdbm/ main/java/jdbm/helper/ main/java/jdbm/recman/ test/java/jdbm/btree/ test/java/jdbm/helper/
Date Thu, 25 Aug 2011 13:29:19 GMT
Author: pamarcelot
Date: Thu Aug 25 13:29:18 2011
New Revision: 1161559

URL: http://svn.apache.org/viewvc?rev=1161559&view=rev
Log:
Added missing parts of Selcuk AYA's patch.

Added:
    directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/ActionRecordManager.java
    directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ActionContext.java
    directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ActionVersioning.java
    directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/EntryIO.java
    directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ExplicitList.java
    directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/LRUCache.java
    directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/recman/SnapshotRecordManager.java
    directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/btree/SnapshotBTree.java
    directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/helper/
    directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/helper/TestActionVersioning.java
    directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/helper/TestVersionedCache.java

Added: directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/ActionRecordManager.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/ActionRecordManager.java?rev=1161559&view=auto
==============================================================================
--- directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/ActionRecordManager.java (added)
+++ directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/ActionRecordManager.java Thu Aug 25 13:29:18 2011
@@ -0,0 +1,18 @@
+package jdbm;
+
+import jdbm.helper.ActionContext;
+
+
+public interface ActionRecordManager extends RecordManager
+
+{
+    ActionContext beginAction( boolean readOnly );
+    
+    void endAction( ActionContext context );
+    
+    void abortAction( ActionContext context );
+    
+    void setCurrentActionContext( ActionContext context );
+    
+    void unsetCurrentActionContext( ActionContext context );
+}

Added: directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ActionContext.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ActionContext.java?rev=1161559&view=auto
==============================================================================
--- directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ActionContext.java (added)
+++ directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ActionContext.java Thu Aug 25 13:29:18 2011
@@ -0,0 +1,49 @@
+package jdbm.helper;
+
+/**
+ * Used to store Action specific context.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class ActionContext
+    {
+
+        boolean readOnly;
+        ActionVersioning.Version version;
+        
+        public void beginAction( boolean readOnly, ActionVersioning.Version version )
+        {
+            this.readOnly = readOnly;
+            this.version = version;
+        }
+        
+        public void endAction()
+        {
+            assert( version != null );
+            version = null;
+        }
+        
+        public boolean isReadOnlyAction()
+        {
+            return ( readOnly && this.version != null );
+        }
+        
+        public boolean isWriteAction()
+        {
+            return ( !readOnly && this.version != null );
+            
+        }
+        
+        public boolean isActive()
+        {
+            return ( this.version != null );
+        }
+        
+        public ActionVersioning.Version getVersion()
+        {
+            return version;
+        }
+    
+        
+    }
+    
\ No newline at end of file

Added: directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ActionVersioning.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ActionVersioning.java?rev=1161559&view=auto
==============================================================================
--- directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ActionVersioning.java (added)
+++ directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ActionVersioning.java Thu Aug 25 13:29:18 2011
@@ -0,0 +1,199 @@
+package jdbm.helper;
+
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+
+/**
+ * This is a helper class to keep track of versions assigned to actions. As client begin
+ * read only and read write actions, they call into this class and get the version they
+ * can use. As the clients end their actions, minimum read version any action is using is
+ * advanced and piggybacked to the client
+ * 
+ * This class assumes that there is one readWrite action at a time. 
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+
+public class ActionVersioning
+{
+
+    /** Current write version */
+    private Version nextVersion;
+    
+    /** Current read version reference */
+    private AtomicReference<Version> readReference;
+    
+    /** List to put versions on */
+    private ExplicitList<Version> versions = new ExplicitList<Version>();
+    
+    /** Lock to protect the list */
+    private Lock listLock = new ReentrantLock();
+    
+    
+    public ActionVersioning()
+    {
+        Version readVersion = new Version( 0 );
+        nextVersion = new Version( 1 ); 
+        readReference = new AtomicReference<Version>( readVersion );
+
+        versions.addFirst( nextVersion.getVersionsLink() );
+        versions.addFirst( readVersion.getVersionsLink() );
+    }
+    
+    
+    /**
+     * Returns back the new version to be used with the read/write action.
+     * Assume one read/write action at a time.
+     *
+     * @return new version for the action system
+     */
+    public Version beginWriteAction()
+    {
+        return nextVersion;
+    }
+    
+    
+    /**
+     * Called when the read/write action completes. Advances the version of action subsystem 
+     * and publishes a new version for the readers. Assume one read/write action at a time.
+     *
+     * @return minimum read version for the action subsystem
+     */
+    public Version endWriteAction()
+    {
+        Version minVersion;
+        
+        // Allocate the new nextVersion
+        Version newNextVersion = new Version( nextVersion.getVersion() + 1 );
+        
+        // Post the commited version as the new read version
+        Version oldReadVersion = readReference.getAndSet( nextVersion );
+        
+        // add the newnextversion to the versions list
+        listLock.lock();
+        versions.addLast( newNextVersion.getVersionsLink() );
+        
+        if ( oldReadVersion.getNumActions().get() == 0 && 
+            oldReadVersion.getVersionsLink().isLinked() )
+        {
+            versions.remove( oldReadVersion.getVersionsLink() );
+            oldReadVersion.getVersionsLink().uninit();
+        }
+        
+        minVersion = versions.begin().getElement();
+        listLock.unlock();
+        
+        nextVersion = newNextVersion;
+        return minVersion;
+    }
+    
+    /**
+     * Returns a version that can be used by the read only action
+     *
+     * @return version to be used by the action
+     */
+    public Version beginReadAction()
+    {
+        Version readVersion = readReference.get();
+        
+        readVersion.getNumActions().incrementAndGet();
+        
+        /*
+         * If the write txn just finished and published
+         * a new version to read, check if we can still
+         * use our version for reading
+         */
+        if ( readVersion != readReference.get() )
+        {
+            listLock.lock();
+            if ( readVersion.getVersionsLink().isUnLinked() )
+            {
+                readVersion = readReference.get();
+                readVersion.getNumActions().incrementAndGet();
+            }
+            listLock.unlock();
+
+        }
+        
+        return readVersion;
+    }
+    
+    /**
+     * Called when the read action with the given action is ended.
+     * Checks whether the minimum read version advanced
+     *
+     * @param version version of the read only action
+     * @return returns the miminum read version. Might return null if read version did not 
+     * advance for sure.
+     */
+    public Version endReadAction( Version version )
+    {
+        long numActions = version.getNumActions().decrementAndGet();
+        
+        assert( numActions >= 0 );
+        
+        
+        
+        if ( numActions > 0 || version == readReference.get() )
+        {
+            // minimum read version did not change for sure
+            return null;
+        }
+        
+        Version minVersion = null;
+        listLock.lock();
+        if ( version.getNumActions().get() == 0 && 
+            version.getVersionsLink().isLinked() )
+        {
+            version.getVersionsLink().remove();
+            version.getVersionsLink().uninit();
+        }
+        minVersion = versions.begin().getElement();
+        listLock.unlock();
+        
+        return minVersion;
+        
+    }
+    
+    public static class Version
+    {
+        /** Represented version */
+        private long version;
+        
+        /** Used to put on versions chain */
+        private ExplicitList.Link<Version> versionsLink;
+        
+        /** Number of txns running with this version */
+        private  AtomicInteger numActions;
+        
+        
+        public Version ( long version )
+        {
+            this.version = version;
+            
+            versionsLink = new ExplicitList.Link<ActionVersioning.Version>( this );
+            
+            numActions = new AtomicInteger( 0 );
+        }
+        
+        private ExplicitList.Link<Version> getVersionsLink()
+        {
+            return versionsLink;
+        }
+        
+        private AtomicInteger getNumActions()
+        {
+            return numActions;
+        }
+        
+        public long getVersion()
+        {
+            return version;
+        }
+    }
+}
\ No newline at end of file

Added: directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/EntryIO.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/EntryIO.java?rev=1161559&view=auto
==============================================================================
--- directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/EntryIO.java (added)
+++ directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/EntryIO.java Thu Aug 25 13:29:18 2011
@@ -0,0 +1,9 @@
+package jdbm.helper;
+
+import java.io.IOException;
+
+public interface EntryIO<K, V>
+{
+	public V read( K key, Serializer serializer) throws IOException;
+	public void write( K key, V value, Serializer serializer ) throws IOException;
+}
\ No newline at end of file

Added: directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ExplicitList.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ExplicitList.java?rev=1161559&view=auto
==============================================================================
--- directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ExplicitList.java (added)
+++ directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/ExplicitList.java Thu Aug 25 13:29:18 2011
@@ -0,0 +1,137 @@
+package jdbm.helper;
+
+/**
+ * A imple doubly linked list implementation that can be used when fast remove operations are desired.
+ * Objects are inserted into the list through an anchor (Link). When object is to be removed from the
+ * list, this anchor is provided by the client again and this class can do the remove operation in O(1)
+ * using the given anchor.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+
+public class ExplicitList<T>
+{
+    
+ Link<T> head = new Link<T>( null );   
+    
+ public static class Link<V>
+ {
+     private V element;
+     private Link<V> next;
+     private Link<V> prev;
+     
+     public Link( V element )
+     {
+         this.element = element;
+         this.reset();
+     }
+     
+     public Link<V> getNext()
+     {
+         return next;
+     }
+     
+     public void setNext( Link<V> next )
+     {
+         this.next = next;
+     }
+     
+     public Link<V> getPrev()
+     {
+         return prev;
+     }
+     
+     public void setPrev( Link<V>  prev )
+     {
+         this.prev = prev;
+     }
+     
+     public void remove()
+     {
+         assert( isLinked() );
+         this.getPrev().setNext( this.getNext() );
+         this.getNext().setPrev( this.getPrev() );
+         this.reset();
+     }
+     
+     public void addAfter( Link<V> after )
+     {
+         after.getNext().setPrev( this );
+         this.setNext( after.getNext() );
+         after.setNext( this );
+         this.setPrev( after );
+     }
+     
+     public void addBefore( Link<V> before )
+     {
+         before.getPrev().setNext(this );
+         this.setPrev( before.getPrev() );
+         before.setPrev( this );
+         this.setNext( before );
+     }
+     
+     public void splice( Link<V> listHead)
+     {
+         Link<V> prevLink = listHead.getPrev();
+         listHead.setPrev( this );
+         prevLink.setNext( this );
+         this.setNext( listHead );
+         this.setPrev( prevLink );
+     }
+     
+     public boolean isUnLinked()
+     {
+         return ( prev == this && next == this  );
+     }
+     
+     public boolean isLinked()
+     {
+         return ( !this.isUnLinked() );
+     }
+     
+     public void reset()
+     {
+         next = this;
+         prev = this;      
+     }
+     
+     public void uninit()
+     {
+         assert ( this.isUnLinked() );
+         element = null;
+     }
+     
+     public V getElement()
+     {
+         return this.element;
+     }
+ }
+ 
+ 
+ public void remove( Link<T> link )
+ {
+     link.remove();
+ }
+ 
+ public void addFirst( Link<T> link )
+ {
+     link.addAfter( head );
+ }
+ 
+ public void addLast( Link<T> link )
+ {
+    link.addBefore( head );
+ }
+ 
+ public Link<T> begin()
+ {
+     return ( head.getNext() );
+ }
+ 
+ public Link<T> end()
+ {
+     return head;
+ }
+ 
+ 
+}
\ No newline at end of file

Added: directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/LRUCache.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/LRUCache.java?rev=1161559&view=auto
==============================================================================
--- directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/LRUCache.java (added)
+++ directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/helper/LRUCache.java Thu Aug 25 13:29:18 2011
@@ -0,0 +1,1071 @@
+package jdbm.helper;
+
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Iterator;
+import java.util.PriorityQueue;
+
+
+import java.util.Comparator;
+
+import java.util.Random;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.Condition;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import java.io.IOException;
+
+
+/**
+ * This class implements a versioned lru cache. Entries in the cache are identified with a key. 
+ * When clients get a reference to the an entry, they point to the same object. Hence when the 
+ * client wants to update and put a new version of the entry to the cache, he should not modify
+ * the object it got through the get interface (might copy on write).
+ * 
+ * As new versions of the entries are put, cache maintains the previous versions of the entry.
+ * CLients specify the version of the cache entry they want to read. 
+ * Clients are supposed to update the minimum read version the clients might request so that
+ * cache can purge older versions of data.
+ * 
+ * For now, this class assumes clients make sure that put operations are serialized.
+ *   
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+
+/*
+ * TODO handle cache eviction exception, abort of actions and closing of cache
+ */
+
+public class LRUCache<K, V>
+{
+   
+    /** Array of hash buckets */
+    private List<CacheEntry> buckets[];
+    
+    /** Array of latches protecting buckets */
+    private Lock latches[];
+
+    /** Power of two number of buckets */
+    private final int numBuckets;
+    
+    /** Log of number of hash buckets each latch protects */
+    private final static int LOG_BUCKET_PER_LATCH = 3;
+    
+    /** Number of lrus */
+    private final static int NUM_LRUS = 16;
+    
+    /** Min number of entries */
+    private final static int MIN_ENTRIES = 1024;
+    
+    /** lru list */
+    LRU lrus[];
+    
+    /** Random number generator used for randomizing access to LRUS */
+    Random lruRandomizer = new Random();
+    
+    /** current number of cache entries */
+    private AtomicInteger numEntries;
+    
+    /** maximum number of entries */
+    private final int maxEntries;
+
+    /** Callback used to initialize entries not in cache */
+    private final EntryIO<K, V> entryIO;
+   
+    /** minimum version cache has to satisfy during reads */
+    private long minReadVersion;
+    
+
+
+    @SuppressWarnings("unchecked") 
+    public LRUCache( EntryIO<K, V> entryIO, int cacheSize )
+    {
+        int idx;
+        this.entryIO =entryIO;
+        
+        if ( cacheSize < MIN_ENTRIES )
+            cacheSize = MIN_ENTRIES;
+        
+        maxEntries = cacheSize;
+        
+        int numHashBuckets = MIN_ENTRIES;
+        while ( numHashBuckets < maxEntries )
+            numHashBuckets  = numHashBuckets << 1;
+        
+        if ( numHashBuckets >  maxEntries)
+            numBuckets = numHashBuckets >> 1;
+        else
+            numBuckets  = numHashBuckets;
+        
+       buckets = ( List<CacheEntry>[] )new LinkedList[numBuckets];
+       for ( idx = 0; idx < numBuckets; idx++ ) 
+       {
+           buckets[idx] = new LinkedList<CacheEntry>(); 
+       }
+        
+       int numLatches = numBuckets >> LOG_BUCKET_PER_LATCH;
+       latches = new Lock[numLatches];      
+       for ( idx = 0; idx < numLatches; idx++ )          
+       {
+           latches[idx] = new ReentrantLock(); 
+       }
+       
+       lrus = ( LRUCache.LRU[] ) new LRUCache.LRU[NUM_LRUS];
+       for ( idx = 0; idx < NUM_LRUS; idx++ )          
+       {
+           lrus[idx] = new LRU(); 
+       }
+       
+       numEntries = new AtomicInteger( 0 );
+        
+    }
+    
+    /**
+     * Called as the minimum version that readers will use advances. This lets
+     * cache get rid of the older versions of entries.
+     *
+     * @param minVersion mimimum version that will be read from the cache
+     */
+    public void advanceMinReadVersion( long minVersion )
+    {
+        minReadVersion = minVersion;
+    }
+    
+    /**
+     * Updates the entry identified with the key with the new value.
+     *   
+     *   
+     * @param key identifier of the entry
+     * @param value new value of the entry
+     * @param newVersion version of the new value
+     * @param serializer used in case of IO
+     * @throws IOException
+     */
+    public void put( K key, V value, long newVersion , Serializer serializer ) throws IOException, CacheEvictionException
+    {
+        int hashValue = hash(key);
+        int hashIndex = ( hashValue & ( numBuckets - 1 ) );
+        int latchIndex = ( hashIndex >> LOG_BUCKET_PER_LATCH );
+        boolean done = false;
+        
+        /*
+         * Lock the hash bucket and find the current version of the entry: 
+         * 1) If entry exists
+         *   1.1) if the existing entry is the most recent version for the given key, 
+         *   a new version is created and a snapshot version of it is initialized from the existing entry.
+         *   1.2) else if the existing entry is being read in, wait for the read
+         *         and do like in step 1.1 
+         *   1.3) else read the entry and do like in step  1.1
+         * 2) If entry does not exist then that means the current version
+         * of it needs to be read. Read it and do like in step 1.1.
+         * 
+         * While reading or waiting, latch is released.
+         */
+        
+        
+        latches[latchIndex].lock();
+        boolean entryExists = false;
+        
+        Iterator<CacheEntry> it = buckets[hashIndex].listIterator();
+        CacheEntry entry = null;
+        
+        while (it.hasNext() )
+        {
+            entry = it.next();
+            if ( entry.getKey().equals( key ) )
+            {
+                entryExists = true;
+                break;
+            }
+        }
+
+        try
+        {
+            if ( entryExists )
+            {
+                switch ( entry.getState() )
+                {
+    
+                    case ENTRY_READY: // should be the common case
+                        
+                        if ( !entry.isCurrentVersion() )
+                        {
+                            CacheEntry newEntry = this.findNewEntry( key, latchIndex );
+                            
+                            /*
+                             * Remove existing entry, chain as a snapshot
+                             * entry to the new entry and add newentry to the
+                             * list.
+                             */
+                            buckets[hashIndex].remove( entry );
+                            newEntry.getVersionsLink().splice( entry.getVersionsLink() );
+                            buckets[hashIndex].add( newEntry );
+                            entry = newEntry;
+                            this.doRead( entry, latches[latchIndex], serializer );
+                        }
+                        
+                        
+                        this.putNewVersion( entry, key, value, newVersion, hashIndex, 
+                            latches[latchIndex], serializer );                       
+                        break;
+                    case ENTRY_READING:
+                        // Somebody is reading our entry, wait until the read is done and then retry
+                        this.doWaitForStateChange( entry, latches[latchIndex] );
+                        if ( entry.getState() == EntryState.ENTRY_READY )
+                        {
+                            this.putNewVersion( entry, key, value, newVersion, hashIndex, latches[latchIndex], 
+                                serializer );
+                            break;
+                        }
+                        // FALLTHROUGH
+                    case ENTRY_INITIAL:
+                        this.doRead( entry, latches[latchIndex], serializer );
+                        this.putNewVersion( entry, key, value, newVersion, hashIndex, latches[latchIndex], serializer );
+                        break;
+                    case ENTRY_WRITING:
+                        // FALLTHROUGH
+                    default:
+                        assert ( false );
+                }
+    
+            }
+            else
+            {
+                entry = this.findNewEntry( key, latchIndex );
+                buckets[hashIndex].add( entry );
+                this.doRead( entry, latches[latchIndex], serializer );
+                this.putNewVersion( entry, key, value, newVersion, hashIndex, latches[latchIndex], 
+                    serializer );
+            }            
+        }
+        finally
+        {
+            latches[latchIndex].unlock();
+        }
+    }
+    
+    
+
+    /**
+     * Finds and returns the entry corresponding to the given key and version.
+     *
+     * @param key the identifier for the entry
+     * @param version version the caller want to read
+     * @param serializer used in case of IO
+     * @return value read
+     * @throws IOException
+     */
+    public V get( K key, long version, Serializer serializer ) throws IOException, CacheEvictionException
+    {
+        int hashValue = hash(key);
+        int hashIndex = ( hashValue & ( numBuckets - 1 ) );
+        int latchIndex = ( hashIndex >> LOG_BUCKET_PER_LATCH );
+        V value = null;
+        
+        
+        
+        /*
+         * 1) If entry exists
+         *   1.1) if the version chain contains the desired version, then return it, otherwise read
+         *   the most recent version from disk and return the value from the version chain.
+         *   1.2) else if the existing entry is being read in, wait for the read
+         *         and do like in step 1.1 
+         *   1.3) else read the entry and do like in step  1.1
+         * 2) If entry does not exist then that means the current version
+         * of it needs to be read. Read it and do like in step 1.1.
+         * 
+         * While reading or waiting, latch is released.
+         */
+        
+        latches[latchIndex].lock();
+        boolean chainExists = false;
+        
+        Iterator<CacheEntry> it = buckets[hashIndex].listIterator();
+        CacheEntry entry = null;
+        
+        while ( it.hasNext() )
+        {
+            entry = it.next();
+            if ( entry.getKey().equals( key ) )
+            {
+                chainExists = true;
+                break;
+            }
+        }
+
+        try
+        {
+            if ( chainExists )
+            {
+                switch ( entry.getState() )
+                {
+                    case ENTRY_READY: // should be the common case
+                        if ( !entry.isCurrentVersion() )
+                        {
+                            value = this.searchChainForVersion( entry, version );
+                            
+                            if (value != null)
+                                break;
+                             
+                            CacheEntry newEntry = this.findNewEntry( key, latchIndex );
+    
+                            /*
+                             * Remove existing entry, chain as a snapshot
+                             * entry to the new entry and add newentry to the
+                             * list.
+                             */
+                            buckets[hashIndex].remove( entry );
+                            newEntry.getVersionsLink().splice( entry.getVersionsLink() );
+                            buckets[hashIndex].add( newEntry );
+                            entry = newEntry;
+                            this.doRead( entry, latches[latchIndex], serializer );
+                        }
+                        
+                        // FALLTHROUGH
+                    case ENTRY_WRITING:    // being written entry is always at current version                        
+                        value = this.searchChainForVersion( entry, version );
+                        break;
+                    case ENTRY_READING:
+                        // Somebody is reading our entry, wait until the read is done and then retry
+                        this.doWaitForStateChange( entry, latches[latchIndex] );
+                        if ( entry.getState() == EntryState.ENTRY_READY )
+                        {
+                            value = this.searchChainForVersion( entry, version );
+                            break;
+                        }
+                        // FALLTHROUGH
+                    case ENTRY_INITIAL:
+                        
+                        // TODO remove this, this can happen when there is an io exception
+                        // with the thread that we waited for the IO.
+                        assert( false );
+                        
+                        this.doRead( entry, latches[latchIndex], serializer );
+                        value = this.searchChainForVersion( entry, version );
+                        break;                
+                    default:
+                        assert ( false );
+                }
+    
+            }
+            else
+            {
+                entry = this.findNewEntry( key, latchIndex );
+                buckets[hashIndex].add( entry );
+                this.doRead( entry, latches[latchIndex], serializer );
+                value = this.searchChainForVersion( entry, version );
+            }
+        }
+        finally
+        {
+            latches[latchIndex].unlock();
+        }
+        
+        return value;
+        
+    }
+     
+    /**
+     * Creates a new version of the given entry with the given new version.
+     *
+     * @param entry entry for which a new version will be created
+     * @param key identifier for the entry
+     * @param value new value for the entry
+     * @param newVersion new version of the entry
+     * @param hashIndex hash bucket index which covers the enrtry 
+     * @param latch lock covering the entry
+     * @param serializer used in case of IO
+     * @throws IOException
+     */
+    private void putNewVersion( CacheEntry entry, K key, V value, long newVersion, int hashIndex, 
+        Lock latch, Serializer serializer ) throws IOException
+    {
+        if ( entry.getStartVersion() != newVersion  )
+        {
+            CacheEntry newEntry = this.findNewEntry( key, hashIndex >> LOG_BUCKET_PER_LATCH );
+
+            // Initialize and set to new version 
+            newEntry.initialize( key );
+            newEntry.setAsCurrentVersion( value, newVersion );
+
+            /*
+             * Remove existing entry, chain as a snapshot
+             * entry to the new entry and add newentry to the
+             * list.
+             */
+            buckets[hashIndex].remove( entry );
+            entry.setAsSnapshotVersion( newVersion );
+            newEntry.getVersionsLink().splice( entry.getVersionsLink() );  // splices entry and its chain after the newEntry
+            buckets[hashIndex].add( newEntry );
+            entry = newEntry;
+        }
+        else
+        {
+            assert( entry.isCurrentVersion() );
+            
+            // Entry already at current version. Just update the value
+            entry.setAsCurrentVersion( value, newVersion );
+        }
+        
+        entry.setState( EntryState.ENTRY_WRITING );
+        latch.unlock();
+        
+        try
+        {
+         entryIO.write( key, value, serializer );
+        }
+        catch( IOException e )
+        {
+            /*
+             * Not much we can do here, just leave the entry in an
+             * inconsistent state.
+             */
+            entry.setState( EntryState.ENTRY_INITIAL );
+            if ( entry.anyWaiters() )
+                entry.getStateCondition( latch ).notifyAll();
+            else
+            {
+                LRU lru = entry.getLru();
+                lru.getLock().lock();
+                lru.moveToTail( entry );
+                lru.getLock().unlock();
+            }
+            
+            latch.unlock();
+            
+            throw e;
+            
+        }
+        
+        latch.lock();
+        entry.setState( EntryState.ENTRY_READY );
+
+    }
+    
+    /**
+     * Searches the given version for the entry that can satisfy the read with the 
+     * given version and returns the value of that entry. Cache is responsible is for
+     * maintaining the versions of entries that readers might still be interested in.
+     *
+     * @param head head of the version chain
+     * @param version version the caller wants to read at
+     * @return value found.
+     */
+    private V searchChainForVersion( CacheEntry head, long version )
+    {
+        ExplicitList.Link<CacheEntry> curLink = head.getVersionsLink();
+        CacheEntry curEntry;
+        boolean mustFind = true;
+        long curStartVersion = 0;
+        
+        V value = null;
+        
+        if ( head.getState() !=  EntryState.ENTRY_READY || !head.isCurrentVersion() )
+            mustFind = false;
+        
+        do
+        {
+            curEntry = curLink.getElement();
+            if ( curEntry.getState() != EntryState.ENTRY_READY )
+            {
+                assert( curEntry == head );
+                curLink = curLink.getNext();
+                continue;
+            }
+        
+            if ( curStartVersion != 0 && ( curEntry.getEndVersion() != curStartVersion ) )
+                assert( false );
+            
+            curStartVersion = curEntry.getStartVersion();
+            
+            
+            if ( !curEntry.canReadFrom( version ) )
+            {
+                curLink = curLink.getNext();
+                continue;
+            }
+            
+            // found it
+            
+            if ( curEntry.isCurrentVersion() )
+            {
+                // Warm the entry in the lru
+                LRU lru = curEntry.getLru();
+                lru.getLock().lock();
+                lru.touch( curEntry );
+                lru.getLock().unlock();
+            }
+            
+            value = curEntry.getValue();
+            break;
+        }while( curLink != head.getVersionsLink() );
+        
+        if ( value == null && mustFind == true )            
+            assert( false );
+        
+        return value;
+    }
+    
+    /**
+     * Wait for the state change to happen. Usually used to wait for another 
+     * thread to complete the IO.Latch covering the entry is held at the entry.
+     *
+     * @param entry cache entry for which we do the wait
+     * @param latch latch which covers the bucket the entry corresponds to
+     */
+    private void doWaitForStateChange( CacheEntry entry, Lock latch )
+    {
+        EntryState curState = entry.getState();
+        Condition cond = entry.getStateCondition( latch );
+        entry.bumpWaiters();
+        do
+        {
+            cond.awaitUninterruptibly();
+            
+        } while ( curState == entry.getState() );
+        
+        entry.decrementWaiters();
+    }
+    
+   /**
+    * Does read the value for the given entry. At entry, latch is held. It is
+    * dropped during the read and regotten after a successful read. 
+    *
+    * @param entry entry for which we will do the read
+    * @param latch latch protecting the entry to which the bucket belongs
+    * @param serializer used in case of IO
+    * @throws IOException
+    */
+    private void doRead( CacheEntry entry, Lock latch, Serializer serializer ) throws IOException
+    {
+        V value = null;
+        entry.setState( EntryState.ENTRY_READING );
+        latch.unlock();
+        
+        try
+        {
+           value = entryIO.read( entry.getKey(), serializer ); 
+        }
+        catch ( IOException e )
+        {
+            // do cleanup and rethrow
+            latch.lock();
+            entry.setState( EntryState.ENTRY_INITIAL );
+            if ( entry.anyWaiters() )
+                entry.getStateCondition( latch ).notifyAll();
+            else
+            {
+                LRU lru = entry.getLru();
+                lru.getLock().lock();
+                lru.moveToTail( entry );
+                lru.getLock().unlock();
+            }
+            
+            latch.unlock();
+            
+            throw e;
+        }
+        
+        latch.lock();
+        
+        // set the version range
+        ExplicitList.Link<CacheEntry> nextLink = entry.getVersionsLink().getNext();
+        long startVersion;
+        if ( entry.getVersionsLink().isUnLinked() )
+            startVersion = 0;
+        else
+            startVersion = nextLink.getElement().getEndVersion();
+        
+        entry.setAsCurrentVersion( value, startVersion );
+        if ( entry.anyWaiters() )
+            entry.getStateCondition( latch ).signalAll();
+    }
+    
+    /**
+     * Finds a victim entry to be replaced by the given key. 
+     * 
+     *
+     * @param key identifier which we try to put into the cache 
+     * @param latchIndex index of the currently held hash bucket lock 
+     * @return
+     */
+    private CacheEntry findNewEntry( K key, int latchIndex )
+    {
+        LRU lru;
+        int index = lruRandomizer.nextInt( NUM_LRUS );
+        int id, curIndex;
+        boolean lruLocked = false;
+        
+        
+        // if under max entries, allocate a new one and add it to the lru with the index.. numEntries check is dirty
+        if ( numEntries.get() < maxEntries )
+        {
+            numEntries.incrementAndGet();
+            CacheEntry newEntry  = new CacheEntry( index );
+            lru = lrus[index];
+            lru.getLock().lock();
+            lru.addToLRU( newEntry );
+            lru.getLock().unlock();
+            newEntry.initialize( key );
+            return newEntry;
+        }
+        
+        /*
+         * We start with a lru determined by the lru randomizer and try to lock the lru without waiting. 
+         * If this doesnt work, we wait on the first lru lock. Once we get the lru, we walk over each lru
+         * (this time waiting on the lock when we switch to a new lru) and try to find a victim. 
+         */
+        CacheEntry victimEntry = null;
+        lru = null;
+        curIndex = 0;
+        for ( id = 0; id < NUM_LRUS; id++ )
+        {
+            curIndex = ( index + id ) % NUM_LRUS;
+            lru = lrus[curIndex];
+            
+            if ( lru.getLock().tryLock() == true )
+            {
+                lruLocked = true;
+                break;
+            }
+        }
+        
+        if ( !lruLocked )
+        {
+            curIndex = index;
+            lru = lrus[curIndex];
+            lru.getLock().lock();
+        }
+        
+        int startingIndex = curIndex;
+        do
+        {
+            victimEntry = lru.findVictim( latchIndex );
+            lru.getLock().unlock();
+            
+            if ( victimEntry != null )
+            {
+                break;
+            }
+                
+            curIndex = (curIndex + 1) % NUM_LRUS;
+            if ( curIndex == startingIndex )
+                break;
+            
+            lru = lrus[curIndex];
+            lru.getLock().lock();
+        }while ( true );
+        
+        if ( victimEntry != null )
+            victimEntry.initialize( key );
+        else
+        {
+            // TODO handle cache eviction failure.
+        }
+        
+        return victimEntry;
+    }
+    
+   
+    private int hash( K key )
+    {
+        int h = key.hashCode();
+        h += ~( h << 9 );
+        h ^= ( h >>> 14 );
+        h += ( h << 4 );
+        h ^= ( h >>> 10 );
+        return h;
+    }
+    
+    
+    private enum EntryState
+    {
+        ENTRY_INITIAL,
+        ENTRY_READING,
+        ENTRY_WRITING,
+        ENTRY_READY
+    }
+
+    private class CacheEntry
+    {
+        /** identifier of the entry */
+        private K key;
+
+        /** value of the entry */
+        private V value;
+
+        /** Starting version for which entry is valid */
+        private long startVersion;
+
+        /** End of valid range. endVersion == Long.MAX_VALUE iff entry current version */
+        private long endVersion;
+        
+        /** hash index of the key */
+        private int hashIndex;
+
+        /** Used when waiting on being initialized entries */
+        private Condition stateCondition;
+
+        /** Number of waiters waiting on state change */
+        private int numWaiters;
+
+        /** Current state */
+        private EntryState state;
+
+        /** Used to build a sorted chain of versions with the most current entry at the head */
+        private ExplicitList.Link<CacheEntry> versionsLink;
+                
+        /** Used to put on lru list */
+        private ExplicitList.Link<CacheEntry> lruLink;
+        
+        /** id of lru this cache entry lives on */
+        int lruid;
+        
+        public CacheEntry(int lruid)
+        {
+            versionsLink = new ExplicitList.Link<CacheEntry>( this );
+            lruLink = new ExplicitList.Link<CacheEntry>( this );
+            this.lruid = lruid;        
+        }
+
+        /**
+         *  inits the fields..used after a cache entry is replaced 
+         *
+         *  @param key new identifier for the entry
+         */
+        public void initialize( K key )
+        {
+            this.key = key;
+            value = null;
+            startVersion = endVersion = 0;
+
+            stateCondition = null;
+            assert ( numWaiters == 0 );
+            state = EntryState.ENTRY_INITIAL;
+
+            assert ( versionsLink.isUnLinked() == true );
+            
+            hashIndex = hash( key ) & ( numBuckets - 1 );
+        }
+
+
+        public K getKey()
+        {
+            return key;
+        }
+
+
+        public V getValue()
+        {
+            return value;
+        }
+        
+        public int getHashIndex()
+        {
+            return hashIndex;
+        }
+        
+
+        public LRU getLru()
+        {
+            return lrus[lruid];
+        }
+
+        public Condition getStateCondition( Lock lock )
+        {
+            if ( stateCondition == null )
+                stateCondition = lock.newCondition();
+
+            return stateCondition;
+        }
+
+
+        public void bumpWaiters()
+        {
+            numWaiters++;
+        }
+
+
+        public void decrementWaiters()
+        {
+            assert ( numWaiters > 0 );
+            numWaiters--;
+        }
+
+
+        public boolean anyWaiters()
+        {
+            return numWaiters > 0;
+        }
+
+        public long getEndVersion()
+        {
+            return endVersion;
+        }
+        
+        
+        public long getStartVersion()
+        {
+            return startVersion;
+        }
+        
+        /**
+         * Check if entry is the most recent version for its key
+         * 
+         * @return true if entry is current
+         */
+        public boolean isCurrentVersion()
+        {
+            return ( endVersion == Long.MAX_VALUE );
+        }
+
+
+        /**
+         * Checks if read for the given version can be satisfied 
+         * from the entry 
+         *  
+         * @param readVersion 
+         * @return true if  
+         */
+        public boolean canReadFrom( long readVersion )
+        {
+            return ( readVersion >= startVersion && readVersion < endVersion );
+        }
+
+
+        public EntryState getState()
+        {
+            return state;
+        }
+
+
+        public void setState( EntryState newState )
+        {
+            this.state = newState;
+        }
+
+        public ExplicitList.Link<CacheEntry> getVersionsLink()
+        {
+            return versionsLink;
+        }
+        
+        
+        public ExplicitList.Link<CacheEntry> getLruLink()
+        {
+            return lruLink;
+        }
+        
+        public void setAsCurrentVersion( V newValue, long startVersion )
+        {
+            this.startVersion = startVersion;
+            this.endVersion = Long.MAX_VALUE;
+            this.value = newValue;
+            this.state = EntryState.ENTRY_READY;
+        }
+        
+        public void setAsSnapshotVersion( long newEndVersion )
+        {
+            this.endVersion = newEndVersion;
+            LRU lru = this.getLru();
+            lru.getLock().lock();
+            lru.addToSnapshots( this );
+            lru.getLock().unlock();
+        }
+        
+        public boolean isEntryFreeable()
+        {
+            return ( this.state != EntryState.ENTRY_READING && this.numWaiters == 0 && 
+                this.state != EntryState.ENTRY_WRITING );
+        }
+        
+    }
+    
+        
+    private class LRU
+    {
+        /** List of entries representing most recent versions */
+        private ExplicitList<CacheEntry> mostRecentVersions = new ExplicitList<CacheEntry>();
+        
+        /** List of snapshot entries */
+        private LinkedList<CacheEntry> snapshotVersions = new LinkedList<CacheEntry>(); 
+        
+        /** Lock protecting the list */
+        private Lock lock = new ReentrantLock();
+        
+        public Lock getLock()
+        {
+            return lock;
+        }
+        
+        /**
+         * add the new entry to the head of the lru
+         *
+         * @param entry new entry to be added 
+         */
+        public void addToLRU( CacheEntry entry )
+        {
+            mostRecentVersions.addFirst( entry.getLruLink() );
+        }
+        
+        /**
+         * Removes the entry from the lru list and Adds the entry to the list of snapshot entries.
+         * Entry should a most recent entry. 
+         *
+         * @param entry cache entry to be made snapshot
+         */
+        public void addToSnapshots( CacheEntry entry )
+        {
+            mostRecentVersions.remove( entry.getLruLink() );
+            snapshotVersions.addLast( entry );
+        }
+        
+        /**
+         * Moves the entry to the cold end of the lru. Entry should be a most
+         * recent entry
+         *
+         * @param entry entry to be made cold
+         */
+        public void moveToTail( CacheEntry entry )
+        {
+            mostRecentVersions.remove( entry.getLruLink() );
+            mostRecentVersions.addFirst( entry.getLruLink() );
+        }
+        
+        /**
+         * Increases the hotness of the given entry
+         *
+         * @param entry cahce entry for which we will increase hotness
+         */
+        public void touch( CacheEntry entry )
+        {
+            // Just move to the hot end for now
+            mostRecentVersions.remove( entry.getLruLink() );
+            mostRecentVersions.addLast( entry.getLruLink() );
+        }
+        
+        
+        /**
+         * Finds a freeable entry from the lru.  Lru lock is held at entry and exit.
+         *
+         * @param latchIndex index of the hash lock that is held at entry
+         * @return
+         */
+        public CacheEntry findVictim( int latchIndex )
+        {
+            CacheEntry victimEntry = null;
+            boolean victimFound = false;
+            int victimBucketIndex;
+            int victimLatchIndex;
+            
+            /*
+             * If expired snapshot entries exist, they are preferred, otherwise an entry is
+             * gotten from the tail of the lru.
+             */
+            
+            Iterator<CacheEntry> it = snapshotVersions.listIterator();
+            while ( it.hasNext() )
+            {
+                victimEntry = it.next();
+                
+                if ( victimEntry.getEndVersion() > minReadVersion )
+                    break;
+                
+                assert ( victimEntry.isEntryFreeable() == true );
+                
+                
+                victimBucketIndex = victimEntry.getHashIndex();
+                victimLatchIndex = (victimBucketIndex >> LOG_BUCKET_PER_LATCH );
+                
+                if ( latchIndex != victimLatchIndex && latches[victimLatchIndex].tryLock() == false )
+                    continue;
+                
+                int hashChainIndex = buckets[victimEntry.getHashIndex()].indexOf( victimEntry ); 
+                if ( hashChainIndex != -1 )
+                {
+                    buckets[victimEntry.getHashIndex()].remove( hashChainIndex );
+                    if ( victimEntry.getVersionsLink().isLinked() )
+                    {
+                        ExplicitList.Link<CacheEntry> nextLink = victimEntry.getVersionsLink().getNext();
+                        victimEntry.getVersionsLink().remove();
+                        
+                        CacheEntry newEntry = nextLink.getElement();
+                        buckets[newEntry.getHashIndex()].add( newEntry );
+                    }
+                }
+                else if ( victimEntry.getVersionsLink().isLinked() )
+                {
+                    victimEntry.getVersionsLink().remove();
+                }
+                
+                
+                if ( latchIndex != victimLatchIndex )
+                    latches[victimLatchIndex].unlock();
+                
+                it.remove();
+                this.mostRecentVersions.addLast( victimEntry.lruLink );
+                return victimEntry;
+                
+            }
+            
+            ExplicitList.Link<CacheEntry> curLink = mostRecentVersions.begin();
+            while ( curLink != mostRecentVersions.end() )
+            {
+                victimEntry = curLink.getElement();
+                
+                // Dirty check
+                if ( victimEntry.isEntryFreeable() == false )
+                {
+                    curLink = curLink.getNext();
+                    continue;
+                }
+                    
+                
+                victimBucketIndex = victimEntry.getHashIndex();
+                victimLatchIndex = (victimBucketIndex >> LOG_BUCKET_PER_LATCH );
+                
+                if ( latchIndex != victimLatchIndex && latches[victimLatchIndex].tryLock() == false )
+                {
+                    curLink = curLink.getNext();
+                    continue;
+                }
+                
+                if ( victimEntry.isEntryFreeable() == false )
+                {
+                    if ( latchIndex != victimLatchIndex )
+                        latches[victimLatchIndex].unlock();
+                    
+                    curLink = curLink.getNext();
+                    continue;
+                }
+                
+                
+                buckets[victimEntry.getHashIndex()].remove( victimEntry );
+                if ( victimEntry.getVersionsLink().isLinked() )
+                {
+                    ExplicitList.Link<CacheEntry> nextLink = victimEntry.getVersionsLink().getNext();
+                    victimEntry.getVersionsLink().remove();
+                    
+                    CacheEntry newEntry = nextLink.getElement();
+                    buckets[newEntry.getHashIndex()].add( newEntry );
+                }
+                
+                if ( latchIndex != victimLatchIndex )
+                    latches[victimLatchIndex].unlock();
+                
+                this.touch( victimEntry );
+                return victimEntry;
+                
+            }
+            
+            return null;
+           
+        }
+       
+    }
+
+
+}
\ No newline at end of file

Added: directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/recman/SnapshotRecordManager.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/recman/SnapshotRecordManager.java?rev=1161559&view=auto
==============================================================================
--- directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/recman/SnapshotRecordManager.java (added)
+++ directory/apacheds/branches/apacheds-jdbm/jdbm/src/main/java/jdbm/recman/SnapshotRecordManager.java Thu Aug 25 13:29:18 2011
@@ -0,0 +1,470 @@
+
+package jdbm.recman;
+
+import java.io.IOException;
+import java.util.Enumeration;
+
+import jdbm.RecordManager;
+import jdbm.ActionRecordManager;
+import jdbm.helper.DefaultSerializer;
+import jdbm.helper.Serializer;
+import jdbm.helper.CacheEvictionException;
+import jdbm.helper.EntryIO;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import jdbm.helper.ActionVersioning;
+import jdbm.helper.LRUCache;
+import jdbm.helper.ActionContext;
+
+import org.apache.directory.server.i18n.I18n;
+
+import jdbm.helper.CacheEvictionException;
+
+public class SnapshotRecordManager implements ActionRecordManager
+{
+    /** Wrapped RecordManager */
+    protected RecordManager recordManager;
+    
+    
+    /** Per thread action context */
+    private static final ThreadLocal < ActionContext > actionContextVar = 
+         new ThreadLocal < ActionContext > () {
+             @Override 
+             protected ActionContext initialValue()
+             {
+                 return null;
+             }
+     };
+     
+    /** Used for keeping track of actions versions */
+    ActionVersioning versioning = new ActionVersioning();
+    
+    /** Versioned cache */
+    LRUCache<Long, Object> versionedCache;
+    
+    /** Passed to cache as IO callback */
+    RecordIO recordIO = new RecordIO();
+    
+    /** Lock used to serialize write actions and some management operatins */
+    Lock bigLock = new ReentrantLock();
+
+    /**
+     * Construct a SanshotRecordManager wrapping another RecordManager
+     *
+     * @param recordManager Wrapped RecordManager
+     */
+    public SnapshotRecordManager( RecordManager recordManager, int size)
+    {
+        if ( recordManager == null ) 
+        {
+            throw new IllegalArgumentException( I18n.err( I18n.ERR_517 ) );
+        }
+
+        this.recordManager = recordManager;
+        
+        versionedCache = new LRUCache<Long ,Object>(recordIO, size);
+    }
+    
+   
+    
+         
+     public ActionContext beginAction( boolean readOnly )
+     {
+         ActionContext actionContext = new ActionContext();
+         ActionVersioning.Version version;
+         
+         if ( readOnly )
+         {
+             version = versioning.beginReadAction();
+         }
+         else
+         {
+             bigLock.lock();
+             version = versioning.beginWriteAction();
+         }
+         
+         actionContext.beginAction( readOnly, version );
+         return actionContext;
+     }
+     
+     public void setCurrentActionContext( ActionContext context )
+     {
+         ActionContext actionContext = actionContextVar.get();
+         actionContextVar.set( context );
+     }
+     
+     public void unsetCurrentActionContext( ActionContext context )
+     {
+         ActionContext actionContext = actionContextVar.get();
+         assert( actionContext == context );
+         actionContextVar.set( null );
+     }
+     
+     public void endAction( ActionContext actionContext )
+     {
+         ActionVersioning.Version minVersion = null;
+         if ( actionContext.isReadOnlyAction() )
+         {
+             ActionVersioning.Version version = actionContext.getVersion(); 
+             minVersion = versioning.endReadAction( version );
+             actionContext.endAction();
+         }
+         else if ( actionContext.isWriteAction() )
+         {
+             minVersion = versioning.endWriteAction();
+             actionContext.endAction();
+             bigLock.unlock();
+         }
+         else
+         {
+             assert( false );
+         }
+         
+         if ( minVersion != null )
+             versionedCache.advanceMinReadVersion( minVersion.getVersion() );
+     }
+     
+     public void abortAction( ActionContext context )
+     {
+         // TODO handle this
+     }
+     
+         
+    /**
+     * Get the underlying Record Manager.
+     *
+     * @return underlying RecordManager
+     */
+    public RecordManager getRecordManager()
+    {
+        return recordManager;
+    }
+
+    
+    /**
+     * Inserts a new record using a custom serializer.
+     *
+     * @param obj the object for the new record.
+     * @return the rowid for the new record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public long insert( Object obj ) throws IOException
+    {
+        return insert( obj, DefaultSerializer.INSTANCE );
+    }
+        
+        
+    /**
+     * Inserts a new record using a custom serializer.
+     *
+     * @param obj the object for the new record.
+     * @param serializer a custom serializer
+     * @return the rowid for the new record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public long insert( Object obj, Serializer serializer ) throws IOException
+    {
+        checkIfClosed();
+        
+        ActionContext actionContext = actionContextVar.get();
+        assert( actionContext.isWriteAction() == true );
+
+        long recid = recordManager.insert( obj, serializer );
+        
+        try 
+        {
+            versionedCache.put( new Long( recid ), obj, actionContext.getVersion().getVersion(),
+                serializer );
+        } 
+        catch ( CacheEvictionException except ) 
+        {
+            throw new IOException( except.getLocalizedMessage() );
+        }
+        
+        return recid;
+    }
+
+
+    /**
+     * Deletes a record.
+     *
+     * @param recid the rowid for the record that should be deleted.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public void delete( long recid ) throws IOException
+    {
+        checkIfClosed();
+        
+        ActionContext actionContext = actionContextVar.get();
+        assert( actionContext.isWriteAction() == true );
+
+        // Update the cache
+        try 
+        {
+            versionedCache.put( new Long( recid ), null, actionContext.getVersion().getVersion(),
+                null );
+        } 
+        catch ( CacheEvictionException except ) 
+        {
+            throw new IOException( except.getLocalizedMessage() );
+        } 
+    }
+
+
+    /**
+     * Updates a record using standard Java serialization.
+     *
+     * @param recid the recid for the record that is to be updated.
+     * @param obj the new object for the record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public void update( long recid, Object obj ) throws IOException
+    {
+        update( recid, obj, DefaultSerializer.INSTANCE );
+    }
+    
+
+    /**
+     * Updates a record using a custom serializer.
+     *
+     * @param recid the recid for the record that is to be updated.
+     * @param obj the new object for the record.
+     * @param serializer a custom serializer
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public void update( long recid, Object obj, Serializer serializer ) throws IOException
+    {
+        checkIfClosed();
+        ActionContext actionContext = actionContextVar.get();
+        assert( actionContext.isWriteAction() == true );
+
+        try 
+        {
+           versionedCache.put( new Long( recid ), obj, actionContext.getVersion().getVersion(),
+               serializer );       
+        } 
+        catch ( CacheEvictionException except ) 
+        {
+            throw new IOException( except.getLocalizedMessage() );
+        }
+    }
+
+
+    /**
+     * Fetches a record using standard Java serialization.
+     *
+     * @param recid the recid for the record that must be fetched.
+     * @return the object contained in the record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public Object fetch( long recid ) throws IOException
+    {
+        return fetch( recid, DefaultSerializer.INSTANCE );
+    }
+
+        
+    /**
+     * Fetches a record using a custom serializer.
+     *
+     * @param recid the recid for the record that must be fetched.
+     * @param serializer a custom serializer
+     * @return the object contained in the record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public Object fetch( long recid, Serializer serializer ) throws IOException
+    {
+        checkIfClosed();
+        Object obj;
+        ActionContext actionContext = actionContextVar.get();
+        assert( actionContext.isActive() == true );
+        
+        try 
+        {
+           obj = versionedCache.get( new Long( recid ), actionContext.getVersion().getVersion(),
+               serializer );       
+        } 
+        catch ( CacheEvictionException except ) 
+        {
+            throw new IOException( except.getLocalizedMessage() );
+        }
+        
+        return obj;
+    }
+
+
+    /**
+     * Closes the record manager.
+     *
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public void close() throws IOException
+    {
+        checkIfClosed();
+
+        // TODO quiesce all actions
+        recordManager.close();
+        recordManager = null;
+        versionedCache = null;
+        versioning = null;
+    }
+
+
+    /**
+     * Returns the number of slots available for "root" rowids. These slots
+     * can be used to store special rowids, like rowids that point to
+     * other rowids. Root rowids are useful for bootstrapping access to
+     * a set of data.
+     */
+    public int getRootCount()
+    {
+        checkIfClosed();
+
+        return recordManager.getRootCount();
+    }
+
+
+    /**
+     * Returns the indicated root rowid.
+     *
+     * @see #getRootCount
+     */
+    public long getRoot( int id ) throws IOException
+    {
+        bigLock.lock();
+        try
+        {
+            checkIfClosed();
+            return recordManager.getRoot( id );
+        }
+        finally
+        {
+            bigLock.unlock();
+        }
+    }
+
+
+    /**
+     * Sets the indicated root rowid.
+     *
+     * @see #getRootCount
+     */
+    public void setRoot( int id, long rowid ) throws IOException
+    {
+        bigLock.lock();
+        try
+        {
+            checkIfClosed();
+
+            recordManager.setRoot( id, rowid );
+        }
+        finally
+        {
+            bigLock.unlock();
+        }
+    }
+
+
+    /**
+     * Commit (make persistent) all changes since beginning of transaction.
+     */
+    public void commit() throws IOException
+    {
+        bigLock.lock();
+        try
+        {
+            checkIfClosed();
+        
+            recordManager.commit();
+        }
+        finally
+        {
+            bigLock.unlock();
+        }
+    }
+
+
+    /**
+     * Rollback (cancel) all changes since beginning of transaction.
+     */
+    public void rollback() throws IOException
+    {
+      // TODO handle this by quiecesing all actions and throwing away the cache contents    
+    }
+
+
+    /**
+     * Obtain the record id of a named object. Returns 0 if named object
+     * doesn't exist.
+     */
+    public long getNamedObject( String name ) throws IOException
+    {
+        bigLock.lock();
+        try
+        {
+            checkIfClosed();
+
+            return recordManager.getNamedObject( name );
+        }
+        finally
+        {
+            bigLock.unlock();
+        }
+    }
+
+
+    /**
+     * Set the record id of a named object.
+     */
+    public void setNamedObject( String name, long recid ) throws IOException
+    {
+        bigLock.lock();
+        try
+        {
+            checkIfClosed();
+
+            recordManager.setNamedObject( name, recid );
+        }
+        finally
+        {
+            bigLock.unlock();
+        }
+    }
+
+
+    /**
+     * Check if RecordManager has been closed.  If so, throw an IllegalStateException
+     */
+    private void checkIfClosed() throws IllegalStateException
+    {
+        if ( recordManager == null ) 
+        {
+            throw new IllegalStateException( I18n.err( I18n.ERR_538 ) );
+        }
+    }
+   
+    
+   
+    
+    private class RecordIO implements EntryIO<Long, Object>
+    {
+        public Object read( Long key, Serializer serializer) throws IOException
+        {
+            return recordManager.fetch( key.longValue(), serializer );
+        }
+        
+        public void write( Long key, Object value, Serializer serializer ) throws IOException
+        {
+            if ( value != null )
+            {
+                recordManager.update( key.longValue(), value , serializer );
+            }
+            else
+            {
+                recordManager.delete( key.longValue() );
+            }
+        }
+    }
+   
+}

Added: directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/btree/SnapshotBTree.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/btree/SnapshotBTree.java?rev=1161559&view=auto
==============================================================================
--- directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/btree/SnapshotBTree.java (added)
+++ directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/btree/SnapshotBTree.java Thu Aug 25 13:29:18 2011
@@ -0,0 +1,207 @@
+package jdbm.btree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import jdbm.RecordManager;
+import jdbm.RecordManagerFactory;
+import jdbm.helper.CacheEvictionException;
+import jdbm.helper.IntegerComparator;
+import jdbm.helper.LRUCache;
+import jdbm.helper.Tuple;
+import jdbm.helper.TupleBrowser;
+
+import jdbm.recman.SnapshotRecordManager;
+
+import org.junit.runner.RunWith;
+import com.mycila.junit.concurrent.Concurrency;
+import com.mycila.junit.concurrent.ConcurrentJunitRunner;
+
+import org.junit.rules.TemporaryFolder;
+
+import org.junit.Test;
+
+import java.util.concurrent.Semaphore;
+
+@RunWith(ConcurrentJunitRunner.class)
+@Concurrency()
+public class SnapshotBTree
+{
+    public TemporaryFolder folder = new TemporaryFolder();
+    
+    private String getTemporaryFile( String name ) throws IOException
+    {
+        String file = folder.newFile( name ).getAbsolutePath();
+        return file;
+    }
+    
+    @Test
+    public void testBasic1() throws IOException, InterruptedException
+    {
+        RecordManager recman;
+        BTree<Integer, String> tree;
+      
+        int idx;
+        int numReadThreads = 1;
+        TestThread readThreads[] = new TestThread[numReadThreads];
+        TestThread updateThread;
+        
+        Semaphore browseSem = new Semaphore( 0 );
+        Semaphore updateSem = new Semaphore( 0 );
+
+        recman = RecordManagerFactory.createRecordManager( getTemporaryFile( "testBasic1" ) );
+        SnapshotRecordManager snapshotRecman = new SnapshotRecordManager( recman, 1 << 12 );
+        
+        tree = new BTree<Integer, String>( snapshotRecman, new IntegerComparator() );
+     
+        for ( idx = 0; idx < 1024; idx++ )
+        {
+            tree.insert( new Integer( idx ), "value" + idx, true );
+        }
+
+        for ( idx = 0; idx < numReadThreads; idx++ )
+        {
+            readThreads[idx] = new TestThread( true, tree, browseSem, updateSem, numReadThreads );
+        }
+        updateThread = new TestThread( false, tree, browseSem, updateSem, numReadThreads );      
+        
+        updateThread.start();
+        for ( idx = 0; idx < numReadThreads; idx++ )
+        {
+            readThreads[idx].start();
+        }
+        
+        
+        for ( idx = 0; idx < numReadThreads; idx++ )
+        {
+            readThreads[idx].join();
+        }
+        updateThread.join();
+        
+        snapshotRecman.close();
+    }
+    
+    
+    class TestThread extends Thread
+    {
+        boolean readOnly;
+        BTree<Integer, String> btree;
+        Semaphore browseSem;
+        Semaphore updateSem;
+        int numReadThreads;
+
+        TestThread( boolean readOnly, BTree<Integer, String> btree, Semaphore firstBrowse,
+                    Semaphore updateDone, int numReadThreads)
+        {
+            this.readOnly = readOnly;
+            this.btree = btree;
+            this.browseSem = firstBrowse;
+            this.updateSem = updateDone;
+            this.numReadThreads = numReadThreads;
+        }
+
+
+
+        private void readOnlyActions() throws IOException, InterruptedException
+        {
+            int count = 0;
+            int idx;
+            TupleBrowser<Integer, String> browser = btree.browse();
+            Tuple<Integer, String> tuple = new Tuple();
+            browseSem.release();
+            
+            assertTrue( browser.getNext( tuple ) );
+            assertEquals( tuple.getKey().intValue(), 0 );
+            count++;
+            
+            assertTrue( browser.getNext( tuple ) );
+            assertEquals( tuple.getKey().intValue(), 1 );
+            count++;
+            
+            while( browser.getNext( tuple ) )
+            {
+                count++;
+                
+                // Sleep a little randomly.
+                if ( (count & 7) == 0 )
+                    Thread.sleep( 1 );
+                
+                assertTrue( !tuple.getValue().equals( "xxx" ) );
+            }
+            
+            
+            System.out.println( "count is " + count );
+            assertEquals( count, 1024 );            
+            browser.close();
+            
+            updateSem.acquireUninterruptibly();
+            browser = btree.browse( new Integer( 10 ) );
+            
+            browseSem.release();
+            for ( idx = 20; idx < 1024; idx++ )
+            {
+                assertTrue( browser.getNext( tuple ) );
+                
+                System.out.println( "key:"+ tuple.getKey().intValue() + " idx:" + idx );
+                assertTrue( tuple.getKey().intValue() == idx );
+            }
+            browser.close();
+            
+        }
+        
+        private void readWriteActions() throws IOException
+        {
+            int idx;
+            
+            for ( idx = 0; idx < numReadThreads; idx++ )
+                browseSem.acquireUninterruptibly();
+            
+            btree.insert( new Integer(1024), "xxx", true );
+            for ( idx = 1024; idx < 2048; idx++ )
+            {
+                btree.insert( new Integer( 0 ), "value" + idx, true );
+            }
+           
+            btree.insert( new Integer(1), "xxx", true );
+            btree.insert( new Integer(1024), "xxx", true );
+            for ( idx = 10; idx < 20; idx++ )
+            {
+                btree.remove( new Integer( idx ) );
+            }
+            
+            updateSem.release();
+            
+            for ( idx = 0; idx < numReadThreads; idx++ )
+                browseSem.acquireUninterruptibly();
+            
+            for ( idx = 0; idx < 10; idx++ )
+                btree.remove( new Integer( idx ) );
+            
+            for ( idx = 20; idx < 1024; idx++ )
+                btree.remove( new Integer( idx ) );
+            
+        }
+
+
+        public void run()
+        {
+            try
+            {
+                if ( readOnly )
+                    this.readOnlyActions();
+                else
+                    this.readWriteActions();
+            }
+            catch( IOException e )
+            {
+            }
+            catch( InterruptedException e )
+            {
+                
+            }
+            
+        }
+    } // end of class TestThread
+}
\ No newline at end of file

Added: directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/helper/TestActionVersioning.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/helper/TestActionVersioning.java?rev=1161559&view=auto
==============================================================================
--- directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/helper/TestActionVersioning.java (added)
+++ directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/helper/TestActionVersioning.java Thu Aug 25 13:29:18 2011
@@ -0,0 +1,52 @@
+package jdbm.helper;
+
+import org.junit.runner.RunWith;
+import com.mycila.junit.concurrent.Concurrency;
+import com.mycila.junit.concurrent.ConcurrentJunitRunner;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(ConcurrentJunitRunner.class)
+@Concurrency()
+public class TestActionVersioning
+{
+    @Test
+    public void testVersioning()
+    {
+        ActionVersioning.Version version1, version2;
+        ActionVersioning.Version writeVersion;
+        ActionVersioning.Version minVersion;
+        
+        ActionVersioning versioning = new ActionVersioning();
+        version1 = versioning.beginReadAction();
+        assertEquals( version1.getVersion(),  0 );
+        
+        writeVersion = versioning.beginWriteAction();      
+        assertEquals( writeVersion.getVersion(), 1 );
+        
+        version2 = versioning.beginReadAction();
+        assertEquals( version2.getVersion(), 0 );
+        
+        minVersion = versioning.endWriteAction();
+        assertEquals( minVersion.getVersion(), 0 );
+        
+        writeVersion = versioning.beginWriteAction();
+        assertEquals( writeVersion.getVersion(), 2 );
+        
+        minVersion = versioning.endWriteAction();
+        assertEquals( minVersion.getVersion(), 0 );
+        
+        versioning.endReadAction( version1 );
+        minVersion = versioning.endReadAction( version2 );
+        assertEquals( minVersion.getVersion(), 2 );
+        
+        version1  = versioning.beginReadAction();
+        assertEquals( version1.getVersion(), 2 );
+        
+        minVersion = versioning.endReadAction( version1 );
+        assertEquals( minVersion, null );
+        
+    }
+}
\ No newline at end of file

Added: directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/helper/TestVersionedCache.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/helper/TestVersionedCache.java?rev=1161559&view=auto
==============================================================================
--- directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/helper/TestVersionedCache.java (added)
+++ directory/apacheds/branches/apacheds-jdbm/jdbm/src/test/java/jdbm/helper/TestVersionedCache.java Thu Aug 25 13:29:18 2011
@@ -0,0 +1,246 @@
+package jdbm.helper;
+
+
+import java.io.IOException;
+
+import org.junit.runner.RunWith;
+import com.mycila.junit.concurrent.Concurrency;
+import com.mycila.junit.concurrent.ConcurrentJunitRunner;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(ConcurrentJunitRunner.class)
+@Concurrency()
+public class TestVersionedCache
+{
+    private int expectedSum;
+    static final int THREAD_NUMBER = 5;
+    
+    
+    @Test
+    public void testBasics() throws IOException, CacheEvictionException
+    {
+        int idx;
+        int numEntries = 1024;
+        Integer intsArray[] = new Integer[numEntries];
+        
+        for ( idx = 0; idx < numEntries; idx++  )
+        {
+            intsArray[idx] = new Integer( 1 );
+        }
+        
+        ArrayEntryIO arrayIO = new ArrayEntryIO(intsArray);
+        
+        LRUCache<Integer, Integer> cache = new LRUCache<Integer, Integer>( arrayIO, numEntries );
+        
+        Integer val = cache.get( new Integer ( 5 ), 0, null );
+        assertEquals( val.intValue(), 1 );
+        
+        val = cache.get( new Integer ( 20 ), 0, null );
+        assertEquals( val.intValue(), 1 );
+        
+        cache.put( new Integer(1), 2, 1, null );
+        cache.put( new Integer(5), 2, 1, null );
+        cache.put( new Integer(30), 2, 1, null );
+        
+        int sum = 0;
+        for ( idx = 0; idx < numEntries; idx++ )
+        {
+            sum += cache.get( new Integer( idx ), 0, null ).intValue();
+        }
+        
+        assertEquals( sum, numEntries );
+
+        sum = 0;
+        cache.advanceMinReadVersion( 1 );
+        for ( idx = 0; idx < numEntries; idx++ )
+        {
+            sum += cache.get( new Integer( idx ), 1, null ).intValue();
+        }
+        
+        System.out.println( "Sum is: "+ sum);
+        assertEquals( sum, ( numEntries + 3 ) );
+        
+    }
+    
+    @Test
+    public void testMultiThreadedAccess() throws IOException, CacheEvictionException
+    {
+        int idx;
+        int numEntries = 1024;
+        Integer intsArray[] = new Integer[numEntries];
+        
+        for ( idx = 0; idx < numEntries; idx++  )
+        {
+            intsArray[idx] = new Integer( 1 );
+        }
+        
+        ArrayEntryIO arrayIO = new ArrayEntryIO(intsArray, 10, 20);
+        
+        LRUCache<Integer, Integer> cache = new LRUCache<Integer, Integer>( arrayIO, numEntries );
+        
+        TestThread[] threadPool =  new TestThread[THREAD_NUMBER];
+    
+        // create content for the tree, different content for different threads!
+        for ( int threadCount = 0; threadCount < THREAD_NUMBER; threadCount++ )
+        {
+            if ( threadCount == ( THREAD_NUMBER - 1 ) )
+                threadPool[threadCount] = new TestThread( false, intsArray, cache );
+            else
+                threadPool[threadCount] = new TestThread( true, intsArray, cache );
+                
+            threadPool[threadCount].start();
+        }
+
+        // wait until the threads really stop:
+        try
+        {
+            for ( int threadCount = 0; threadCount < THREAD_NUMBER; threadCount++ )
+            {
+                threadPool[threadCount].join();
+            }
+        }
+        catch ( InterruptedException ignore )
+        {
+            ignore.printStackTrace();
+        }
+        
+        int sum = 0;
+        cache.advanceMinReadVersion( 2 );        
+        for ( idx = 0; idx < intsArray.length; idx++ )
+        {
+            sum += cache.get( new Integer( idx ), 2, null ).intValue();
+        }
+        
+        assertEquals( sum, expectedSum );
+        
+
+    }
+    
+    
+    private class ArrayEntryIO implements EntryIO<Integer, Integer>
+    {
+        Integer intsArray[];
+        int readSleepTime;
+        int writeSleepTime;
+        
+        public ArrayEntryIO( Integer intsArray[] )
+        {
+            this.intsArray = intsArray;
+        }
+        
+        public ArrayEntryIO( Integer intsArray[], int readSleepTIme, int writeSleepTime )
+        {
+            this.intsArray = intsArray;
+            this.readSleepTime = readSleepTime;
+            this.writeSleepTime = writeSleepTime;
+        }
+        
+        public Integer read( Integer key, Serializer serializer) throws IOException
+        {
+            if ( readSleepTime != 0 )
+            {
+                try
+                {
+                    Thread.sleep( readSleepTime );
+                }
+                catch ( InterruptedException e )
+                {
+                    // ignore
+                }
+            }
+            
+            return intsArray[key.intValue()];
+        }
+        
+        public void write( Integer key, Integer value, Serializer serializer ) throws IOException
+        {
+            if ( writeSleepTime != 0 )
+            {
+                try
+                {
+                    Thread.sleep( writeSleepTime );
+                }
+                catch ( InterruptedException e )
+                {
+                    // ignore
+                }
+            }
+            
+            intsArray[key.intValue()] = value;
+        }
+    }
+    
+    
+    class TestThread extends Thread
+    {
+        boolean readOnly;
+        Integer intsArray[];
+        LRUCache<Integer, Integer> cache;
+
+        TestThread( boolean readOnly, Integer intsArray[] , LRUCache<Integer, Integer> cache)
+        {
+            this.readOnly = readOnly;
+            this.intsArray = intsArray;
+            this.cache = cache;
+        }
+
+
+
+        private void action() throws IOException, CacheEvictionException
+        {
+            int idx;
+            int sum = 0;
+            if ( readOnly )
+            {
+               
+                for ( idx = 0; idx < intsArray.length; idx++ )
+                {
+                    sum += cache.get( new Integer( idx ), 0, null ).intValue();
+                }
+                
+                assertEquals( sum, intsArray.length );
+            }
+            else
+            {
+                expectedSum = intsArray.length;
+                
+                for ( idx = 0; idx <= intsArray.length; idx = idx + 100)
+                {
+                    cache.put( new Integer( idx ), 2, 1, null );
+                    expectedSum = expectedSum + 1;
+                }
+                
+                for ( idx = 0; idx <= intsArray.length; idx = idx + 100)
+                {
+                    cache.put( new Integer( idx ), 3, 2, null );
+                    expectedSum = expectedSum + 1;
+                }
+                
+                for ( idx = 0; idx < intsArray.length; idx++ )
+                {
+                    sum += cache.get( new Integer( idx ), 2, null ).intValue();
+                }
+                
+                assertEquals( sum, expectedSum );
+            }
+        }
+
+
+        public void run()
+        {
+            try
+            {
+                this.action();
+            }
+            catch ( IOException e)
+            {
+            }
+            catch ( CacheEvictionException e)
+            {
+            }
+        }
+    } // end of class TestThread
+}
\ No newline at end of file



Mime
View raw message