treeMap = new TreeMap<>();
26 |
27 | Random random = new Random();
28 | for (int i = 0; i < numThreads*numUpdatesPerThread; i++) {
29 | pool.submit( () -> {
30 | treeMap.put(random.nextInt(10000), random.nextInt(10000));
31 | });
32 | }
33 |
34 | pool.shutdown();
35 | pool.awaitTermination(1, TimeUnit.DAYS);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/tree_map_corruption/java/src/main/java/ExploringTreeMap.java:
--------------------------------------------------------------------------------
1 | import java.lang.reflect.Field;
2 | import java.util.Arrays;
3 | import java.util.TreeMap;
4 |
5 | public class ExploringTreeMap {
6 |
7 | /**
8 | * In order to run you need to add these JVM args:
9 | *
10 | * --add-opens java.base/java.util=ALL-UNNAMED
11 | *
12 | * @param args
13 | */
14 | public static void main(String[] args) throws Exception {
15 |
16 | TreeMap treeMap = new TreeMap<>();
17 | for (int i = 0; i < 10; i++) {
18 | treeMap.put(i, i);
19 | }
20 |
21 | TreeMapExplorer treeMapExplorer = new TreeMapExplorer(treeMap);
22 | treeMapExplorer.print();
23 | }
24 |
25 |
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/tree_map_corruption/java/src/main/java/GrpcRepro.java:
--------------------------------------------------------------------------------
1 | import java.util.ArrayList;
2 | import java.util.List;
3 | import java.util.Random;
4 | import java.util.TreeMap;
5 | import java.util.concurrent.TimeUnit;
6 |
7 | import com.google.common.util.concurrent.ListenableFuture;
8 | import io.grpc.ManagedChannel;
9 | import io.grpc.ManagedChannelBuilder;
10 | import io.grpc.Server;
11 | import io.grpc.ServerBuilder;
12 | import io.grpc.stub.StreamObserver;
13 |
14 | /**
15 | * Reproduces the corrupted TreeMap issues through Grpc rather than directly through threads.
16 | *
17 | * Demonstrates that the simplified 2 thread reproduce code isn't that unrealistic. Exceptionsm
18 | * get accidentally swallowed all the time especially if there are RuntimeExceptions like NPEs.
19 | */
20 | public class GrpcRepro {
21 | public static void main(String[] args) throws Exception {
22 | final Server server = ServerBuilder.forPort(0)
23 | .addService(new ReceiptProcessorServiceImpl())
24 | .build()
25 | .start();
26 | Runtime.getRuntime().addShutdownHook(new Thread(() -> {
27 | server.shutdownNow();
28 | }));
29 |
30 | final int port = server.getPort();
31 | System.out.println("Server started, listening on " + port);
32 |
33 | final ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", port)
34 | .usePlaintext()
35 | .build();
36 | final ReceiptProcessorServiceGrpc.ReceiptProcessorServiceFutureStub futureStub = ReceiptProcessorServiceGrpc.newFutureStub(channel);
37 |
38 | List> futures = new ArrayList<>();
39 | Random random = new Random();
40 | for (int i = 0; i < 100_000; i++) {
41 | ReceiptProcessorServiceOuterClass.AddReceiptRequest request = ReceiptProcessorServiceOuterClass.AddReceiptRequest.newBuilder()
42 | .setTimestamp(random.nextInt(10_000))
43 | .setTotalPrice(random.nextInt(10_000))
44 | .build();
45 | futures.add(futureStub.addReceipt(request));
46 | }
47 |
48 | for (ListenableFuture> future : futures) {
49 | future.get();
50 | }
51 |
52 | server.shutdown();
53 | server.awaitTermination(1, TimeUnit.DAYS);
54 | }
55 | }
56 |
57 | class ReceiptProcessorServiceImpl extends ReceiptProcessorServiceGrpc.ReceiptProcessorServiceImplBase {
58 | private final TreeMap receipts = new TreeMap<>();
59 |
60 | @Override
61 | public void addReceipt(
62 | ReceiptProcessorServiceOuterClass.AddReceiptRequest req,
63 | StreamObserver responseObserver
64 | ) {
65 | int timestamp = req.getTimestamp();
66 | int totalPrice = req.getTotalPrice();
67 | receipts.put(timestamp, totalPrice);
68 | ReceiptProcessorServiceOuterClass.AddReceiptResponse response = ReceiptProcessorServiceOuterClass.AddReceiptResponse.newBuilder().build();
69 | responseObserver.onNext(response);
70 | responseObserver.onCompleted();
71 | }
72 | }
--------------------------------------------------------------------------------
/tree_map_corruption/java/src/main/java/GrpcThrowNPE.java:
--------------------------------------------------------------------------------
1 | import java.util.ArrayList;
2 | import java.util.List;
3 | import java.util.Random;
4 | import java.util.TreeMap;
5 | import java.util.concurrent.TimeUnit;
6 |
7 | import com.google.common.util.concurrent.ListenableFuture;
8 | import io.grpc.ManagedChannel;
9 | import io.grpc.ManagedChannelBuilder;
10 | import io.grpc.Server;
11 | import io.grpc.ServerBuilder;
12 | import io.grpc.stub.StreamObserver;
13 |
14 | public class GrpcThrowNPE {
15 | public static void main(String[] args) throws Exception {
16 | final Server server = ServerBuilder.forPort(0)
17 | .addService(new ReceiptProcessorThrowNPEImpl())
18 | .build()
19 | .start();
20 | Runtime.getRuntime().addShutdownHook(new Thread(() -> {
21 | server.shutdownNow();
22 | }));
23 |
24 | final int port = server.getPort();
25 | System.out.println("Server started, listening on " + port);
26 |
27 | final ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", port)
28 | .usePlaintext()
29 | .build();
30 | final ReceiptProcessorServiceGrpc.ReceiptProcessorServiceFutureStub futureStub = ReceiptProcessorServiceGrpc.newFutureStub(channel);
31 |
32 | List> futures = new ArrayList<>();
33 | Random random = new Random();
34 | for (int i = 0; i < 100; i++) {
35 | ReceiptProcessorServiceOuterClass.AddReceiptRequest request = ReceiptProcessorServiceOuterClass.AddReceiptRequest.newBuilder()
36 | .setTimestamp(random.nextInt(10_000))
37 | .setTotalPrice(random.nextInt(10_000))
38 | .build();
39 | futures.add(futureStub.addReceipt(request));
40 | }
41 |
42 | for (ListenableFuture> future : futures) {
43 | future.get();
44 | }
45 |
46 | server.shutdown();
47 | server.awaitTermination(1, TimeUnit.DAYS);
48 | }
49 | }
50 |
51 | class ReceiptProcessorThrowNPEImpl extends ReceiptProcessorServiceGrpc.ReceiptProcessorServiceImplBase {
52 | @Override
53 | public void addReceipt(
54 | ReceiptProcessorServiceOuterClass.AddReceiptRequest req,
55 | StreamObserver responseObserver
56 | ) {
57 | throw new NullPointerException();
58 | }
59 | }
--------------------------------------------------------------------------------
/tree_map_corruption/java/src/main/java/ProtectedSimpleRepro.java:
--------------------------------------------------------------------------------
1 | import java.util.*;
2 |
3 | public class ProtectedSimpleRepro {
4 |
5 | public static void main(String[] args) throws Exception {
6 |
7 | final int numThreads;
8 | if (args.length >= 1) {
9 | numThreads = Integer.parseInt(args[0]);
10 | } else {
11 | numThreads = 5;
12 | }
13 |
14 | final int numUpdates;
15 | if (args.length >= 2) {
16 | numUpdates = Integer.parseInt(args[1]);
17 | } else {
18 | numUpdates = 1000;
19 | }
20 |
21 | final ProtectedTreeMap treeMap = new ProtectedTreeMap<>();
22 |
23 | List threads = new ArrayList<>();
24 | for (int i = 0; i < numThreads; i++) {
25 | threads.add(new Thread(() -> {
26 | Random random = new Random();
27 | for(int j = 0; j < numUpdates; j++) {
28 | try {
29 | treeMap.put(random.nextInt(1000), random.nextInt(1000));
30 | } catch (NullPointerException e) {
31 | // let it keep going so we can reproduce the issue.
32 | }
33 | }
34 | }));
35 | }
36 |
37 | for (Thread thread : threads) {
38 | thread.start();
39 | }
40 |
41 | for (Thread thread : threads) {
42 | thread.join();
43 | }
44 |
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/tree_map_corruption/java/src/main/java/ReproDetectDump.java:
--------------------------------------------------------------------------------
1 | import java.lang.reflect.Field;
2 | import java.util.ArrayList;
3 | import java.util.Arrays;
4 | import java.util.HashSet;
5 | import java.util.IdentityHashMap;
6 | import java.util.List;
7 | import java.util.Random;
8 | import java.util.Set;
9 | import java.util.TreeMap;
10 |
11 | /**
12 | * In order to run on jdk11+ you need to add these JVM args:
13 | *
14 | * --add-opens java.base/java.util=ALL-UNNAMED
15 | *
16 | *
17 | * Need to run many experiments in order to reproduce the issue with so few entries.
18 | */
19 | public class ReproDetectDump {
20 |
21 |
22 | public static void main(String[] args) throws Exception {
23 |
24 | final int numThreads;
25 | if (args.length >= 1) {
26 | numThreads = Integer.parseInt(args[0]);
27 | } else {
28 | numThreads = 3;
29 | }
30 |
31 | final int numUpdates;
32 | if (args.length >= 2) {
33 | numUpdates = Integer.parseInt(args[1]);
34 | } else {
35 | numUpdates = 4;
36 | }
37 |
38 | final int maxVal;
39 | if (args.length >= 3) {
40 | maxVal = Integer.parseInt(args[2]);
41 | } else {
42 | maxVal = 30;
43 | }
44 |
45 | final int numExperiments;
46 | if (args.length >= 4) {
47 | numExperiments = Integer.parseInt(args[3]);
48 | } else {
49 | numExperiments = 1000;
50 | }
51 |
52 | for (int experiment = 1; experiment <= numExperiments; experiment++) {
53 | final TreeMap treeMap = new TreeMap<>();
54 |
55 |
56 | List threads = new ArrayList<>();
57 | for (int i = 0; i < numThreads; i++) {
58 | threads.add(new Thread(() -> {
59 | Random random = new Random();
60 | for (int j = 0; j < numUpdates; j++) {
61 | try {
62 | treeMap.put(random.nextInt(maxVal), random.nextInt(1000));
63 | } catch (NullPointerException e) {
64 | // let it keep going so we can reproduce the issue.
65 | }
66 | }
67 | }));
68 | }
69 |
70 | DetectorThread detectorThread = new DetectorThread(treeMap, threads);
71 |
72 | for (Thread thread : threads) {
73 | thread.start();
74 | }
75 | detectorThread.start();
76 |
77 | for (Thread thread : threads) {
78 | thread.join();
79 | }
80 |
81 | detectorThread.interrupt();
82 | }
83 | }
84 |
85 | }
86 |
87 | class DetectorThread extends Thread {
88 | private final TreeMap treeMap;
89 | private final List threadsToStop;
90 | private static final Field treeMapRootField;
91 | private static final Field treeMapEntryLeft;
92 | private static final Field treeMapEntryRight;
93 | private static final Field treeMapEntryKey;
94 |
95 | static {
96 | try {
97 | treeMapRootField = TreeMap.class.getDeclaredField("root");
98 | treeMapRootField.setAccessible(true);
99 |
100 | Class treeMapEntryClass = Arrays.stream(TreeMap.class.getDeclaredClasses())
101 | .filter(clazz -> "java.util.TreeMap$Entry".equals(clazz.getName()))
102 | .findAny()
103 | .get();
104 |
105 | treeMapEntryLeft = treeMapEntryClass.getDeclaredField("left");
106 | treeMapEntryLeft.setAccessible(true);
107 | treeMapEntryRight = treeMapEntryClass.getDeclaredField("right");
108 | treeMapEntryRight.setAccessible(true);
109 | treeMapEntryKey = treeMapEntryClass.getDeclaredField("key");
110 | treeMapEntryKey.setAccessible(true);
111 |
112 | } catch (NoSuchFieldException e) {
113 | throw new RuntimeException(e);
114 | }
115 | }
116 |
117 | public DetectorThread(
118 | TreeMap treeMap,
119 | List threadsToStop
120 | ) {
121 | this.treeMap = treeMap;
122 | this.threadsToStop = threadsToStop;
123 | }
124 |
125 | private boolean isLoopDetected() throws Exception {
126 | return isLoopDetected(treeMapRootField.get(treeMap), new IdentityHashMap<>());
127 | }
128 |
129 | /**
130 | * DFS to detect loop
131 | */
132 | private boolean isLoopDetected(Object treeMapEntry, IdentityHashMap