Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added a new kind of cache, a ClockCache that tries to mimic an LRU bu…

…t with less synchronization.

Test cases for the above
Used that to implement IndexSearcher and IndexWriter caches that tend to behave like the current LRU based ones but with better synchronization behavior
Used the above in LuceneDataSource in place of the existing solution. Based on that, removed some synchronization making the common get paths on index readers and writes lock free.
  • Loading branch information...
commit 31a3cc85400223955dba59b6d289c5e1f537c05b 1 parent ab37a01
Chris Gioran digitalstain authored
347 kernel/src/main/java/org/neo4j/kernel/impl/cache/ClockCache.java
... ... @@ -0,0 +1,347 @@
  1 +/**
  2 + * Copyright (c) 2002-2012 "Neo Technology,"
  3 + * Network Engine for Objects in Lund AB [http://neotechnology.com]
  4 + *
  5 + * This file is part of Neo4j.
  6 + *
  7 + * Neo4j is free software: you can redistribute it and/or modify
  8 + * it under the terms of the GNU General Public License as published by
  9 + * the Free Software Foundation, either version 3 of the License, or
  10 + * (at your option) any later version.
  11 + *
  12 + * This program is distributed in the hope that it will be useful,
  13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15 + * GNU General Public License for more details.
  16 + *
  17 + * You should have received a copy of the GNU General Public License
  18 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19 + */
  20 +package org.neo4j.kernel.impl.cache;
  21 +
  22 +import java.util.Collection;
  23 +import java.util.HashMap;
  24 +import java.util.HashSet;
  25 +import java.util.Map;
  26 +import java.util.Queue;
  27 +import java.util.Set;
  28 +import java.util.concurrent.ConcurrentHashMap;
  29 +import java.util.concurrent.ConcurrentLinkedQueue;
  30 +import java.util.concurrent.atomic.AtomicBoolean;
  31 +import java.util.concurrent.atomic.AtomicInteger;
  32 +import java.util.concurrent.atomic.AtomicReference;
  33 +
  34 +/**
  35 + * This is a barebones implementation of a clock cache that tries to approximate
  36 + * LRU. The primary benefit is less synchronization required (none on get) and
  37 + * less write operations needed for putting stuff in. The tradeoff is reduced
  38 + * predictability on the contents at any given time, though the targeted
  39 + * behaviour is close to LRU. Keys are always kept, so this is useful only
  40 + * for highly utilized caches or very restricted key spaces.
  41 + *
  42 + * The basic structure is a ConcurrentHashMap that points to Pages which are
  43 + * also present in a lock free queue and they contain a boolean flag and a field
  44 + * which holds the value. Eviction happens on a clock fashion, meaning that the
  45 + * head of the queue is popped and if the flag is false then the value is
  46 + * evicted, otherwise the flag is set to false and the page re-inserted at the
  47 + * end of the queue. This only happens on put, so it is synchronized. get() does
  48 + * not lock but because of that it is possible that a value which was just
  49 + * retrieved will not be present even if older values exist in the cache. This
  50 + * should happen very rarely though and that's ok.
  51 + */
  52 +public class ClockCache<K, V>
  53 +{
  54 + private final Queue<Page<V>> clock = new ConcurrentLinkedQueue<Page<V>>();
  55 + private final ConcurrentHashMap<K, Page<V>> cache = new ConcurrentHashMap<K, Page<V>>();
  56 + private final int maxSize;
  57 + private final AtomicInteger currentSize = new AtomicInteger( 0 );
  58 + private final String name;
  59 +
  60 + /**
  61 + * Constructs a Clock cache with the given value and size.
  62 + *
  63 + * @param name The name of the cache, must be non-null
  64 + * @param size The size of the cache (the number of values at most present).
  65 + * Must be positive.
  66 + */
  67 + public ClockCache( String name, int size )
  68 + {
  69 + if ( name == null )
  70 + {
  71 + throw new IllegalArgumentException( "name cannot be null" );
  72 + }
  73 + if ( size <= 0 )
  74 + {
  75 + throw new IllegalArgumentException( size + " is not > 0" );
  76 + }
  77 + this.name = name;
  78 + this.maxSize = size;
  79 + }
  80 +
  81 + /**
  82 + * Puts the given key-value pair in the cache. This method is synchronized
  83 + * and calls evict if this put would make the cache exceed capacity.
  84 + *
  85 + * @param key The key, cannot be null
  86 + * @param value The value, cannot be null
  87 + */
  88 + public synchronized void put( K key, V value )
  89 + {
  90 + if ( key == null )
  91 + {
  92 + throw new IllegalArgumentException( "null key not allowed" );
  93 + }
  94 + if ( value == null )
  95 + {
  96 + throw new IllegalArgumentException( "null value not allowed" );
  97 + }
  98 + Page<V> theValue = cache.get( key );
  99 + if ( theValue == null )
  100 + {
  101 + theValue = new Page<V>();
  102 + if ( cache.putIfAbsent( key, theValue ) == null )
  103 + {
  104 + clock.offer( theValue );
  105 + }
  106 + else
  107 + {
  108 + System.out.println( "Ouch, for key " + key );
  109 + }
  110 + }
  111 + V myValue = theValue.value.get();
  112 + theValue.flag.set( true );
  113 + theValue.value.set( value );
  114 + if ( myValue == null )
  115 + {
  116 + if ( currentSize.incrementAndGet() > maxSize )
  117 + {
  118 + evict();
  119 + }
  120 + assert currentSize.get() <= maxSize : "put: " + currentSize.get();
  121 + }
  122 + }
  123 +
  124 + /**
  125 + * Returns the value for this key. The result will be null for keys that
  126 + * have no value present. There is no inherent differentiation between
  127 + * values that were never present and values that were inserted but are now
  128 + * evicted.
  129 + *
  130 + * @param key The key for which the value should be retrieved. Cannot be
  131 + * null
  132 + * @return The value associated with the key or null if no such value could
  133 + * be found
  134 + */
  135 + public V get( K key )
  136 + {
  137 + if ( key == null )
  138 + {
  139 + throw new IllegalArgumentException( "cannot get null key" );
  140 + }
  141 + Page<V> theElement = cache.get( key );
  142 + if ( theElement == null || theElement.value.get() == null )
  143 + {
  144 + return null;
  145 + }
  146 + V theValue = theElement.value.get();
  147 + theElement.flag.set( true );
  148 + if ( theValue == null )
  149 + {
  150 + theElement.flag.set( false );
  151 + }
  152 + return theValue;
  153 + }
  154 +
  155 + private void evict()
  156 + {
  157 + /*
  158 + * This is an inverse clock. Instead of moving a pointer around an array,
  159 + * we move the array around the pointer. And what better way to do that than
  160 + * a queue? The head of the queue is the pointer and we rotate around that until
  161 + * we meet something that has the flag set to false - then evict that. What changes
  162 + * under our nose is get()s, which may change a flag to true after we change it to false.
  163 + * Note that this is supposed to be run ONLY FROM put(), which is synchronized.
  164 + */
  165 + Page<V> theElement = null;
  166 + while ( ( theElement = clock.poll() ) != null && currentSize.get() > maxSize )
  167 + {
  168 + if ( !theElement.flag.compareAndSet( true, false ) )
  169 + {
  170 + V valueCleaned = theElement.value.get();
  171 + if ( valueCleaned == null)
  172 + {
  173 + theElement.flag.set( false );
  174 + }
  175 + else if ( theElement.value.compareAndSet( valueCleaned, null ) )
  176 + {
  177 + elementCleaned( valueCleaned );
  178 + currentSize.decrementAndGet();
  179 + }
  180 + }
  181 + clock.offer( theElement );
  182 + }
  183 + if ( theElement != null )
  184 + {
  185 + clock.offer( theElement );
  186 + }
  187 + }
  188 +
  189 + /**
  190 + * Called whenever an element is evicted from the cache. This is expected to
  191 + * be overridden by subclassing implementations expressly written for the
  192 + * purpose of managing evicted elements. The default implementation does
  193 + * nothing.
  194 + *
  195 + * <b>NOTE:</b> This is called during {@link #put(Object, Object)} which is
  196 + * synchronized. It should be assumed that this method is also synchronized
  197 + * on this and take care to avoid deadlocks.
  198 + *
  199 + * @param element The element that was evicted. When the method is called
  200 + * there is no reference in the cache to that element so unless
  201 + * one is kept outside of the cache it will be garbage collected
  202 + * on the next GC cycle.
  203 + */
  204 + protected void elementCleaned( V element )
  205 + {
  206 + }
  207 +
  208 + /**
  209 + * Returns a collection of values present in the cache. This method is not
  210 + * synchronized, so what it returns may not be an accurate view of the
  211 + * contents of the cache at any point in time. For example, it is possible
  212 + * that putting value A may evict value B but a call to values() in between
  213 + * the put and the evict will have both A and B.
  214 + *
  215 + * @return A possibly inaccurate collection of values contained in the
  216 + * cache. The collection returned is not backed by the cache.
  217 + */
  218 + public Collection<V> values()
  219 + {
  220 + Set<V> toReturn = new HashSet<V>();
  221 + for ( Page<V> page : cache.values() )
  222 + {
  223 + if ( page.value.get() != null )
  224 + {
  225 + toReturn.add( page.value.get() );
  226 + }
  227 + }
  228 + return toReturn;
  229 + }
  230 +
  231 + /**
  232 + * Returns a collection of key/value pairs present in the cache. This method
  233 + * is not synchronized, so what it returns may not be an accurate view of
  234 + * the contents of the cache at any point in time. For example, it is
  235 + * possible that putting value A may evict value B but a call to entrySet()
  236 + * in between the put and the evict will have both A and B.
  237 + *
  238 + * @return A possibly inaccurate collection of key/value pairs contained in
  239 + * the cache. The collection returned is not backed by the cache.
  240 + */
  241 + public synchronized Set<Map.Entry<K, V>> entrySet()
  242 + {
  243 + Map<K, V> temp = new HashMap<K, V>();
  244 + for ( K key : cache.keySet() )
  245 + {
  246 + Page<V> value = cache.get( key );
  247 + if ( value.value.get() != null )
  248 + {
  249 + temp.put( key, value.value.get() );
  250 + }
  251 + }
  252 + return temp.entrySet();
  253 + }
  254 +
  255 + /**
  256 + * Removes the value mapped to this key. If the mapping does not exist
  257 + * nothing happens.
  258 + *
  259 + * @param key The key for which to remove the mapping. Cannot be null.
  260 + * @return The value that was associated with this key, possibly null.
  261 + */
  262 + public synchronized V remove( K key )
  263 + {
  264 + if ( key == null )
  265 + {
  266 + throw new IllegalArgumentException( "cannot remove null key" );
  267 + }
  268 + Page<V> toRemove = cache.remove( key );
  269 + if ( toRemove == null || toRemove.value == null )
  270 + {
  271 + return null;
  272 + }
  273 + V toReturn = toRemove.value.get();
  274 + toRemove.value.compareAndSet( toReturn, null );
  275 + toRemove.flag.set( false );
  276 + return toReturn;
  277 + }
  278 +
  279 + /**
  280 + * @return The name of the cache.
  281 + */
  282 + public String getName()
  283 + {
  284 + return name;
  285 + }
  286 +
  287 + /**
  288 + * Clears the cache of all contents. After this any call to
  289 + * {@link #get(Object)} will return null and {@link #size()} will return
  290 + * null
  291 + */
  292 + public synchronized void clear()
  293 + {
  294 + cache.clear();
  295 + clock.clear();
  296 + currentSize.set( 0 );
  297 + }
  298 +
  299 + /**
  300 + * Returns the size of the cache, specifically the number of values present.
  301 + * It is synchronized, so it is expected to always return a value larger
  302 + * than 0 and at most equals to the size argument passed in the constructor.
  303 + *
  304 + * @return The number of values currently present in the cache.
  305 + */
  306 + public synchronized int size()
  307 + {
  308 + return currentSize.get();
  309 + }
  310 +
  311 + private static class Page<E>
  312 + {
  313 + final AtomicBoolean flag = new AtomicBoolean( true );
  314 + final AtomicReference<E> value = new AtomicReference<E>();
  315 +
  316 + @Override
  317 + public boolean equals( Object obj )
  318 + {
  319 + if ( obj == null )
  320 + {
  321 + return false;
  322 + }
  323 + if ( !( obj instanceof Page ) )
  324 + {
  325 + return false;
  326 + }
  327 + Page<?> other = (Page<?>) obj;
  328 + if ( value == null )
  329 + {
  330 + return other.value == null;
  331 + }
  332 + return value.equals( other.value );
  333 + }
  334 +
  335 + @Override
  336 + public int hashCode()
  337 + {
  338 + return value == null ? 0 : value.hashCode();
  339 + }
  340 +
  341 + @Override
  342 + public String toString()
  343 + {
  344 + return ( value.get() != null ? "->" : "" ) + "[Flag: " + flag + ", value: " + value.get() + "]";
  345 + }
  346 + }
  347 +}
292 kernel/src/test/java/org/neo4j/kernel/impl/cache/TestClockCache.java
... ... @@ -0,0 +1,292 @@
  1 +/**
  2 + * Copyright (c) 2002-2012 "Neo Technology,"
  3 + * Network Engine for Objects in Lund AB [http://neotechnology.com]
  4 + *
  5 + * This file is part of Neo4j.
  6 + *
  7 + * Neo4j is free software: you can redistribute it and/or modify
  8 + * it under the terms of the GNU General Public License as published by
  9 + * the Free Software Foundation, either version 3 of the License, or
  10 + * (at your option) any later version.
  11 + *
  12 + * This program is distributed in the hope that it will be useful,
  13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15 + * GNU General Public License for more details.
  16 + *
  17 + * You should have received a copy of the GNU General Public License
  18 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19 + */
  20 +package org.neo4j.kernel.impl.cache;
  21 +
  22 +import static org.junit.Assert.assertEquals;
  23 +import static org.junit.Assert.assertFalse;
  24 +import static org.junit.Assert.assertNotNull;
  25 +import static org.junit.Assert.assertNull;
  26 +import static org.junit.Assert.fail;
  27 +
  28 +import java.util.HashMap;
  29 +import java.util.LinkedList;
  30 +import java.util.List;
  31 +import java.util.Map;
  32 +import java.util.Random;
  33 +import java.util.concurrent.ConcurrentHashMap;
  34 +import java.util.concurrent.ExecutorService;
  35 +import java.util.concurrent.Executors;
  36 +import java.util.concurrent.TimeUnit;
  37 +
  38 +import org.junit.Ignore;
  39 +import org.junit.Test;
  40 +
  41 +public class TestClockCache
  42 +{
  43 + @Test
  44 + public void testCreate()
  45 + {
  46 + try
  47 + {
  48 + new ClockCache<Object, Object>( "TestCache", 0 );
  49 + fail( "Illegal maxSize should throw exception" );
  50 + }
  51 + catch ( IllegalArgumentException e )
  52 + { // good
  53 + }
  54 + ClockCache<Object, Object> cache = new ClockCache<Object, Object>(
  55 + "TestCache", 70 );
  56 + try
  57 + {
  58 + cache.put( null, new Object() );
  59 + fail( "Null key should throw exception" );
  60 + }
  61 + catch ( IllegalArgumentException e )
  62 + { // good
  63 + }
  64 + try
  65 + {
  66 + cache.put( new Object(), null );
  67 + fail( "Null element should throw exception" );
  68 + }
  69 + catch ( IllegalArgumentException e )
  70 + { // good
  71 + }
  72 + try
  73 + {
  74 + cache.get( null );
  75 + fail( "Null key should throw exception" );
  76 + }
  77 + catch ( IllegalArgumentException e )
  78 + { // good
  79 + }
  80 + try
  81 + {
  82 + cache.remove( null );
  83 + fail( "Null key should throw exception" );
  84 + }
  85 + catch ( IllegalArgumentException e )
  86 + { // good
  87 + }
  88 + cache.put( new Object(), new Object() );
  89 + cache.clear();
  90 + }
  91 +
  92 + private static class ClockCacheTest<K, E> extends ClockCache<K, E>
  93 + {
  94 + private E cleanedElement = null;
  95 +
  96 + ClockCacheTest( String name, int maxSize )
  97 + {
  98 + super( name, maxSize );
  99 + }
  100 +
  101 + @Override
  102 + public void elementCleaned( E element )
  103 + {
  104 + cleanedElement = element;
  105 + }
  106 +
  107 + E getLastCleanedElement()
  108 + {
  109 + return cleanedElement;
  110 + }
  111 + }
  112 +
  113 + @Test
  114 + public void testSimple()
  115 + {
  116 + ClockCacheTest<Integer, String> cache = new ClockCacheTest<Integer, String>(
  117 + "TestCache", 3 );
  118 + Map<String, Integer> valueToKey = new HashMap<String, Integer>();
  119 + Map<Integer, String> keyToValue = new HashMap<Integer, String>();
  120 +
  121 + String s1 = new String( "1" );
  122 + Integer key1 = new Integer( 1 );
  123 + valueToKey.put( s1, key1 );
  124 + keyToValue.put( key1, s1 );
  125 +
  126 + String s2 = new String( "2" );
  127 + Integer key2 = new Integer( 2 );
  128 + valueToKey.put( s2, key2 );
  129 + keyToValue.put( key2, s2 );
  130 +
  131 + String s3 = new String( "3" );
  132 + Integer key3 = new Integer( 3 );
  133 + valueToKey.put( s3, key3 );
  134 + keyToValue.put( key3, s3 );
  135 +
  136 + String s4 = new String( "4" );
  137 + Integer key4 = new Integer( 4 );
  138 + valueToKey.put( s4, key4 );
  139 + keyToValue.put( key4, s4 );
  140 +
  141 + String s5 = new String( "5" );
  142 + Integer key5 = new Integer( 5 );
  143 + valueToKey.put( s5, key5 );
  144 + keyToValue.put( key5, s5 );
  145 +
  146 + List<Integer> cleanedElements = new LinkedList<Integer>();
  147 + List<Integer> existingElements = new LinkedList<Integer>();
  148 +
  149 + cache.put( key1, s1 );
  150 + cache.put( key2, s2 );
  151 + cache.put( key3, s3 );
  152 + assertEquals( null, cache.getLastCleanedElement() );
  153 +
  154 + String fromKey2 = cache.get( key2 );
  155 + assertEquals( s2, fromKey2 );
  156 + String fromKey1 = cache.get( key1 );
  157 + assertEquals( s1, fromKey1 );
  158 + String fromKey3 = cache.get( key3 );
  159 + assertEquals( s3, fromKey3 );
  160 +
  161 + cache.put( key4, s4 );
  162 + assertFalse( s4.equals( cache.getLastCleanedElement() ) );
  163 + assertNotNull( cache.getLastCleanedElement() );
  164 + String lastCleaned = cache.getLastCleanedElement();
  165 + cleanedElements.add( valueToKey.get( lastCleaned ) );
  166 + existingElements.remove( valueToKey.get( lastCleaned ) );
  167 +
  168 + cache.put( key5, s5 );
  169 + assertNotNull( cache.getLastCleanedElement() );
  170 + assertFalse( lastCleaned.equals( cache.getLastCleanedElement() ) );
  171 + lastCleaned = cache.getLastCleanedElement();
  172 + assertFalse( s4.equals( lastCleaned ) );
  173 + assertFalse( s5.equals( lastCleaned ) );
  174 + cleanedElements.add( valueToKey.get( lastCleaned ) );
  175 + existingElements.remove( valueToKey.get( lastCleaned ) );
  176 +
  177 + int size = cache.size();
  178 + assertEquals( 3, size );
  179 + for ( Integer key : cleanedElements )
  180 + {
  181 + assertEquals( "for key " + key, null, cache.get( key ) );
  182 + }
  183 + for ( Integer key : existingElements )
  184 + {
  185 + assertEquals( keyToValue.get( key ), cache.get( key ) );
  186 + }
  187 + cache.clear();
  188 + assertEquals( 0, cache.size() );
  189 + for ( Integer key : keyToValue.keySet() )
  190 + {
  191 + assertEquals( null, cache.get( key ) );
  192 + }
  193 + }
  194 +
  195 + @Test
  196 + public void testMoreSimple()
  197 + {
  198 + ClockCacheTest<Integer, String> cache = new ClockCacheTest<Integer, String>( "TestCacheSingle", 2 );
  199 + cache.put( 1, "1" );
  200 + cache.put( 2, "2" );
  201 + cache.put( 3, "3" );
  202 + assertNull( cache.get( 1 ) );
  203 + assertEquals( "2", cache.get( 2 ) );
  204 + assertEquals( "3", cache.get( 3 ) );
  205 + assertEquals( "1", cache.getLastCleanedElement() );
  206 + cache.put( 1, "1-1" );
  207 + assertEquals( "3", cache.getLastCleanedElement() );
  208 + assertEquals( "1-1", cache.get( 1 ) );
  209 + cache.put( 1, "1" );
  210 +
  211 + int entryCounter = 0;
  212 + for ( Map.Entry<Integer, String> entry : cache.entrySet() )
  213 + {
  214 + entryCounter++;
  215 + assertEquals( entry.getKey().toString(), entry.getValue() );
  216 + }
  217 + assertEquals( 2, entryCounter );
  218 + assertEquals( entryCounter, cache.size() );
  219 + }
  220 +
  221 + @Test
  222 + @Ignore( "Takes a lot of time and the cache is non hard on guarantees. Run by hand instead" )
  223 + public void testMultiThreaded() throws InterruptedException
  224 + {
  225 + final int cacheSize = 10;
  226 + Map<String, Long> theControl = new ConcurrentHashMap<String, Long>();
  227 + for ( int i = 0; i < 100; i++ )
  228 + {
  229 + theControl.put( "" + i, System.currentTimeMillis() );
  230 + }
  231 + ExecutorService executor = Executors.newFixedThreadPool( 20 );
  232 +
  233 + // key is an integer, value is the integer with a '-value' appended
  234 + ClockCache<String, String> theCache = new ClockCache<String, String>( "under test", 10 );
  235 + Random r = new Random();
  236 + for ( int i = 0; i < 1000000; i++ )
  237 + {
  238 + executor.execute( new ClockCacheWorker( theCache, theControl, r.nextInt( 100 ) ) );
  239 + }
  240 + executor.shutdown();
  241 + while ( !executor.awaitTermination( 5, TimeUnit.SECONDS ) )
  242 + {
  243 + System.out.println( "waiting more" );
  244 + }
  245 + assertEquals( cacheSize, theCache.size() );
  246 + long now = System.currentTimeMillis();
  247 + int entryCounter = 0;
  248 + for ( Map.Entry<String, String> entry : theCache.entrySet() )
  249 + {
  250 + System.out.println( "Entry " + entry.getKey() + " is in cache, last messed with "
  251 + + ( now - theControl.get( entry.getKey() ) ) + " ms" );
  252 + assertNotNull( "null for key " + entry.getKey(), entry.getValue() );
  253 + assertEquals( "wrong value for key " + entry.getKey(), entry.getKey() + "-value", entry.getValue() );
  254 + entryCounter++;
  255 + }
  256 + assertEquals( "Entry counting was wrong", theCache.size(), entryCounter );
  257 + /*
  258 + for ( Map.Entry<String, Long> entry : theControl.entrySet() )
  259 + {
  260 + System.out.println( "Entry " + entry.getKey() + " is in control, " + ( now - entry.getValue() )
  261 + + " since last messed with" );
  262 + }
  263 + */
  264 + }
  265 +
  266 + private static class ClockCacheWorker implements Runnable
  267 + {
  268 + private final ClockCache<String, String> theCache;
  269 + private final Map<String, Long> theControl;
  270 + private final int startAt;
  271 +
  272 + public ClockCacheWorker( ClockCache<String, String> cache, Map<String, Long> control, int startAt )
  273 + {
  274 + this.theCache = cache;
  275 + this.theControl = control;
  276 + this.startAt = startAt;
  277 + }
  278 +
  279 + @Override
  280 + public void run()
  281 + {
  282 + for ( int i = 0; i < 50; i++ )
  283 + {
  284 + String toMessWith = ( ( startAt + i ) % 100 ) + "";
  285 + String toMessWithValue = toMessWith + "-value";
  286 + theCache.put( toMessWith, toMessWithValue );
  287 + theCache.get( toMessWith );
  288 + theControl.put( toMessWith, System.currentTimeMillis() );
  289 + }
  290 + }
  291 + }
  292 +}
49 lucene-index/src/main/java/org/neo4j/index/impl/lucene/IndexSearcherClockCache.java
... ... @@ -0,0 +1,49 @@
  1 +/**
  2 + * Copyright (c) 2002-2012 "Neo Technology,"
  3 + * Network Engine for Objects in Lund AB [http://neotechnology.com]
  4 + *
  5 + * This file is part of Neo4j.
  6 + *
  7 + * Neo4j is free software: you can redistribute it and/or modify
  8 + * it under the terms of the GNU General Public License as published by
  9 + * the Free Software Foundation, either version 3 of the License, or
  10 + * (at your option) any later version.
  11 + *
  12 + * This program is distributed in the hope that it will be useful,
  13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15 + * GNU General Public License for more details.
  16 + *
  17 + * You should have received a copy of the GNU General Public License
  18 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19 + */
  20 +package org.neo4j.index.impl.lucene;
  21 +
  22 +import java.io.IOException;
  23 +import java.util.concurrent.atomic.AtomicBoolean;
  24 +
  25 +import org.neo4j.helpers.Pair;
  26 +import org.neo4j.kernel.impl.cache.ClockCache;
  27 +
  28 +/**
  29 + * A cache for keeping the index readers, keyed by an IndexIdentifier. It acts
  30 + * like an approximate LRU, since it is based on clock. The benefit is no need
  31 + * to synchronize on get.
  32 + */
  33 +public class IndexSearcherClockCache extends ClockCache<IndexIdentifier, Pair<IndexSearcherRef, AtomicBoolean>>
  34 +{
  35 + public IndexSearcherClockCache( int maxSize )
  36 + {
  37 + super( "IndexSearcherCache", maxSize );
  38 + }
  39 +
  40 + @Override
  41 + protected void elementCleaned( Pair<IndexSearcherRef, AtomicBoolean> searcher )
  42 + {
  43 + try {
  44 + searcher.first().dispose();
  45 + } catch (IOException e) {
  46 + throw new RuntimeException( e );
  47 + }
  48 + }
  49 +}
48 lucene-index/src/main/java/org/neo4j/index/impl/lucene/IndexWriterClockCache.java
... ... @@ -0,0 +1,48 @@
  1 +/**
  2 + * Copyright (c) 2002-2012 "Neo Technology,"
  3 + * Network Engine for Objects in Lund AB [http://neotechnology.com]
  4 + *
  5 + * This file is part of Neo4j.
  6 + *
  7 + * Neo4j is free software: you can redistribute it and/or modify
  8 + * it under the terms of the GNU General Public License as published by
  9 + * the Free Software Foundation, either version 3 of the License, or
  10 + * (at your option) any later version.
  11 + *
  12 + * This program is distributed in the hope that it will be useful,
  13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15 + * GNU General Public License for more details.
  16 + *
  17 + * You should have received a copy of the GNU General Public License
  18 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19 + */
  20 +package org.neo4j.index.impl.lucene;
  21 +
  22 +import java.io.IOException;
  23 +
  24 +import org.apache.lucene.index.IndexWriter;
  25 +import org.neo4j.kernel.impl.cache.ClockCache;
  26 +
  27 +/**
  28 + * A cache for keeping the index writers, keyed by an IndexIdentifier. It acts
  29 + * like an approximate LRU, since it is based on clock. The benefit is no need
  30 + * to synchronize on get.
  31 + */
  32 +public class IndexWriterClockCache extends ClockCache<IndexIdentifier, IndexWriter>
  33 +{
  34 + public IndexWriterClockCache( int maxSize )
  35 + {
  36 + super( "IndexWriterCache", maxSize );
  37 + }
  38 +
  39 + @Override
  40 + protected void elementCleaned( IndexWriter writer )
  41 + {
  42 + try {
  43 + writer.close( true );
  44 + } catch (IOException e) {
  45 + throw new RuntimeException( e );
  46 + }
  47 + }
  48 +}
101 lucene-index/src/main/java/org/neo4j/index/impl/lucene/LuceneDataSource.java
@@ -19,6 +19,9 @@
19 19 */
20 20 package org.neo4j.index.impl.lucene;
21 21
  22 +import static org.neo4j.index.impl.lucene.MultipleBackupDeletionPolicy.SNAPSHOT_ID;
  23 +import static org.neo4j.kernel.impl.nioneo.store.NeoStore.versionStringToLong;
  24 +
22 25 import java.io.File;
23 26 import java.io.IOException;
24 27 import java.io.Reader;
@@ -32,6 +35,7 @@
32 35 import java.util.Map;
33 36 import java.util.concurrent.atomic.AtomicBoolean;
34 37 import java.util.concurrent.locks.ReentrantReadWriteLock;
  38 +
35 39 import org.apache.lucene.analysis.Analyzer;
36 40 import org.apache.lucene.analysis.KeywordAnalyzer;
37 41 import org.apache.lucene.analysis.LowerCaseFilter;
@@ -86,9 +90,6 @@
86 90 import org.neo4j.kernel.impl.transaction.xaframework.XaTransaction;
87 91 import org.neo4j.kernel.impl.transaction.xaframework.XaTransactionFactory;
88 92
89   -import static org.neo4j.index.impl.lucene.MultipleBackupDeletionPolicy.*;
90   -import static org.neo4j.kernel.impl.nioneo.store.NeoStore.*;
91   -
92 93 /**
93 94 * An {@link XaDataSource} optimized for the {@link LuceneIndexImplementation}.
94 95 * This class is public because the XA framework requires it.
@@ -100,14 +101,14 @@
100 101 {
101 102 public static final GraphDatabaseSetting.IntegerSetting lucene_searcher_cache_size = GraphDatabaseSettings.lucene_searcher_cache_size;
102 103 public static final GraphDatabaseSetting.IntegerSetting lucene_writer_cache_size = GraphDatabaseSettings.lucene_writer_cache_size;
103   -
  104 +
104 105 public static final GraphDatabaseSetting.BooleanSetting read_only = GraphDatabaseSettings.read_only;
105 106 public static final GraphDatabaseSetting.BooleanSetting allow_store_upgrade = GraphDatabaseSettings.allow_store_upgrade;
106   -
  107 +
107 108 public static final GraphDatabaseSetting.BooleanSetting ephemeral = AbstractGraphDatabase.Configuration.ephemeral;
108 109 public static final GraphDatabaseSetting.StringSetting store_dir = NeoStoreXaDataSource.Configuration.store_dir;
109 110 }
110   -
  111 +
111 112 public static final Version LUCENE_VERSION = Version.LUCENE_35;
112 113 public static final String DEFAULT_NAME = "lucene-index";
113 114 public static final byte[] DEFAULT_BRANCH_ID = UTF8.encode( "162374" );
@@ -149,8 +150,8 @@ public String toString()
149 150
150 151 public static final Analyzer KEYWORD_ANALYZER = new KeywordAnalyzer();
151 152
152   - private final IndexWriterLruCache indexWriters;
153   - private final IndexSearcherLruCache indexSearchers;
  153 + private final IndexWriterClockCache indexWriters;
  154 + private final IndexSearcherClockCache indexSearchers;
154 155
155 156 private final XaContainer xaContainer;
156 157 private final String baseStorePath;
@@ -175,8 +176,8 @@ public String toString()
175 176 public LuceneDataSource( Config config, IndexStore indexStore, FileSystemAbstraction fileSystemAbstraction, XaFactory xaFactory)
176 177 {
177 178 super( DEFAULT_BRANCH_ID, DEFAULT_NAME );
178   - indexSearchers = new IndexSearcherLruCache( config.getInteger( Configuration.lucene_searcher_cache_size ));
179   - indexWriters = new IndexWriterLruCache( config.getInteger( Configuration.lucene_writer_cache_size ));
  179 + indexSearchers = new IndexSearcherClockCache( config.getInteger( Configuration.lucene_searcher_cache_size ) );
  180 + indexWriters = new IndexWriterClockCache( config.getInteger( Configuration.lucene_writer_cache_size ) );
180 181 caching = new Cache();
181 182 String storeDir = config.get( Configuration.store_dir );
182 183 this.baseStorePath = getStoreDir( storeDir ).first();
@@ -467,7 +468,7 @@ void releaseWriteLock()
467 468 * scratch.
468 469 *
469 470 * @param searcher the {@link IndexSearcher} to refresh.
470   - * @param writer
  471 + * @param writer
471 472 * @return a refreshed version of the searcher or, if nothing has changed,
472 473 * {@code null}.
473 474 * @throws IOException if there's a problem with the index.
@@ -528,7 +529,46 @@ static TopFieldCollector scoringCollector( Sort sorting, int n ) throws IOExcept
528 529 return TopFieldCollector.create( sorting, n, false, true, false, true );
529 530 }
530 531
531   - synchronized IndexSearcherRef getIndexSearcher( IndexIdentifier identifier, boolean incRef )
  532 + IndexSearcherRef getIndexSearcher( IndexIdentifier identifier, boolean incRef )
  533 + {
  534 + Pair<IndexSearcherRef, AtomicBoolean> searcher = indexSearchers.get( identifier );
  535 + if ( searcher == null )
  536 + {
  537 + /*
  538 + * Double checked locking here. If we don't get a reference we need to create. But
  539 + * we need to lock to avoid double instantiations and we have to recheck no one
  540 + * created the stuff while we were waiting for the lock.
  541 + */
  542 + searcher = synchGetIndexSearcher( identifier, incRef );
  543 + }
  544 + else
  545 + {
  546 + if ( searcher.other().get() )
  547 + {
  548 + synchronized ( searcher )
  549 + {
  550 + if ( searcher.other().get() )
  551 + {
  552 + IndexWriter writer = getIndexWriter( identifier );
  553 + searcher = refreshSearcher( searcher, writer );
  554 + if ( searcher != null )
  555 + {
  556 + indexSearchers.put( identifier, searcher );
  557 + }
  558 + searcher.other().set( false );
  559 + }
  560 + }
  561 + }
  562 + }
  563 + if ( incRef )
  564 + {
  565 + searcher.first().incRef();
  566 + }
  567 + return searcher.first();
  568 + }
  569 +
  570 + private synchronized Pair<IndexSearcherRef, AtomicBoolean> synchGetIndexSearcher( IndexIdentifier identifier,
  571 + boolean incRef )
532 572 {
533 573 try
534 574 {
@@ -552,11 +592,7 @@ synchronized IndexSearcherRef getIndexSearcher( IndexIdentifier identifier, bool
552 592 }
553 593 }
554 594 }
555   - if ( incRef )
556   - {
557   - searcher.first().incRef();
558   - }
559   - return searcher.first();
  595 + return searcher;
560 596 }
561 597 catch ( IOException e )
562 598 {
@@ -570,12 +606,15 @@ XaTransaction createTransaction( int identifier,
570 606 return new LuceneTransaction( identifier, logicalLog, this );
571 607 }
572 608
573   - synchronized void invalidateIndexSearcher( IndexIdentifier identifier )
  609 + void invalidateIndexSearcher( IndexIdentifier identifier )
574 610 {
575 611 Pair<IndexSearcherRef, AtomicBoolean> searcher = indexSearchers.get( identifier );
576 612 if ( searcher != null )
577 613 {
578   - searcher.other().set( true );
  614 + synchronized ( searcher )
  615 + {
  616 + searcher.other().set( true );
  617 + }
579 618 }
580 619 }
581 620
@@ -616,7 +655,25 @@ private static void deleteFileOrDirectory( File file )
616 655 }
617 656 }
618 657
619   - synchronized IndexWriter getIndexWriter( IndexIdentifier identifier )
  658 + IndexWriter getIndexWriter( IndexIdentifier identifier )
  659 + {
  660 + if ( closed ) throw new IllegalStateException( "Index has been shut down" );
  661 +
  662 + IndexWriter writer = indexWriters.get( identifier );
  663 + if ( writer != null )
  664 + {
  665 + return writer;
  666 + }
  667 + else
  668 + {
  669 + /*
  670 + * Double checked locking, check out the reader getter.
  671 + */
  672 + return synchGetIndexWriter( identifier );
  673 + }
  674 + }
  675 +
  676 + private synchronized IndexWriter synchGetIndexWriter( IndexIdentifier identifier )
620 677 {
621 678 if ( closed ) throw new IllegalStateException( "Index has been shut down" );
622 679
@@ -874,7 +931,7 @@ private void makeSureAllIndexesAreInstantiated()
874 931 }
875 932 }
876 933 }
877   -
  934 +
878 935 private static enum DirectoryGetter
879 936 {
880 937 FS
@@ -893,7 +950,7 @@ Directory getDirectory( String baseStorePath, IndexIdentifier identifier )
893 950 return new RAMDirectory();
894 951 }
895 952 };
896   -
  953 +
897 954 abstract Directory getDirectory( String baseStorePath, IndexIdentifier identifier ) throws IOException;
898 955 }
899 956 }

0 comments on commit 31a3cc8

Please sign in to comment.
Something went wrong with that request. Please try again.