You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AsynchronousFileCacheBacking.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. /*******************************************************************************
  2. * Copyright (c) 2012 Contributors.
  3. * All rights reserved.
  4. * This program and the accompanying materials are made available
  5. * under the terms of the Eclipse Public License v1.0
  6. * which accompanies this distribution and is available at
  7. * http://eclipse.org/legal/epl-v10.html
  8. *
  9. * Contributors:
  10. * Lyor Goldstein (vmware) add support for weaved class being re-defined
  11. *******************************************************************************/
  12. package org.aspectj.weaver.tools.cache;
  13. import java.io.File;
  14. import java.util.ArrayList;
  15. import java.util.Collections;
  16. import java.util.List;
  17. import java.util.Map;
  18. import java.util.concurrent.BlockingQueue;
  19. import java.util.concurrent.ExecutorService;
  20. import java.util.concurrent.Executors;
  21. import java.util.concurrent.Future;
  22. import java.util.concurrent.LinkedBlockingQueue;
  23. import org.aspectj.util.FileUtil;
  24. import org.aspectj.util.LangUtil;
  25. import org.aspectj.weaver.tools.Trace;
  26. import org.aspectj.weaver.tools.TraceFactory;
  27. /**
  28. * Uses a background thread to do the actual I/O and for caching "persistence"
  29. * so that the caching works faster on repeated activations of the application.
  30. * The class maintains an in-memory cache, and uses a queue of {@link AsyncCommand}s
  31. * to signal to a background thread various actions required to "synchronize"
  32. * the in-memory cache with the persisted copy. Whenever there is a cache miss
  33. * from the {@link #get(CachedClassReference, byte[])} call, the weaver issues a
  34. * {@link #put(CachedClassEntry, byte[])} call. This call has 2 side-effects:
  35. * <UL>
  36. * <LI>
  37. * The in-memory cache is updated so that subsequent calls to {@link #get(CachedClassReference, byte[])}
  38. * will not return the mapped value.
  39. * </LI>
  40. *
  41. * <LI>
  42. * An &quot;update index&quot; {@link AsyncCommand} is posted to the background
  43. * thread so that the newly mapped value will be persisted (eventually)
  44. * </LI>
  45. * </UL>
  46. * The actual persistence is implemented by the <U>concrete</U> classes
  47. */
  48. public abstract class AsynchronousFileCacheBacking extends AbstractIndexedFileCacheBacking {
  49. private static final BlockingQueue<AsyncCommand> commandsQ= new LinkedBlockingQueue<>();
  50. private static final ExecutorService execService=Executors.newSingleThreadExecutor();
  51. private static Future<?> commandsRunner;
  52. protected final Map<String, IndexEntry> index, exposedIndex;
  53. protected final Map<String, byte[]> bytesMap, exposedBytes;
  54. protected AsynchronousFileCacheBacking (File cacheDir) {
  55. super(cacheDir);
  56. index = readIndex(cacheDir, getIndexFile());
  57. exposedIndex = Collections.unmodifiableMap(index);
  58. bytesMap = readClassBytes(index, cacheDir);
  59. exposedBytes = Collections.unmodifiableMap(bytesMap);
  60. }
  61. @Override
  62. protected Map<String, IndexEntry> getIndex() {
  63. return index;
  64. }
  65. public CachedClassEntry get(CachedClassReference ref, byte[] originalBytes) {
  66. String key=ref.getKey();
  67. final IndexEntry indexEntry;
  68. synchronized(index) {
  69. if ((indexEntry=index.get(key)) == null) {
  70. return null;
  71. }
  72. }
  73. if (crc(originalBytes) != indexEntry.crcClass) {
  74. if ((logger != null) && logger.isTraceEnabled()) {
  75. logger.debug("get(" + getCacheDirectory() + ") mismatched original class bytes CRC for " + key);
  76. }
  77. remove(key);
  78. return null;
  79. }
  80. if (indexEntry.ignored) {
  81. return new CachedClassEntry(ref, WeavedClassCache.ZERO_BYTES, CachedClassEntry.EntryType.IGNORED);
  82. }
  83. final byte[] bytes;
  84. synchronized(bytesMap) {
  85. /*
  86. * NOTE: we assume that keys represent classes so if we have their
  87. * bytes they will not be re-created
  88. */
  89. if ((bytes=bytesMap.remove(key)) == null) {
  90. return null;
  91. }
  92. }
  93. if (indexEntry.generated) {
  94. return new CachedClassEntry(ref, bytes, CachedClassEntry.EntryType.GENERATED);
  95. } else {
  96. return new CachedClassEntry(ref, bytes, CachedClassEntry.EntryType.WEAVED);
  97. }
  98. }
  99. public void put(CachedClassEntry entry, byte[] originalBytes) {
  100. String key=entry.getKey();
  101. byte[] bytes=entry.isIgnored() ? null : entry.getBytes();
  102. synchronized(index) {
  103. IndexEntry indexEntry=index.get(key);
  104. if (indexEntry != null) {
  105. return;
  106. }
  107. /*
  108. * Note: we do not cache the class bytes - only send them to
  109. * be saved. The assumption is that the 'put' call was invoked
  110. * because 'get' failed to return any bytes. And since we assume
  111. * that each class bytes are required only once, there is no
  112. * need to cache them
  113. */
  114. indexEntry = createIndexEntry(entry, originalBytes);
  115. index.put(key, indexEntry);
  116. }
  117. if (!postCacheCommand(new InsertCommand(this, key, bytes))) {
  118. if ((logger != null) && logger.isTraceEnabled()) {
  119. logger.error("put(" + getCacheDirectory() + ") Failed to post insert command for " + key);
  120. }
  121. }
  122. if ((logger != null) && logger.isTraceEnabled()) {
  123. logger.debug("put(" + getCacheDirectory() + ")[" + key + "] inserted");
  124. }
  125. }
  126. public void remove(CachedClassReference ref) {
  127. remove(ref.getKey());
  128. }
  129. protected IndexEntry remove (String key) {
  130. IndexEntry entry;
  131. synchronized(index) {
  132. entry = index.remove(key);
  133. }
  134. synchronized(bytesMap) {
  135. bytesMap.remove(key);
  136. }
  137. if (!postCacheCommand(new RemoveCommand(this, key))) {
  138. if ((logger != null) && logger.isTraceEnabled()) {
  139. logger.error("remove(" + getCacheDirectory() + ") Failed to post remove command for " + key);
  140. }
  141. }
  142. if (entry != null) {
  143. if (!key.equals(entry.key)) {
  144. if ((logger != null) && logger.isTraceEnabled()) {
  145. logger.error("remove(" + getCacheDirectory() + ") Mismatched keys: " + key + " / " + entry.key);
  146. }
  147. } else if ((logger != null) && logger.isTraceEnabled()) {
  148. logger.debug("remove(" + getCacheDirectory() + ")[" + key + "] removed");
  149. }
  150. }
  151. return entry;
  152. }
  153. public List<IndexEntry> getIndexEntries () {
  154. synchronized(index) {
  155. if (index.isEmpty()) {
  156. return Collections.emptyList();
  157. } else {
  158. return new ArrayList<>(index.values());
  159. }
  160. }
  161. }
  162. public Map<String, IndexEntry> getIndexMap () {
  163. return exposedIndex;
  164. }
  165. public Map<String, byte[]> getBytesMap () {
  166. return exposedBytes;
  167. }
  168. public void clear() {
  169. synchronized(index) {
  170. index.clear();
  171. }
  172. if (!postCacheCommand(new ClearCommand(this))) {
  173. if ((logger != null) && logger.isTraceEnabled()) {
  174. logger.error("Failed to post clear command for " + getIndexFile());
  175. }
  176. }
  177. }
  178. protected void executeCommand (AsyncCommand cmd) throws Exception {
  179. if (cmd instanceof ClearCommand) {
  180. executeClearCommand();
  181. } else if (cmd instanceof UpdateIndexCommand) {
  182. executeUpdateIndexCommand();
  183. } else if (cmd instanceof InsertCommand) {
  184. executeInsertCommand((InsertCommand) cmd);
  185. } else if (cmd instanceof RemoveCommand) {
  186. executeRemoveCommand((RemoveCommand) cmd);
  187. } else {
  188. throw new UnsupportedOperationException("Unknown command: " + cmd);
  189. }
  190. }
  191. protected void executeClearCommand () throws Exception {
  192. FileUtil.deleteContents(getIndexFile());
  193. FileUtil.deleteContents(getCacheDirectory());
  194. }
  195. protected void executeUpdateIndexCommand () throws Exception {
  196. writeIndex(getIndexFile(), getIndexEntries());
  197. }
  198. protected void executeInsertCommand (InsertCommand cmd) throws Exception {
  199. writeIndex(getIndexFile(), getIndexEntries());
  200. byte[] bytes=cmd.getClassBytes();
  201. if (bytes != null) {
  202. writeClassBytes(cmd.getKey(), bytes);
  203. }
  204. }
  205. protected void executeRemoveCommand (RemoveCommand cmd) throws Exception {
  206. Exception err=null;
  207. try {
  208. removeClassBytes(cmd.getKey());
  209. } catch(Exception e) {
  210. err = e;
  211. }
  212. writeIndex(getIndexFile(), getIndexEntries());
  213. if (err != null) {
  214. throw err; // check if the class bytes remove had any problems
  215. }
  216. }
  217. /**
  218. * Helper for {@link #executeRemoveCommand(RemoveCommand)}
  219. * @param key The key representing the class whose bytes are to be removed
  220. * @throws Exception if failed to remove class bytes
  221. */
  222. protected abstract void removeClassBytes (String key) throws Exception;
  223. protected abstract Map<String, byte[]> readClassBytes (Map<String,IndexEntry> indexMap, File cacheDir);
  224. @Override
  225. public String toString() {
  226. return getClass().getSimpleName() + "[" + String.valueOf(getCacheDirectory()) + "]";
  227. }
  228. protected static final <T extends AsynchronousFileCacheBacking> T createBacking (
  229. File cacheDir, AsynchronousFileCacheBackingCreator<T> creator) {
  230. final Trace trace=TraceFactory.getTraceFactory().getTrace(AsynchronousFileCacheBacking.class);
  231. if (!cacheDir.exists()) {
  232. if (!cacheDir.mkdirs()) {
  233. if ((trace != null) && trace.isTraceEnabled()) {
  234. trace.error("Unable to create cache directory at " + cacheDir.getAbsolutePath());
  235. }
  236. return null;
  237. }
  238. }
  239. if (!cacheDir.canWrite()) {
  240. if ((trace != null) && trace.isTraceEnabled()) {
  241. trace.error("Cache directory is not writable at " + cacheDir.getAbsolutePath());
  242. }
  243. return null;
  244. }
  245. // start the service (if needed) only if successfully create the backing instance
  246. T backing=creator.create(cacheDir);
  247. synchronized(execService) {
  248. if (commandsRunner == null) {
  249. commandsRunner = execService.submit(new Runnable() {
  250. @SuppressWarnings("synthetic-access")
  251. public void run() {
  252. for ( ; ; ) {
  253. try {
  254. AsyncCommand cmd=commandsQ.take();
  255. try {
  256. AsynchronousFileCacheBacking cache=cmd.getCache();
  257. cache.executeCommand(cmd);
  258. } catch(Exception e) {
  259. if ((trace != null) && trace.isTraceEnabled()) {
  260. trace.error("Failed (" + e.getClass().getSimpleName() + ")"
  261. + " to execute " + cmd + ": " + e.getMessage(), e);
  262. }
  263. }
  264. } catch(InterruptedException e) {
  265. if ((trace != null) && trace.isTraceEnabled()) {
  266. trace.warn("Interrupted");
  267. }
  268. Thread.currentThread().interrupt();
  269. break;
  270. }
  271. }
  272. }
  273. });
  274. }
  275. }
  276. // fire-up an update-index command in case index was changed by the constructor
  277. if (!postCacheCommand(new UpdateIndexCommand(backing))) {
  278. if ((trace != null) && trace.isTraceEnabled()) {
  279. trace.warn("Failed to offer update index command to " + cacheDir.getAbsolutePath());
  280. }
  281. }
  282. return backing;
  283. }
  284. public static final boolean postCacheCommand (AsyncCommand cmd) {
  285. return commandsQ.offer(cmd);
  286. }
  287. public interface AsynchronousFileCacheBackingCreator<T extends AsynchronousFileCacheBacking> {
  288. T create (File cacheDir);
  289. }
  290. /**
  291. * Represents an asynchronous command that can be sent to the
  292. * {@link AsynchronousFileCacheBacking} instance to be executed
  293. * on it <U>asynchronously</U>
  294. */
  295. public interface AsyncCommand {
  296. /**
  297. * @return The {@link AsynchronousFileCacheBacking} on which
  298. * this command is supposed to be executed
  299. * @see AsynchronousFileCacheBacking#executeCommand(AsyncCommand)
  300. */
  301. AsynchronousFileCacheBacking getCache ();
  302. }
  303. public static abstract class AbstractCommand implements AsyncCommand {
  304. private final AsynchronousFileCacheBacking cache;
  305. protected AbstractCommand (AsynchronousFileCacheBacking backing) {
  306. if ((cache=backing) == null) {
  307. throw new IllegalStateException("No backing cache specified");
  308. }
  309. }
  310. public final AsynchronousFileCacheBacking getCache () {
  311. return cache;
  312. }
  313. @Override
  314. public String toString() {
  315. return getClass().getSimpleName() + "[" + getCache() + "]";
  316. }
  317. }
  318. public static class ClearCommand extends AbstractCommand {
  319. public ClearCommand (AsynchronousFileCacheBacking cache) {
  320. super(cache);
  321. }
  322. }
  323. public static class UpdateIndexCommand extends AbstractCommand {
  324. public UpdateIndexCommand (AsynchronousFileCacheBacking cache) {
  325. super(cache);
  326. }
  327. }
  328. /**
  329. * Base class for {@link AbstractCommand}s that refer to a cache key
  330. */
  331. public static abstract class KeyedCommand extends AbstractCommand {
  332. private final String key;
  333. protected KeyedCommand (AsynchronousFileCacheBacking cache, String keyValue) {
  334. super(cache);
  335. if (LangUtil.isEmpty(keyValue)) {
  336. throw new IllegalStateException("No key value");
  337. }
  338. key = keyValue;
  339. }
  340. public final String getKey () {
  341. return key;
  342. }
  343. @Override
  344. public String toString() {
  345. return super.toString() + "[" + getKey() + "]";
  346. }
  347. }
  348. public static class RemoveCommand extends KeyedCommand {
  349. public RemoveCommand (AsynchronousFileCacheBacking cache, String keyValue) {
  350. super(cache, keyValue);
  351. }
  352. }
  353. public static class InsertCommand extends KeyedCommand {
  354. private final byte[] bytes;
  355. public InsertCommand (AsynchronousFileCacheBacking cache, String keyValue, byte[] classBytes) {
  356. super(cache, keyValue);
  357. bytes = classBytes;
  358. }
  359. public final byte[] getClassBytes () {
  360. return bytes;
  361. }
  362. }
  363. }