|
1 | 1 | package com.googlecode.objectify.cache; |
2 | 2 |
|
| 3 | +import java.util.concurrent.ConcurrentHashMap; |
3 | 4 | import java.util.concurrent.Future; |
| 5 | +import java.util.logging.Level; |
| 6 | +import java.util.logging.Logger; |
4 | 7 |
|
5 | 8 | /** |
6 | | - * <p>This bit of appengine magic hooks into the ApiProxy and does the heavy lifting of |
7 | | - * making the TriggerFuture<?> work.</p> |
8 | | - * |
9 | 9 | * <p>This class maintains a thread local list of all the outstanding Future<?> objects |
10 | 10 | * that have pending triggers. When a Future<?> is done and its trigger is executed, |
11 | | - * it is removed from the list. At various times (anytime an API call is made) the registered |
12 | | - * futures are checked for doneness and processed.</p> |
13 | | - * |
14 | | - * <p>The AsyncCacheFilter is necessary to guarantee that any pending triggers are processed |
15 | | - * at the end of the request. A future GAE SDK which allows us to hook into the Future<?> |
16 | | - * creation process might make this extra Filter unnecessary.</p> |
| 11 | + * it is removed from the list.</p> |
17 | 12 | * |
18 | 13 | * @author Jeff Schnitzer <jeff@infohazard.org> |
19 | 14 | */ |
20 | 15 | public class PendingFutures |
21 | 16 | { |
22 | | - /** The thread local value will be removed (null) if there are none pending */ |
23 | | - private static ThreadLocal<Pending> pending = new ThreadLocal<>(); |
24 | | - |
| 17 | + /** */ |
| 18 | + private static final Logger log = Logger.getLogger(PendingFutures.class.getName()); |
| 19 | + |
| 20 | + /** |
| 21 | + * We use ConcurrentHashMap not for concurrency but because it doesn't throw |
| 22 | + * ConcurrentModificationException. We need to be able to iterate while Futures remove |
| 23 | + * themselves from the set. A Set is just a Map of key to key. |
| 24 | + */ |
| 25 | + private static ThreadLocal<ConcurrentHashMap<Future<?>, Future<?>>> pending = new ThreadLocal<ConcurrentHashMap<Future<?>, Future<?>>>() { |
| 26 | + @Override |
| 27 | + protected ConcurrentHashMap<Future<?>, Future<?>> initialValue() { |
| 28 | + return new ConcurrentHashMap<>(64, 0.75f, 1); |
| 29 | + } |
| 30 | + }; |
25 | 31 |
|
26 | 32 | /** |
27 | 33 | * Register a pending Future that has a callback. |
28 | 34 | * @param future must have at least one callback |
29 | 35 | */ |
30 | | - public static void addPending(Future<?> future) |
31 | | - { |
32 | | - Pending pend = pending.get(); |
33 | | - if (pend == null) |
34 | | - { |
35 | | - pend = new Pending(); |
36 | | - pending.set(pend); |
37 | | - } |
38 | | - |
39 | | - pend.add(future); |
| 36 | + public static void addPending(Future<?> future) { |
| 37 | + pending.get().put(future, future); |
40 | 38 | } |
41 | 39 |
|
42 | 40 | /** |
43 | 41 | * Deregister a pending Future that had a callback. |
44 | 42 | */ |
45 | | - public static void removePending(Future<?> future) |
46 | | - { |
47 | | - Pending pend = pending.get(); |
48 | | - if (pend != null) |
49 | | - { |
50 | | - pend.remove(future); |
51 | | - |
52 | | - // When the last one is gone, we don't need this thread local anymore |
53 | | - if (pend.isEmpty()) |
54 | | - pending.remove(); |
55 | | - } |
| 43 | + public static void removePending(Future<?> future) { |
| 44 | + pending.get().remove(future); |
56 | 45 | } |
57 | 46 |
|
58 | | -// /** |
59 | | -// * If any futures are pending, check if they are done. This will process their callbacks. |
60 | | -// * Don't use this method - see comments on Pending.checkPendingFutures() |
61 | | -// */ |
62 | | -// @Deprecated |
63 | | -// public static void checkPendingFutures() |
64 | | -// { |
65 | | -// Pending pend = pending.get(); |
66 | | -// if (pend != null) |
67 | | -// pend.checkPendingFutures(); |
68 | | -// } |
69 | | - |
70 | 47 | /** |
71 | 48 | * Iterate through all pending futures and get() them, forcing any callbacks to be called. |
72 | | - * This is used only by the AsyncCacheFilter because we don't have a proper hook otherwise. |
| 49 | + * This is used only by the AsyncCacheFilter (if using cache without Objectify) or ObjectifyFilter |
| 50 | + * (if using Objectify normally) because we don't have a proper hook otherwise. |
73 | 51 | */ |
74 | | - public static void completeAllPendingFutures() |
75 | | - { |
76 | | - Pending pend = pending.get(); |
77 | | - if (pend != null) |
78 | | - pend.completeAllPendingFutures(); |
| 52 | + public static void completeAllPendingFutures() { |
| 53 | + // This will cause done Futures to fire callbacks and remove themselves |
| 54 | + for (Future<?> fut: pending.get().keySet()) { |
| 55 | + try { |
| 56 | + fut.get(); |
| 57 | + } |
| 58 | + catch (Exception e) { |
| 59 | + log.log(Level.SEVERE, "Error cleaning up pending Future: " + fut, e); |
| 60 | + } |
| 61 | + } |
79 | 62 | } |
80 | 63 | } |
0 commit comments