|
36 | 36 |
|
37 | 37 | import com.google.api.core.ApiFuture;
|
38 | 38 | import com.google.api.core.ApiFutures;
|
| 39 | +import com.google.api.core.SettableApiFuture; |
39 | 40 | import com.google.api.gax.batching.BatcherImpl.BatcherReference;
|
40 | 41 | import com.google.api.gax.rpc.ApiCallContext;
|
41 | 42 | import com.google.api.gax.rpc.UnaryCallable;
|
42 | 43 | import com.google.api.gax.rpc.testing.FakeBatchableApi.LabeledIntList;
|
43 | 44 | import com.google.api.gax.rpc.testing.FakeBatchableApi.LabeledIntSquarerCallable;
|
44 | 45 | import com.google.api.gax.rpc.testing.FakeBatchableApi.SquarerBatchingDescriptorV2;
|
| 46 | +import com.google.common.collect.ImmutableList; |
45 | 47 | import com.google.common.collect.Queues;
|
46 | 48 | import java.util.ArrayList;
|
47 | 49 | import java.util.List;
|
|
56 | 58 | import java.util.concurrent.ScheduledExecutorService;
|
57 | 59 | import java.util.concurrent.ScheduledFuture;
|
58 | 60 | import java.util.concurrent.TimeUnit;
|
| 61 | +import java.util.concurrent.TimeoutException; |
59 | 62 | import java.util.concurrent.atomic.AtomicBoolean;
|
60 | 63 | import java.util.concurrent.atomic.AtomicInteger;
|
61 | 64 | import java.util.logging.Filter;
|
@@ -644,6 +647,70 @@ public boolean isLoggable(LogRecord record) {
|
644 | 647 | }
|
645 | 648 | }
|
646 | 649 |
|
| 650 | + @Test |
| 651 | + public void testCloseRace() throws ExecutionException, InterruptedException, TimeoutException { |
| 652 | + int iterations = 1_000_000; |
| 653 | + |
| 654 | + ExecutorService executor = Executors.newFixedThreadPool(100); |
| 655 | + |
| 656 | + try { |
| 657 | + List<Future<?>> closeFutures = new ArrayList<>(); |
| 658 | + |
| 659 | + for (int i = 0; i < iterations; i++) { |
| 660 | + final SettableApiFuture<List<Integer>> result = SettableApiFuture.create(); |
| 661 | + |
| 662 | + UnaryCallable<LabeledIntList, List<Integer>> callable = |
| 663 | + new UnaryCallable<LabeledIntList, List<Integer>>() { |
| 664 | + @Override |
| 665 | + public ApiFuture<List<Integer>> futureCall( |
| 666 | + LabeledIntList request, ApiCallContext context) { |
| 667 | + return result; |
| 668 | + } |
| 669 | + }; |
| 670 | + final Batcher<Integer, Integer> batcher = |
| 671 | + new BatcherImpl<>( |
| 672 | + SQUARER_BATCHING_DESC_V2, callable, labeledIntList, batchingSettings, EXECUTOR); |
| 673 | + |
| 674 | + batcher.add(1); |
| 675 | + |
| 676 | + executor.execute( |
| 677 | + new Runnable() { |
| 678 | + @Override |
| 679 | + public void run() { |
| 680 | + result.set(ImmutableList.of(1)); |
| 681 | + } |
| 682 | + }); |
| 683 | + Future<?> f = |
| 684 | + executor.submit( |
| 685 | + new Runnable() { |
| 686 | + @Override |
| 687 | + public void run() { |
| 688 | + try { |
| 689 | + batcher.close(); |
| 690 | + } catch (InterruptedException e) { |
| 691 | + Thread.currentThread().interrupt(); |
| 692 | + throw new RuntimeException(e); |
| 693 | + } |
| 694 | + } |
| 695 | + }); |
| 696 | + |
| 697 | + closeFutures.add(f); |
| 698 | + } |
| 699 | + |
| 700 | + // Make sure that none hang |
| 701 | + for (Future<?> f : closeFutures) { |
| 702 | + try { |
| 703 | + // Should never take this long, but padded just in case this runs on a limited machine |
| 704 | + f.get(1, TimeUnit.MINUTES); |
| 705 | + } catch (TimeoutException e) { |
| 706 | + assertWithMessage("BatcherImpl.close() is deadlocked").fail(); |
| 707 | + } |
| 708 | + } |
| 709 | + } finally { |
| 710 | + executor.shutdownNow(); |
| 711 | + } |
| 712 | + } |
| 713 | + |
647 | 714 | private void testElementTriggers(BatchingSettings settings) throws Exception {
|
648 | 715 | underTest =
|
649 | 716 | new BatcherImpl<>(
|
|
0 commit comments