Skip to content

Commit fe17b98

Browse files
committed
Add a new public API to group Deferreds and preserve their order.
1 parent 4e56d7e commit fe17b98

File tree

3 files changed

+108
-24
lines changed

3 files changed

+108
-24
lines changed

NEWS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
The SUAsync Library - User visible changes.
22

3+
* Version 1.4.0 (2013-??-??) [???????]
4+
5+
New public APIs:
6+
- `groupInOrder(Collection)' was added to provide an alternative to
7+
`group(...)' that preserves the order of the results.
8+
9+
310
* Version 1.3.3 (2013-06-16) [fc509ea]
411

512
Noteworthy changes:

src/Deferred.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -889,11 +889,28 @@ public String toString() {
889889
* <p>
890890
* There's no guarantee on the order of the results in the deferred list
891891
* returned, it depends on the order in which the {@code Deferred}s in the
892-
* group complete.
892+
* group complete. If you want to preserve the order, use
893+
* {@link #groupInOrder(Collection)} instead.
893894
*/
894895
public static <T>
895896
Deferred<ArrayList<T>> group(final Collection<Deferred<T>> deferreds) {
896-
return new DeferredGroup<T>(deferreds).getDeferred();
897+
return new DeferredGroup<T>(deferreds, false).getDeferred();
898+
}
899+
900+
/**
901+
* Groups multiple {@code Deferred}s together in a single one.
902+
* <p>
903+
* This is the same thing as {@link #group(Collection)} except that we
904+
* guarantee we preserve the order of the {@code Deferred}s.
905+
* @param deferreds All the {@code Deferred}s to group together.
906+
* @return A new {@code Deferred} that will be called back once all the
907+
* {@code Deferred}s given in argument have been called back.
908+
* @see #group(Collection)
909+
* @since 1.4
910+
*/
911+
public static <T>
912+
Deferred<ArrayList<T>> groupInOrder(final Collection<Deferred<T>> deferreds) {
913+
return new DeferredGroup<T>(deferreds, true).getDeferred();
897914
}
898915

899916
/**
@@ -915,7 +932,7 @@ Deferred<ArrayList<T>> group(final Deferred<T> d1, final Deferred<T> d2) {
915932
final ArrayList<Deferred<T>> tmp = new ArrayList<Deferred<T>>(2);
916933
tmp.add(d1);
917934
tmp.add(d2);
918-
return new DeferredGroup<T>(tmp).getDeferred();
935+
return new DeferredGroup<T>(tmp, false).getDeferred();
919936
}
920937

921938
/**
@@ -941,7 +958,7 @@ Deferred<ArrayList<T>> group(final Deferred<T> d1,
941958
tmp.add(d1);
942959
tmp.add(d2);
943960
tmp.add(d3);
944-
return new DeferredGroup<T>(tmp).getDeferred();
961+
return new DeferredGroup<T>(tmp, false).getDeferred();
945962
}
946963

947964
/**

src/DeferredGroup.java

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ final class DeferredGroup<T> {
4141
*/
4242
private final Deferred<ArrayList<T>> parent = new Deferred<ArrayList<T>>();
4343

44-
/** How many results do we expect? */
45-
private final int nresults;
44+
/**
45+
* How many results do we expect?.
46+
* Need to acquires this' monitor before changing.
47+
*/
48+
private int nresults;
4649

4750
/**
4851
* All the results for each Deferred we're grouping.
@@ -54,8 +57,17 @@ final class DeferredGroup<T> {
5457
/**
5558
* Constructor.
5659
* @param deferreds All the {@link Deferred}s we want to group.
60+
* @param ordered If true, the results will be presented in the same order
61+
* as the {@link Deferred}s are in the {@code deferreds} argument.
62+
* If false, results will be presented in the order in which they arrive.
63+
* In other words, assuming that {@code deferreds} is a list of three
64+
* {@link Deferred} objects {@code [A, B, C]}, then if {@code ordered} is
65+
* true, {@code results} will be {@code [result A, result B, result C]}
66+
* whereas if {@code ordered} is false then the order in {@code results}
67+
* is determined by the order in which callbacks fire on A, B, and C.
5768
*/
58-
public DeferredGroup(final Collection<Deferred<T>> deferreds) {
69+
public DeferredGroup(final Collection<Deferred<T>> deferreds,
70+
final boolean ordered) {
5971
nresults = deferreds.size();
6072
results = new ArrayList<Object>(nresults);
6173

@@ -64,6 +76,7 @@ public DeferredGroup(final Collection<Deferred<T>> deferreds) {
6476
return;
6577
}
6678

79+
// Callback used to collect results in the order in which they appear.
6780
final class Notify<T> implements Callback<T, T> {
6881
public T call(final T arg) {
6982
recordCompletion(arg);
@@ -74,10 +87,37 @@ public String toString() {
7487
}
7588
};
7689

77-
final Notify<T> notify = new Notify<T>();
90+
// Callback that preserves the original orders of the Deferreds.
91+
final class NotifyOrdered<T> implements Callback<T, T> {
92+
private final int index;
93+
NotifyOrdered(int index) {
94+
this.index = index;
95+
}
96+
public T call(final T arg) {
97+
recordCompletion(arg, index);
98+
return arg;
99+
}
100+
public String toString() {
101+
return "notify #" + index + " DeferredGroup@"
102+
+ DeferredGroup.super.hashCode();
103+
}
104+
};
78105

79-
for (final Deferred<T> d : deferreds) {
80-
d.addBoth(notify);
106+
if (ordered) {
107+
int i = 0;
108+
for (final Deferred<T> d : deferreds) {
109+
results.add(null); // ensures results.set(i, result) is valid.
110+
// Note: it's important to add the callback after the line above,
111+
// as the callback can fire at any time once it's been added, and
112+
// if it fires before results.set(i, result) is valid, we'll get
113+
// an IndexOutOfBoundsException.
114+
d.addBoth(new NotifyOrdered<T>(i++));
115+
}
116+
} else {
117+
final Notify<T> notify = new Notify<T>();
118+
for (final Deferred<T> d : deferreds) {
119+
d.addBoth(notify);
120+
}
81121
}
82122
}
83123

@@ -93,30 +133,50 @@ public Deferred<ArrayList<T>> getDeferred() {
93133
* @param result The result of the deferred.
94134
*/
95135
private void recordCompletion(final Object result) {
96-
int size;
136+
int left;
97137
synchronized (this) {
98138
results.add(result);
99-
size = results.size();
139+
left = --nresults;
140+
}
141+
if (left == 0) {
142+
done();
100143
}
101-
if (size == nresults) {
102-
// From this point on, we no longer need to synchronize in order to
103-
// access `results' since we know we're done, so no other thread is
104-
// going to call this method on this instance again.
105-
for (final Object r : results) {
106-
if (r instanceof Exception) {
107-
parent.callback(new DeferredGroupException(results, (Exception) r));
108-
return;
109-
}
144+
}
145+
146+
/**
147+
* Called back when one of the {@link Deferred} in the group completes.
148+
* @param result The result of the deferred.
149+
* @param index The index of the result.
150+
*/
151+
private void recordCompletion(final Object result, final int index) {
152+
int left;
153+
synchronized (this) {
154+
results.set(index, result);
155+
left = --nresults;
156+
}
157+
if (left == 0) {
158+
done();
159+
}
160+
}
161+
162+
/** Called once we have obtained all the results of this group. */
163+
private void done() {
164+
// From this point on, we no longer need to synchronize in order to
165+
// access `results' since we know we're done, so no other thread is
166+
// going to call recordCompletion() again.
167+
for (final Object r : results) {
168+
if (r instanceof Exception) {
169+
parent.callback(new DeferredGroupException(results, (Exception) r));
170+
return;
110171
}
111-
parent.callback(results);
112172
}
173+
parent.callback(results);
113174
}
114175

115176
public String toString() {
116177
return "DeferredGroup"
117178
+ "(parent=" + parent
118-
+ ", # results=" + results.size() + " / " + nresults
119-
+ ')';
179+
+ ", # results=" + results.size() + " / " + nresults + " left)";
120180
}
121181

122182
}

0 commit comments

Comments
 (0)