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.StorageConfiguration;
026import org.cache2k.storage.CacheStorage;
027import org.cache2k.storage.CacheStorageContext;
028import org.cache2k.storage.FlushableStorage;
029import org.cache2k.storage.PurgeableStorage;
030import org.cache2k.storage.StorageEntry;
031
032import java.io.IOException;
033import java.util.ArrayList;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037import java.util.concurrent.Future;
038
039/**
040 * Record cache operations during a storage clear and playback later.
041 *
042 * <p/>By decoupling we still have fast concurrent access on the cache
043 * while the storage "does its thing" and remove files, etc. This is
044 * now just used for the clear() operation, so it is assumed that
045 * the initial state of the storage is empty.
046 *
047 * <p/>After playback starts, and if the requests come in faster than
048 * the storage can handle it we will end up queuing up requests forever,
049 * which means we will loose the cache properties and run out of heap space.
050 * To avoid this, we slow down the acceptance of new requests as soon as
051 * the storage playback starts.
052 *
053 * <p/>The normal operations like (get, put and remove) fill the playback
054 * queue. They also check within the lock whether the storage is online again.
055 * This is needed to make the reconnect of the storage atomically. It must
056 * be assured that all operations are sent and that the order is kept intact.
057 *
058 * <p/>Right now, the storage is only operated single threaded by the buffer
059 * playback.
060 *
061 * <p>TODO-C: Multi threaded storage playback
062 *
063 * @author Jens Wilke; created: 2014-04-20
064 */
065public class ClearStorageBuffer implements CacheStorage, FlushableStorage, PurgeableStorage {
066
067  long operationsCnt = 0;
068  long operationsAtTransferStart = 0;
069  long sentOperationsCnt = 0;
070  long sendingStart = 0;
071
072  /** Average time for one storage operation in microseconds */
073  long microRate = 0;
074
075  /** Added up rest of microseconds to wait */
076  long microWaitRest = 0;
077
078  boolean shouldStop = false;
079
080  List<Op> operations = new ArrayList<Op>();
081  Map<Object, StorageEntry> key2entry = new HashMap<Object, StorageEntry>();
082
083  CacheStorage forwardStorage;
084
085  CacheStorage nextStorage;
086
087  Future<Void> clearThreadFuture;
088
089  Throwable exception;
090
091  /**
092   * Stall this thread until the op is executed. However, the
093   * op is doing nothing and just notifies us. The flush
094   * on the storage can run in parallel with other tasks.
095   * This method is only allowed to finish when the flush is done.
096   */
097  @Override
098  public void flush(final FlushableStorage.FlushContext ctx, long now) throws Exception {
099    Op op = null;
100    boolean _forward;
101    synchronized (this) {
102      _forward = forwardStorage != null;
103      if (!_forward) {
104        op = new OpFlush();
105        addOp(op);
106      }
107    }
108    if (_forward) {
109      ((FlushableStorage) forwardStorage).flush(ctx, now);
110    }
111    synchronized (op) {
112      op.wait();
113      if (exception != null) {
114        throw new CacheStorageException(exception);
115      }
116    }
117    ((FlushableStorage) forwardStorage).flush(ctx, System.currentTimeMillis());
118  }
119
120  /**
121   * Does nothing. We were cleared lately anyway. Next purge may go to the storage.
122   */
123  @Override
124  public PurgeResult purge(PurgeContext ctx, long _valueExpiryTime, long _entryExpiryTime) { return null; }
125
126  @Override
127  public void close() throws Exception {
128    boolean _forward;
129    synchronized (this) {
130      _forward = forwardStorage != null;
131      if (!_forward) {
132        addOp(new OpClose());
133      }
134    }
135    if (_forward) {
136      forwardStorage.close();
137    }
138  }
139
140  @Override
141  public void clear() throws Exception {
142    throw new IllegalStateException("never called");
143  }
144
145  @Override
146  public void put(StorageEntry e) throws Exception {
147    boolean _forward;
148    synchronized (this) {
149      _forward = forwardStorage != null;
150      if (!_forward) {
151        e = new CopyStorageEntry(e);
152        key2entry.put(e.getKey(), e);
153        addOp(new OpPut(e));
154      }
155    }
156    if (_forward) {
157      forwardStorage.put(e);
158    } else {
159      throttle();
160    }
161  }
162
163  @Override
164  public StorageEntry get(Object key) throws Exception {
165    boolean _forward;
166    StorageEntry e = null;
167    synchronized (this) {
168      _forward = forwardStorage != null;
169      if (!_forward) {
170        addOp(new OpGet(key));
171        e = key2entry.get(key);
172      }
173    }
174    if (_forward) {
175      e = forwardStorage.get(key);
176    } else {
177      throttle();
178    }
179    return e;
180  }
181
182  @Override
183  public boolean remove(Object key) throws Exception {
184    StorageEntry e = null;
185    boolean _forward;
186    synchronized (this) {
187      _forward = forwardStorage != null;
188      if (!_forward) {
189        addOp(new OpRemove(key));
190        e = key2entry.remove(key);
191      }
192    }
193    if (_forward) {
194      return forwardStorage.remove(key);
195    } else {
196      throttle();
197    }
198    return e != null;
199  }
200
201  @Override
202  public boolean contains(Object key) throws Exception {
203    boolean b = false;
204    boolean _forward;
205    synchronized (this) {
206      _forward = forwardStorage != null;
207      if (!_forward) {
208        addOp(new OpContains(key));
209        b = key2entry.containsKey(key);
210      }
211    }
212    if (_forward) {
213      b = forwardStorage.contains(key);
214    } else {
215      throttle();
216    }
217    return b;
218  }
219
220  @Override
221  public void visit(VisitContext ctx, EntryFilter f, EntryVisitor v) throws Exception {
222    List<StorageEntry> l = null;
223    boolean _forward;
224    synchronized (this) {
225      _forward = forwardStorage != null;
226      if (!_forward) {
227        l = new ArrayList<StorageEntry>(key2entry.size());
228        for (StorageEntry e : key2entry.values()) {
229          if (f == null || f.shouldInclude(e.getKey())) {
230            l.add(e);
231          }
232        }
233      }
234    }
235    if (_forward) {
236      forwardStorage.visit(ctx, f, v);
237    } else {
238      for (StorageEntry e : l) {
239        if (ctx.shouldStop()) {
240          return;
241        }
242        v.visit(e);
243      }
244    }
245  }
246
247  @Override
248  public int getEntryCount() {
249    return key2entry.size();
250  }
251
252  private void addOp(Op op) {
253    operationsCnt++;
254    operations.add(op);
255  }
256
257  /**
258   * Throttle
259   */
260  private void throttle() {
261    if (sentOperationsCnt == 0) {
262      return;
263    }
264    long _waitMicros;
265    synchronized (this) {
266      if (operationsCnt % 7 == 0) {
267        long _factor = 1000000;
268        long _refilledSinceTransferStart = operationsCnt - operationsAtTransferStart;
269        if (_refilledSinceTransferStart > sentOperationsCnt) {
270          _factor = _refilledSinceTransferStart * 1000000 / sentOperationsCnt;
271        }
272        long _delta = System.currentTimeMillis() - sendingStart;
273        microRate = _delta * _factor / sentOperationsCnt;
274      }
275      _waitMicros = microRate + microRate * 3 / 100 + microWaitRest;
276      microWaitRest = _waitMicros % 1000000;
277    }
278    try {
279      long _millis = _waitMicros / 1000000;
280      Thread.sleep(_millis);
281    } catch (InterruptedException e) {
282    }
283  }
284
285  public void startTransfer() {
286    synchronized (this) {
287      sendingStart = System.currentTimeMillis();
288      operationsAtTransferStart = operationsCnt;
289    }
290  }
291
292  public boolean isTransferringToStorage() {
293    return sendingStart > 0;
294  }
295
296  public void transfer() throws Exception {
297    CacheStorage _target = getOriginalStorage();
298    List<Op> _workList;
299    while (true) {
300      synchronized (this) {
301        if (shouldStop) {
302          return;
303        }
304        if (operations.size() == 0) {
305          forwardStorage = nextStorage;
306          return;
307        }
308        _workList = operations;
309        operations = new ArrayList<Op>();
310      }
311      for (Op op : _workList) {
312        sentOperationsCnt++;
313        op.execute(_target);
314        if (shouldStop) {
315          if (exception != null) {
316            throw new CacheStorageException(exception);
317          }
318        }
319      }
320    }
321  }
322
323  public void disableOnFailure(Throwable t) {
324    exception = t;
325    shouldStop = true;
326    for (Op op : operations) {
327      synchronized (op) {
328        op.notifyAll();
329      }
330    }
331    CacheStorage _storage = nextStorage;
332    if (_storage instanceof ClearStorageBuffer) {
333      ((ClearStorageBuffer) _storage).disableOnFailure(t);
334    }
335  }
336
337  CacheStorage getOriginalStorage() {
338    if (nextStorage instanceof ClearStorageBuffer) {
339      return ((ClearStorageBuffer) nextStorage).getOriginalStorage();
340    }
341    return nextStorage;
342  }
343
344  void waitForAll() throws Exception {
345    if (clearThreadFuture != null && !clearThreadFuture.isDone()) {
346      clearThreadFuture.get();
347    }
348    if (nextStorage instanceof ClearStorageBuffer) {
349      ((ClearStorageBuffer) nextStorage).waitForAll();
350    }
351  }
352
353  CacheStorage getNextStorage() {
354    return nextStorage;
355  }
356
357  static abstract class Op {
358
359    abstract void execute(CacheStorage _target) throws Exception;
360
361  }
362
363  static class OpPut extends Op {
364
365    StorageEntry entry;
366
367    OpPut(StorageEntry entry) {
368      this.entry = entry;
369    }
370
371    @Override
372    void execute(CacheStorage _target) throws Exception {
373      _target.put(entry);
374    }
375
376    @Override
377    public String toString() {
378      return "OpPut(key=" + entry.getKey() + ", value=" + entry.getValueOrException() + ")";
379    }
380  }
381
382  static class OpRemove extends Op {
383
384    Object key;
385
386    OpRemove(Object key) {
387      this.key = key;
388    }
389
390    @Override
391    void execute(CacheStorage _target) throws Exception {
392      _target.remove(key);
393    }
394
395    @Override
396    public String toString() {
397      return "OpRemove(key=" + key + ")";
398    }
399
400  }
401
402  static class OpContains extends Op {
403
404    Object key;
405
406    OpContains(Object key) {
407      this.key = key;
408    }
409
410    @Override
411    void execute(CacheStorage _target) throws Exception {
412      _target.contains(key);
413    }
414
415    @Override
416    public String toString() {
417      return "OpContains(key=" + key + ")";
418    }
419
420  }
421
422  static class OpGet extends Op {
423
424    Object key;
425
426    OpGet(Object key) {
427      this.key = key;
428    }
429
430    @Override
431    void execute(CacheStorage _target) throws Exception {
432      _target.get(key);
433    }
434
435    @Override
436    public String toString() {
437      return "OpGet(key=" + key + ")";
438    }
439
440  }
441
442  static class OpClose extends Op {
443
444    @Override
445    void execute(CacheStorage _target) throws Exception {
446      _target.close();
447    }
448
449    @Override
450    public String toString() {
451      return "OpClose";
452    }
453
454  }
455
456  static class OpFlush extends Op {
457
458    @Override
459    void execute(CacheStorage _target) throws Exception {
460      synchronized (this) {
461        notify();
462      }
463    }
464
465    @Override
466    public String toString() {
467      return "OpFlush";
468    }
469
470  }
471
472  static class CopyStorageEntry implements StorageEntry {
473
474    Object key;
475    Object value;
476    long expiryTime;
477    long createdOrUpdated;
478    long lastUsed;
479    long maxIdleTime;
480
481    CopyStorageEntry(StorageEntry e) {
482      key = e.getKey();
483      value = e.getValueOrException();
484      expiryTime = e.getValueExpiryTime();
485      createdOrUpdated = e.getCreatedOrUpdated();
486      lastUsed = e.getEntryExpiryTime();
487    }
488
489    @Override
490    public Object getKey() {
491      return key;
492    }
493
494    @Override
495    public Object getValueOrException() {
496      return value;
497    }
498
499    @Override
500    public long getCreatedOrUpdated() {
501      return createdOrUpdated;
502    }
503
504    @Override
505    public long getValueExpiryTime() {
506      return expiryTime;
507    }
508
509    @Override
510    public long getEntryExpiryTime() {
511      return lastUsed;
512    }
513
514  }
515
516}