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}