|
15 | 15 | package com.google.common.util.concurrent; |
16 | 16 |
|
17 | 17 | import static com.google.common.truth.Truth.assertThat; |
| 18 | +import static com.google.common.util.concurrent.Futures.allAsList; |
18 | 19 | import static com.google.common.util.concurrent.Futures.getDone; |
19 | 20 | import static com.google.common.util.concurrent.MoreExecutors.directExecutor; |
20 | 21 | import static java.util.concurrent.TimeUnit.SECONDS; |
21 | 22 |
|
| 23 | +import com.google.common.annotations.GwtIncompatible; |
| 24 | +import com.google.common.testing.GcFinalization; |
22 | 25 | import com.google.common.testing.TestLogHandler; |
| 26 | +import com.google.j2objc.annotations.J2ObjCIncompatible; |
| 27 | +import java.lang.ref.WeakReference; |
23 | 28 | import java.util.ArrayList; |
24 | 29 | import java.util.List; |
25 | 30 | import java.util.concurrent.Callable; |
@@ -146,6 +151,36 @@ public Boolean call() { |
146 | 151 | assertThat(getDone(future2)).isFalse(); |
147 | 152 | } |
148 | 153 |
|
| 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 | + |
149 | 184 | public void testCancellationDuringReentrancy() throws Exception { |
150 | 185 | TestLogHandler logHandler = new TestLogHandler(); |
151 | 186 | Logger.getLogger(AbstractFuture.class.getName()).addHandler(logHandler); |
@@ -191,6 +226,171 @@ public Void call() { |
191 | 226 | assertThat(logHandler.getStoredLogRecords()).isEmpty(); |
192 | 227 | } |
193 | 228 |
|
| 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 | + |
194 | 394 | public void testToString() { |
195 | 395 | Future<?> first = serializer.submitAsync(firstCallable, directExecutor()); |
196 | 396 | TestCallable secondCallable = new TestCallable(SettableFuture.<Void>create()); |
|
0 commit comments