|
16 | 16 |
|
17 | 17 | package io.objectbox.query;
|
18 | 18 |
|
| 19 | +import java.util.ArrayDeque; |
| 20 | +import java.util.Deque; |
19 | 21 | import java.util.List;
|
20 | 22 | import java.util.Set;
|
21 | 23 | import java.util.concurrent.CopyOnWriteArraySet;
|
|
29 | 31 | import io.objectbox.reactive.DataPublisher;
|
30 | 32 | import io.objectbox.reactive.DataPublisherUtils;
|
31 | 33 | import io.objectbox.reactive.DataSubscription;
|
| 34 | +import io.objectbox.reactive.SubscriptionBuilder; |
32 | 35 |
|
| 36 | +/** |
| 37 | + * A {@link DataPublisher} that subscribes to an ObjectClassPublisher if there is at least one observer. |
| 38 | + * Publishing is requested if the ObjectClassPublisher reports changes, a subscription is |
| 39 | + * {@link SubscriptionBuilder#observer(DataObserver) observed} or {@link Query#publish()} is called. |
| 40 | + * For publishing the query is re-run and the result delivered to the current observers. |
| 41 | + * Results are published on a single thread, one at a time, in the order publishing was requested. |
| 42 | + */ |
33 | 43 | @Internal
|
34 |
| -class QueryPublisher<T> implements DataPublisher<List<T>> { |
| 44 | +class QueryPublisher<T> implements DataPublisher<List<T>>, Runnable { |
35 | 45 |
|
36 | 46 | private final Query<T> query;
|
37 | 47 | private final Box<T> box;
|
38 | 48 | private final Set<DataObserver<List<T>>> observers = new CopyOnWriteArraySet<>();
|
| 49 | + private final Deque<DataObserver<List<T>>> publishQueue = new ArrayDeque<>(); |
| 50 | + private volatile boolean publisherRunning = false; |
| 51 | + |
| 52 | + private static class AllObservers<T> implements DataObserver<List<T>> { |
| 53 | + @Override |
| 54 | + public void onData(List<T> data) { |
| 55 | + } |
| 56 | + } |
| 57 | + /** Placeholder observer if all observers should be notified. */ |
| 58 | + private final AllObservers<T> ALL_OBSERVERS = new AllObservers<>(); |
39 | 59 |
|
40 | 60 | private DataObserver<Class<T>> objectClassObserver;
|
41 | 61 | private DataSubscription objectClassSubscription;
|
@@ -76,26 +96,60 @@ public void onData(Class<T> objectClass) {
|
76 | 96 | }
|
77 | 97 |
|
78 | 98 | @Override
|
79 |
| - public void publishSingle(final DataObserver<List<T>> observer, @Nullable Object param) { |
80 |
| - box.getStore().internalScheduleThread(new Runnable() { |
81 |
| - @Override |
82 |
| - public void run() { |
83 |
| - List<T> result = query.find(); |
84 |
| - observer.onData(result); |
| 99 | + public void publishSingle(DataObserver<List<T>> observer, @Nullable Object param) { |
| 100 | + synchronized (publishQueue) { |
| 101 | + publishQueue.add(observer); |
| 102 | + if (!publisherRunning) { |
| 103 | + publisherRunning = true; |
| 104 | + box.getStore().internalScheduleThread(this); |
85 | 105 | }
|
86 |
| - }); |
| 106 | + } |
87 | 107 | }
|
88 | 108 |
|
89 | 109 | void publish() {
|
90 |
| - box.getStore().internalScheduleThread(new Runnable() { |
91 |
| - @Override |
92 |
| - public void run() { |
| 110 | + synchronized (publishQueue) { |
| 111 | + publishQueue.add(ALL_OBSERVERS); |
| 112 | + if (!publisherRunning) { |
| 113 | + publisherRunning = true; |
| 114 | + box.getStore().internalScheduleThread(this); |
| 115 | + } |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + @Override |
| 120 | + public void run() { |
| 121 | + /* |
| 122 | + * Process publish requests for this query on a single thread to avoid an older request |
| 123 | + * racing a new one (and causing outdated results to be delivered last). |
| 124 | + */ |
| 125 | + try { |
| 126 | + while (true) { |
| 127 | + // Get next observer(s). |
| 128 | + DataObserver<List<T>> observer; |
| 129 | + synchronized (publishQueue) { |
| 130 | + observer = publishQueue.pollFirst(); |
| 131 | + if (observer == null) { |
| 132 | + publisherRunning = false; |
| 133 | + break; |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + // Query, then notify observer(s). |
93 | 138 | List<T> result = query.find();
|
94 |
| - for (DataObserver<List<T>> observer : observers) { |
| 139 | + if (ALL_OBSERVERS.equals(observer)) { |
| 140 | + // Use current list of observers to avoid notifying unsubscribed observers. |
| 141 | + Set<DataObserver<List<T>>> observers = this.observers; |
| 142 | + for (DataObserver<List<T>> dataObserver : observers) { |
| 143 | + dataObserver.onData(result); |
| 144 | + } |
| 145 | + } else { |
95 | 146 | observer.onData(result);
|
96 | 147 | }
|
97 | 148 | }
|
98 |
| - }); |
| 149 | + } finally { |
| 150 | + // Re-set if wrapped code throws, otherwise this publisher can no longer publish. |
| 151 | + publisherRunning = false; |
| 152 | + } |
99 | 153 | }
|
100 | 154 |
|
101 | 155 | @Override
|
|
0 commit comments