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.Cache; 026import org.cache2k.CacheConfig; 027import org.cache2k.ClosableIterator; 028import org.cache2k.StorageConfiguration; 029import org.cache2k.impl.threading.Futures; 030import org.cache2k.impl.threading.LimitedPooledExecutor; 031import org.cache2k.impl.timer.TimerListener; 032import org.cache2k.impl.timer.TimerService; 033import org.cache2k.spi.SingleProviderResolver; 034import org.cache2k.storage.CacheStorage; 035import org.cache2k.storage.CacheStorageContext; 036import org.cache2k.storage.CacheStorageProvider; 037import org.cache2k.storage.FlushableStorage; 038import org.cache2k.storage.MarshallerFactory; 039import org.cache2k.storage.Marshallers; 040import org.cache2k.storage.TransientStorageClass; 041import org.cache2k.storage.PurgeableStorage; 042import org.cache2k.storage.StorageEntry; 043import org.cache2k.impl.util.Log; 044import org.cache2k.impl.util.TunableConstants; 045import org.cache2k.impl.util.TunableFactory; 046 047import java.io.NotSerializableException; 048import java.util.HashSet; 049import java.util.Iterator; 050import java.util.Properties; 051import java.util.Set; 052import java.util.concurrent.ArrayBlockingQueue; 053import java.util.concurrent.BlockingQueue; 054import java.util.concurrent.Callable; 055import java.util.concurrent.ExecutionException; 056import java.util.concurrent.ExecutorService; 057import java.util.concurrent.Executors; 058import java.util.concurrent.Future; 059import java.util.concurrent.FutureTask; 060import java.util.concurrent.SynchronousQueue; 061import java.util.concurrent.ThreadFactory; 062import java.util.concurrent.ThreadPoolExecutor; 063import java.util.concurrent.TimeUnit; 064import java.util.concurrent.atomic.AtomicInteger; 065 066/** 067 * Passes cache operation to the storage layer. Implements common 068 * services for the storage layer. This class heavily interacts 069 * with the base cache and contains mostly everything special 070 * needed if a storage is defined. This means the design is not 071 * perfectly layered, in some cases the 072 * e.g. the get operation does interacts with the 073 * underlying storage, wheres iterate 074 * 075 * @author Jens Wilke; created: 2014-05-08 076 */ 077@SuppressWarnings({"unchecked", "SynchronizeOnNonFinalField"}) 078public class PassingStorageAdapter extends StorageAdapter { 079 080 private Tunable tunable = TunableFactory.get(Tunable.class); 081 private BaseCache cache; 082 CacheStorage storage; 083 boolean passivation = false; 084 boolean storageIsTransient = false; 085 long errorCount = 0; 086 Set<Object> deletedKeys = null; 087 StorageContext context; 088 StorageConfiguration config; 089 ExecutorService executor; 090 091 long flushIntervalMillis = 0; 092 Object flushLock = new Object(); 093 TimerService.CancelHandle flushTimerHandle; 094 Future<Void> lastExecutingFlush = new Futures.FinishedFuture<Void>(); 095 096 Object purgeRunningLock = new Object(); 097 098 Log log; 099 StorageAdapter.Parent parent; 100 101 public PassingStorageAdapter(BaseCache _cache, CacheConfig _cacheConfig, 102 StorageConfiguration _storageConfig) { 103 cache = _cache; 104 parent = _cache; 105 context = new StorageContext(_cache); 106 context.keyType = _cacheConfig.getKeyType(); 107 context.valueType = _cacheConfig.getValueType(); 108 config = _storageConfig; 109 if (tunable.useManagerThreadPool) { 110 executor = new LimitedPooledExecutor(cache.manager.getThreadPool()); 111 } else { 112 executor = Executors.newCachedThreadPool(); 113 } 114 log = Log.getLog(Cache.class.getName() + ".storage/" + cache.getCompleteName()); 115 context.log = log; 116 } 117 118 /** 119 * By default log lifecycle operations as info. 120 */ 121 protected void logLifecycleOperation(String s) { 122 log.info(s); 123 } 124 125 public void open() { 126 try { 127 CacheStorageProvider<?> pr = (CacheStorageProvider) 128 SingleProviderResolver.getInstance().resolve(config.getImplementation()); 129 storage = pr.create(context, config); 130 if (storage instanceof TransientStorageClass) { 131 storageIsTransient = true; 132 } 133 flushIntervalMillis = config.getFlushIntervalMillis(); 134 if (!(storage instanceof FlushableStorage)) { 135 flushIntervalMillis = -1; 136 } 137 if (config.isPassivation() || storageIsTransient) { 138 deletedKeys = new HashSet<Object>(); 139 passivation = true; 140 } 141 logLifecycleOperation("opened, state: " + storage); 142 } catch (Exception ex) { 143 if (config.isReliable()) { 144 disableAndThrow("error initializing, disabled", ex); 145 } else { 146 disable("error initializing, disabled", ex); 147 } 148 } 149 } 150 151 /** 152 * Store entry on cache put. Entry must be locked, since we use the 153 * entry directly for handing it over to the storage, it is not 154 * allowed to change. The expiry time in the entry does not have 155 * a valid value yet, so that is why it is transferred separately. 156 */ 157 public void put(Entry e, long _nextRefreshTime) { 158 if (passivation) { 159 synchronized (deletedKeys) { 160 deletedKeys.remove(e.getKey()); 161 } 162 return; 163 } 164 StorageEntryForPut se = 165 new StorageEntryForPut(e.getKey(), e.getValue(), e.getCreatedOrUpdated(), _nextRefreshTime); 166 doPut(se); 167 } 168 169 private void doPut(StorageEntry e) { 170 try { 171 storage.put(e); 172 checkStartFlushTimer(); 173 } catch (Exception ex) { 174 if (config.isReliable() || ex instanceof NotSerializableException) { 175 disableAndThrow("exception in storage.put()", ex); 176 } else { 177 storageUnreliableError(ex); 178 try { 179 if (!storage.contains(e.getKey())) { 180 return; 181 } 182 storage.remove(e.getKey()); 183 checkStartFlushTimer(); 184 } catch (Exception ex2) { 185 ex.addSuppressed(ex2); 186 disableAndThrow("exception in storage.put(), mitigation failed, entry state unknown", ex); 187 } 188 } 189 } 190 } 191 192 void storageUnreliableError(Throwable ex) { 193 if (errorCount == 0) { 194 log.warn("Storage exception, only first exception is logged, see error counter (reliable=false)", ex); 195 } 196 errorCount++; 197 } 198 199 public StorageEntry get(Object k) { 200 if (deletedKeys != null) { 201 synchronized (deletedKeys) { 202 if (deletedKeys.contains(k)) { 203 return null; 204 } 205 } 206 } 207 try { 208 return storage.get(k); 209 } catch (Exception ex) { 210 storageUnreliableError(ex); 211 if (config.isReliable()) { 212 throw new CacheStorageException("cache get", ex); 213 } 214 return null; 215 } 216 } 217 218 /** 219 * If passivation is not enabled, then we need to do nothing here since, the 220 * entry was transferred to the storage on the {@link StorageAdapter#put(Entry, long)} 221 * operation. With passivation enabled, the entries need to be transferred when evicted from 222 * the heap. 223 * 224 * <p/>The storage operation is done in the calling thread, which should be a client thread. 225 * The cache client will be throttled until the I/O operation is finished. This is what we 226 * want in general. To decouple it, we need to implement async storage I/O support. 227 */ 228 public void evict(Entry e) { 229 if (passivation) { 230 putEventually(e); 231 } 232 } 233 234 /** 235 * An expired entry was removed from the memory cache and 236 * can also be removed from the storage. For the moment, this 237 * does nothing, since we cannot do the removal in the calling 238 * thread and decoupling yields bad race conditions. 239 * Expired entries in the storage are remove by the purge run. 240 */ 241 public void expire(Entry e) { 242 } 243 244 /** 245 * Called upon evict, when in passivation mode. 246 * Store it in the storage if needed, that is if it is dirty 247 * or if the entry is not yet in the storage. When an off heap 248 * and persistent storage is aggregated, evicted entries will 249 * be put to the off heap storage, but not into the persistent 250 * storage again. 251 */ 252 private void putEventually(Entry e) { 253 if (!e.isDirty()) { 254 try { 255 if (storage.contains(e.getKey())) { 256 return; 257 } 258 } catch (Exception ex) { 259 disableAndThrow("storage.contains(), unknown state", ex); 260 } 261 } 262 doPut(e); 263 } 264 265 public boolean remove(Object key) { 266 try { 267 if (deletedKeys != null) { 268 synchronized (deletedKeys) { 269 if (!deletedKeys.contains(key) && storage.contains(key)) { 270 deletedKeys.add(key); 271 return true; 272 } 273 return false; 274 } 275 } 276 boolean f = storage.remove(key); 277 checkStartFlushTimer(); 278 return f; 279 } catch (Exception ex) { 280 disableAndThrow("storage.remove()", ex); 281 } 282 return false; 283 } 284 285 @Override 286 public ClosableIterator<Entry> iterateAll() { 287 final CompleteIterator it = new CompleteIterator(); 288 if (tunable.iterationQueueCapacity > 0) { 289 it.queue = new ArrayBlockingQueue<StorageEntry>(tunable.iterationQueueCapacity); 290 } else { 291 it.queue = new SynchronousQueue<StorageEntry>(); 292 } 293 synchronized (cache.lock) { 294 it.heapIteration = cache.iterateAllHeapEntries(); 295 it.heapIteration.setKeepIterated(true); 296 it.keepHashCtrlForClearDetection = cache.mainHashCtrl; 297 } 298 it.executorForStorageCall = executor; 299 long now = System.currentTimeMillis(); 300 it.runnable = new StorageVisitCallable(now, it); 301 return it; 302 } 303 304 public void purge() { 305 synchronized (purgeRunningLock) { 306 long now = System.currentTimeMillis(); 307 PurgeableStorage.PurgeResult res; 308 if (storage instanceof PurgeableStorage) { 309 try { 310 PurgeableStorage.PurgeContext ctx = new MyPurgeContext(); 311 res = ((PurgeableStorage) storage).purge(ctx, now, now); 312 } catch (Exception ex) { 313 disable("expire exception", ex); 314 return; 315 } 316 } else { 317 res = purgeByVisit(now, now); 318 } 319 if (log.isInfoEnabled()) { 320 long t = System.currentTimeMillis(); 321 log.info("purge (force): " + 322 "runtimeMillis=" + (t - now) + ", " + 323 "scanned=" + res.getEntriesScanned() + ", " + 324 "purged=" + res.getEntriesPurged() + ", " + 325 "until=" + now + 326 (res.getBytesFreed() >=0 ? ", " + "freedBytes=" + res.getBytesFreed() : "")); 327 328 } 329 } 330 } 331 332 /** 333 * Use visit and iterate over all entries in the storage. 334 * It is not possible to remove the entry directly from the storage, since 335 * this would introduce a race. To avoid this, the entry is inserted 336 * in the heap cache and the removal is done under the entry lock. 337 */ 338 PurgeableStorage.PurgeResult purgeByVisit( 339 final long _valueExpiryTime, 340 final long _entryExpireTime) { 341 CacheStorage.EntryFilter f = new CacheStorage.EntryFilter() { 342 @Override 343 public boolean shouldInclude(Object _key) { 344 return true; 345 } 346 }; 347 CacheStorage.VisitContext ctx = new BaseVisitContext() { 348 @Override 349 public boolean needMetaData() { 350 return true; 351 } 352 353 @Override 354 public boolean needValue() { 355 return false; 356 } 357 }; 358 final AtomicInteger _scanCount = new AtomicInteger(); 359 final AtomicInteger _purgeCount = new AtomicInteger(); 360 CacheStorage.EntryVisitor v = new CacheStorage.EntryVisitor() { 361 @Override 362 public void visit(final StorageEntry _storageEntry) throws Exception { 363 _scanCount.incrementAndGet(); 364 if ((_storageEntry.getEntryExpiryTime() > 0 && _storageEntry.getEntryExpiryTime() < _entryExpireTime) || 365 (_storageEntry.getValueExpiryTime() > 0 && _storageEntry.getValueExpiryTime() < _valueExpiryTime)) { 366 PurgeableStorage.PurgeAction _action = new PurgeableStorage.PurgeAction() { 367 @Override 368 public StorageEntry checkAndPurge(Object key) { 369 try { 370 StorageEntry e2 = storage.get(key); 371 if (_storageEntry.getEntryExpiryTime() == e2.getEntryExpiryTime() && 372 _storageEntry.getValueExpiryTime() == e2.getValueExpiryTime()) { 373 storage.remove(key); 374 _purgeCount.incrementAndGet(); 375 return null; 376 } 377 return e2; 378 } catch (Exception ex) { 379 disable("storage.remove()", ex); 380 return null; 381 } 382 } 383 }; 384 cache.lockAndRunForPurge(_storageEntry.getKey(), _action); 385 } 386 } 387 }; 388 try { 389 storage.visit(ctx, f, v); 390 ctx.awaitTermination(); 391 } catch (Exception ex) { 392 disable("visit exception", ex); 393 } 394 if (_purgeCount.get() > 0) { 395 checkStartFlushTimer(); 396 } 397 return new PurgeableStorage.PurgeResult() { 398 @Override 399 public long getBytesFreed() { 400 return -1; 401 } 402 403 @Override 404 public int getEntriesPurged() { 405 return _purgeCount.get(); 406 } 407 408 @Override 409 public int getEntriesScanned() { 410 return _scanCount.get(); 411 } 412 }; 413 } 414 415 abstract class BaseVisitContext extends MyMultiThreadContext implements CacheStorage.VisitContext { 416 417 } 418 419 class MyPurgeContext extends MyMultiThreadContext implements PurgeableStorage.PurgeContext { 420 421 @Override 422 public void lockAndRun(Object key, PurgeableStorage.PurgeAction _action) { 423 cache.lockAndRunForPurge(key, _action); 424 } 425 426 } 427 428 class StorageVisitCallable implements LimitedPooledExecutor.NeverRunInCallingTask<Void> { 429 430 long now; 431 CompleteIterator it; 432 433 StorageVisitCallable(long now, CompleteIterator it) { 434 this.now = now; 435 this.it = it; 436 } 437 438 @Override 439 public Void call() { 440 final BlockingQueue<StorageEntry> _queue = it.queue; 441 CacheStorage.EntryVisitor v = new CacheStorage.EntryVisitor() { 442 @Override 443 public void visit(StorageEntry se) throws InterruptedException { 444 if (se.getValueExpiryTime() != 0 && se.getValueExpiryTime() <= now) { return; } 445 _queue.put(se); 446 } 447 }; 448 CacheStorage.EntryFilter f = new CacheStorage.EntryFilter() { 449 @Override 450 public boolean shouldInclude(Object _key) { 451 return !Hash.contains(it.keysIterated, _key, cache.modifiedHash(_key.hashCode())); 452 } 453 }; 454 try { 455 storage.visit(it, f, v); 456 } catch (Exception ex) { 457 it.abortOnException(ex); 458 _queue.clear(); 459 } finally { 460 try { 461 it.awaitTermination(); 462 } catch (InterruptedException ex) { 463 } 464 for (;;) { 465 try { 466 _queue.put(LAST_ENTRY); 467 break; 468 } catch (InterruptedException ex) { 469 } 470 } 471 } 472 return null; 473 } 474 475 } 476 477 static final Entry LAST_ENTRY = new Entry(); 478 479 class MyMultiThreadContext implements CacheStorage.MultiThreadedContext { 480 481 ExecutorService executorForVisitThread; 482 boolean abortFlag; 483 Throwable abortException; 484 485 @Override 486 public ExecutorService getExecutorService() { 487 if (executorForVisitThread == null) { 488 if (tunable.useManagerThreadPool) { 489 LimitedPooledExecutor ex = new LimitedPooledExecutor(cache.manager.getThreadPool()); 490 ex.setExceptionListener(new LimitedPooledExecutor.ExceptionListener() { 491 @Override 492 public void exceptionWasThrown(Throwable ex) { 493 abortOnException(ex); 494 } 495 }); 496 executorForVisitThread = ex; 497 } else { 498 executorForVisitThread = createOperationExecutor(); 499 } 500 } 501 return executorForVisitThread; 502 } 503 504 @Override 505 public void awaitTermination() throws InterruptedException { 506 if (executorForVisitThread != null) { 507 if (!executorForVisitThread.isTerminated()) { 508 if (shouldStop()) { 509 executorForVisitThread.shutdownNow(); 510 } else { 511 executorForVisitThread.shutdown(); 512 } 513 boolean _terminated = false; 514 if (tunable.terminationInfoSeconds > 0) { 515 _terminated = executorForVisitThread.awaitTermination( 516 tunable.terminationInfoSeconds, TimeUnit.SECONDS); 517 } 518 if (!_terminated) { 519 if (log.isInfoEnabled() && tunable.terminationInfoSeconds > 0) { 520 log.info( 521 "still waiting for thread termination after " + 522 tunable.terminationInfoSeconds + " seconds," + 523 " keep waiting for " + tunable.terminationTimeoutSeconds + " seconds..."); 524 } 525 _terminated = executorForVisitThread.awaitTermination( 526 tunable.terminationTimeoutSeconds - tunable.terminationInfoSeconds, TimeUnit.SECONDS); 527 if (!_terminated) { 528 log.warn("threads not terminated after " + tunable.terminationTimeoutSeconds + " seconds"); 529 } 530 } 531 } 532 } 533 if (abortException != null) { 534 throw new RuntimeException("execution exception", abortException); 535 } 536 } 537 538 @Override 539 public synchronized void abortOnException(Throwable ex) { 540 if (abortException == null) { 541 abortException = ex; 542 } 543 abortFlag = true; 544 } 545 546 @Override 547 public boolean shouldStop() { 548 return abortFlag; 549 } 550 551 } 552 553 class CompleteIterator 554 extends MyMultiThreadContext 555 implements ClosableIterator<Entry>, CacheStorage.VisitContext { 556 557 Hash keepHashCtrlForClearDetection; 558 Entry[] keysIterated; 559 ClosableConcurrentHashEntryIterator heapIteration; 560 StorageEntry entry; 561 BlockingQueue<StorageEntry> queue; 562 Callable<Void> runnable; 563 Future<Void> futureToCheckAbnormalTermination; 564 ExecutorService executorForStorageCall; 565 566 @Override 567 public boolean needMetaData() { 568 return true; 569 } 570 571 @Override 572 public boolean needValue() { 573 return true; 574 } 575 576 @Override 577 public boolean hasNext() { 578 if (heapIteration != null) { 579 while (heapIteration.hasNext()) { 580 Entry e; 581 entry = e = heapIteration.next(); 582 if (e.isDataValidState()) { 583 return true; 584 } 585 } 586 keysIterated = heapIteration.iterated; 587 futureToCheckAbnormalTermination = 588 executorForStorageCall.submit(runnable); 589 heapIteration = null; 590 } 591 if (queue != null) { 592 if (abortException != null) { 593 queue = null; 594 throw new StorageIterationException(abortException); 595 } 596 if (cache.shutdownInitiated) { 597 throw new CacheClosedException(); 598 } 599 if (keepHashCtrlForClearDetection.isCleared()) { 600 close(); 601 return false; 602 } 603 try { 604 for (;;) { 605 entry = queue.poll(1234, TimeUnit.MILLISECONDS); 606 if (entry == null) { 607 if (!futureToCheckAbnormalTermination.isDone()) { 608 continue; 609 } 610 futureToCheckAbnormalTermination.get(); 611 } 612 break; 613 } 614 if (entry != LAST_ENTRY) { 615 return true; 616 } 617 } catch (InterruptedException _ignore) { 618 } catch (ExecutionException ex) { 619 if (abortException == null) { 620 abortException = ex; 621 } 622 } 623 queue = null; 624 if (abortException != null) { 625 throw new CacheStorageException(abortException); 626 } 627 } 628 return false; 629 } 630 631 @Override 632 public Entry next() { 633 return cache.insertEntryFromStorage(entry, false); 634 } 635 636 @Override 637 public void remove() { 638 throw new UnsupportedOperationException(); 639 } 640 641 @Override 642 public void close() { 643 if (heapIteration != null) { 644 heapIteration.close(); 645 heapIteration = null; 646 } 647 if (executorForStorageCall != null) { 648 executorForStorageCall.shutdownNow(); 649 executorForStorageCall = null; 650 queue = null; 651 } 652 } 653 654 } 655 656 static class StorageIterationException extends CacheStorageException { 657 658 StorageIterationException(Throwable cause) { 659 super(cause); 660 } 661 662 } 663 664 /** 665 * Start timer to flush data or do nothing if flush already scheduled. 666 */ 667 private void checkStartFlushTimer() { 668 if (flushIntervalMillis <= 0) { 669 return; 670 } 671 synchronized (flushLock) { 672 if (flushTimerHandle != null) { 673 return; 674 } 675 scheduleFlushTimer(); 676 } 677 } 678 679 private void scheduleFlushTimer() { 680 TimerListener l = new TimerListener() { 681 @Override 682 public void fire(long _time) { 683 onFlushTimerEvent(); 684 } 685 }; 686 long _fireTime = System.currentTimeMillis() + config.getFlushIntervalMillis(); 687 flushTimerHandle = cache.timerService.add(l, _fireTime); 688 } 689 690 protected void onFlushTimerEvent() { 691 synchronized (flushLock) { 692 flushTimerHandle.cancel(); 693 flushTimerHandle = null; 694 if (storage instanceof ClearStorageBuffer || 695 (!lastExecutingFlush.isDone())) { 696 checkStartFlushTimer(); 697 return; 698 } 699 Callable<Void> c = new LimitedPooledExecutor.NeverRunInCallingTask<Void>() { 700 @Override 701 public Void call() throws Exception { 702 doStorageFlush(); 703 return null; 704 } 705 }; 706 lastExecutingFlush = executor.submit(c); 707 } 708 } 709 710 /** 711 * Initiate flush on the storage. If a concurrent flush is going on, wait for 712 * it until initiating a new one. 713 */ 714 public void flush() { 715 synchronized (flushLock) { 716 if (flushTimerHandle != null) { 717 flushTimerHandle.cancel(); 718 flushTimerHandle = null; 719 } 720 } 721 Callable<Void> c = new Callable<Void>() { 722 @Override 723 public Void call() throws Exception { 724 doStorageFlush(); 725 return null; 726 } 727 }; 728 FutureTask<Void> _inThreadFlush = new FutureTask<Void>(c); 729 boolean _anotherFlushSubmittedNotByUs = false; 730 for (;;) { 731 if (!lastExecutingFlush.isDone()) { 732 try { 733 lastExecutingFlush.get(); 734 if (_anotherFlushSubmittedNotByUs) { 735 return; 736 } 737 } catch (Exception ex) { 738 disableAndThrow("flush execution", ex); 739 } 740 } 741 synchronized (this) { 742 if (!lastExecutingFlush.isDone()) { 743 _anotherFlushSubmittedNotByUs = true; 744 continue; 745 } 746 lastExecutingFlush = _inThreadFlush; 747 } 748 _inThreadFlush.run(); 749 break; 750 } 751 } 752 753 private void doStorageFlush() throws Exception { 754 FlushableStorage.FlushContext ctx = new MyFlushContext(); 755 FlushableStorage _storage = (FlushableStorage) storage; 756 _storage.flush(ctx, System.currentTimeMillis()); 757 log.info("flushed, state: " + storage); 758 } 759 760 /** may be executed more than once */ 761 public synchronized Future<Void> cancelTimerJobs() { 762 synchronized (flushLock) { 763 if (flushIntervalMillis >= 0) { 764 flushIntervalMillis = -1; 765 } 766 if (flushTimerHandle != null) { 767 flushTimerHandle.cancel(); 768 flushTimerHandle = null; 769 } 770 if (!lastExecutingFlush.isDone()) { 771 lastExecutingFlush.cancel(false); 772 return lastExecutingFlush; 773 } 774 } 775 return new Futures.FinishedFuture<Void>(); 776 } 777 778 public Future<Void> shutdown() { 779 if (storage instanceof ClearStorageBuffer) { 780 throw new CacheInternalError("Clear is supposed to be in shutdown wait task queue, so shutdown waits for it."); 781 } 782 Callable<Void> _closeTaskChain = new Callable<Void>() { 783 @Override 784 public Void call() throws Exception { 785 if (config.isFlushOnClose() || config.isReliable()) { 786 flush(); 787 } else { 788 Future<Void> _previousFlush = lastExecutingFlush; 789 if (_previousFlush != null) { 790 _previousFlush.cancel(true); 791 _previousFlush.get(); 792 } 793 } 794 logLifecycleOperation("closing, state: " + storage); 795 storage.close(); 796 return null; 797 } 798 }; 799 if (passivation && !storageIsTransient) { 800 final Callable<Void> _before = _closeTaskChain; 801 _closeTaskChain = new Callable<Void>() { 802 @Override 803 public Void call() throws Exception { 804 passivateHeapEntriesOnShutdown(); 805 executor.submit(_before); 806 return null; 807 } 808 }; 809 } 810 return executor.submit(_closeTaskChain); 811 } 812 813 /** 814 * Iterate through the heap entries and store them in the storage. 815 */ 816 private void passivateHeapEntriesOnShutdown() { 817 Iterator<Entry> it; 818 try { 819 synchronized (cache.lock) { 820 it = cache.iterateAllHeapEntries(); 821 } 822 while (it.hasNext()) { 823 Entry e = it.next(); 824 synchronized (e) { 825 putEventually(e); 826 } 827 } 828 if (deletedKeys != null) { 829 for (Object k : deletedKeys) { 830 storage.remove(k); 831 } 832 } 833 } catch (Exception ex) { 834 rethrow("shutdown passivation", ex); 835 } 836 } 837 838 /** 839 * True means actually no operations started on the storage again, yet 840 */ 841 public boolean checkStorageStillDisconnectedForClear() { 842 if (storage instanceof ClearStorageBuffer) { 843 ClearStorageBuffer _buffer = (ClearStorageBuffer) storage; 844 if (!_buffer.isTransferringToStorage()) { 845 return true; 846 } 847 } 848 return false; 849 } 850 851 /** 852 * Disconnect storage so cache can wait for all entry operations to finish. 853 */ 854 public void disconnectStorageForClear() { 855 synchronized (this) { 856 ClearStorageBuffer _buffer = new ClearStorageBuffer(); 857 _buffer.nextStorage = storage; 858 storage = _buffer; 859 if (_buffer.nextStorage instanceof ClearStorageBuffer) { 860 ClearStorageBuffer _ongoingClear = (ClearStorageBuffer) _buffer.nextStorage; 861 if (_ongoingClear.clearThreadFuture != null) { 862 _ongoingClear.shouldStop = true; 863 } 864 } 865 } 866 } 867 868 /** 869 * Called in a (maybe) separate thread after disconnect. Cache 870 * already is doing operations meanwhile and the storage operations 871 * are buffered. Here we have multiple race conditions. A clear() exists 872 * immediately but the storage is still working on the first clear. 873 * All previous clear processes will be cancelled and the last one may 874 * win. However, this method is not necessarily executed in the order 875 * the clear or the disconnect took place. This is checked also. 876 */ 877 public Future<Void> clearAndReconnect() { 878 FutureTask<Void> f; 879 synchronized (this) { 880 final ClearStorageBuffer _buffer = (ClearStorageBuffer) storage; 881 if (_buffer.clearThreadFuture != null) { 882 return _buffer.clearThreadFuture; 883 } 884 ClearStorageBuffer _previousBuffer = null; 885 if (_buffer.getNextStorage() instanceof ClearStorageBuffer) { 886 _previousBuffer = (ClearStorageBuffer) _buffer.getNextStorage(); 887 _buffer.nextStorage = _buffer.getOriginalStorage(); 888 } 889 final ClearStorageBuffer _waitingBufferStack = _previousBuffer; 890 Callable<Void> c = new LimitedPooledExecutor.NeverRunInCallingTask<Void>() { 891 @Override 892 public Void call() throws Exception { 893 try { 894 if (_waitingBufferStack != null) { 895 _waitingBufferStack.waitForAll(); 896 } 897 } catch (Exception ex) { 898 disable("exception during waiting for previous clear", ex); 899 throw new CacheStorageException(ex); 900 } 901 synchronized (this) { 902 if (_buffer.shouldStop) { 903 return null; 904 } 905 } 906 try { 907 _buffer.getOriginalStorage().clear(); 908 } catch (Exception ex) { 909 disable("exception during clear", ex); 910 throw new CacheStorageException(ex); 911 } 912 synchronized (cache.lock) { 913 _buffer.startTransfer(); 914 } 915 try { 916 _buffer.transfer(); 917 } catch (Exception ex) { 918 disable("exception during clear, operations replay", ex); 919 throw new CacheStorageException(ex); 920 } 921 synchronized (this) { 922 if (_buffer.shouldStop) { return null; } 923 storage = _buffer.getOriginalStorage(); 924 } 925 return null; 926 } 927 }; 928 _buffer.clearThreadFuture = f = new FutureTask<Void>(c); 929 } 930 f.run(); 931 return f; 932 } 933 934 public void disableAndThrow(String _logMessage, Throwable ex) { 935 errorCount++; 936 disable(ex); 937 rethrow(_logMessage, ex); 938 } 939 940 public void disable(String _logMessage, Throwable ex) { 941 log.warn(_logMessage, ex); 942 disable(ex); 943 } 944 945 public void disable(Throwable ex) { 946 if (storage == null) { return; } 947 synchronized (cache.lock) { 948 synchronized (this) { 949 if (storage == null) { return; } 950 CacheStorage _storage = storage; 951 if (_storage instanceof ClearStorageBuffer) { 952 ClearStorageBuffer _buffer = (ClearStorageBuffer) _storage; 953 _buffer.disableOnFailure(ex); 954 } 955 try { 956 _storage.close(); 957 } catch (Throwable _ignore) { 958 } 959 960 storage = null; 961 parent.resetStorage(this, new NoopStorageAdapter(cache)); 962 } 963 } 964 } 965 966 /** 967 * orange alert level if buffer is active, so we get alerted if storage 968 * clear isn't finished. 969 */ 970 @Override 971 public int getAlert() { 972 if (errorCount > 0) { 973 return 1; 974 } 975 if (storage instanceof ClearStorageBuffer) { 976 return 1; 977 } 978 return 0; 979 } 980 981 /** 982 * Calculates the cache size, depending on the persistence configuration 983 */ 984 @Override 985 public int getTotalEntryCount() { 986 if (!passivation) { 987 return storage.getEntryCount(); 988 } 989 return storage.getEntryCount() + cache.getLocalSize(); 990 } 991 992 @Override 993 public String toString() { 994 return "PassingStorageAdapter(implementation=" + getImplementation() + ")"; 995 } 996 997 public CacheStorage getImplementation() { 998 return storage; 999 } 1000 1001 class MyFlushContext 1002 extends MyMultiThreadContext 1003 implements FlushableStorage.FlushContext { 1004 1005 } 1006 1007 static class StorageContext implements CacheStorageContext { 1008 1009 Log log; 1010 BaseCache cache; 1011 Class<?> keyType; 1012 Class<?> valueType; 1013 1014 StorageContext(BaseCache cache) { 1015 this.cache = cache; 1016 } 1017 1018 @Override 1019 public Properties getProperties() { 1020 return null; 1021 } 1022 1023 @Override 1024 public String getManagerName() { 1025 return cache.manager.getName(); 1026 } 1027 1028 @Override 1029 public String getCacheName() { 1030 return cache.getName(); 1031 } 1032 1033 @Override 1034 public Class<?> getKeyType() { 1035 return keyType; 1036 } 1037 1038 @Override 1039 public Class<?> getValueType() { 1040 return valueType; 1041 } 1042 1043 @Override 1044 public MarshallerFactory getMarshallerFactory() { 1045 return Marshallers.getInstance(); 1046 } 1047 1048 @Override 1049 public Log getLog() { 1050 return log; 1051 } 1052 1053 @Override 1054 public void requestMaintenanceCall(int _intervalMillis) { 1055 } 1056 1057 @Override 1058 public void notifyEvicted(StorageEntry e) { 1059 } 1060 1061 @Override 1062 public void notifyExpired(StorageEntry e) { 1063 } 1064 1065 } 1066 1067 ExecutorService createOperationExecutor() { 1068 return 1069 new ThreadPoolExecutor( 1070 0, Runtime.getRuntime().availableProcessors() * 123 / 100, 1071 21, TimeUnit.SECONDS, 1072 new SynchronousQueue<Runnable>(), 1073 THREAD_FACTORY, 1074 new ThreadPoolExecutor.CallerRunsPolicy()); 1075 } 1076 1077 static final ThreadFactory THREAD_FACTORY = new MyThreadFactory(); 1078 1079 @SuppressWarnings("NullableProblems") 1080 static class MyThreadFactory implements ThreadFactory { 1081 1082 AtomicInteger count = new AtomicInteger(); 1083 1084 @Override 1085 public Thread newThread(Runnable r) { 1086 Thread t = new Thread(r, "cache2k-storage#" + count.incrementAndGet()); 1087 if (t.isDaemon()) { 1088 t.setDaemon(false); 1089 } 1090 if (t.getPriority() != Thread.NORM_PRIORITY) { 1091 t.setPriority(Thread.NORM_PRIORITY); 1092 } 1093 return t; 1094 } 1095 1096 } 1097 1098 static class StorageEntryForPut implements StorageEntry { 1099 1100 Object key; 1101 Object value; 1102 long creationTime; 1103 long expiryTime; 1104 1105 StorageEntryForPut(Object key, Object value, long creationTime, long expiryTime) { 1106 this.key = key; 1107 this.value = value; 1108 this.creationTime = creationTime; 1109 this.expiryTime = expiryTime; 1110 } 1111 1112 @Override 1113 public Object getKey() { 1114 return key; 1115 } 1116 1117 @Override 1118 public Object getValueOrException() { 1119 return value; 1120 } 1121 1122 @Override 1123 public long getCreatedOrUpdated() { 1124 return creationTime; 1125 } 1126 1127 @Override 1128 public long getValueExpiryTime() { 1129 return expiryTime; 1130 } 1131 1132 @Override 1133 public long getEntryExpiryTime() { 1134 return 0; 1135 } 1136 } 1137 1138 public static class Tunable extends TunableConstants { 1139 1140 /** 1141 * If the iteration client needs more time then the read threads, 1142 * the queue fills up. When the capacity is reached the reading 1143 * threads block until the client is requesting the next entry. 1144 * 1145 * <p>A low number makes sense here just to make sure that the read threads are 1146 * not waiting if the iterator client is doing some processing. We should 1147 * never put a large number here, to keep overall memory capacity control 1148 * within the cache and don't introduce additional buffers. 1149 * 1150 * <p>When the value is 0 a {@link java.util.concurrent.SynchronousQueue} 1151 * is used. 1152 */ 1153 public int iterationQueueCapacity = 3; 1154 1155 /** 1156 * User global thread pool are a separate one. 1157 * FIXME: Don't use global pool, there are some lingering bugs... 1158 */ 1159 public boolean useManagerThreadPool = false; 1160 1161 /** 1162 * Thread termination writes a info log message, if we still wait for termination. 1163 * Set to 0 to disable. Default: 5 1164 */ 1165 public int terminationInfoSeconds = 5; 1166 1167 /** 1168 * Maximum time to await the termination of all executor threads. Default: 2000 1169 */ 1170 public int terminationTimeoutSeconds = 200; 1171 1172 } 1173 1174}