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.CacheException; 027import org.cache2k.CacheManager; 028import org.cache2k.impl.threading.Futures; 029import org.cache2k.impl.threading.GlobalPooledExecutor; 030import org.cache2k.impl.util.Cache2kVersion; 031import org.cache2k.impl.util.Log; 032 033import java.io.IOException; 034import java.io.InputStream; 035import java.net.URL; 036import java.util.ArrayList; 037import java.util.HashMap; 038import java.util.HashSet; 039import java.util.Iterator; 040import java.util.List; 041import java.util.Map; 042import java.util.ServiceLoader; 043import java.util.Set; 044import java.util.concurrent.ArrayBlockingQueue; 045import java.util.concurrent.ExecutionException; 046import java.util.concurrent.ExecutorService; 047import java.util.concurrent.Future; 048import java.util.concurrent.ThreadFactory; 049import java.util.concurrent.ThreadPoolExecutor; 050import java.util.concurrent.TimeUnit; 051import java.util.concurrent.atomic.AtomicInteger; 052import java.util.jar.Manifest; 053 054/** 055 * @author Jens Wilke; created: 2013-07-01 056 */ 057public class CacheManagerImpl extends CacheManager { 058 059 static List<CacheLifeCycleListener> cacheLifeCycleListeners = new ArrayList<CacheLifeCycleListener>(); 060 static List<CacheManagerLifeCycleListener> cacheManagerLifeCycleListeners = new ArrayList<CacheManagerLifeCycleListener>(); 061 062 static { 063 for (CacheLifeCycleListener l : ServiceLoader.load(CacheLifeCycleListener.class)) { 064 cacheLifeCycleListeners.add(l); 065 } 066 for (CacheManagerLifeCycleListener l : ServiceLoader.load(CacheManagerLifeCycleListener.class)) { 067 cacheManagerLifeCycleListeners.add(l); 068 } 069 } 070 071 private Log log; 072 private String name; 073 private Map<String, BaseCache> cacheNames = new HashMap<String, BaseCache>(); 074 private Set<Cache> caches = new HashSet<Cache>(); 075 private int disambiguationCounter = 1; 076 private GlobalPooledExecutor threadPool; 077 private AtomicInteger evictionThreadCount = new AtomicInteger(); 078 private ExecutorService evictionExecutor; 079 private String version; 080 private String buildNumber; 081 082 public CacheManagerImpl() { 083 this(getDefaultName()); 084 } 085 086 public CacheManagerImpl(String _name) { 087 name = _name; 088 log = Log.getLog(CacheManager.class.getName() + '.' + name); 089 String _buildNumber = Cache2kVersion.getBuildNumber(); 090 String _version = Cache2kVersion.getVersion(); 091 StringBuilder sb = new StringBuilder(); 092 sb.append("org.cache2k manager starting. "); 093 sb.append("name="); sb.append(name); 094 sb.append(", version="); sb.append(_version); 095 sb.append(", build="); sb.append(_buildNumber); 096 log.info(sb.toString()); 097 for (CacheManagerLifeCycleListener lc : cacheManagerLifeCycleListeners) { 098 lc.managerCreated(this); 099 } 100 } 101 102 private void sendCreatedEvent(Cache c) { 103 for (CacheLifeCycleListener e : cacheLifeCycleListeners) { 104 e.cacheCreated(this, c); 105 } 106 } 107 108 private void sendDestroyedEvent(Cache c) { 109 for (CacheLifeCycleListener e : cacheLifeCycleListeners) { 110 e.cacheDestroyed(this, c); 111 } 112 } 113 114 /** 115 * Don't accept a cache name with too weird characters. Rather then escaping the 116 * name, so we can use it for JMX, it is better to just reject it. 117 */ 118 private void checkName(String s) { 119 for (char c : s.toCharArray()) { 120 if (c == '.' || 121 c == '-' || 122 c == '~') { 123 continue; 124 } 125 if (!Character.isJavaIdentifierPart(c)) { 126 throw new CacheUsageExcpetion( 127 "Cache name contains illegal chars: '" + c + "', name=\"" + s + "\""); 128 } 129 } 130 } 131 132 static class StackTrace extends Exception { } 133 134 /* called by builder */ 135 public synchronized void newCache(Cache c) { 136 BaseCache bc = (BaseCache) c; 137 String _requestedName = c.getName(); 138 String _name = _requestedName; 139 while (cacheNames.containsKey(_name)) { 140 _name = _requestedName + "~" + Integer.toString(disambiguationCounter++, 36); 141 } 142 if (!_requestedName.equals(_name)) { 143 log.warn("duplicate name, disambiguating: " + _requestedName + " -> " + _name, new StackTrace()); 144 bc.setName(_name); 145 } 146 checkName(_name); 147 148 caches.add(c); 149 sendCreatedEvent(c); 150 bc.setCacheManager(this); 151 cacheNames.put(c.getName(), bc); 152 } 153 154 /* called by cache or CM */ 155 public synchronized void cacheDestroyed(Cache c) { 156 cacheNames.remove(c.getName()); 157 caches.remove(c); 158 sendDestroyedEvent(c); 159 } 160 161 @Override 162 public String getName() { 163 return name; 164 } 165 166 @Override 167 public synchronized Iterator<Cache> iterator() { 168 checkClosed(); 169 return caches.iterator(); 170 } 171 172 @Override 173 public Cache getCache(String name) { 174 return cacheNames.get(name); 175 } 176 177 @Override 178 public synchronized void clear() { 179 checkClosed(); 180 for (Cache c : caches) { 181 c.clear(); 182 } 183 } 184 185 @Override 186 public synchronized void destroy() { 187 close(); 188 } 189 190 /** 191 * The shutdown takes place in two phases. First all caches are notified to 192 * cancel their scheduled timer jobs, after that the shutdown is done. Cancelling 193 * the timer jobs first is needed, because there may be cache stacking and 194 * a timer job of one cache may call an already closed cache. 195 * 196 * <p/>Rationale exception handling: Exceptions on shutdown just could silently 197 * ignored, because a shutdown is done any way. Exceptions could be happened 198 * long before in a parallel task, shutdown is the last point where this could 199 * be propagated to the application. Silently ignoring is bad anyway, because 200 * this will cause that serious problems keep undetected. The exception and 201 * error handling within the cache tries everything that exceptions will be 202 * routed through as early and directly as possible. 203 */ 204 @Override 205 public synchronized void close() { 206 List<Throwable> _suppressedExceptions = new ArrayList<Throwable>(); 207 if (caches != null) { 208 Futures.WaitForAllFuture<Void> _wait = new Futures.WaitForAllFuture<Void>(); 209 for (Cache c : caches) { 210 if (c instanceof BaseCache) { 211 try { 212 Future<Void> f = ((BaseCache) c).cancelTimerJobs(); 213 _wait.add(f); 214 } catch (Throwable t) { 215 _suppressedExceptions.add(t); 216 } 217 } 218 } 219 try { 220 _wait.get(3000, TimeUnit.MILLISECONDS); 221 } catch (Exception ex) { 222 _suppressedExceptions.add(ex); 223 } 224 if (!_wait.isDone()) { 225 for (Cache c : caches) { 226 if (c instanceof BaseCache) { 227 BaseCache bc = (BaseCache) c; 228 try { 229 Future<Void> f = bc.cancelTimerJobs(); 230 if (!f.isDone()) { 231 bc.getLog().info("fetches ongoing, terminating anyway..."); 232 } 233 } catch (Throwable t) { 234 _suppressedExceptions.add(t); 235 } 236 } 237 } 238 } 239 Set<Cache> _caches = new HashSet<Cache>(); 240 _caches.addAll(caches); 241 for (Cache c : _caches) { 242 try { 243 c.destroy(); 244 } catch (Throwable t) { 245 _suppressedExceptions.add(t); 246 } 247 } 248 try { 249 if (threadPool != null) { 250 threadPool.close(); 251 } 252 for (CacheManagerLifeCycleListener lc : cacheManagerLifeCycleListeners) { 253 lc.managerDestroyed(this); 254 } 255 caches = null; 256 } catch (Throwable t) { 257 _suppressedExceptions.add(t); 258 } 259 } 260 eventuallyThrowException(_suppressedExceptions); 261 } 262 263 /** 264 * Throw exception if the list contains exceptions. The the thrown exception 265 * all exceptions get added as suppressed exceptions. The main cause of the 266 * exception will be the first detected error or the first detected 267 * exception. 268 * 269 * @throws org.cache2k.impl.CacheInternalError if list contains an error 270 * @throws org.cache2k.CacheException if list does not contain an error 271 */ 272 private void eventuallyThrowException(List<Throwable> _suppressedExceptions) { 273 if (_suppressedExceptions.size() > 0) { 274 Throwable _error = null; 275 for (Throwable t : _suppressedExceptions) { 276 if (t instanceof Error) { _error = t; } 277 if (t instanceof ExecutionException && 278 ((ExecutionException) t).getCause() instanceof Error) { 279 _error = t; 280 } 281 } 282 Throwable _throwNow; 283 String _text = "shutdown"; 284 if (_suppressedExceptions.size() > 1) { 285 _text = " (" + _suppressedExceptions.size() + " exceptions)"; 286 } 287 if (_error != null) { 288 _throwNow = new CacheInternalError(_text, _error); 289 } else { 290 _throwNow = new CacheException(_text, _suppressedExceptions.get(0)); 291 _suppressedExceptions.remove(0); 292 } 293 for (Throwable t : _suppressedExceptions) { 294 if (t != _error) { 295 _throwNow.addSuppressed(t); 296 } 297 } 298 if (_error != null) { 299 throw (Error) _throwNow; 300 } else { 301 throw (RuntimeException) _throwNow; 302 } 303 } 304 } 305 306 @Override 307 public boolean isDestroyed() { 308 return caches == null; 309 } 310 311 private String getThreadNamePrefix() { 312 if (!DEFAULT_MANAGER_NAME.equals(name)) { 313 return "cache2k:" + name + ":"; 314 } 315 return "cache2k-"; 316 } 317 318 /** 319 * Lazy creation of thread pool, usable for all caches managed by the cache 320 * manager. 321 */ 322 public synchronized GlobalPooledExecutor getThreadPool() { 323 if (threadPool == null) { 324 threadPool = new GlobalPooledExecutor(getThreadNamePrefix() + "pool-"); 325 } 326 return threadPool; 327 } 328 329 public ExecutorService createEvictionExecutor() { 330 ThreadFactory _threadFactory = new ThreadFactory() { 331 @Override 332 public Thread newThread(Runnable r) { 333 String _name = 334 getThreadNamePrefix() + "eviction-" + evictionThreadCount.incrementAndGet(); 335 Thread t = new Thread(new ExceptionWrapper(r), _name); 336 t.setDaemon(true); 337 return t; 338 } 339 }; 340 return 341 new ThreadPoolExecutor( 342 0, Integer.MAX_VALUE, 17, TimeUnit.SECONDS, 343 new ArrayBlockingQueue<Runnable>(Runtime.getRuntime().availableProcessors() * 2), _threadFactory); 344 } 345 346 class ExceptionWrapper implements Runnable { 347 348 Runnable runnable; 349 350 ExceptionWrapper(Runnable runnable) { 351 this.runnable = runnable; 352 } 353 354 @Override 355 public void run() { 356 try { 357 runnable.run(); 358 } catch (CacheClosedException ignore) { 359 } catch (Throwable t) { 360 log.warn("Exception in thread \"" + Thread.currentThread().getName() + "\"", t); 361 } 362 } 363 364 } 365 366 public ExecutorService getEvictionExecutor() { 367 if (evictionExecutor == null) { 368 evictionExecutor = createEvictionExecutor(); 369 } 370 return evictionExecutor; 371 } 372 373 /** 374 * Only return thread pool if created before. For JMX bean access. 375 */ 376 public GlobalPooledExecutor getThreadPoolEventually() { 377 return threadPool; 378 } 379 380 private void checkClosed() { 381 if (caches == null) { 382 throw new IllegalStateException("CacheManager already closed"); 383 } 384 } 385 386 public String getVersion() { 387 return version; 388 } 389 390 public String getBuildNumber() { 391 return buildNumber; 392 } 393 394}