001package org.cache2k.impl; 002 003/* 004 * #%L 005 * cache2k core package 006 * %% 007 * Copyright (C) 2000 - 2015 headissue GmbH, Munich 008 * %% 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as 011 * published by the Free Software Foundation, either version 3 of the 012 * License, or (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public 020 * License along with this program. If not, see 021 * <http://www.gnu.org/licenses/gpl-3.0.html>. 022 * #L% 023 */ 024 025import org.cache2k.BulkCacheSource; 026import org.cache2k.CacheEntry; 027import org.cache2k.CacheException; 028import org.cache2k.CacheMisconfigurationException; 029import org.cache2k.ClosableIterator; 030import org.cache2k.EntryExpiryCalculator; 031import org.cache2k.ExceptionExpiryCalculator; 032import org.cache2k.ExperimentalBulkCacheSource; 033import org.cache2k.Cache; 034import org.cache2k.CacheConfig; 035import org.cache2k.RefreshController; 036import org.cache2k.StorageConfiguration; 037import org.cache2k.ValueWithExpiryTime; 038import org.cache2k.impl.threading.Futures; 039import org.cache2k.impl.threading.LimitedPooledExecutor; 040import org.cache2k.impl.timer.GlobalTimerService; 041import org.cache2k.impl.timer.TimerService; 042import org.cache2k.impl.util.ThreadDump; 043import org.cache2k.CacheSourceWithMetaInfo; 044import org.cache2k.CacheSource; 045import org.cache2k.PropagatedCacheException; 046 047import org.cache2k.storage.PurgeableStorage; 048import org.cache2k.storage.StorageEntry; 049import org.cache2k.impl.util.Log; 050import org.cache2k.impl.util.TunableConstants; 051import org.cache2k.impl.util.TunableFactory; 052 053import java.lang.reflect.Array; 054import java.security.SecureRandom; 055import java.util.AbstractList; 056import java.util.AbstractMap; 057import java.util.AbstractSet; 058import java.util.Arrays; 059import java.util.BitSet; 060import java.util.Date; 061import java.util.HashSet; 062import java.util.Iterator; 063import java.util.List; 064import java.util.Map; 065import java.util.Random; 066import java.util.Set; 067import java.util.Timer; 068import java.util.concurrent.Future; 069import java.util.concurrent.FutureTask; 070import java.util.concurrent.TimeUnit; 071import java.util.concurrent.TimeoutException; 072 073import static org.cache2k.impl.util.Util.*; 074 075/** 076 * Foundation for all cache variants. All common functionality is in here. 077 * For a (in-memory) cache we need three things: a fast hash table implementation, an 078 * LRU list (a simple double linked list), and a fast timer. 079 * The variants implement different eviction strategies. 080 * 081 * <p>Locking: The cache has a single structure lock obtained via {@link #lock} and also 082 * locks on each entry for operations on it. Though, mutation operations that happen on a 083 * single entry get serialized. 084 * 085 * @author Jens Wilke; created: 2013-07-09 086 */ 087@SuppressWarnings({"unchecked", "SynchronizationOnLocalVariableOrMethodParameter"}) 088public abstract class BaseCache<E extends Entry, K, T> 089 implements Cache<K, T>, CanCheckIntegrity, Iterable<CacheEntry<K, T>>, StorageAdapter.Parent { 090 091 static final Random SEED_RANDOM = new Random(new SecureRandom().nextLong()); 092 static int cacheCnt = 0; 093 094 protected static final Tunable TUNABLE = TunableFactory.get(Tunable.class); 095 096 /** 097 * Instance of expiry calculator that extracts the expiry time from the value. 098 */ 099 final static EntryExpiryCalculator<?, ValueWithExpiryTime> ENTRY_EXPIRY_CALCULATOR_FROM_VALUE = new 100 EntryExpiryCalculator<Object, ValueWithExpiryTime>() { 101 @Override 102 public long calculateExpiryTime( 103 Object _key, ValueWithExpiryTime _value, long _fetchTime, 104 CacheEntry<Object, ValueWithExpiryTime> _oldEntry) { 105 return _value.getCacheExpiryTime(); 106 } 107 }; 108 109 protected int hashSeed; 110 111 { 112 if (TUNABLE.disableHashRandomization) { 113 hashSeed = TUNABLE.hashSeed; 114 } else { 115 hashSeed = SEED_RANDOM.nextInt(); 116 } 117 } 118 119 /** Maximum amount of elements in cache */ 120 protected int maxSize = 5000; 121 122 protected String name; 123 protected CacheManagerImpl manager; 124 protected CacheSourceWithMetaInfo<K, T> source; 125 /** Statistics */ 126 127 /** Time in milliseconds we keep an element */ 128 protected long maxLinger = 10 * 60 * 1000; 129 130 protected long exceptionMaxLinger = 1 * 60 * 1000; 131 132 protected EntryExpiryCalculator<K, T> entryExpiryCalculator; 133 134 protected ExceptionExpiryCalculator<K> exceptionExpiryCalculator; 135 136 protected Info info; 137 138 protected long clearedTime = 0; 139 protected long startedTime; 140 protected long touchedTime; 141 protected int timerCancelCount = 0; 142 143 protected long keyMutationCount = 0; 144 protected long putCnt = 0; 145 protected long putNewEntryCnt = 0; 146 protected long removedCnt = 0; 147 protected long expiredKeptCnt = 0; 148 protected long expiredRemoveCnt = 0; 149 protected long evictedCnt = 0; 150 protected long refreshCnt = 0; 151 protected long suppressedExceptionCnt = 0; 152 protected long fetchExceptionCnt = 0; 153 /* that is a miss, but a hit was already counted. */ 154 protected long peekHitNotFreshCnt = 0; 155 /* no heap hash hit */ 156 protected long peekMissCnt = 0; 157 158 protected long fetchCnt = 0; 159 160 protected long fetchButHitCnt = 0; 161 protected long bulkGetCnt = 0; 162 protected long fetchMillis = 0; 163 protected long refreshHitCnt = 0; 164 protected long newEntryCnt = 0; 165 166 /** 167 * Loaded from storage, but the entry was not fresh and cannot be returned. 168 */ 169 protected long loadNonFreshCnt = 0; 170 171 /** 172 * Entry was loaded from storage and fresh. 173 */ 174 protected long loadHitCnt = 0; 175 176 /** 177 * Separate counter for loaded entries that needed a fetch. 178 */ 179 protected long loadNonFreshAndFetchedCnt; 180 181 protected long refreshSubmitFailedCnt = 0; 182 183 /** 184 * An exception that should not have happened and was not thrown to the 185 * application. Only used for the refresh thread yet. 186 */ 187 protected long internalExceptionCnt = 0; 188 189 /** 190 * Needed to correct the counter invariants, because during eviction the entry 191 * might be removed from the replacement list, but still in the hash. 192 */ 193 protected int evictedButInHashCnt = 0; 194 195 /** 196 * Storage did not contain the requested entry. 197 */ 198 protected long loadMissCnt = 0; 199 200 /** 201 * A newly inserted entry was removed by the eviction without the fetch to complete. 202 */ 203 protected long virginEvictCnt = 0; 204 205 protected int maximumBulkFetchSize = 100; 206 207 /** 208 * Structure lock of the cache. Every operation that needs a consistent structure 209 * of the cache or modifies it needs to synchronize on this. Since this is a global 210 * lock, locking on it should be avoided and any operation under the lock should be 211 * quick. 212 */ 213 protected final Object lock = new Object(); 214 215 protected CacheRefreshThreadPool refreshPool; 216 217 protected Hash<E> mainHashCtrl; 218 protected E[] mainHash; 219 220 protected Hash<E> refreshHashCtrl; 221 protected E[] refreshHash; 222 223 protected ExperimentalBulkCacheSource<K, T> experimentalBulkCacheSource; 224 225 protected BulkCacheSource<K, T> bulkCacheSource; 226 227 protected Timer timer; 228 229 /** Stuff that we need to wait for before shutdown may complete */ 230 protected Futures.WaitForAllFuture<?> shutdownWaitFuture; 231 232 protected boolean shutdownInitiated = false; 233 234 /** 235 * Flag during operation that indicates, that the cache is full and eviction needs 236 * to be done. Eviction is only allowed to happen after an entry is fetched, so 237 * at the end of an cache operation that increased the entry count we check whether 238 * something needs to be evicted. 239 */ 240 protected boolean evictionNeeded = false; 241 242 protected Class keyType; 243 244 protected Class valueType; 245 246 protected StorageAdapter storage; 247 248 protected TimerService timerService = GlobalTimerService.getInstance(); 249 250 /** 251 * Stops creation of new entries when clear is ongoing. 252 */ 253 protected boolean waitForClear = false; 254 255 private int featureBits = 0; 256 257 private static final int SHARP_TIMEOUT_FEATURE = 1; 258 private static final int KEEP_AFTER_EXPIRED = 2; 259 private static final int SUPPRESS_EXCEPTIONS = 4; 260 261 protected final boolean hasSharpTimeout() { 262 return (featureBits & SHARP_TIMEOUT_FEATURE) > 0; 263 } 264 265 protected final boolean hasKeepAfterExpired() { 266 return (featureBits & KEEP_AFTER_EXPIRED) > 0; 267 } 268 269 protected final boolean hasSuppressExceptions() { 270 return (featureBits & SUPPRESS_EXCEPTIONS) > 0; 271 } 272 273 protected final void setFeatureBit(int _bitmask, boolean _flag) { 274 if (_flag) { 275 featureBits |= _bitmask; 276 } else { 277 featureBits &= ~_bitmask; 278 } 279 } 280 281 /** 282 * Enabling background refresh means also serving expired values. 283 */ 284 protected final boolean hasBackgroundRefreshAndServesExpiredValues() { 285 return refreshPool != null; 286 } 287 288 /** 289 * Returns name of the cache with manager name. 290 */ 291 protected String getCompleteName() { 292 if (manager != null) { 293 return manager.getName() + ":" + name; 294 } 295 return name; 296 } 297 298 /** 299 * Normally a cache itself logs nothing, so just construct when needed. 300 */ 301 protected Log getLog() { 302 return 303 Log.getLog(Cache.class.getName() + '/' + getCompleteName()); 304 } 305 306 @Override 307 public void resetStorage(StorageAdapter _from, StorageAdapter to) { 308 synchronized (lock) { 309 storage = to; 310 } 311 } 312 313 /** called via reflection from CacheBuilder */ 314 public void setCacheConfig(CacheConfig c) { 315 valueType = c.getValueType(); 316 keyType = c.getKeyType(); 317 if (name != null) { 318 throw new IllegalStateException("already configured"); 319 } 320 setName(c.getName()); 321 maxSize = c.getMaxSize(); 322 if (c.getHeapEntryCapacity() >= 0) { 323 maxSize = c.getHeapEntryCapacity(); 324 } 325 if (c.isBackgroundRefresh()) { 326 refreshPool = CacheRefreshThreadPool.getInstance(); 327 } 328 long _expiryMillis = c.getExpiryMillis(); 329 if (_expiryMillis == Long.MAX_VALUE || _expiryMillis < 0) { 330 maxLinger = -1; 331 } else if (_expiryMillis >= 0) { 332 maxLinger = _expiryMillis; 333 } 334 long _exceptionExpiryMillis = c.getExceptionExpiryMillis(); 335 if (_exceptionExpiryMillis == -1) { 336 if (maxLinger == -1) { 337 exceptionMaxLinger = -1; 338 } else { 339 exceptionMaxLinger = maxLinger / 10; 340 } 341 } else { 342 exceptionMaxLinger = _exceptionExpiryMillis; 343 } 344 setFeatureBit(KEEP_AFTER_EXPIRED, c.isKeepDataAfterExpired()); 345 setFeatureBit(SHARP_TIMEOUT_FEATURE, c.isSharpExpiry()); 346 setFeatureBit(SUPPRESS_EXCEPTIONS, c.isSuppressExceptions()); 347 /* 348 if (c.isPersistent()) { 349 storage = new PassingStorageAdapter(); 350 } 351 -*/ 352 List<StorageConfiguration> _stores = c.getStorageModules(); 353 if (_stores.size() == 1) { 354 StorageConfiguration cfg = _stores.get(0); 355 if (cfg.getEntryCapacity() < 0) { 356 cfg.setEntryCapacity(c.getMaxSize()); 357 } 358 storage = new PassingStorageAdapter(this, c, _stores.get(0)); 359 } else if (_stores.size() > 1) { 360 throw new UnsupportedOperationException("no aggregation support yet"); 361 } 362 if (ValueWithExpiryTime.class.isAssignableFrom(c.getValueType()) && 363 entryExpiryCalculator == null) { 364 entryExpiryCalculator = 365 (EntryExpiryCalculator<K, T>) 366 ENTRY_EXPIRY_CALCULATOR_FROM_VALUE; 367 } 368 } 369 370 public void setEntryExpiryCalculator(EntryExpiryCalculator<K, T> v) { 371 entryExpiryCalculator = v; 372 } 373 374 public void setExceptionExpiryCalculator(ExceptionExpiryCalculator<K> v) { 375 exceptionExpiryCalculator = v; 376 } 377 378 /** called via reflection from CacheBuilder */ 379 public void setRefreshController(final RefreshController<T> lc) { 380 entryExpiryCalculator = new EntryExpiryCalculator<K, T>() { 381 @Override 382 public long calculateExpiryTime(K _key, T _value, long _fetchTime, CacheEntry<K, T> _oldEntry) { 383 if (_oldEntry != null) { 384 return lc.calculateNextRefreshTime(_oldEntry.getValue(), _value, _oldEntry.getLastModification(), _fetchTime); 385 } else { 386 return lc.calculateNextRefreshTime(null, _value, 0L, _fetchTime); 387 } 388 } 389 }; 390 } 391 392 @SuppressWarnings("unused") 393 public void setSource(CacheSourceWithMetaInfo<K, T> eg) { 394 source = eg; 395 } 396 397 @SuppressWarnings("unused") 398 public void setSource(final CacheSource<K, T> g) { 399 if (g != null) { 400 source = new CacheSourceWithMetaInfo<K, T>() { 401 @Override 402 public T get(K key, long _currentTime, T _previousValue, long _timeLastFetched) throws Throwable { 403 return g.get(key); 404 } 405 }; 406 } 407 } 408 409 @SuppressWarnings("unused") 410 public void setExperimentalBulkCacheSource(ExperimentalBulkCacheSource<K, T> g) { 411 experimentalBulkCacheSource = g; 412 if (source == null) { 413 source = new CacheSourceWithMetaInfo<K, T>() { 414 @Override 415 public T get(K key, long _currentTime, T _previousValue, long _timeLastFetched) throws Throwable { 416 K[] ka = (K[]) Array.newInstance(keyType, 1); 417 ka[0] = key; 418 T[] ra = (T[]) Array.newInstance(valueType, 1); 419 BitSet _fetched = new BitSet(1); 420 experimentalBulkCacheSource.getBulk(ka, ra, _fetched, 0, 1); 421 return ra[0]; 422 } 423 }; 424 } 425 } 426 427 public void setBulkCacheSource(BulkCacheSource<K, T> s) { 428 bulkCacheSource = s; 429 if (source == null) { 430 source = new CacheSourceWithMetaInfo<K, T>() { 431 @Override 432 public T get(final K key, final long _currentTime, final T _previousValue, final long _timeLastFetched) throws Throwable { 433 final CacheEntry<K, T> entry = new CacheEntry<K, T>() { 434 @Override 435 public K getKey() { 436 return key; 437 } 438 439 @Override 440 public T getValue() { 441 return _previousValue; 442 } 443 444 @Override 445 public Throwable getException() { 446 return null; 447 } 448 449 @Override 450 public long getLastModification() { 451 return _timeLastFetched; 452 } 453 }; 454 List<CacheEntry<K, T>> _entryList = new AbstractList<CacheEntry<K, T>>() { 455 @Override 456 public CacheEntry<K, T> get(int index) { 457 return entry; 458 } 459 460 @Override 461 public int size() { 462 return 1; 463 } 464 }; 465 return bulkCacheSource.getValues(_entryList, _currentTime).get(0); 466 } 467 }; 468 } 469 } 470 471 /** 472 * Set the name and configure a logging, used within cache construction. 473 */ 474 public void setName(String n) { 475 if (n == null) { 476 n = this.getClass().getSimpleName() + "#" + cacheCnt++; 477 } 478 name = n; 479 } 480 481 /** 482 * Set the time in seconds after which the cache does an refresh of the 483 * element. -1 means the element will be hold forever. 484 * 0 means the element will not be cached at all. 485 */ 486 public void setExpirySeconds(int s) { 487 if (s < 0 || s == Integer.MAX_VALUE) { 488 maxLinger = -1; 489 return; 490 } 491 maxLinger = s * 1000; 492 } 493 494 public String getName() { 495 return name; 496 } 497 498 public void setCacheManager(CacheManagerImpl cm) { 499 manager = cm; 500 } 501 502 public StorageAdapter getStorage() { return storage; } 503 504 /** 505 * Registers the cache in a global set for the clearAllCaches function and 506 * registers it with the resource monitor. 507 */ 508 public void init() { 509 synchronized (lock) { 510 if (name == null) { 511 name = "" + cacheCnt++; 512 } 513 514 if (storage == null && maxSize == 0) { 515 throw new IllegalArgumentException("maxElements must be >0"); 516 } 517 if (storage != null) { 518 bulkCacheSource = null; 519 storage.open(); 520 } 521 initializeHeapCache(); 522 initTimer(); 523 if (refreshPool != null && 524 source == null) { 525 throw new CacheMisconfigurationException("backgroundRefresh, but no source"); 526 } 527 if (refreshPool != null && timer == null) { 528 if (maxLinger == 0) { 529 getLog().warn("Background refresh is enabled, but elements are fetched always. Disable background refresh!"); 530 } else { 531 getLog().warn("Background refresh is enabled, but elements are eternal. Disable background refresh!"); 532 } 533 refreshPool.destroy(); 534 refreshPool = null; 535 } 536 } 537 } 538 539 boolean isNeedingTimer() { 540 return 541 maxLinger > 0 || entryExpiryCalculator != null || 542 exceptionMaxLinger > 0 || exceptionExpiryCalculator != null; 543 } 544 545 /** 546 * Either add a timer or remove the timer if needed or not needed. 547 */ 548 private void initTimer() { 549 if (isNeedingTimer()) { 550 if (timer == null) { 551 timer = new Timer(name, true); 552 } 553 } else { 554 if (timer != null) { 555 timer.cancel(); 556 timer = null; 557 } 558 } 559 } 560 561 /** 562 * Clear may be called during operation, e.g. to reset all the cache content. We must make sure 563 * that there is no ongoing operation when we send the clear to the storage. That is because the 564 * storage implementation has a guarantee that there is only one storage operation ongoing for 565 * one entry or key at any time. Clear, of course, affects all entries. 566 */ 567 private void processClearWithStorage() { 568 StorageClearTask t = new StorageClearTask(); 569 boolean _untouchedHeapCache; 570 synchronized (lock) { 571 checkClosed(); 572 waitForClear = true; 573 _untouchedHeapCache = touchedTime == clearedTime && getLocalSize() == 0; 574 if (!storage.checkStorageStillDisconnectedForClear()) { 575 t.allLocalEntries = iterateAllHeapEntries(); 576 t.allLocalEntries.setStopOnClear(false); 577 } 578 t.storage = storage; 579 t.storage.disconnectStorageForClear(); 580 } 581 try { 582 if (_untouchedHeapCache) { 583 FutureTask<Void> f = new FutureTask<Void>(t); 584 updateShutdownWaitFuture(f); 585 f.run(); 586 } else { 587 updateShutdownWaitFuture(manager.getThreadPool().execute(t)); 588 } 589 } catch (Exception ex) { 590 throw new CacheStorageException(ex); 591 } 592 synchronized (lock) { 593 if (isClosed()) { throw new CacheClosedException(); } 594 clearLocalCache(); 595 waitForClear = false; 596 lock.notifyAll(); 597 } 598 } 599 600 protected void updateShutdownWaitFuture(Future<?> f) { 601 synchronized (lock) { 602 if (shutdownWaitFuture == null || shutdownWaitFuture.isDone()) { 603 shutdownWaitFuture = new Futures.WaitForAllFuture(f); 604 } else { 605 shutdownWaitFuture.add((Future) f); 606 } 607 } 608 } 609 610 class StorageClearTask implements LimitedPooledExecutor.NeverRunInCallingTask<Void> { 611 612 ClosableConcurrentHashEntryIterator<Entry> allLocalEntries; 613 StorageAdapter storage; 614 615 @Override 616 public Void call() { 617 try { 618 if (allLocalEntries != null) { 619 waitForEntryOperations(); 620 } 621 storage.clearAndReconnect(); 622 storage = null; 623 return null; 624 } catch (Throwable t) { 625 if (allLocalEntries != null) { 626 allLocalEntries.close(); 627 } 628 getLog().warn("clear exception, when signalling storage", t); 629 storage.disable(t); 630 throw new CacheStorageException(t); 631 } 632 } 633 634 private void waitForEntryOperations() { 635 Iterator<Entry> it = allLocalEntries; 636 while (it.hasNext()) { 637 Entry e = it.next(); 638 synchronized (e) { } 639 } 640 } 641 } 642 643 protected void checkClosed() { 644 if (isClosed()) { 645 throw new CacheClosedException(); 646 } 647 while (waitForClear) { 648 try { 649 lock.wait(); 650 } catch (InterruptedException ignore) { } 651 } 652 } 653 654 public final void clear() { 655 if (storage != null) { 656 processClearWithStorage(); 657 return; 658 } 659 synchronized (lock) { 660 checkClosed(); 661 clearLocalCache(); 662 } 663 } 664 665 protected final void clearLocalCache() { 666 Iterator<Entry> it = iterateAllHeapEntries(); 667 int _count = 0; 668 while (it.hasNext()) { 669 Entry e = it.next(); 670 e.removedFromList(); 671 cancelExpiryTimer(e); 672 _count++; 673 } 674 removedCnt += getLocalSize(); 675 initializeHeapCache(); 676 clearedTime = System.currentTimeMillis(); 677 touchedTime = clearedTime; 678 } 679 680 protected void initializeHeapCache() { 681 if (mainHashCtrl != null) { 682 mainHashCtrl.cleared(); 683 refreshHashCtrl.cleared(); 684 } 685 mainHashCtrl = new Hash<E>(); 686 refreshHashCtrl = new Hash<E>(); 687 mainHash = mainHashCtrl.init((Class<E>) newEntry().getClass()); 688 refreshHash = refreshHashCtrl.init((Class<E>) newEntry().getClass()); 689 if (startedTime == 0) { 690 startedTime = System.currentTimeMillis(); 691 } 692 if (timer != null) { 693 timer.cancel(); 694 timer = null; 695 initTimer(); 696 } 697 } 698 699 public void clearTimingStatistics() { 700 synchronized (lock) { 701 fetchCnt = 0; 702 fetchMillis = 0; 703 } 704 } 705 706 /** 707 * Preparation for shutdown. Cancel all pending timer jobs e.g. for 708 * expiry/refresh or flushing the storage. 709 */ 710 Future<Void> cancelTimerJobs() { 711 synchronized (lock) { 712 if (timer != null) { 713 timer.cancel(); 714 } 715 Future<Void> _waitFuture = new Futures.BusyWaitFuture<Void>() { 716 @Override 717 public boolean isDone() { 718 synchronized (lock) { 719 return getFetchesInFlight() == 0; 720 } 721 } 722 }; 723 if (storage != null) { 724 Future<Void> f = storage.cancelTimerJobs(); 725 if (f != null) { 726 _waitFuture = new Futures.WaitForAllFuture(_waitFuture, f); 727 } 728 } 729 return _waitFuture; 730 } 731 } 732 733 public void purge() { 734 if (storage != null) { 735 storage.purge(); 736 } 737 } 738 739 public void flush() { 740 if (storage != null) { 741 storage.flush(); 742 } 743 } 744 745 protected boolean isClosed() { 746 return shutdownInitiated; 747 } 748 749 @Override 750 public void destroy() { 751 close(); 752 } 753 754 @Override 755 public void close() { 756 synchronized (lock) { 757 if (shutdownInitiated) { 758 return; 759 } 760 shutdownInitiated = true; 761 } 762 Future<Void> _await = cancelTimerJobs(); 763 try { 764 _await.get(TUNABLE.waitForTimerJobsSeconds, TimeUnit.SECONDS); 765 } catch (TimeoutException ex) { 766 int _fetchesInFlight; 767 synchronized (lock) { 768 _fetchesInFlight = getFetchesInFlight(); 769 } 770 if (_fetchesInFlight > 0) { 771 getLog().warn( 772 "Fetches still in progress after " + 773 TUNABLE.waitForTimerJobsSeconds + " seconds. " + 774 "fetchesInFlight=" + _fetchesInFlight); 775 } else { 776 getLog().warn( 777 "timeout waiting for timer jobs termination" + 778 " (" + TUNABLE.waitForTimerJobsSeconds + " seconds)", ex); 779 getLog().warn("Thread dump:\n" + ThreadDump.generateThredDump()); 780 } 781 getLog().warn("State: " + toString()); 782 } catch (Exception ex) { 783 getLog().warn("exception waiting for timer jobs termination", ex); 784 } 785 synchronized (lock) { 786 mainHashCtrl.close(); 787 refreshHashCtrl.close(); 788 } 789 try { 790 Future<?> _future = shutdownWaitFuture; 791 if (_future != null) { 792 _future.get(); 793 } 794 } catch (Exception ex) { 795 throw new CacheException(ex); 796 } 797 Future<Void> _waitForStorage = null; 798 if (storage != null) { 799 _waitForStorage = storage.shutdown(); 800 } 801 if (_waitForStorage != null) { 802 try { 803 _waitForStorage.get(); 804 } catch (Exception ex) { 805 StorageAdapter.rethrow("shutdown", ex); 806 } 807 } 808 synchronized (lock) { 809 storage = null; 810 } 811 synchronized (lock) { 812 if (timer != null) { 813 timer.cancel(); 814 timer = null; 815 } 816 if (refreshPool != null) { 817 refreshPool.destroy(); 818 refreshPool = null; 819 } 820 mainHash = refreshHash = null; 821 source = null; 822 if (manager != null) { 823 manager.cacheDestroyed(this); 824 manager = null; 825 } 826 } 827 } 828 829 /** 830 * Complete iteration of all entries in the cache, including 831 * storage / persisted entries. The iteration may include expired 832 * entries or entries with no valid data. 833 */ 834 protected ClosableIterator<Entry> iterateLocalAndStorage() { 835 if (storage == null) { 836 synchronized (lock) { 837 return iterateAllHeapEntries(); 838 } 839 } else { 840 return storage.iterateAll(); 841 } 842 } 843 844 @Override 845 public ClosableIterator<CacheEntry<K, T>> iterator() { 846 return new IteratorFilterEntry2Entry(iterateLocalAndStorage()); 847 } 848 849 /** 850 * Filter out non valid entries and wrap each entry with a cache 851 * entry object. 852 */ 853 class IteratorFilterEntry2Entry implements ClosableIterator<CacheEntry<K, T>> { 854 855 ClosableIterator<Entry> iterator; 856 Entry entry; 857 858 IteratorFilterEntry2Entry(ClosableIterator<Entry> it) { iterator = it; } 859 860 /** 861 * Between hasNext() and next() an entry may be evicted or expired. 862 * In practise we have to deliver a next entry if we return hasNext() with 863 * true, furthermore, there should be no big gap between the calls to 864 * hasNext() and next(). 865 */ 866 @Override 867 public boolean hasNext() { 868 while (iterator.hasNext()) { 869 entry = iterator.next(); 870 if (entry.hasFreshData()) { 871 return true; 872 } 873 } 874 return false; 875 } 876 877 @Override 878 public void close() { 879 if (iterator != null) { 880 iterator.close(); 881 iterator = null; 882 } 883 } 884 885 @Override 886 public CacheEntry<K, T> next() { return returnEntry(entry); } 887 888 @Override 889 public void remove() { 890 BaseCache.this.remove((K) entry.getKey()); 891 } 892 } 893 894 protected static void removeFromList(final Entry e) { 895 e.prev.next = e.next; 896 e.next.prev = e.prev; 897 e.removedFromList(); 898 } 899 900 protected static void insertInList(final Entry _head, final Entry e) { 901 e.prev = _head; 902 e.next = _head.next; 903 e.next.prev = e; 904 _head.next = e; 905 } 906 907 protected static final int getListEntryCount(final Entry _head) { 908 Entry e = _head.next; 909 int cnt = 0; 910 while (e != _head) { 911 cnt++; 912 if (e == null) { 913 return -cnt; 914 } 915 e = e.next; 916 } 917 return cnt; 918 } 919 920 protected static final <E extends Entry> void moveToFront(final E _head, final E e) { 921 removeFromList(e); 922 insertInList(_head, e); 923 } 924 925 protected static final <E extends Entry> E insertIntoTailCyclicList(final E _head, final E e) { 926 if (_head == null) { 927 return (E) e.shortCircuit(); 928 } 929 e.next = _head; 930 e.prev = _head.prev; 931 _head.prev = e; 932 e.prev.next = e; 933 return _head; 934 } 935 936 /** 937 * Insert X into A B C, yields: A X B C. 938 */ 939 protected static final <E extends Entry> E insertAfterHeadCyclicList(final E _head, final E e) { 940 if (_head == null) { 941 return (E) e.shortCircuit(); 942 } 943 e.prev = _head; 944 e.next = _head.next; 945 _head.next.prev = e; 946 _head.next = e; 947 return _head; 948 } 949 950 /** Insert element at the head of the list */ 951 protected static final <E extends Entry> E insertIntoHeadCyclicList(final E _head, final E e) { 952 if (_head == null) { 953 return (E) e.shortCircuit(); 954 } 955 e.next = _head; 956 e.prev = _head.prev; 957 _head.prev.next = e; 958 _head.prev = e; 959 return e; 960 } 961 962 protected static <E extends Entry> E removeFromCyclicList(final E _head, E e) { 963 if (e.next == e) { 964 e.removedFromList(); 965 return null; 966 } 967 Entry _eNext = e.next; 968 e.prev.next = _eNext; 969 e.next.prev = e.prev; 970 e.removedFromList(); 971 return e == _head ? (E) _eNext : _head; 972 } 973 974 protected static Entry removeFromCyclicList(final Entry e) { 975 Entry _eNext = e.next; 976 e.prev.next = _eNext; 977 e.next.prev = e.prev; 978 e.removedFromList(); 979 return _eNext == e ? null : _eNext; 980 } 981 982 protected static int getCyclicListEntryCount(Entry e) { 983 if (e == null) { return 0; } 984 final Entry _head = e; 985 int cnt = 0; 986 do { 987 cnt++; 988 e = e.next; 989 if (e == null) { 990 return -cnt; 991 } 992 } while (e != _head); 993 return cnt; 994 } 995 996 protected static boolean checkCyclicListIntegrity(Entry e) { 997 if (e == null) { return true; } 998 Entry _head = e; 999 do { 1000 if (e.next == null) { 1001 return false; 1002 } 1003 if (e.next.prev == null) { 1004 return false; 1005 } 1006 if (e.next.prev != e) { 1007 return false; 1008 } 1009 e = e.next; 1010 } while (e != _head); 1011 return true; 1012 } 1013 1014 /** 1015 * Record an entry hit. 1016 */ 1017 protected abstract void recordHit(E e); 1018 1019 /** 1020 * New cache entry, put it in the replacement algorithm structure 1021 */ 1022 protected abstract void insertIntoReplacementList(E e); 1023 1024 /** 1025 * Entry object factory. Return an entry of the proper entry subtype for 1026 * the replacement/eviction algorithm. 1027 */ 1028 protected abstract E newEntry(); 1029 1030 1031 /** 1032 * Find an entry that should be evicted. Called within structure lock. 1033 * After doing some checks the cache will call {@link #removeEntryFromReplacementList(Entry)} 1034 * if this entry will be really evicted. Pinned entries may be skipped. A 1035 * good eviction algorithm returns another candidate on sequential calls, even 1036 * if the candidate was not removed. 1037 * 1038 * <p/>Rationale: Within the structure lock we can check for an eviction candidate 1039 * and may remove it from the list. However, we cannot process additional operations or 1040 * events which affect the entry. For this, we need to acquire the lock on the entry 1041 * first. 1042 */ 1043 protected E findEvictionCandidate() { 1044 return null; 1045 } 1046 1047 1048 /** 1049 * 1050 */ 1051 protected void removeEntryFromReplacementList(E e) { 1052 removeFromList(e); 1053 } 1054 1055 /** 1056 * Check whether we have an entry in the ghost table 1057 * remove it from ghost and insert it into the replacement list. 1058 * null if nothing there. This may also do an optional eviction 1059 * if the size limit of the cache is reached, because some replacement 1060 * algorithms (ARC) do this together. 1061 */ 1062 protected E checkForGhost(K key, int hc) { return null; } 1063 1064 /** 1065 * Implement unsynchronized lookup if it is supported by the eviction. 1066 * If a null is returned the lookup is redone synchronized. 1067 */ 1068 protected E lookupEntryUnsynchronized(K key, int hc) { return null; } 1069 1070 @Override 1071 public T get(K key) { 1072 return returnValue(getEntryInternal(key)); 1073 } 1074 1075 /** 1076 * Wrap entry in a separate object instance. We can return the entry directly, however we lock on 1077 * the entry object. 1078 */ 1079 protected CacheEntry<K, T> returnEntry(final Entry<E, K, T> e) { 1080 if (e == null) { 1081 return null; 1082 } 1083 synchronized (e) { 1084 final K _key = e.getKey(); 1085 final T _value = e.getValue(); 1086 final Throwable _exception = e.getException(); 1087 final long _lastModification = e.getLastModification(); 1088 CacheEntry<K, T> ce = new CacheEntry<K, T>() { 1089 @Override 1090 public K getKey() { 1091 return _key; 1092 } 1093 1094 @Override 1095 public T getValue() { 1096 return _value; 1097 } 1098 1099 @Override 1100 public Throwable getException() { 1101 return _exception; 1102 } 1103 1104 @Override 1105 public long getLastModification() { 1106 return _lastModification; 1107 } 1108 1109 @Override 1110 public String toString() { 1111 long _expiry = e.getValueExpiryTime(); 1112 return "CacheEntry(" + 1113 "key=" + getKey() + ", " + 1114 "value=" + getValue() + ", " + 1115 ((getException() != null) ? "exception=" + e.getException() + ", " : "") + 1116 "updated=" + formatMillis(getLastModification()) + ", " + 1117 "expiry=" + (_expiry != 0 ? (formatMillis(_expiry)) : "-") + ", " + 1118 "flags=" + (_expiry == 0 ? e.nextRefreshTime : "-") + ")"; 1119 } 1120 1121 }; 1122 return ce; 1123 } 1124 } 1125 1126 @Override 1127 public CacheEntry<K, T> getEntry(K key) { 1128 return returnEntry(getEntryInternal(key)); 1129 } 1130 1131 protected E getEntryInternal(K key) { 1132 for (;;) { 1133 E e = lookupOrNewEntrySynchronized(key); 1134 if (e.hasFreshData()) { return e; } 1135 synchronized (e) { 1136 e.waitForFetch(); 1137 if (e.hasFreshData()) { 1138 return e; 1139 } 1140 if (e.isRemovedState()) { 1141 continue; 1142 } 1143 e.startFetch(); 1144 } 1145 boolean _finished = false; 1146 try { 1147 e.finishFetch(fetch(e)); 1148 _finished = true; 1149 } finally { 1150 e.ensureFetchAbort(_finished); 1151 } 1152 evictEventually(); 1153 return e; 1154 } 1155 } 1156 1157 /** 1158 * Insert the storage entry in the heap cache and return it. Used when storage entries 1159 * are queried. We need to check whether the entry is present meanwhile, get the entry lock 1160 * and maybe fetch it from the source. Doubles with {@link #getEntryInternal(Object)} 1161 * except we do not need to retrieve the data from the storage again. 1162 * 1163 * @param _needsFetch if true the entry is fetched from CacheSource when expired. 1164 * @return a cache entry is always returned, however, it may be outdated 1165 */ 1166 protected Entry insertEntryFromStorage(StorageEntry se, boolean _needsFetch) { 1167 int _spinCount = TUNABLE.maximumEntryLockSpins; 1168 for (;;) { 1169 if (_spinCount-- <= 0) { throw new CacheLockSpinsExceededError(); } 1170 E e = lookupOrNewEntrySynchronized((K) se.getKey()); 1171 if (e.hasFreshData()) { return e; } 1172 synchronized (e) { 1173 if (!e.isDataValidState()) { 1174 if (e.isRemovedState()) { 1175 continue; 1176 } 1177 if (e.isFetchInProgress()) { 1178 e.waitForFetch(); 1179 return e; 1180 } 1181 e.startFetch(); 1182 } 1183 } 1184 boolean _finished = false; 1185 try { 1186 e.finishFetch(insertEntryFromStorage(se, e, _needsFetch)); 1187 _finished = true; 1188 } finally { 1189 e.ensureFetchAbort(_finished); 1190 } 1191 evictEventually(); 1192 return e; 1193 } 1194 } 1195 1196 /** 1197 * Insert a cache entry for the given key and run action under the entry 1198 * lock. If the cache entry has fresh data, we do not run the action. 1199 * Called from storage. The entry referenced by the key is expired and 1200 * will be purged. 1201 */ 1202 protected void lockAndRunForPurge(Object key, PurgeableStorage.PurgeAction _action) { 1203 int _spinCount = TUNABLE.maximumEntryLockSpins; 1204 E e; 1205 boolean _virgin; 1206 for (;;) { 1207 if (_spinCount-- <= 0) { throw new CacheLockSpinsExceededError(); } 1208 e = lookupOrNewEntrySynchronized((K) key); 1209 if (e.hasFreshData()) { return; } 1210 synchronized (e) { 1211 e.waitForFetch(); 1212 if (e.isDataValidState()) { 1213 return; 1214 } 1215 if (e.isRemovedState()) { 1216 continue; 1217 } 1218 _virgin = e.isVirgin(); 1219 e.startFetch(); 1220 break; 1221 } 1222 } 1223 boolean _finished = false; 1224 try { 1225 StorageEntry se = _action.checkAndPurge(key); 1226 synchronized (e) { 1227 if (_virgin) { 1228 e.finishFetch(Entry.VIRGIN_STATE); 1229 evictEntryFromHeap(e); 1230 } else { 1231 e.finishFetch(Entry.LOADED_NON_VALID); 1232 evictEntryFromHeap(e); 1233 } 1234 } 1235 _finished = true; 1236 } finally { 1237 e.ensureFetchAbort(_finished); 1238 } 1239 } 1240 1241 protected final void evictEventually() { 1242 int _spinCount = TUNABLE.maximumEvictSpins; 1243 E _previousCandidate = null; 1244 while (evictionNeeded) { 1245 if (_spinCount-- <= 0) { return; } 1246 E e; 1247 synchronized (lock) { 1248 checkClosed(); 1249 if (getLocalSize() <= maxSize) { 1250 evictionNeeded = false; 1251 return; 1252 } 1253 e = findEvictionCandidate(); 1254 } 1255 boolean _shouldStore; 1256 synchronized (e) { 1257 if (e.isRemovedState()) { 1258 continue; 1259 } 1260 if (e.isPinned()) { 1261 if (e != _previousCandidate) { 1262 _previousCandidate = e; 1263 continue; 1264 } else { 1265 return; 1266 } 1267 } 1268 1269 boolean _storeEvenImmediatelyExpired = hasKeepAfterExpired() && (e.isDataValidState() || e.isExpiredState() || e.nextRefreshTime == Entry.FETCH_NEXT_TIME_STATE); 1270 _shouldStore = 1271 (storage != null) && (_storeEvenImmediatelyExpired || e.hasFreshData()); 1272 if (_shouldStore) { 1273 e.startFetch(); 1274 } else { 1275 evictEntryFromHeap(e); 1276 } 1277 } 1278 if (_shouldStore) { 1279 try { 1280 storage.evict(e); 1281 } finally { 1282 synchronized (e) { 1283 e.finishFetch(Entry.FETCH_ABORT); 1284 evictEntryFromHeap(e); 1285 } 1286 } 1287 } 1288 } 1289 } 1290 1291 private void evictEntryFromHeap(E e) { 1292 synchronized (lock) { 1293 if (e.isRemovedFromReplacementList()) { 1294 if (removeEntryFromHash(e)) { 1295 evictedButInHashCnt--; 1296 evictedCnt++; 1297 } 1298 } else { 1299 if (removeEntry(e)) { 1300 evictedCnt++; 1301 } 1302 } 1303 evictionNeeded = getLocalSize() > maxSize; 1304 } 1305 e.notifyAll(); 1306 } 1307 1308 /** 1309 * Remove the entry from the hash and the replacement list. 1310 * There is a race condition to catch: The eviction may run 1311 * in a parallel thread and may have already selected this 1312 * entry. 1313 */ 1314 protected boolean removeEntry(E e) { 1315 if (!e.isRemovedFromReplacementList()) { 1316 removeEntryFromReplacementList(e); 1317 } 1318 return removeEntryFromHash(e); 1319 } 1320 1321 /** 1322 * Return the entry, if it is in the cache, without invoking the 1323 * cache source. 1324 * 1325 * <p>The cache storage is asked whether the entry is present. 1326 * If the entry is not present, this result is cached in the local 1327 * cache. 1328 */ 1329 protected E peekEntryInternal(K key) { 1330 final int hc = modifiedHash(key.hashCode()); 1331 int _spinCount = TUNABLE.maximumEntryLockSpins; 1332 for (;;) { 1333 if (_spinCount-- <= 0) { throw new CacheLockSpinsExceededError(); } 1334 E e = lookupEntryUnsynchronized(key, hc); 1335 if (e == null) { 1336 synchronized (lock) { 1337 e = lookupEntry(key, hc); 1338 if (e == null && storage != null) { 1339 e = newEntry(key, hc); 1340 } 1341 } 1342 } 1343 if (e == null) { 1344 peekMissCnt++; 1345 return null; 1346 } 1347 if (e.hasFreshData()) { return e; } 1348 boolean _hasFreshData = false; 1349 if (storage != null) { 1350 boolean _needsLoad; 1351 synchronized (e) { 1352 e.waitForFetch(); 1353 if (e.isRemovedState()) { 1354 continue; 1355 } 1356 if (e.hasFreshData()) { 1357 return e; 1358 } 1359 _needsLoad = conditionallyStartProcess(e); 1360 } 1361 if (_needsLoad) { 1362 boolean _finished = false; 1363 try { 1364 long t = fetchWithStorage(e, false); 1365 e.finishFetch(t); 1366 _hasFreshData = e.hasFreshData(System.currentTimeMillis(), t); 1367 _finished = true; 1368 } finally { 1369 e.ensureFetchAbort(_finished); 1370 } 1371 } 1372 1373 } 1374 evictEventually(); 1375 if (_hasFreshData) { 1376 return e; 1377 } 1378 peekHitNotFreshCnt++; 1379 return null; 1380 } 1381 } 1382 1383 @Override 1384 public boolean contains(K key) { 1385 E e = peekEntryInternal(key); 1386 if (e != null) { 1387 return true; 1388 } 1389 return false; 1390 } 1391 1392 @Override 1393 public T peek(K key) { 1394 E e = peekEntryInternal(key); 1395 if (e != null) { 1396 return returnValue(e); 1397 } 1398 return null; 1399 } 1400 1401 @Override 1402 public CacheEntry<K, T> peekEntry(K key) { 1403 return returnEntry(peekEntryInternal(key)); 1404 } 1405 1406 @Override 1407 public boolean putIfAbsent(K key, T value) { 1408 if (storage == null) { 1409 int _spinCount = TUNABLE.maximumEntryLockSpins; 1410 E e; 1411 for (;;) { 1412 if (_spinCount-- <= 0) { 1413 throw new CacheLockSpinsExceededError(); 1414 } 1415 e = lookupOrNewEntrySynchronized(key); 1416 synchronized (e) { 1417 if (e.isRemovedState()) { 1418 continue; 1419 } 1420 long t = System.currentTimeMillis(); 1421 if (e.hasFreshData(t)) { 1422 return false; 1423 } 1424 synchronized (lock) { 1425 peekHitNotFreshCnt++; 1426 } 1427 e.nextRefreshTime = insertOnPut(e, value, t, t); 1428 return true; 1429 } 1430 } 1431 } 1432 int _spinCount = TUNABLE.maximumEntryLockSpins; 1433 E e; long t; 1434 for (;;) { 1435 if (_spinCount-- <= 0) { throw new CacheLockSpinsExceededError(); } 1436 e = lookupOrNewEntrySynchronized(key); 1437 synchronized (e) { 1438 e.waitForFetch(); 1439 if (e.isRemovedState()) { 1440 continue; 1441 } 1442 t = System.currentTimeMillis(); 1443 if (e.hasFreshData(t)) { 1444 return false; 1445 } 1446 e.startFetch(); 1447 break; 1448 } 1449 } 1450 if (e.isVirgin()) { 1451 long _result = fetchWithStorage(e, false); 1452 long now = System.currentTimeMillis(); 1453 if (e.hasFreshData(now, _result)) { 1454 e.finishFetch(_result); 1455 return false; 1456 } 1457 e.nextRefreshTime = Entry.LOADED_NON_VALID_AND_PUT; 1458 } 1459 e.finishFetch(insertOnPut(e, value, t, t)); 1460 evictEventually(); 1461 return true; 1462 } 1463 1464 @Override 1465 public void put(K key, T value) { 1466 int _spinCount = TUNABLE.maximumEntryLockSpins; 1467 E e; 1468 for (;;) { 1469 if (_spinCount-- <= 0) { throw new CacheLockSpinsExceededError(); } 1470 e = lookupOrNewEntrySynchronized(key); 1471 synchronized (e) { 1472 if (e.isRemovedState()) { 1473 continue; 1474 } 1475 if (e.isFetchInProgress()) { 1476 e.waitForFetch(); 1477 continue; 1478 } else { 1479 e.startFetch(); 1480 break; 1481 } 1482 } 1483 } 1484 boolean _finished = false; 1485 try { 1486 long t = System.currentTimeMillis(); 1487 e.finishFetch(insertOnPut(e, value, t, t)); 1488 _finished = true; 1489 } finally { 1490 e.ensureFetchAbort(_finished); 1491 } 1492 evictEventually(); 1493 } 1494 1495 /** 1496 * Remove the object mapped to a key from the cache. 1497 * 1498 * <p>Operation with storage: If there is no entry within the cache there may 1499 * be one in the storage, so we need to send the remove to the storage. However, 1500 * if a remove() and a get() is going on in parallel it may happen that the entry 1501 * gets removed from the storage and added again by the tail part of get(). To 1502 * keep the cache and the storage consistent it must be ensured that this thread 1503 * is the only one working on the entry. 1504 */ 1505 public boolean removeWithFlag(K key) { 1506 if (storage == null) { 1507 E e = lookupEntrySynchronized(key); 1508 if (e != null) { 1509 synchronized (e) { 1510 e.waitForFetch(); 1511 if (!e.isRemovedState()) { 1512 synchronized (lock) { 1513 boolean f = e.hasFreshData(); 1514 if (removeEntry(e)) { 1515 removedCnt++; 1516 return f; 1517 } 1518 return false; 1519 } 1520 } 1521 } 1522 } 1523 return false; 1524 } 1525 int _spinCount = TUNABLE.maximumEntryLockSpins; 1526 E e; 1527 boolean _hasFreshData; 1528 for (;;) { 1529 if (_spinCount-- <= 0) { 1530 throw new CacheLockSpinsExceededError(); 1531 } 1532 e = lookupOrNewEntrySynchronized(key); 1533 synchronized (e) { 1534 e.waitForFetch(); 1535 if (e.isRemovedState()) { 1536 continue; 1537 } 1538 _hasFreshData = e.hasFreshData(); 1539 e.startFetch(); 1540 break; 1541 } 1542 } 1543 boolean _finished = false; 1544 try { 1545 if (!_hasFreshData && e.isVirgin()) { 1546 long t = fetchWithStorage(e, false); 1547 _hasFreshData = e.hasFreshData(System.currentTimeMillis(), t); 1548 } 1549 if (_hasFreshData) { 1550 storage.remove(key); 1551 } 1552 synchronized (e) { 1553 e.finishFetch(Entry.LOADED_NON_VALID); 1554 if (_hasFreshData) { 1555 synchronized (lock) { 1556 if (removeEntry(e)) { 1557 removedCnt++; 1558 } 1559 } 1560 } 1561 } 1562 _finished = true; 1563 } finally { 1564 e.ensureFetchAbort(_finished); 1565 } 1566 return _hasFreshData; 1567 } 1568 1569 @Override 1570 public void remove(K key) { 1571 removeWithFlag(key); 1572 } 1573 1574 @Override 1575 public void prefetch(final K key) { 1576 if (refreshPool == null || 1577 lookupEntrySynchronized(key) != null) { 1578 return; 1579 } 1580 Runnable r = new Runnable() { 1581 @Override 1582 public void run() { 1583 get(key); 1584 } 1585 }; 1586 refreshPool.submit(r); 1587 } 1588 1589 public void prefetch(final List<K> keys, final int _startIndex, final int _endIndexExclusive) { 1590 if (keys.size() == 0 || _startIndex == _endIndexExclusive) { 1591 return; 1592 } 1593 if (keys.size() <= _endIndexExclusive) { 1594 throw new IndexOutOfBoundsException("end > size"); 1595 } 1596 if (_startIndex > _endIndexExclusive) { 1597 throw new IndexOutOfBoundsException("start > end"); 1598 } 1599 if (_startIndex > 0) { 1600 throw new IndexOutOfBoundsException("end < 0"); 1601 } 1602 Set<K> ks = new AbstractSet<K>() { 1603 @Override 1604 public Iterator<K> iterator() { 1605 return new Iterator<K>() { 1606 int idx = _startIndex; 1607 @Override 1608 public boolean hasNext() { 1609 return idx < _endIndexExclusive; 1610 } 1611 1612 @Override 1613 public K next() { 1614 return keys.get(idx++); 1615 } 1616 1617 @Override 1618 public void remove() { 1619 throw new UnsupportedOperationException(); 1620 } 1621 }; 1622 } 1623 1624 @Override 1625 public int size() { 1626 return _endIndexExclusive - _startIndex; 1627 } 1628 }; 1629 prefetch(ks); 1630 } 1631 1632 @Override 1633 public void prefetch(final Set<K> keys) { 1634 if (refreshPool == null) { 1635 getAll(keys); 1636 return; 1637 } 1638 boolean _complete = true; 1639 for (K k : keys) { 1640 if (lookupEntryUnsynchronized(k, modifiedHash(k.hashCode())) == null) { 1641 _complete = false; break; 1642 } 1643 } 1644 if (_complete) { 1645 return; 1646 } 1647 Runnable r = new Runnable() { 1648 @Override 1649 public void run() { 1650 getAll(keys); 1651 } 1652 }; 1653 refreshPool.submit(r); 1654 } 1655 1656 /** 1657 * Lookup or create a new entry. The new entry is created, because we need 1658 * it for locking within the data fetch. 1659 */ 1660 protected E lookupOrNewEntrySynchronized(K key) { 1661 int hc = modifiedHash(key.hashCode()); 1662 E e = lookupEntryUnsynchronized(key, hc); 1663 if (e == null) { 1664 synchronized (lock) { 1665 checkClosed(); 1666 e = lookupEntry(key, hc); 1667 if (e == null) { 1668 e = newEntry(key, hc); 1669 } 1670 } 1671 } 1672 return e; 1673 } 1674 1675 protected T returnValue(Entry<E, K,T> e) { 1676 T v = e.value; 1677 if (v instanceof ExceptionWrapper) { 1678 ExceptionWrapper w = (ExceptionWrapper) v; 1679 if (w.additionalExceptionMessage == null) { 1680 synchronized (e) { 1681 long t = e.getValueExpiryTime(); 1682 w.additionalExceptionMessage = "(expiry=" + (t > 0 ? formatMillis(t) : "none") + ") " + w.getException(); 1683 } 1684 } 1685 throw new PropagatedCacheException(w.additionalExceptionMessage, w.getException()); 1686 } 1687 return v; 1688 } 1689 1690 protected E lookupEntrySynchronized(K key) { 1691 int hc = modifiedHash(key.hashCode()); 1692 E e = lookupEntryUnsynchronized(key, hc); 1693 if (e == null) { 1694 synchronized (lock) { 1695 e = lookupEntry(key, hc); 1696 } 1697 } 1698 return e; 1699 } 1700 1701 protected E lookupEntry(K key, int hc) { 1702 E e = Hash.lookup(mainHash, key, hc); 1703 if (e != null) { 1704 recordHit(e); 1705 return e; 1706 } 1707 e = refreshHashCtrl.remove(refreshHash, key, hc); 1708 if (e != null) { 1709 refreshHitCnt++; 1710 mainHash = mainHashCtrl.insert(mainHash, e); 1711 recordHit(e); 1712 return e; 1713 } 1714 return null; 1715 } 1716 1717 1718 /** 1719 * Insert new entry in all structures (hash and replacement list). May evict an 1720 * entry if the maximum capacity is reached. 1721 */ 1722 protected E newEntry(K key, int hc) { 1723 if (getLocalSize() >= maxSize) { 1724 evictionNeeded = true; 1725 } 1726 E e = checkForGhost(key, hc); 1727 if (e == null) { 1728 e = newEntry(); 1729 e.key = key; 1730 e.hashCode = hc; 1731 insertIntoReplacementList(e); 1732 } 1733 mainHash = mainHashCtrl.insert(mainHash, e); 1734 newEntryCnt++; 1735 return e; 1736 } 1737 1738 /** 1739 * Called when expiry of an entry happens. Remove it from the 1740 * main cache, refresh cache and from the (lru) list. Also cancel the timer. 1741 * Called under big lock. 1742 */ 1743 1744 /** 1745 * The entry is already removed from the replacement list. stop/reset timer, if needed. 1746 * Called under big lock. 1747 */ 1748 private boolean removeEntryFromHash(E e) { 1749 boolean f = mainHashCtrl.remove(mainHash, e) || refreshHashCtrl.remove(refreshHash, e); 1750 checkForHashCodeChange(e); 1751 cancelExpiryTimer(e); 1752 if (e.isVirgin()) { 1753 virginEvictCnt++; 1754 } 1755 e.setRemovedState(); 1756 return f; 1757 } 1758 1759 private void cancelExpiryTimer(Entry e) { 1760 if (e.task != null) { 1761 e.task.cancel(); 1762 timerCancelCount++; 1763 if (timerCancelCount >= 10000) { 1764 timer.purge(); 1765 timerCancelCount = 0; 1766 } 1767 e.task = null; 1768 } 1769 } 1770 1771 /** 1772 * Check whether the key was modified during the stay of the entry in the cache. 1773 * We only need to check this when the entry is removed, since we expect that if 1774 * the key has changed, the stored hash code in the cache will not match any more and 1775 * the item is evicted very fast. 1776 */ 1777 private void checkForHashCodeChange(Entry e) { 1778 if (modifiedHash(e.key.hashCode()) != e.hashCode && !e.isStale()) { 1779 if (keyMutationCount == 0) { 1780 getLog().warn("Key mismatch! Key hashcode changed! keyClass=" + e.key.getClass().getName()); 1781 String s; 1782 try { 1783 s = e.key.toString(); 1784 if (s != null) { 1785 getLog().warn("Key mismatch! key.toString(): " + s); 1786 } 1787 } catch (Throwable t) { 1788 getLog().warn("Key mismatch! key.toString() threw exception", t); 1789 } 1790 } 1791 keyMutationCount++; 1792 } 1793 } 1794 1795 /** 1796 * Time when the element should be fetched again from the underlying storage. 1797 * If 0 then the object should not be cached at all. -1 means no expiry. 1798 * 1799 * @param _newObject might be a fetched value or an exception wrapped into the {@link ExceptionWrapper} 1800 */ 1801 static <K, T> long calcNextRefreshTime( 1802 K _key, T _newObject, long now, Entry _entry, 1803 EntryExpiryCalculator<K, T> ec, long _maxLinger, 1804 ExceptionExpiryCalculator<K> _exceptionEc, long _exceptionMaxLinger) { 1805 if (!(_newObject instanceof ExceptionWrapper)) { 1806 if (_maxLinger == 0) { 1807 return 0; 1808 } 1809 if (ec != null) { 1810 long t = ec.calculateExpiryTime(_key, _newObject, now, _entry); 1811 return limitExpiryToMaxLinger(now, _maxLinger, t); 1812 } 1813 if (_maxLinger > 0) { 1814 return _maxLinger + now; 1815 } 1816 return -1; 1817 } 1818 if (_exceptionMaxLinger == 0) { 1819 return 0; 1820 } 1821 if (_exceptionEc != null) { 1822 long t = _exceptionEc.calculateExpiryTime(_key, ((ExceptionWrapper) _newObject).getException(), now); 1823 return limitExpiryToMaxLinger(now, _exceptionMaxLinger, t); 1824 } 1825 if (_exceptionMaxLinger > 0) { 1826 return _exceptionMaxLinger + now; 1827 } else { 1828 return _exceptionMaxLinger; 1829 } 1830 } 1831 1832 static long limitExpiryToMaxLinger(long now, long _maxLinger, long t) { 1833 if (_maxLinger > 0) { 1834 long _tMaximum = _maxLinger + now; 1835 if (t > _tMaximum) { 1836 return _tMaximum; 1837 } 1838 if (t < -1 && -t > _tMaximum) { 1839 return -_tMaximum; 1840 } 1841 } 1842 return t; 1843 } 1844 1845 protected long calcNextRefreshTime(K _key, T _newObject, long now, Entry _entry) { 1846 return calcNextRefreshTime( 1847 _key, _newObject, now, _entry, 1848 entryExpiryCalculator, maxLinger, 1849 exceptionExpiryCalculator, exceptionMaxLinger); 1850 } 1851 1852 protected long fetch(final E e) { 1853 if (storage != null) { 1854 return fetchWithStorage(e, true); 1855 } else { 1856 return fetchFromSource(e); 1857 } 1858 } 1859 1860 protected boolean conditionallyStartProcess(E e) { 1861 if (!e.isVirgin()) { 1862 return false; 1863 } 1864 e.startFetch(); 1865 return true; 1866 } 1867 1868 /** 1869 * 1870 * @param e 1871 * @param _needsFetch true if value needs to be fetched from the cache source. 1872 * This is false, when the we only need to peek for an value already mapped. 1873 */ 1874 protected long fetchWithStorage(E e, boolean _needsFetch) { 1875 if (!e.isVirgin()) { 1876 if (_needsFetch) { 1877 return fetchFromSource(e); 1878 } 1879 return Entry.LOADED_NON_VALID; 1880 } 1881 StorageEntry se = storage.get(e.key); 1882 if (se == null) { 1883 if (_needsFetch) { 1884 synchronized (lock) { 1885 loadMissCnt++; 1886 } 1887 return fetchFromSource(e); 1888 } 1889 synchronized (lock) { 1890 touchedTime = System.currentTimeMillis(); 1891 loadNonFreshCnt++; 1892 } 1893 return Entry.LOADED_NON_VALID; 1894 } 1895 return insertEntryFromStorage(se, e, _needsFetch); 1896 } 1897 1898 protected long insertEntryFromStorage(StorageEntry se, E e, boolean _needsFetch) { 1899 e.setLastModificationFromStorage(se.getCreatedOrUpdated()); 1900 long now = System.currentTimeMillis(); 1901 T v = (T) se.getValueOrException(); 1902 long _nextRefreshTime = maxLinger == 0 ? 0 : Long.MAX_VALUE; 1903 long _expiryTimeFromStorage = se.getValueExpiryTime(); 1904 boolean _expired = _expiryTimeFromStorage != 0 && _expiryTimeFromStorage <= now; 1905 if (!_expired && timer != null) { 1906 _nextRefreshTime = calcNextRefreshTime((K) se.getKey(), v, se.getCreatedOrUpdated(), null); 1907 _expired = _nextRefreshTime > Entry.EXPIRY_TIME_MIN && _nextRefreshTime <= now; 1908 } 1909 boolean _fetchAlways = timer == null && maxLinger == 0; 1910 if (_expired || _fetchAlways) { 1911 if (_needsFetch) { 1912 e.value = se.getValueOrException(); 1913 e.setLoadedNonValidAndFetch(); 1914 return fetchFromSource(e); 1915 } else { 1916 synchronized (lock) { 1917 touchedTime = now; 1918 loadNonFreshCnt++; 1919 } 1920 return Entry.LOADED_NON_VALID; 1921 } 1922 } 1923 return insert(e, (T) se.getValueOrException(), 0, now, INSERT_STAT_UPDATE, _nextRefreshTime); 1924 } 1925 1926 protected long fetchFromSource(E e) { 1927 T v; 1928 long t0 = System.currentTimeMillis(); 1929 try { 1930 if (source == null) { 1931 throw new CacheUsageExcpetion("source not set"); 1932 } 1933 if (e.isVirgin() || e.hasException()) { 1934 v = source.get((K) e.key, t0, null, e.getLastModification()); 1935 } else { 1936 v = source.get((K) e.key, t0, (T) e.getValue(), e.getLastModification()); 1937 } 1938 e.setLastModification(t0); 1939 } catch (Throwable _ouch) { 1940 v = (T) new ExceptionWrapper(_ouch); 1941 } 1942 long t = System.currentTimeMillis(); 1943 return insertFetched(e, v, t0, t); 1944 } 1945 1946 protected final long insertFetched(E e, T v, long t0, long t) { 1947 return insert(e, v, t0, t, INSERT_STAT_UPDATE); 1948 } 1949 1950 protected final long insertOnPut(E e, T v, long t0, long t) { 1951 e.setLastModification(t0); 1952 return insert(e, v, t0, t, INSERT_STAT_PUT); 1953 } 1954 1955 /** 1956 * Calculate the next refresh time if a timer / expiry is needed and call insert. 1957 */ 1958 protected final long insert(E e, T v, long t0, long t, byte _updateStatistics) { 1959 long _nextRefreshTime = maxLinger == 0 ? 0 : Long.MAX_VALUE; 1960 if (timer != null) { 1961 if (e.isVirgin() || e.hasException()) { 1962 _nextRefreshTime = calcNextRefreshTime((K) e.getKey(), v, t0, null); 1963 } else { 1964 _nextRefreshTime = calcNextRefreshTime((K) e.getKey(), v, t0, e); 1965 } 1966 } 1967 return insert(e,v,t0,t, _updateStatistics, _nextRefreshTime); 1968 } 1969 1970 final static byte INSERT_STAT_NO_UPDATE = 0; 1971 final static byte INSERT_STAT_UPDATE = 1; 1972 final static byte INSERT_STAT_PUT = 2; 1973 1974 /** 1975 * @param _nextRefreshTime -1/MAXVAL: eternal, 0: expires immediately 1976 */ 1977 protected final long insert(E e, T _value, long t0, long t, byte _updateStatistics, long _nextRefreshTime) { 1978 if (_nextRefreshTime == -1) { 1979 _nextRefreshTime = Long.MAX_VALUE; 1980 } 1981 boolean _suppressException = 1982 _value instanceof ExceptionWrapper && hasSuppressExceptions() && e.getValue() != Entry.INITIAL_VALUE && !e.hasException(); 1983 if (!_suppressException) { 1984 e.value = _value; 1985 } 1986 1987 CacheStorageException _storageException = null; 1988 if (storage != null && e.isDirty() && (_nextRefreshTime != 0 || hasKeepAfterExpired())) { 1989 try { 1990 storage.put(e, _nextRefreshTime); 1991 } catch (CacheStorageException ex) { 1992 _storageException = ex; 1993 } catch (Throwable ex) { 1994 _storageException = new CacheStorageException(ex); 1995 } 1996 } 1997 1998 long _nextRefreshTimeWithState; 1999 synchronized (lock) { 2000 checkClosed(); 2001 touchedTime = t; 2002 if (_updateStatistics == INSERT_STAT_UPDATE) { 2003 if (t0 == 0) { 2004 loadHitCnt++; 2005 } else { 2006 if (_suppressException) { 2007 suppressedExceptionCnt++; 2008 fetchExceptionCnt++; 2009 } else { 2010 if (_value instanceof ExceptionWrapper) { 2011 Log log = getLog(); 2012 if (log.isDebugEnabled()) { 2013 log.debug( 2014 "caught exception, expires at: " + formatMillis(_nextRefreshTime), 2015 ((ExceptionWrapper) _value).getException()); 2016 } 2017 fetchExceptionCnt++; 2018 } 2019 } 2020 fetchCnt++; 2021 fetchMillis += t - t0; 2022 if (e.isGettingRefresh()) { 2023 refreshCnt++; 2024 } 2025 if (e.isLoadedNonValidAndFetch()) { 2026 loadNonFreshAndFetchedCnt++; 2027 } else if (!e.isVirgin()) { 2028 fetchButHitCnt++; 2029 } 2030 } 2031 } else if (_updateStatistics == INSERT_STAT_PUT) { 2032 putCnt++; 2033 if (e.isVirgin()) { 2034 putNewEntryCnt++; 2035 } 2036 if (e.nextRefreshTime == Entry.LOADED_NON_VALID_AND_PUT) { 2037 peekHitNotFreshCnt++; 2038 } 2039 } 2040 if (_storageException != null) { 2041 throw _storageException; 2042 } 2043 _nextRefreshTimeWithState = stopStartTimer(_nextRefreshTime, e, t); 2044 } // synchronized (lock) 2045 2046 return _nextRefreshTimeWithState; 2047 } 2048 2049 protected long stopStartTimer(long _nextRefreshTime, E e, long now) { 2050 if (e.task != null) { 2051 e.task.cancel(); 2052 } 2053 if (hasSharpTimeout() && _nextRefreshTime > Entry.EXPIRY_TIME_MIN) { 2054 _nextRefreshTime = -_nextRefreshTime; 2055 } 2056 if (timer != null && 2057 (_nextRefreshTime > Entry.EXPIRY_TIME_MIN || _nextRefreshTime < -1)) { 2058 if (_nextRefreshTime < -1) { 2059 long _timerTime = 2060 -_nextRefreshTime - TUNABLE.sharpExpirySafetyGapMillis; 2061 if (_timerTime >= now) { 2062 MyTimerTask tt = new MyTimerTask(); 2063 tt.entry = e; 2064 timer.schedule(tt, new Date(_timerTime)); 2065 e.task = tt; 2066 _nextRefreshTime = -_nextRefreshTime; 2067 } 2068 } else { 2069 MyTimerTask tt = new MyTimerTask(); 2070 tt.entry = e; 2071 timer.schedule(tt, new Date(_nextRefreshTime)); 2072 e.task = tt; 2073 } 2074 } else { 2075 _nextRefreshTime = _nextRefreshTime == Long.MAX_VALUE ? Entry.FETCHED_STATE : Entry.FETCH_NEXT_TIME_STATE; 2076 } 2077 return _nextRefreshTime; 2078 } 2079 2080 /** 2081 * When the time has come remove the entry from the cache. 2082 */ 2083 protected void timerEvent(final E e, long _executionTime) { 2084 if (e.isRemovedFromReplacementList()) { 2085 return; 2086 } 2087 if (refreshPool != null) { 2088 synchronized (lock) { 2089 if (isClosed()) { return; } 2090 if (e.task == null) { 2091 return; 2092 } 2093 if (e.isRemovedFromReplacementList()) { 2094 return; 2095 } 2096 if (mainHashCtrl.remove(mainHash, e)) { 2097 refreshHash = refreshHashCtrl.insert(refreshHash, e); 2098 if (e.hashCode != modifiedHash(e.key.hashCode())) { 2099 synchronized (lock) { 2100 synchronized (e) { 2101 if (!e.isRemovedState() && removeEntryFromHash(e)) { 2102 expiredRemoveCnt++; 2103 } 2104 } 2105 } 2106 return; 2107 } 2108 Runnable r = new Runnable() { 2109 @Override 2110 public void run() { 2111 synchronized (e) { 2112 if (e.isRemovedFromReplacementList() || e.isRemovedState() || e.isFetchInProgress()) { 2113 return; 2114 } 2115 e.setGettingRefresh(); 2116 } 2117 try { 2118 long t = fetch(e); 2119 e.finishFetch(t); 2120 } catch (CacheClosedException ignore) { 2121 } catch (Throwable ex) { 2122 e.ensureFetchAbort(false); 2123 synchronized (lock) { 2124 internalExceptionCnt++; 2125 } 2126 getLog().warn("Refresh exception", ex); 2127 try { 2128 expireEntry(e); 2129 } catch (CacheClosedException ignore) { } 2130 } 2131 } 2132 }; 2133 boolean _submitOkay = refreshPool.submit(r); 2134 if (_submitOkay) { 2135 return; 2136 } 2137 refreshSubmitFailedCnt++; 2138 } 2139 2140 } 2141 2142 } else { 2143 if (_executionTime < e.nextRefreshTime) { 2144 synchronized (e) { 2145 if (!e.isRemovedState()) { 2146 long t = System.currentTimeMillis(); 2147 if (t < e.nextRefreshTime) { 2148 e.nextRefreshTime = -e.nextRefreshTime; 2149 return; 2150 } else { 2151 try { 2152 expireEntry(e); 2153 } catch (CacheClosedException ignore) { } 2154 } 2155 } 2156 } 2157 return; 2158 } 2159 } 2160 synchronized (e) { 2161 long t = System.currentTimeMillis(); 2162 if (t >= e.nextRefreshTime) { 2163 try { 2164 expireEntry(e); 2165 } catch (CacheClosedException ignore) { } 2166 } 2167 } 2168 } 2169 2170 protected void expireEntry(E e) { 2171 synchronized (e) { 2172 if (e.isRemovedState() || e.isExpiredState()) { 2173 return; 2174 } 2175 if (e.isFetchInProgress()) { 2176 e.nextRefreshTime = Entry.FETCH_IN_PROGRESS_NON_VALID; 2177 return; 2178 } 2179 e.setExpiredState(); 2180 synchronized (lock) { 2181 checkClosed(); 2182 if (hasKeepAfterExpired()) { 2183 expiredKeptCnt++; 2184 } else { 2185 if (removeEntry(e)) { 2186 expiredRemoveCnt++; 2187 } 2188 } 2189 } 2190 } 2191 } 2192 2193 /** 2194 * Returns all cache entries within the heap cache. Entries that 2195 * are expired or contain no valid data are not filtered out. 2196 */ 2197 final protected ClosableConcurrentHashEntryIterator<Entry> iterateAllHeapEntries() { 2198 return 2199 new ClosableConcurrentHashEntryIterator( 2200 mainHashCtrl, mainHash, refreshHashCtrl, refreshHash); 2201 } 2202 2203 @Override 2204 public void removeAllAtOnce(Set<K> _keys) { 2205 } 2206 2207 2208 /** JSR107 convenience getAll from array */ 2209 public Map<K, T> getAll(K[] _keys) { 2210 return getAll(new HashSet<K>(Arrays.asList(_keys))); 2211 } 2212 2213 /** 2214 * JSR107 bulk interface 2215 */ 2216 public Map<K, T> getAll(final Set<? extends K> _keys) { 2217 K[] ka = (K[]) new Object[_keys.size()]; 2218 int i = 0; 2219 for (K k : _keys) { 2220 ka[i++] = k; 2221 } 2222 T[] va = (T[]) new Object[ka.length]; 2223 getBulk(ka, va, new BitSet(), 0, ka.length); 2224 return new AbstractMap<K, T>() { 2225 @Override 2226 public T get(Object key) { 2227 if (containsKey(key)) { 2228 return BaseCache.this.get((K) key); 2229 } 2230 return null; 2231 } 2232 2233 @Override 2234 public boolean containsKey(Object key) { 2235 return _keys.contains(key); 2236 } 2237 2238 @Override 2239 public Set<Entry<K, T>> entrySet() { 2240 return new AbstractSet<Entry<K, T>>() { 2241 @Override 2242 public Iterator<Entry<K, T>> iterator() { 2243 return new Iterator<Entry<K, T>>() { 2244 Iterator<? extends K> it = _keys.iterator(); 2245 @Override 2246 public boolean hasNext() { 2247 return it.hasNext(); 2248 } 2249 2250 @Override 2251 public Entry<K, T> next() { 2252 final K k = it.next(); 2253 final T t = BaseCache.this.get(k); 2254 return new Entry<K, T>() { 2255 @Override 2256 public K getKey() { 2257 return k; 2258 } 2259 2260 @Override 2261 public T getValue() { 2262 return t; 2263 } 2264 2265 @Override 2266 public T setValue(T value) { 2267 throw new UnsupportedOperationException(); 2268 } 2269 }; 2270 } 2271 2272 @Override 2273 public void remove() { 2274 throw new UnsupportedOperationException(); 2275 } 2276 }; 2277 } 2278 2279 @Override 2280 public int size() { 2281 return _keys.size(); 2282 } 2283 }; 2284 } 2285 }; 2286 } 2287 2288 /** 2289 * Retrieve 2290 */ 2291 @SuppressWarnings("unused") 2292 public void getBulk(K[] _keys, T[] _result, BitSet _fetched, int s, int e) { 2293 sequentialGetFallBack(_keys, _result, _fetched, s, e); 2294 } 2295 2296 2297 final void sequentialFetch(E[] ea, K[] _keys, T[] _result, BitSet _fetched, int s, int end) { 2298 for (int i = s; i < end; i++) { 2299 E e = ea[i]; 2300 if (e == null) { continue; } 2301 if (!e.isDataValidState()) { 2302 synchronized (e) { 2303 if (!e.isDataValidState()) { 2304 fetch(e); 2305 } 2306 _result[i] = (T) e.getValue(); 2307 } 2308 } 2309 } 2310 } 2311 2312 final void sequentialGetFallBack(K[] _keys, T[] _result, BitSet _fetched, int s, int e) { 2313 for (int i = s; i < e; i++) { 2314 if (!_fetched.get(i)) { 2315 try { 2316 _result[i] = get(_keys[i]); 2317 } catch (Exception ignore) { 2318 } 2319 } 2320 } 2321 } 2322 2323 public abstract long getHitCnt(); 2324 2325 protected final int calculateHashEntryCount() { 2326 return Hash.calcEntryCount(mainHash) + Hash.calcEntryCount(refreshHash); 2327 } 2328 2329 protected final int getLocalSize() { 2330 return mainHashCtrl.size + refreshHashCtrl.size; 2331 } 2332 2333 public final int getTotalEntryCount() { 2334 synchronized (lock) { 2335 if (storage != null) { 2336 return storage.getTotalEntryCount(); 2337 } 2338 return getLocalSize(); 2339 } 2340 } 2341 2342 public long getExpiredCnt() { 2343 return expiredRemoveCnt + expiredKeptCnt; 2344 } 2345 2346 /** 2347 * For peek no fetch is counted if there is a storage miss, hence the extra counter. 2348 */ 2349 public long getFetchesBecauseOfNewEntries() { 2350 return fetchCnt - fetchButHitCnt; 2351 } 2352 2353 protected int getFetchesInFlight() { 2354 long _fetchesBecauseOfNoEntries = getFetchesBecauseOfNewEntries(); 2355 return (int) (newEntryCnt - putNewEntryCnt - virginEvictCnt 2356 - loadNonFreshCnt 2357 - loadHitCnt 2358 - _fetchesBecauseOfNoEntries); 2359 } 2360 2361 protected IntegrityState getIntegrityState() { 2362 synchronized (lock) { 2363 return new IntegrityState() 2364 .checkEquals( 2365 "newEntryCnt - virginEvictCnt == " + 2366 "getFetchesBecauseOfNewEntries() + getFetchesInFlight() + putNewEntryCnt + loadNonFreshCnt + loadHitCnt", 2367 newEntryCnt - virginEvictCnt, 2368 getFetchesBecauseOfNewEntries() + getFetchesInFlight() + putNewEntryCnt + loadNonFreshCnt + loadHitCnt) 2369 .checkLessOrEquals("getFetchesInFlight() <= 100", getFetchesInFlight(), 100) 2370 .checkEquals("newEntryCnt == getSize() + evictedCnt + expiredRemoveCnt + removeCnt", newEntryCnt, getLocalSize() + evictedCnt + expiredRemoveCnt + removedCnt) 2371 .checkEquals("newEntryCnt == getSize() + evictedCnt + getExpiredCnt() - expiredKeptCnt + removeCnt", newEntryCnt, getLocalSize() + evictedCnt + getExpiredCnt() - expiredKeptCnt + removedCnt) 2372 .checkEquals("mainHashCtrl.size == Hash.calcEntryCount(mainHash)", mainHashCtrl.size, Hash.calcEntryCount(mainHash)) 2373 .checkEquals("refreshHashCtrl.size == Hash.calcEntryCount(refreshHash)", refreshHashCtrl.size, Hash.calcEntryCount(refreshHash)) 2374 .check("!!evictionNeeded | (getSize() <= maxSize)", !!evictionNeeded | (getLocalSize() <= maxSize)) 2375 .check("storage => storage.getAlert() < 2", storage == null || storage.getAlert() < 2); 2376 } 2377 } 2378 2379 /** Check internal data structures and throw and exception if something is wrong, used for unit testing */ 2380 public final void checkIntegrity() { 2381 synchronized (lock) { 2382 IntegrityState is = getIntegrityState(); 2383 if (is.getStateFlags() > 0) { 2384 throw new CacheIntegrityError(is.getStateDescriptor(), is.getFailingChecks(), toString()); 2385 } 2386 } 2387 } 2388 2389 2390 public final Info getInfo() { 2391 synchronized (lock) { 2392 long t = System.currentTimeMillis(); 2393 if (info != null && 2394 (info.creationTime + info.creationDeltaMs * 17 + 333 > t)) { 2395 return info; 2396 } 2397 info = getLatestInfo(t); 2398 } 2399 return info; 2400 } 2401 2402 public final Info getLatestInfo() { 2403 return getLatestInfo(System.currentTimeMillis()); 2404 } 2405 2406 private Info getLatestInfo(long t) { 2407 synchronized (lock) { 2408 info = new Info(); 2409 info.creationTime = t; 2410 info.creationDeltaMs = (int) (System.currentTimeMillis() - t); 2411 return info; 2412 } 2413 } 2414 2415 protected String getExtraStatistics() { return ""; } 2416 2417 static String timestampToString(long t) { 2418 if (t == 0) { 2419 return "-"; 2420 } 2421 return formatMillis(t); 2422 } 2423 2424 /** 2425 * Return status information. The status collection is time consuming, so this 2426 * is an expensive operation. 2427 */ 2428 @Override 2429 public String toString() { 2430 synchronized (lock) { 2431 Info fo = getLatestInfo(); 2432 return "Cache{" + name + "}" 2433 + "(" 2434 + "size=" + fo.getSize() + ", " 2435 + "maxSize=" + fo.getMaxSize() + ", " 2436 + "usageCnt=" + fo.getUsageCnt() + ", " 2437 + "missCnt=" + fo.getMissCnt() + ", " 2438 + "fetchCnt=" + fo.getFetchCnt() + ", " 2439 + "fetchButHitCnt=" + fetchButHitCnt + ", " 2440 + "heapHitCnt=" + fo.hitCnt + ", " 2441 + "virginEvictCnt=" + virginEvictCnt + ", " 2442 + "fetchesInFlightCnt=" + fo.getFetchesInFlightCnt() + ", " 2443 + "newEntryCnt=" + fo.getNewEntryCnt() + ", " 2444 + "bulkGetCnt=" + fo.getBulkGetCnt() + ", " 2445 + "refreshCnt=" + fo.getRefreshCnt() + ", " 2446 + "refreshSubmitFailedCnt=" + fo.getRefreshSubmitFailedCnt() + ", " 2447 + "refreshHitCnt=" + fo.getRefreshHitCnt() + ", " 2448 + "putCnt=" + fo.getPutCnt() + ", " 2449 + "putNewEntryCnt=" + fo.getPutNewEntryCnt() + ", " 2450 + "expiredCnt=" + fo.getExpiredCnt() + ", " 2451 + "evictedCnt=" + fo.getEvictedCnt() + ", " 2452 + "removedCnt=" + fo.getRemovedCnt() + ", " 2453 + "storageLoadCnt=" + fo.getStorageLoadCnt() + ", " 2454 + "storageMissCnt=" + fo.getStorageMissCnt() + ", " 2455 + "storageHitCnt=" + fo.getStorageHitCnt() + ", " 2456 + "hitRate=" + fo.getDataHitString() + ", " 2457 + "collisionCnt=" + fo.getCollisionCnt() + ", " 2458 + "collisionSlotCnt=" + fo.getCollisionSlotCnt() + ", " 2459 + "longestCollisionSize=" + fo.getLongestCollisionSize() + ", " 2460 + "hashQuality=" + fo.getHashQualityInteger() + ", " 2461 + "msecs/fetch=" + (fo.getMillisPerFetch() >= 0 ? fo.getMillisPerFetch() : "-") + ", " 2462 + "created=" + timestampToString(fo.getStarted()) + ", " 2463 + "cleared=" + timestampToString(fo.getCleared()) + ", " 2464 + "touched=" + timestampToString(fo.getTouched()) + ", " 2465 + "fetchExceptionCnt=" + fo.getFetchExceptionCnt() + ", " 2466 + "suppressedExceptionCnt=" + fo.getSuppressedExceptionCnt() + ", " 2467 + "internalExceptionCnt=" + fo.getInternalExceptionCnt() + ", " 2468 + "keyMutationCnt=" + fo.getKeyMutationCnt() + ", " 2469 + "infoCreated=" + timestampToString(fo.getInfoCreated()) + ", " 2470 + "infoCreationDeltaMs=" + fo.getInfoCreationDeltaMs() + ", " 2471 + "impl=\"" + getClass().getSimpleName() + "\"" 2472 + getExtraStatistics() + ", " 2473 + "integrityState=" + fo.getIntegrityDescriptor() + ")"; 2474 } 2475 } 2476 2477 /** 2478 * Stable interface to request information from the cache, the object 2479 * safes values that need a longer calculation time, other values are 2480 * requested directly. 2481 */ 2482 public class Info { 2483 2484 int size = BaseCache.this.getLocalSize(); 2485 long creationTime; 2486 int creationDeltaMs; 2487 long missCnt = fetchCnt - refreshCnt + peekHitNotFreshCnt + peekMissCnt; 2488 long storageMissCnt = loadMissCnt + loadNonFreshCnt + loadNonFreshAndFetchedCnt; 2489 long storageLoadCnt = storageMissCnt + loadHitCnt; 2490 long newEntryCnt = BaseCache.this.newEntryCnt - virginEvictCnt; 2491 long hitCnt = getHitCnt(); 2492 long usageCnt = 2493 hitCnt + newEntryCnt + peekMissCnt; 2494 CollisionInfo collisionInfo; 2495 String extraStatistics; 2496 int fetchesInFlight = BaseCache.this.getFetchesInFlight(); 2497 2498 { 2499 collisionInfo = new CollisionInfo(); 2500 Hash.calcHashCollisionInfo(collisionInfo, mainHash); 2501 Hash.calcHashCollisionInfo(collisionInfo, refreshHash); 2502 extraStatistics = BaseCache.this.getExtraStatistics(); 2503 if (extraStatistics.startsWith(", ")) { 2504 extraStatistics = extraStatistics.substring(2); 2505 } 2506 } 2507 2508 IntegrityState integrityState = getIntegrityState(); 2509 2510 String percentString(double d) { 2511 String s = Double.toString(d); 2512 return (s.length() > 5 ? s.substring(0, 5) : s) + "%"; 2513 } 2514 2515 public String getName() { return name; } 2516 public String getImplementation() { return BaseCache.this.getClass().getSimpleName(); } 2517 public int getSize() { return size; } 2518 public int getMaxSize() { return maxSize; } 2519 public long getStorageHitCnt() { return loadHitCnt; } 2520 public long getStorageLoadCnt() { return storageLoadCnt; } 2521 public long getStorageMissCnt() { return storageMissCnt; } 2522 public long getReadUsageCnt() { return usageCnt - putCnt; } 2523 public long getUsageCnt() { return usageCnt; } 2524 public long getMissCnt() { return missCnt; } 2525 public long getNewEntryCnt() { return newEntryCnt; } 2526 public long getFetchCnt() { return fetchCnt; } 2527 public int getFetchesInFlightCnt() { return fetchesInFlight; } 2528 public long getBulkGetCnt() { return bulkGetCnt; } 2529 public long getRefreshCnt() { return refreshCnt; } 2530 public long getInternalExceptionCnt() { return internalExceptionCnt; } 2531 public long getRefreshSubmitFailedCnt() { return refreshSubmitFailedCnt; } 2532 public long getSuppressedExceptionCnt() { return suppressedExceptionCnt; } 2533 public long getFetchExceptionCnt() { return fetchExceptionCnt; } 2534 public long getRefreshHitCnt() { return refreshHitCnt; } 2535 public long getExpiredCnt() { return BaseCache.this.getExpiredCnt(); } 2536 public long getEvictedCnt() { return evictedCnt - virginEvictCnt; } 2537 public long getRemovedCnt() { return BaseCache.this.removedCnt; } 2538 public long getPutNewEntryCnt() { return putNewEntryCnt; } 2539 public long getPutCnt() { return putCnt; } 2540 public long getKeyMutationCnt() { return keyMutationCount; } 2541 public double getDataHitRate() { 2542 long cnt = getReadUsageCnt(); 2543 return cnt == 0 ? 100 : (cnt - missCnt) * 100D / cnt; 2544 } 2545 public String getDataHitString() { return percentString(getDataHitRate()); } 2546 public double getEntryHitRate() { return usageCnt == 0 ? 100 : (usageCnt - newEntryCnt + putCnt) * 100D / usageCnt; } 2547 public String getEntryHitString() { return percentString(getEntryHitRate()); } 2548 /** How many items will be accessed with collision */ 2549 public int getCollisionPercentage() { 2550 return 2551 (size - collisionInfo.collisionCnt) * 100 / size; 2552 } 2553 /** 100 means each collision has its own slot */ 2554 public int getSlotsPercentage() { 2555 return collisionInfo.collisionSlotCnt * 100 / collisionInfo.collisionCnt; 2556 } 2557 public int getHq0() { 2558 return Math.max(0, 105 - collisionInfo.longestCollisionSize * 5) ; 2559 } 2560 public int getHq1() { 2561 final int _metricPercentageBase = 60; 2562 int m = 2563 getCollisionPercentage() * ( 100 - _metricPercentageBase) / 100 + _metricPercentageBase; 2564 m = Math.min(100, m); 2565 m = Math.max(0, m); 2566 return m; 2567 } 2568 public int getHq2() { 2569 final int _metricPercentageBase = 80; 2570 int m = 2571 getSlotsPercentage() * ( 100 - _metricPercentageBase) / 100 + _metricPercentageBase; 2572 m = Math.min(100, m); 2573 m = Math.max(0, m); 2574 return m; 2575 } 2576 public int getHashQualityInteger() { 2577 if (size == 0 || collisionInfo.collisionSlotCnt == 0) { 2578 return 100; 2579 } 2580 int _metric0 = getHq0(); 2581 int _metric1 = getHq1(); 2582 int _metric2 = getHq2(); 2583 if (_metric1 < _metric0) { 2584 int v = _metric0; 2585 _metric0 = _metric1; 2586 _metric1 = v; 2587 } 2588 if (_metric2 < _metric0) { 2589 int v = _metric0; 2590 _metric0 = _metric2; 2591 _metric2 = v; 2592 } 2593 if (_metric2 < _metric1) { 2594 int v = _metric1; 2595 _metric1 = _metric2; 2596 _metric2 = v; 2597 } 2598 if (_metric0 <= 0) { 2599 return 0; 2600 } 2601 _metric0 = _metric0 + ((_metric1 - 50) * 5 / _metric0); 2602 _metric0 = _metric0 + ((_metric2 - 50) * 2 / _metric0); 2603 _metric0 = Math.max(0, _metric0); 2604 _metric0 = Math.min(100, _metric0); 2605 return _metric0; 2606 } 2607 public double getMillisPerFetch() { return fetchCnt == 0 ? -1 : (fetchMillis * 1D / fetchCnt); } 2608 public long getFetchMillis() { return fetchMillis; } 2609 public int getCollisionCnt() { return collisionInfo.collisionCnt; } 2610 public int getCollisionSlotCnt() { return collisionInfo.collisionSlotCnt; } 2611 public int getLongestCollisionSize() { return collisionInfo.longestCollisionSize; } 2612 public String getIntegrityDescriptor() { return integrityState.getStateDescriptor(); } 2613 public long getStarted() { return startedTime; } 2614 public long getCleared() { return clearedTime; } 2615 public long getTouched() { return touchedTime; } 2616 public long getInfoCreated() { return creationTime; } 2617 public int getInfoCreationDeltaMs() { return creationDeltaMs; } 2618 public int getHealth() { 2619 if (storage != null && storage.getAlert() == 2) { 2620 return 2; 2621 } 2622 if (integrityState.getStateFlags() > 0 || 2623 getHashQualityInteger() < 5) { 2624 return 2; 2625 } 2626 if (storage != null && storage.getAlert() == 1) { 2627 return 1; 2628 } 2629 if (getHashQualityInteger() < 30 || 2630 getKeyMutationCnt() > 0 || 2631 getInternalExceptionCnt() > 0) { 2632 return 1; 2633 } 2634 return 0; 2635 } 2636 public String getExtraStatistics() { 2637 return extraStatistics; 2638 } 2639 2640 } 2641 2642 static class CollisionInfo { 2643 int collisionCnt; int collisionSlotCnt; int longestCollisionSize; 2644 } 2645 2646 /** 2647 * This function calculates a modified hash code. The intention is to 2648 * "rehash" the incoming integer hash codes to overcome weak hash code 2649 * implementations. We expect good results for integers also. 2650 * Also add a random seed to the hash to protect against attacks on hashes. 2651 * This is actually a slightly reduced version of the java.util.HashMap 2652 * hash modification. 2653 */ 2654 protected final int modifiedHash(int h) { 2655 h ^= hashSeed; 2656 h ^= h >>> 7; 2657 h ^= h >>> 15; 2658 return h; 2659 2660 } 2661 2662 protected class MyTimerTask extends java.util.TimerTask { 2663 E entry; 2664 2665 public void run() { 2666 timerEvent(entry, scheduledExecutionTime()); 2667 } 2668 } 2669 2670 public static class Tunable extends TunableConstants { 2671 2672 /** 2673 * Implementation class to use by default. 2674 */ 2675 public Class<? extends BaseCache> defaultImplementation = LruCache.class; 2676 2677 /** 2678 * Log exceptions from the source just as they happen. The log goes to the debug output 2679 * of the cache log, debug level of the cache log must be enabled also. 2680 */ 2681 public boolean logSourceExceptions = false; 2682 2683 public int waitForTimerJobsSeconds = 5; 2684 2685 /** 2686 * Limits the number of spins until an entry lock is expected to 2687 * succeed. The limit is to detect deadlock issues during development 2688 * and testing. It is set to an arbitrary high value to result in 2689 * an exception after about one second of spinning. 2690 */ 2691 public int maximumEntryLockSpins = 333333; 2692 2693 /** 2694 * Maximum number of tries to find an entry for eviction if maximum size 2695 * is reached. 2696 */ 2697 public int maximumEvictSpins = 5; 2698 2699 /** 2700 * Size of the hash table before inserting the first entry. Must be power 2701 * of two. Default: 64. 2702 */ 2703 public int initialHashSize = 64; 2704 2705 /** 2706 * Fill percentage limit. When this is reached the hash table will get 2707 * expanded. Default: 64. 2708 */ 2709 public int hashLoadPercent = 64; 2710 2711 /** 2712 * The hash code will randomized by default. This is a countermeasure 2713 * against from outside that know the hash function. 2714 */ 2715 public boolean disableHashRandomization = false; 2716 2717 /** 2718 * Seed used when randomization is disabled. Default: 0. 2719 */ 2720 public int hashSeed = 0; 2721 2722 /** 2723 * When sharp expiry is enabled, the expiry timer goes 2724 * before the actual expiry to switch back to a time checking 2725 * scheme when the get method is invoked. This prevents 2726 * that an expired value gets served by the cache if the time 2727 * is too late. A recent GC should not produce more then 200 2728 * milliseconds stall. If longer GC stalls are expected, this 2729 * value needs to be changed. A value of LONG.MaxValue 2730 * suppresses the timer usage completely. 2731 */ 2732 public long sharpExpirySafetyGapMillis = 666; 2733 2734 2735 } 2736 2737}