blob: 1f843a782a32485cd41e8f91a4dfe5419a2cc91b [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2015 The Bazel Authors. All rights reserved.
Nathan Harmata53bd3bf2015-04-03 16:12:59 +00002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.skyframe;
15
Googler2fa2c1e2015-06-19 17:16:36 +000016import static com.google.common.truth.Truth.assertThat;
Googler2fa2c1e2015-06-19 17:16:36 +000017import static org.junit.Assert.fail;
18
tomlua155b532017-11-08 20:12:47 +010019import com.google.common.base.Preconditions;
Nathan Harmatac7e974a2015-10-02 22:07:35 +000020import com.google.common.collect.ImmutableList;
Googler2fa2c1e2015-06-19 17:16:36 +000021import com.google.common.collect.Iterables;
22import com.google.common.collect.Sets;
23import com.google.devtools.build.lib.concurrent.ExecutorUtil;
Janak Ramakrishnan52455102015-08-03 17:05:05 +000024import com.google.devtools.build.lib.testutil.TestRunnableWrapper;
Googler2fa2c1e2015-06-19 17:16:36 +000025import com.google.devtools.build.lib.testutil.TestUtils;
26import com.google.devtools.build.lib.util.GroupedList.GroupedListHelper;
27import com.google.devtools.build.skyframe.GraphTester.StringValue;
28import com.google.devtools.build.skyframe.NodeEntry.DependencyState;
Nathan Harmata3b47b1f2016-07-26 18:41:41 +000029import com.google.devtools.build.skyframe.QueryableGraph.Reason;
Googler2fa2c1e2015-06-19 17:16:36 +000030import java.util.ArrayList;
31import java.util.List;
32import java.util.Map;
33import java.util.Random;
34import java.util.Set;
35import java.util.concurrent.CountDownLatch;
36import java.util.concurrent.ExecutorService;
37import java.util.concurrent.Executors;
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +000038import java.util.concurrent.TimeUnit;
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +000039import org.junit.Before;
40import org.junit.Test;
Googler2fa2c1e2015-06-19 17:16:36 +000041
Googler2dd4c182016-10-31 14:54:37 +000042/** Base class for sanity tests on {@link EvaluableGraph} implementations. */
43public abstract class GraphTest {
Nathan Harmata3b8deab2015-10-28 21:18:35 +000044 protected ProcessableGraph graph;
45 protected TestRunnableWrapper wrapper;
Shreya Bhattaraicc808782016-02-10 22:17:00 +000046 private final Version startingVersion = getStartingVersion();
Janak Ramakrishnan52455102015-08-03 17:05:05 +000047
48 // This code should really be in a @Before method, but @Before methods are executed from the
49 // top down, and this class's @Before method calls #getGraph, so makeGraph must have already
50 // been called.
51 protected abstract void makeGraph() throws Exception;
52
53 protected abstract ProcessableGraph getGraph(Version version) throws Exception;
54
Shreya Bhattaraicc808782016-02-10 22:17:00 +000055 protected abstract Version getStartingVersion();
56
57 protected abstract Version getNextVersion(Version version);
Nathan Harmata53bd3bf2015-04-03 16:12:59 +000058
nharmataba14f672017-12-18 12:36:15 -080059 protected boolean checkRdeps() {
60 return true;
61 }
62
Nathan Harmata53bd3bf2015-04-03 16:12:59 +000063 @Before
Janak Ramakrishnan52455102015-08-03 17:05:05 +000064 public void init() throws Exception {
65 makeGraph();
Shreya Bhattaraicc808782016-02-10 22:17:00 +000066 Version startingVersion = getStartingVersion();
Janak Ramakrishnan52455102015-08-03 17:05:05 +000067 this.graph = getGraph(startingVersion);
68 this.wrapper = new TestRunnableWrapper("GraphConcurrencyTest");
Nathan Harmata53bd3bf2015-04-03 16:12:59 +000069 }
70
Nathan Harmata3b8deab2015-10-28 21:18:35 +000071 protected SkyKey key(String name) {
janakr5fb2a482018-03-02 17:48:57 -080072 return GraphTester.toSkyKey(name);
Nathan Harmata53bd3bf2015-04-03 16:12:59 +000073 }
74
75 @Test
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +000076 public void createIfAbsentBatchSanity() throws InterruptedException {
Nathan Harmata3b47b1f2016-07-26 18:41:41 +000077 graph.createIfAbsentBatch(null, Reason.OTHER, ImmutableList.of(key("cat"), key("dog")));
Nathan Harmata53bd3bf2015-04-03 16:12:59 +000078 }
79
Janak Ramakrishnandce56752015-10-12 20:03:36 +000080 @Test
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +000081 public void createIfAbsentConcurrentWithGet() throws InterruptedException {
Janak Ramakrishnandce56752015-10-12 20:03:36 +000082 int numIters = 50;
83 final SkyKey key = key("key");
84 for (int i = 0; i < numIters; i++) {
85 Thread t =
86 new Thread(
87 wrapper.wrap(
88 new Runnable() {
89 @Override
90 public void run() {
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +000091 try {
92 graph.get(null, Reason.OTHER, key);
93 } catch (InterruptedException e) {
94 throw new IllegalStateException(e);
95 }
Janak Ramakrishnandce56752015-10-12 20:03:36 +000096 }
97 }));
98 t.start();
Nathan Harmata3b47b1f2016-07-26 18:41:41 +000099 assertThat(graph.createIfAbsentBatch(null, Reason.OTHER, ImmutableList.of(key))).isNotEmpty();
Janak Ramakrishnandce56752015-10-12 20:03:36 +0000100 graph.remove(key);
101 }
102 }
103
Janak Ramakrishnan353988a2016-06-23 19:07:36 +0000104 @Test
105 public void testCreateIfAbsentWithConcurrentGet() throws Exception {
106 final SkyKey key = key("foo");
107 int numThreads = 50;
108 final CountDownLatch startThreads = new CountDownLatch(1);
109 Runnable createRunnable =
110 new Runnable() {
111 @Override
112 public void run() {
113 TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(
114 startThreads, "threads not started");
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000115 try {
116 graph.createIfAbsentBatch(null, Reason.OTHER, ImmutableList.of(key));
117 } catch (InterruptedException e) {
118 throw new IllegalStateException(e);
119 }
Janak Ramakrishnan353988a2016-06-23 19:07:36 +0000120 }
121 };
122 Runnable noCreateRunnable =
123 new Runnable() {
124 @Override
125 public void run() {
126 TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(
127 startThreads, "threads not started");
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000128 try {
129 graph.get(null, Reason.OTHER, key);
130 } catch (InterruptedException e) {
131 throw new IllegalStateException(e);
132 }
Janak Ramakrishnan353988a2016-06-23 19:07:36 +0000133 }
134 };
135 List<Thread> threads = new ArrayList<>(2 * numThreads);
136 for (int i = 0; i < numThreads; i++) {
137 Thread createThread = new Thread(createRunnable);
138 createThread.start();
139 threads.add(createThread);
140 Thread noCreateThread = new Thread(noCreateRunnable);
141 noCreateThread.start();
142 threads.add(noCreateThread);
143 }
144 startThreads.countDown();
145 for (Thread thread : threads) {
146 thread.join();
147 }
148 }
149
Googler2fa2c1e2015-06-19 17:16:36 +0000150 // Tests adding and removing Rdeps of a {@link NodeEntry} while a node transitions from
151 // not done to done.
152 @Test
153 public void testAddRemoveRdeps() throws Exception {
154 SkyKey key = key("foo");
Googler2dd4c182016-10-31 14:54:37 +0000155 final NodeEntry entry =
156 Iterables.getOnlyElement(
157 graph.createIfAbsentBatch(null, Reason.OTHER, ImmutableList.of(key)).values());
Googler2fa2c1e2015-06-19 17:16:36 +0000158 // These numbers are arbitrary.
159 int numThreads = 50;
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000160 int numKeys = numThreads;
Googler2fa2c1e2015-06-19 17:16:36 +0000161 // One chunk will be used to add and remove rdeps before setting the node value. The second
162 // chunk of work will have the node value set and the last chunk will be to add and remove
163 // rdeps after the value has been set.
164 final int chunkSize = 40;
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000165 final int numIterations = chunkSize * 2;
Googler2fa2c1e2015-06-19 17:16:36 +0000166 // This latch is used to signal that the runnables have been submitted to the executor.
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000167 final CountDownLatch waitForStart = new CountDownLatch(1);
Googler2fa2c1e2015-06-19 17:16:36 +0000168 // This latch is used to signal to the main thread that we have begun the second chunk
169 // for sufficiently many keys. The minimum of numThreads and numKeys is used to prevent
170 // thread starvation from causing a delay here.
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000171 final CountDownLatch waitForAddedRdep = new CountDownLatch(numThreads);
Googler2fa2c1e2015-06-19 17:16:36 +0000172 // This latch is used to guarantee that we set the node's value before we enter the third
173 // chunk for any key.
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000174 final CountDownLatch waitForSetValue = new CountDownLatch(1);
Googler2fa2c1e2015-06-19 17:16:36 +0000175 ExecutorService pool = Executors.newFixedThreadPool(numThreads);
176 // Add single rdep before transition to done.
lberkiaea56b32017-05-30 12:35:33 +0200177 assertThat(entry.addReverseDepAndCheckIfDone(key("rdep")))
178 .isEqualTo(DependencyState.NEEDS_SCHEDULING);
Janak Ramakrishnan3b987d22015-11-03 04:40:47 +0000179 List<SkyKey> rdepKeys = new ArrayList<>();
180 for (int i = 0; i < numKeys; i++) {
181 rdepKeys.add(key("rdep" + i));
182 }
Nathan Harmata3b47b1f2016-07-26 18:41:41 +0000183 graph.createIfAbsentBatch(null, Reason.OTHER, rdepKeys);
Googler2fa2c1e2015-06-19 17:16:36 +0000184 for (int i = 0; i < numKeys; i++) {
185 final int j = i;
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000186 Runnable r =
187 new Runnable() {
188 @Override
189 public void run() {
190 try {
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000191 waitForStart.await(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000192 assertThat(entry.addReverseDepAndCheckIfDone(key("rdep" + j)))
193 .isNotEqualTo(DependencyState.DONE);
194 waitForAddedRdep.countDown();
195 waitForSetValue.await(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
Googler7a0b5182016-09-13 14:51:46 +0000196 for (int k = chunkSize; k <= numIterations; k++) {
197 entry.removeReverseDep(key("rdep" + j));
198 entry.addReverseDepAndCheckIfDone(key("rdep" + j));
nharmataba14f672017-12-18 12:36:15 -0800199 if (checkRdeps()) {
200 entry.getReverseDepsForDoneEntry();
201 }
Googler7a0b5182016-09-13 14:51:46 +0000202 }
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000203 } catch (InterruptedException e) {
204 fail("Test failed: " + e.toString());
Googler2fa2c1e2015-06-19 17:16:36 +0000205 }
Googler2fa2c1e2015-06-19 17:16:36 +0000206 }
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000207 };
Googler2fa2c1e2015-06-19 17:16:36 +0000208 pool.execute(wrapper.wrap(r));
209 }
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000210 waitForStart.countDown();
211 waitForAddedRdep.await(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000212 entry.setValue(new StringValue("foo1"), startingVersion);
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000213 waitForSetValue.countDown();
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000214 wrapper.waitForTasksAndMaybeThrow();
lberkiaea56b32017-05-30 12:35:33 +0200215 assertThat(ExecutorUtil.interruptibleShutdown(pool)).isFalse();
216 assertThat(graph.get(null, Reason.OTHER, key).getValue()).isEqualTo(new StringValue("foo1"));
nharmataba14f672017-12-18 12:36:15 -0800217 if (checkRdeps()) {
218 assertThat(graph.get(null, Reason.OTHER, key).getReverseDepsForDoneEntry())
219 .hasSize(numKeys + 1);
220 }
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000221
Shreya Bhattaraicc808782016-02-10 22:17:00 +0000222 graph = getGraph(getNextVersion(startingVersion));
Nathan Harmata3b47b1f2016-07-26 18:41:41 +0000223 NodeEntry sameEntry = Preconditions.checkNotNull(graph.get(null, Reason.OTHER, key));
Googler2fa2c1e2015-06-19 17:16:36 +0000224 // Mark the node as dirty again and check that the reverse deps have been preserved.
mschaller29a7e052018-06-18 09:15:42 -0700225 assertThat(sameEntry.markDirty(true).wasCallRedundant()).isFalse();
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000226 startEvaluation(sameEntry);
Janak Ramakrishnanf76c9592016-05-17 21:42:50 +0000227 sameEntry.markRebuilding();
Shreya Bhattaraicc808782016-02-10 22:17:00 +0000228 sameEntry.setValue(new StringValue("foo2"), getNextVersion(startingVersion));
lberkiaea56b32017-05-30 12:35:33 +0200229 assertThat(graph.get(null, Reason.OTHER, key).getValue()).isEqualTo(new StringValue("foo2"));
nharmataba14f672017-12-18 12:36:15 -0800230 if (checkRdeps()) {
231 assertThat(graph.get(null, Reason.OTHER, key).getReverseDepsForDoneEntry())
232 .hasSize(numKeys + 1);
233 }
Googler2fa2c1e2015-06-19 17:16:36 +0000234 }
235
236 // Tests adding inflight nodes with a given key while an existing node with the same key
237 // undergoes a transition from not done to done.
238 @Test
239 public void testAddingInflightNodes() throws Exception {
240 int numThreads = 50;
Googler2fa2c1e2015-06-19 17:16:36 +0000241 ExecutorService pool = Executors.newFixedThreadPool(numThreads);
242 final int numKeys = 500;
Nathan Harmatac7e974a2015-10-02 22:07:35 +0000243 // Add each pair of keys 10 times.
Googler2fa2c1e2015-06-19 17:16:36 +0000244 final Set<SkyKey> nodeCreated = Sets.newConcurrentHashSet();
245 final Set<SkyKey> valuesSet = Sets.newConcurrentHashSet();
246 for (int i = 0; i < 10; i++) {
247 for (int j = 0; j < numKeys; j++) {
Nathan Harmatac7e974a2015-10-02 22:07:35 +0000248 for (int k = j + 1; k < numKeys; k++) {
249 final int keyNum1 = j;
250 final int keyNum2 = k;
251 final SkyKey key1 = key("foo" + keyNum1);
252 final SkyKey key2 = key("foo" + keyNum2);
253 final Iterable<SkyKey> keys = ImmutableList.of(key1, key2);
254 Runnable r =
255 new Runnable() {
Googler7a0b5182016-09-13 14:51:46 +0000256 @Override
Nathan Harmatac7e974a2015-10-02 22:07:35 +0000257 public void run() {
Janak Ramakrishnandce56752015-10-12 20:03:36 +0000258 for (SkyKey key : keys) {
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000259 NodeEntry entry = null;
260 try {
261 entry = graph.get(null, Reason.OTHER, key);
262 } catch (InterruptedException e) {
263 throw new IllegalStateException(e);
264 }
Janak Ramakrishnandce56752015-10-12 20:03:36 +0000265 if (entry == null) {
266 nodeCreated.add(key);
Nathan Harmatac7e974a2015-10-02 22:07:35 +0000267 }
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000268 }
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000269 Map<SkyKey, ? extends NodeEntry> entries;
270 try {
271 entries = graph.createIfAbsentBatch(null, Reason.OTHER, keys);
272 } catch (InterruptedException e) {
273 throw new IllegalStateException(e);
274 }
Nathan Harmatac7e974a2015-10-02 22:07:35 +0000275 for (Integer keyNum : ImmutableList.of(keyNum1, keyNum2)) {
276 SkyKey key = key("foo" + keyNum);
277 NodeEntry entry = entries.get(key);
278 // {@code entry.addReverseDepAndCheckIfDone(null)} should return
279 // NEEDS_SCHEDULING at most once.
Googler7a0b5182016-09-13 14:51:46 +0000280 try {
281 if (startEvaluation(entry).equals(DependencyState.NEEDS_SCHEDULING)) {
lberkiaea56b32017-05-30 12:35:33 +0200282 assertThat(valuesSet.add(key)).isTrue();
Googler7a0b5182016-09-13 14:51:46 +0000283 // Set to done.
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000284 entry.setValue(new StringValue("bar" + keyNum), startingVersion);
Googler7a0b5182016-09-13 14:51:46 +0000285 assertThat(entry.isDone()).isTrue();
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000286 }
Googler7a0b5182016-09-13 14:51:46 +0000287 } catch (InterruptedException e) {
288 throw new IllegalStateException(key + ", " + entry, e);
Nathan Harmatac7e974a2015-10-02 22:07:35 +0000289 }
290 }
291 // This shouldn't cause any problems from the other threads.
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000292 try {
293 graph.createIfAbsentBatch(null, Reason.OTHER, keys);
294 } catch (InterruptedException e) {
295 throw new IllegalStateException(e);
296 }
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000297 }
Nathan Harmatac7e974a2015-10-02 22:07:35 +0000298 };
299 pool.execute(wrapper.wrap(r));
300 }
Googler2fa2c1e2015-06-19 17:16:36 +0000301 }
302 }
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000303 wrapper.waitForTasksAndMaybeThrow();
lberkiaea56b32017-05-30 12:35:33 +0200304 assertThat(ExecutorUtil.interruptibleShutdown(pool)).isFalse();
Googler2fa2c1e2015-06-19 17:16:36 +0000305 // Check that all the values are as expected.
306 for (int i = 0; i < numKeys; i++) {
307 SkyKey key = key("foo" + i);
lberkiaea56b32017-05-30 12:35:33 +0200308 assertThat(nodeCreated).contains(key);
309 assertThat(valuesSet).contains(key);
Nathan Harmata3b47b1f2016-07-26 18:41:41 +0000310 assertThat(graph.get(null, Reason.OTHER, key).getValue())
311 .isEqualTo(new StringValue("bar" + i));
312 assertThat(graph.get(null, Reason.OTHER, key).getVersion()).isEqualTo(startingVersion);
Googler2fa2c1e2015-06-19 17:16:36 +0000313 }
314 }
315
316 /**
Janak Ramakrishnan2553c842016-07-08 18:43:37 +0000317 * Initially calling {@link NodeEntry#setValue} and then making sure concurrent calls to {@link
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000318 * QueryableGraph#get} and {@link QueryableGraph#getBatch} do not interfere with the node.
Googler2fa2c1e2015-06-19 17:16:36 +0000319 */
320 @Test
321 public void testDoneToDirty() throws Exception {
322 final int numKeys = 1000;
323 int numThreads = 50;
324 final int numBatchRequests = 100;
325 // Create a bunch of done nodes.
Nathan Harmatac7e974a2015-10-02 22:07:35 +0000326 ArrayList<SkyKey> keys = new ArrayList<>();
Googler2fa2c1e2015-06-19 17:16:36 +0000327 for (int i = 0; i < numKeys; i++) {
Nathan Harmatac7e974a2015-10-02 22:07:35 +0000328 keys.add(key("foo" + i));
329 }
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000330 Map<SkyKey, ? extends NodeEntry> entries = graph.createIfAbsentBatch(null, Reason.OTHER, keys);
Nathan Harmatac7e974a2015-10-02 22:07:35 +0000331 for (int i = 0; i < numKeys; i++) {
332 NodeEntry entry = entries.get(key("foo" + i));
Googler2fa2c1e2015-06-19 17:16:36 +0000333 startEvaluation(entry);
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000334 entry.setValue(new StringValue("bar"), startingVersion);
Googler2fa2c1e2015-06-19 17:16:36 +0000335 }
336
lberkiaea56b32017-05-30 12:35:33 +0200337 assertThat(graph.get(null, Reason.OTHER, key("foo" + 0))).isNotNull();
Shreya Bhattaraicc808782016-02-10 22:17:00 +0000338 graph = getGraph(getNextVersion(startingVersion));
lberkiaea56b32017-05-30 12:35:33 +0200339 assertThat(graph.get(null, Reason.OTHER, key("foo" + 0))).isNotNull();
Googler2fa2c1e2015-06-19 17:16:36 +0000340 ExecutorService pool1 = Executors.newFixedThreadPool(numThreads);
341 ExecutorService pool2 = Executors.newFixedThreadPool(numThreads);
342 ExecutorService pool3 = Executors.newFixedThreadPool(numThreads);
343
344 // Only start all the threads once the batch requests are ready.
345 final CountDownLatch makeBatchCountDownLatch = new CountDownLatch(numBatchRequests);
346 // Do at least 5 single requests and batch requests before transitioning node.
347 final CountDownLatch getBatchCountDownLatch = new CountDownLatch(5);
348 final CountDownLatch getCountDownLatch = new CountDownLatch(5);
349
350 for (int i = 0; i < numKeys; i++) {
351 final int keyNum = i;
352 // Transition the nodes from done to dirty and then back to done.
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000353 Runnable r1 =
354 new Runnable() {
355 @Override
356 public void run() {
357 try {
358 makeBatchCountDownLatch.await();
359 getBatchCountDownLatch.await();
360 getCountDownLatch.await();
361 } catch (InterruptedException e) {
362 throw new AssertionError(e);
363 }
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000364 NodeEntry entry = null;
365 try {
366 entry = graph.get(null, Reason.OTHER, key("foo" + keyNum));
367 } catch (InterruptedException e) {
368 throw new IllegalStateException(e);
369 }
370 try {
mschaller29a7e052018-06-18 09:15:42 -0700371 assertThat(entry.markDirty(true).wasCallRedundant()).isFalse();
Googler7a0b5182016-09-13 14:51:46 +0000372
373 // Make some changes, like adding a dep and rdep.
374 entry.addReverseDepAndCheckIfDone(key("rdep"));
375 entry.markRebuilding();
376 addTemporaryDirectDep(entry, key("dep"));
377 entry.signalDep();
378
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000379 entry.setValue(new StringValue("bar" + keyNum), getNextVersion(startingVersion));
380 } catch (InterruptedException e) {
381 throw new IllegalStateException(keyNum + ", " + entry, e);
382 }
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000383 }
384 };
Googler2fa2c1e2015-06-19 17:16:36 +0000385
386 // Start a bunch of get() calls while the node transitions from dirty to done and back.
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000387 Runnable r2 =
388 new Runnable() {
389 @Override
390 public void run() {
391 try {
392 makeBatchCountDownLatch.await();
393 } catch (InterruptedException e) {
394 throw new AssertionError(e);
395 }
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000396 NodeEntry entry = null;
397 try {
398 entry = graph.get(null, Reason.OTHER, key("foo" + keyNum));
399 } catch (InterruptedException e) {
400 throw new IllegalStateException(e);
401 }
lberkiaea56b32017-05-30 12:35:33 +0200402 assertThat(entry).isNotNull();
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000403 // Requests for the value are made at the same time that the version increments from
404 // the base. Check that there is no problem in requesting the version and that the
405 // number is sane.
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000406 assertThat(entry.getVersion())
407 .isAnyOf(startingVersion, getNextVersion(startingVersion));
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000408 getCountDownLatch.countDown();
409 }
410 };
Googler2fa2c1e2015-06-19 17:16:36 +0000411 pool1.execute(wrapper.wrap(r1));
412 pool2.execute(wrapper.wrap(r2));
413 }
414 Random r = new Random(TestUtils.getRandomSeed());
415 // Start a bunch of getBatch() calls while the node transitions from dirty to done and back.
416 for (int i = 0; i < numBatchRequests; i++) {
417 final List<SkyKey> batch = new ArrayList<>(numKeys);
418 // Pseudorandomly uniformly sample the powerset of the keys.
419 for (int j = 0; j < numKeys; j++) {
420 if (r.nextBoolean()) {
421 batch.add(key("foo" + j));
422 }
423 }
424 makeBatchCountDownLatch.countDown();
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000425 Runnable r3 =
426 new Runnable() {
427 @Override
428 public void run() {
429 try {
430 makeBatchCountDownLatch.await();
431 } catch (InterruptedException e) {
432 throw new AssertionError(e);
433 }
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000434 Map<SkyKey, ? extends NodeEntry> batchMap = null;
435 try {
436 batchMap = graph.getBatch(null, Reason.OTHER, batch);
437 } catch (InterruptedException e) {
438 throw new IllegalStateException(e);
439 }
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000440 getBatchCountDownLatch.countDown();
441 assertThat(batchMap).hasSize(batch.size());
442 for (NodeEntry entry : batchMap.values()) {
443 // Batch requests are made at the same time that the version increments from the
444 // base. Check that there is no problem in requesting the version and that the
445 // number is sane.
Janak Ramakrishnan2553c842016-07-08 18:43:37 +0000446 assertThat(entry.getVersion())
447 .isAnyOf(startingVersion, getNextVersion(startingVersion));
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000448 }
449 }
450 };
Googler2fa2c1e2015-06-19 17:16:36 +0000451 pool3.execute(wrapper.wrap(r3));
452 }
Janak Ramakrishnan52455102015-08-03 17:05:05 +0000453 wrapper.waitForTasksAndMaybeThrow();
lberkiaea56b32017-05-30 12:35:33 +0200454 assertThat(ExecutorUtil.interruptibleShutdown(pool1)).isFalse();
455 assertThat(ExecutorUtil.interruptibleShutdown(pool2)).isFalse();
456 assertThat(ExecutorUtil.interruptibleShutdown(pool3)).isFalse();
Googler2fa2c1e2015-06-19 17:16:36 +0000457 for (int i = 0; i < numKeys; i++) {
Nathan Harmata3b47b1f2016-07-26 18:41:41 +0000458 NodeEntry entry = graph.get(null, Reason.OTHER, key("foo" + i));
Googler2fa2c1e2015-06-19 17:16:36 +0000459 assertThat(entry.getValue()).isEqualTo(new StringValue("bar" + i));
Shreya Bhattaraicc808782016-02-10 22:17:00 +0000460 assertThat(entry.getVersion()).isEqualTo(getNextVersion(startingVersion));
nharmataba14f672017-12-18 12:36:15 -0800461 if (checkRdeps()) {
462 for (SkyKey key : entry.getReverseDepsForDoneEntry()) {
463 assertThat(key).isEqualTo(key("rdep"));
464 }
Googler2fa2c1e2015-06-19 17:16:36 +0000465 }
466 for (SkyKey key : entry.getDirectDeps()) {
lberkiaea56b32017-05-30 12:35:33 +0200467 assertThat(key).isEqualTo(key("dep"));
Googler2fa2c1e2015-06-19 17:16:36 +0000468 }
469 }
470 }
471
Googler2dd4c182016-10-31 14:54:37 +0000472 @Test
473 public void testGetCurrentlyAvailableNodes() throws Exception {
474 SkyKey foo = key("foo");
475 SkyKey bar = key("bar");
476 SkyKey foobar = key("foobar");
477 graph.createIfAbsentBatch(null, Reason.OTHER, ImmutableList.of(foo, bar));
478
479 Iterable<SkyKey> currentlyAvailable =
480 graph.getCurrentlyAvailableNodes(ImmutableList.of(foo, bar, foobar), Reason.OTHER);
481
482 assertThat(currentlyAvailable).containsExactly(foo, bar);
483 }
484
Googler7a0b5182016-09-13 14:51:46 +0000485 private static DependencyState startEvaluation(NodeEntry entry) throws InterruptedException {
Googler2fa2c1e2015-06-19 17:16:36 +0000486 return entry.addReverseDepAndCheckIfDone(null);
487 }
488
489 private static void addTemporaryDirectDep(NodeEntry entry, SkyKey key) {
490 GroupedListHelper<SkyKey> helper = new GroupedListHelper<>();
491 helper.add(key);
492 entry.addTemporaryDirectDeps(helper);
493 }
Nathan Harmata53bd3bf2015-04-03 16:12:59 +0000494}