Skip to content

Commit 6cc0bb9

Browse files
clmnick-someone
authored andcommitted
Avoid reentrancy in ExecutionSequencer.
RELNOTES=n/a ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=317189958
1 parent ac0c342 commit 6cc0bb9

File tree

5 files changed

+826
-44
lines changed

5 files changed

+826
-44
lines changed

android/guava-tests/test/com/google/common/util/concurrent/ExecutionSequencerTest.java

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,16 @@
1515
package com.google.common.util.concurrent;
1616

1717
import static com.google.common.truth.Truth.assertThat;
18+
import static com.google.common.util.concurrent.Futures.allAsList;
1819
import static com.google.common.util.concurrent.Futures.getDone;
1920
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
2021
import static java.util.concurrent.TimeUnit.SECONDS;
2122

23+
import com.google.common.annotations.GwtIncompatible;
24+
import com.google.common.testing.GcFinalization;
2225
import com.google.common.testing.TestLogHandler;
26+
import com.google.j2objc.annotations.J2ObjCIncompatible;
27+
import java.lang.ref.WeakReference;
2328
import java.util.ArrayList;
2429
import java.util.List;
2530
import java.util.concurrent.Callable;
@@ -146,6 +151,36 @@ public Boolean call() {
146151
assertThat(getDone(future2)).isFalse();
147152
}
148153

154+
@GwtIncompatible
155+
@J2ObjCIncompatible // gc
156+
@AndroidIncompatible
157+
public void testCancellationWithReferencedObject() throws Exception {
158+
Object toBeGCed = new Object();
159+
WeakReference<Object> ref = new WeakReference<>(toBeGCed);
160+
final SettableFuture<Void> settableFuture = SettableFuture.create();
161+
ListenableFuture<?> ignored =
162+
serializer.submitAsync(
163+
new AsyncCallable<Void>() {
164+
@Override
165+
public ListenableFuture<Void> call() {
166+
return settableFuture;
167+
}
168+
},
169+
directExecutor());
170+
serializer.submit(toStringCallable(toBeGCed), directExecutor()).cancel(true);
171+
toBeGCed = null;
172+
GcFinalization.awaitClear(ref);
173+
}
174+
175+
private static Callable<String> toStringCallable(final Object object) {
176+
return new Callable<String>() {
177+
@Override
178+
public String call() {
179+
return object.toString();
180+
}
181+
};
182+
}
183+
149184
public void testCancellationDuringReentrancy() throws Exception {
150185
TestLogHandler logHandler = new TestLogHandler();
151186
Logger.getLogger(AbstractFuture.class.getName()).addHandler(logHandler);
@@ -191,6 +226,171 @@ public Void call() {
191226
assertThat(logHandler.getStoredLogRecords()).isEmpty();
192227
}
193228

229+
public void testAvoidsStackOverflow_manySubmitted() throws Exception {
230+
final SettableFuture<Void> settableFuture = SettableFuture.create();
231+
ArrayList<ListenableFuture<Void>> results = new ArrayList<>(50_001);
232+
results.add(
233+
serializer.submitAsync(
234+
new AsyncCallable<Void>() {
235+
@Override
236+
public ListenableFuture<Void> call() {
237+
return settableFuture;
238+
}
239+
},
240+
directExecutor()));
241+
for (int i = 0; i < 50_000; i++) {
242+
results.add(serializer.submit(Callables.<Void>returning(null), directExecutor()));
243+
}
244+
settableFuture.set(null);
245+
getDone(allAsList(results));
246+
}
247+
248+
public void testAvoidsStackOverflow_manyCancelled() throws Exception {
249+
final SettableFuture<Void> settableFuture = SettableFuture.create();
250+
ListenableFuture<Void> unused =
251+
serializer.submitAsync(
252+
new AsyncCallable<Void>() {
253+
@Override
254+
public ListenableFuture<Void> call() {
255+
return settableFuture;
256+
}
257+
},
258+
directExecutor());
259+
for (int i = 0; i < 50_000; i++) {
260+
serializer.submit(Callables.<Void>returning(null), directExecutor()).cancel(true);
261+
}
262+
ListenableFuture<Integer> stackDepthCheck =
263+
serializer.submit(
264+
new Callable<Integer>() {
265+
@Override
266+
public Integer call() {
267+
return Thread.currentThread().getStackTrace().length;
268+
}
269+
},
270+
directExecutor());
271+
settableFuture.set(null);
272+
assertThat(getDone(stackDepthCheck))
273+
.isLessThan(Thread.currentThread().getStackTrace().length + 100);
274+
}
275+
276+
public void testAvoidsStackOverflow_alternatingCancelledAndSubmitted() throws Exception {
277+
final SettableFuture<Void> settableFuture = SettableFuture.create();
278+
ListenableFuture<Void> unused =
279+
serializer.submitAsync(
280+
new AsyncCallable<Void>() {
281+
@Override
282+
public ListenableFuture<Void> call() {
283+
return settableFuture;
284+
}
285+
},
286+
directExecutor());
287+
for (int i = 0; i < 25_000; i++) {
288+
serializer.submit(Callables.<Void>returning(null), directExecutor()).cancel(true);
289+
unused = serializer.submit(Callables.<Void>returning(null), directExecutor());
290+
}
291+
ListenableFuture<Integer> stackDepthCheck =
292+
serializer.submit(
293+
new Callable<Integer>() {
294+
@Override
295+
public Integer call() {
296+
return Thread.currentThread().getStackTrace().length;
297+
}
298+
},
299+
directExecutor());
300+
settableFuture.set(null);
301+
assertThat(getDone(stackDepthCheck))
302+
.isLessThan(Thread.currentThread().getStackTrace().length + 100);
303+
}
304+
305+
private static final class LongHolder {
306+
long count;
307+
}
308+
309+
private static final int ITERATION_COUNT = 50_000;
310+
private static final int DIRECT_EXECUTIONS_PER_THREAD = 100;
311+
312+
@GwtIncompatible // threads
313+
314+
public void testAvoidsStackOverflow_multipleThreads() throws Exception {
315+
final LongHolder holder = new LongHolder();
316+
final ArrayList<ListenableFuture<Integer>> lengthChecks = new ArrayList<>();
317+
final List<Integer> completeLengthChecks;
318+
final int baseStackDepth;
319+
ExecutorService service = Executors.newFixedThreadPool(5);
320+
try {
321+
// Avoid counting frames from the executor itself, or the ExecutionSequencer
322+
baseStackDepth =
323+
serializer
324+
.submit(
325+
new Callable<Integer>() {
326+
@Override
327+
public Integer call() {
328+
return Thread.currentThread().getStackTrace().length;
329+
}
330+
},
331+
service)
332+
.get();
333+
final SettableFuture<Void> settableFuture = SettableFuture.create();
334+
ListenableFuture<?> unused =
335+
serializer.submitAsync(
336+
new AsyncCallable<Void>() {
337+
@Override
338+
public ListenableFuture<Void> call() {
339+
return settableFuture;
340+
}
341+
},
342+
directExecutor());
343+
for (int i = 0; i < 50_000; i++) {
344+
if (i % DIRECT_EXECUTIONS_PER_THREAD == 0) {
345+
// after some number of iterations, switch threads
346+
unused =
347+
serializer.submit(
348+
new Callable<Void>() {
349+
@Override
350+
public Void call() {
351+
holder.count++;
352+
return null;
353+
}
354+
},
355+
service);
356+
} else if (i % DIRECT_EXECUTIONS_PER_THREAD == DIRECT_EXECUTIONS_PER_THREAD - 1) {
357+
// When at max depth, record stack trace depth
358+
lengthChecks.add(
359+
serializer.submit(
360+
new Callable<Integer>() {
361+
@Override
362+
public Integer call() {
363+
holder.count++;
364+
return Thread.currentThread().getStackTrace().length;
365+
}
366+
},
367+
directExecutor()));
368+
} else {
369+
// Otherwise, schedule a task on directExecutor
370+
unused =
371+
serializer.submit(
372+
new Callable<Void>() {
373+
@Override
374+
public Void call() {
375+
holder.count++;
376+
return null;
377+
}
378+
},
379+
directExecutor());
380+
}
381+
}
382+
settableFuture.set(null);
383+
completeLengthChecks = allAsList(lengthChecks).get();
384+
} finally {
385+
service.shutdown();
386+
}
387+
assertThat(holder.count).isEqualTo(ITERATION_COUNT);
388+
for (int length : completeLengthChecks) {
389+
// Verify that at max depth, less than one stack frame per submitted task was consumed
390+
assertThat(length - baseStackDepth).isLessThan(DIRECT_EXECUTIONS_PER_THREAD / 2);
391+
}
392+
}
393+
194394
public void testToString() {
195395
Future<?> first = serializer.submitAsync(firstCallable, directExecutor());
196396
TestCallable secondCallable = new TestCallable(SettableFuture.<Void>create());

0 commit comments

Comments
 (0)