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}