|
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