Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2015 The Bazel Authors. All rights reserved. |
Nathan Harmata | 53bd3bf | 2015-04-03 16:12:59 +0000 | [diff] [blame] | 2 | // |
| 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. |
| 14 | package com.google.devtools.build.skyframe; |
| 15 | |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 16 | import static com.google.common.truth.Truth.assertThat; |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 17 | import static org.junit.Assert.fail; |
| 18 | |
tomlu | a155b53 | 2017-11-08 20:12:47 +0100 | [diff] [blame] | 19 | import com.google.common.base.Preconditions; |
Nathan Harmata | c7e974a | 2015-10-02 22:07:35 +0000 | [diff] [blame] | 20 | import com.google.common.collect.ImmutableList; |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 21 | import com.google.common.collect.Iterables; |
| 22 | import com.google.common.collect.Sets; |
| 23 | import com.google.devtools.build.lib.concurrent.ExecutorUtil; |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 24 | import com.google.devtools.build.lib.testutil.TestRunnableWrapper; |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 25 | import com.google.devtools.build.lib.testutil.TestUtils; |
| 26 | import com.google.devtools.build.lib.util.GroupedList.GroupedListHelper; |
| 27 | import com.google.devtools.build.skyframe.GraphTester.StringValue; |
| 28 | import com.google.devtools.build.skyframe.NodeEntry.DependencyState; |
Nathan Harmata | 3b47b1f | 2016-07-26 18:41:41 +0000 | [diff] [blame] | 29 | import com.google.devtools.build.skyframe.QueryableGraph.Reason; |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 30 | import java.util.ArrayList; |
| 31 | import java.util.List; |
| 32 | import java.util.Map; |
| 33 | import java.util.Random; |
| 34 | import java.util.Set; |
| 35 | import java.util.concurrent.CountDownLatch; |
| 36 | import java.util.concurrent.ExecutorService; |
| 37 | import java.util.concurrent.Executors; |
Janak Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 38 | import java.util.concurrent.TimeUnit; |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 39 | import org.junit.Before; |
| 40 | import org.junit.Test; |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 41 | |
Googler | 2dd4c18 | 2016-10-31 14:54:37 +0000 | [diff] [blame] | 42 | /** Base class for sanity tests on {@link EvaluableGraph} implementations. */ |
| 43 | public abstract class GraphTest { |
Nathan Harmata | 3b8deab | 2015-10-28 21:18:35 +0000 | [diff] [blame] | 44 | protected ProcessableGraph graph; |
| 45 | protected TestRunnableWrapper wrapper; |
Shreya Bhattarai | cc80878 | 2016-02-10 22:17:00 +0000 | [diff] [blame] | 46 | private final Version startingVersion = getStartingVersion(); |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 47 | |
| 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 Bhattarai | cc80878 | 2016-02-10 22:17:00 +0000 | [diff] [blame] | 55 | protected abstract Version getStartingVersion(); |
| 56 | |
| 57 | protected abstract Version getNextVersion(Version version); |
Nathan Harmata | 53bd3bf | 2015-04-03 16:12:59 +0000 | [diff] [blame] | 58 | |
nharmata | ba14f67 | 2017-12-18 12:36:15 -0800 | [diff] [blame] | 59 | protected boolean checkRdeps() { |
| 60 | return true; |
| 61 | } |
| 62 | |
Nathan Harmata | 53bd3bf | 2015-04-03 16:12:59 +0000 | [diff] [blame] | 63 | @Before |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 64 | public void init() throws Exception { |
| 65 | makeGraph(); |
Shreya Bhattarai | cc80878 | 2016-02-10 22:17:00 +0000 | [diff] [blame] | 66 | Version startingVersion = getStartingVersion(); |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 67 | this.graph = getGraph(startingVersion); |
| 68 | this.wrapper = new TestRunnableWrapper("GraphConcurrencyTest"); |
Nathan Harmata | 53bd3bf | 2015-04-03 16:12:59 +0000 | [diff] [blame] | 69 | } |
| 70 | |
Nathan Harmata | 3b8deab | 2015-10-28 21:18:35 +0000 | [diff] [blame] | 71 | protected SkyKey key(String name) { |
janakr | 5fb2a48 | 2018-03-02 17:48:57 -0800 | [diff] [blame] | 72 | return GraphTester.toSkyKey(name); |
Nathan Harmata | 53bd3bf | 2015-04-03 16:12:59 +0000 | [diff] [blame] | 73 | } |
| 74 | |
| 75 | @Test |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 76 | public void createIfAbsentBatchSanity() throws InterruptedException { |
Nathan Harmata | 3b47b1f | 2016-07-26 18:41:41 +0000 | [diff] [blame] | 77 | graph.createIfAbsentBatch(null, Reason.OTHER, ImmutableList.of(key("cat"), key("dog"))); |
Nathan Harmata | 53bd3bf | 2015-04-03 16:12:59 +0000 | [diff] [blame] | 78 | } |
| 79 | |
Janak Ramakrishnan | dce5675 | 2015-10-12 20:03:36 +0000 | [diff] [blame] | 80 | @Test |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 81 | public void createIfAbsentConcurrentWithGet() throws InterruptedException { |
Janak Ramakrishnan | dce5675 | 2015-10-12 20:03:36 +0000 | [diff] [blame] | 82 | 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 Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 91 | try { |
| 92 | graph.get(null, Reason.OTHER, key); |
| 93 | } catch (InterruptedException e) { |
| 94 | throw new IllegalStateException(e); |
| 95 | } |
Janak Ramakrishnan | dce5675 | 2015-10-12 20:03:36 +0000 | [diff] [blame] | 96 | } |
| 97 | })); |
| 98 | t.start(); |
Nathan Harmata | 3b47b1f | 2016-07-26 18:41:41 +0000 | [diff] [blame] | 99 | assertThat(graph.createIfAbsentBatch(null, Reason.OTHER, ImmutableList.of(key))).isNotEmpty(); |
Janak Ramakrishnan | dce5675 | 2015-10-12 20:03:36 +0000 | [diff] [blame] | 100 | graph.remove(key); |
| 101 | } |
| 102 | } |
| 103 | |
Janak Ramakrishnan | 353988a | 2016-06-23 19:07:36 +0000 | [diff] [blame] | 104 | @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 Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 115 | try { |
| 116 | graph.createIfAbsentBatch(null, Reason.OTHER, ImmutableList.of(key)); |
| 117 | } catch (InterruptedException e) { |
| 118 | throw new IllegalStateException(e); |
| 119 | } |
Janak Ramakrishnan | 353988a | 2016-06-23 19:07:36 +0000 | [diff] [blame] | 120 | } |
| 121 | }; |
| 122 | Runnable noCreateRunnable = |
| 123 | new Runnable() { |
| 124 | @Override |
| 125 | public void run() { |
| 126 | TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions( |
| 127 | startThreads, "threads not started"); |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 128 | try { |
| 129 | graph.get(null, Reason.OTHER, key); |
| 130 | } catch (InterruptedException e) { |
| 131 | throw new IllegalStateException(e); |
| 132 | } |
Janak Ramakrishnan | 353988a | 2016-06-23 19:07:36 +0000 | [diff] [blame] | 133 | } |
| 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 | |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 150 | // 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"); |
Googler | 2dd4c18 | 2016-10-31 14:54:37 +0000 | [diff] [blame] | 155 | final NodeEntry entry = |
| 156 | Iterables.getOnlyElement( |
| 157 | graph.createIfAbsentBatch(null, Reason.OTHER, ImmutableList.of(key)).values()); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 158 | // These numbers are arbitrary. |
| 159 | int numThreads = 50; |
Janak Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 160 | int numKeys = numThreads; |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 161 | // 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 Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 165 | final int numIterations = chunkSize * 2; |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 166 | // This latch is used to signal that the runnables have been submitted to the executor. |
Janak Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 167 | final CountDownLatch waitForStart = new CountDownLatch(1); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 168 | // 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 Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 171 | final CountDownLatch waitForAddedRdep = new CountDownLatch(numThreads); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 172 | // This latch is used to guarantee that we set the node's value before we enter the third |
| 173 | // chunk for any key. |
Janak Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 174 | final CountDownLatch waitForSetValue = new CountDownLatch(1); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 175 | ExecutorService pool = Executors.newFixedThreadPool(numThreads); |
| 176 | // Add single rdep before transition to done. |
lberki | aea56b3 | 2017-05-30 12:35:33 +0200 | [diff] [blame] | 177 | assertThat(entry.addReverseDepAndCheckIfDone(key("rdep"))) |
| 178 | .isEqualTo(DependencyState.NEEDS_SCHEDULING); |
Janak Ramakrishnan | 3b987d2 | 2015-11-03 04:40:47 +0000 | [diff] [blame] | 179 | List<SkyKey> rdepKeys = new ArrayList<>(); |
| 180 | for (int i = 0; i < numKeys; i++) { |
| 181 | rdepKeys.add(key("rdep" + i)); |
| 182 | } |
Nathan Harmata | 3b47b1f | 2016-07-26 18:41:41 +0000 | [diff] [blame] | 183 | graph.createIfAbsentBatch(null, Reason.OTHER, rdepKeys); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 184 | for (int i = 0; i < numKeys; i++) { |
| 185 | final int j = i; |
Janak Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 186 | Runnable r = |
| 187 | new Runnable() { |
| 188 | @Override |
| 189 | public void run() { |
| 190 | try { |
Janak Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 191 | waitForStart.await(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS); |
Janak Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 192 | assertThat(entry.addReverseDepAndCheckIfDone(key("rdep" + j))) |
| 193 | .isNotEqualTo(DependencyState.DONE); |
| 194 | waitForAddedRdep.countDown(); |
| 195 | waitForSetValue.await(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS); |
Googler | 7a0b518 | 2016-09-13 14:51:46 +0000 | [diff] [blame] | 196 | for (int k = chunkSize; k <= numIterations; k++) { |
| 197 | entry.removeReverseDep(key("rdep" + j)); |
| 198 | entry.addReverseDepAndCheckIfDone(key("rdep" + j)); |
nharmata | ba14f67 | 2017-12-18 12:36:15 -0800 | [diff] [blame] | 199 | if (checkRdeps()) { |
| 200 | entry.getReverseDepsForDoneEntry(); |
| 201 | } |
Googler | 7a0b518 | 2016-09-13 14:51:46 +0000 | [diff] [blame] | 202 | } |
Janak Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 203 | } catch (InterruptedException e) { |
| 204 | fail("Test failed: " + e.toString()); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 205 | } |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 206 | } |
Janak Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 207 | }; |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 208 | pool.execute(wrapper.wrap(r)); |
| 209 | } |
Janak Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 210 | waitForStart.countDown(); |
| 211 | waitForAddedRdep.await(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS); |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 212 | entry.setValue(new StringValue("foo1"), startingVersion); |
Janak Ramakrishnan | 5877b8b | 2015-09-22 17:37:10 +0000 | [diff] [blame] | 213 | waitForSetValue.countDown(); |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 214 | wrapper.waitForTasksAndMaybeThrow(); |
lberki | aea56b3 | 2017-05-30 12:35:33 +0200 | [diff] [blame] | 215 | assertThat(ExecutorUtil.interruptibleShutdown(pool)).isFalse(); |
| 216 | assertThat(graph.get(null, Reason.OTHER, key).getValue()).isEqualTo(new StringValue("foo1")); |
nharmata | ba14f67 | 2017-12-18 12:36:15 -0800 | [diff] [blame] | 217 | if (checkRdeps()) { |
| 218 | assertThat(graph.get(null, Reason.OTHER, key).getReverseDepsForDoneEntry()) |
| 219 | .hasSize(numKeys + 1); |
| 220 | } |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 221 | |
Shreya Bhattarai | cc80878 | 2016-02-10 22:17:00 +0000 | [diff] [blame] | 222 | graph = getGraph(getNextVersion(startingVersion)); |
Nathan Harmata | 3b47b1f | 2016-07-26 18:41:41 +0000 | [diff] [blame] | 223 | NodeEntry sameEntry = Preconditions.checkNotNull(graph.get(null, Reason.OTHER, key)); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 224 | // Mark the node as dirty again and check that the reverse deps have been preserved. |
mschaller | 29a7e05 | 2018-06-18 09:15:42 -0700 | [diff] [blame] | 225 | assertThat(sameEntry.markDirty(true).wasCallRedundant()).isFalse(); |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 226 | startEvaluation(sameEntry); |
Janak Ramakrishnan | f76c959 | 2016-05-17 21:42:50 +0000 | [diff] [blame] | 227 | sameEntry.markRebuilding(); |
Shreya Bhattarai | cc80878 | 2016-02-10 22:17:00 +0000 | [diff] [blame] | 228 | sameEntry.setValue(new StringValue("foo2"), getNextVersion(startingVersion)); |
lberki | aea56b3 | 2017-05-30 12:35:33 +0200 | [diff] [blame] | 229 | assertThat(graph.get(null, Reason.OTHER, key).getValue()).isEqualTo(new StringValue("foo2")); |
nharmata | ba14f67 | 2017-12-18 12:36:15 -0800 | [diff] [blame] | 230 | if (checkRdeps()) { |
| 231 | assertThat(graph.get(null, Reason.OTHER, key).getReverseDepsForDoneEntry()) |
| 232 | .hasSize(numKeys + 1); |
| 233 | } |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 234 | } |
| 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; |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 241 | ExecutorService pool = Executors.newFixedThreadPool(numThreads); |
| 242 | final int numKeys = 500; |
Nathan Harmata | c7e974a | 2015-10-02 22:07:35 +0000 | [diff] [blame] | 243 | // Add each pair of keys 10 times. |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 244 | 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 Harmata | c7e974a | 2015-10-02 22:07:35 +0000 | [diff] [blame] | 248 | 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() { |
Googler | 7a0b518 | 2016-09-13 14:51:46 +0000 | [diff] [blame] | 256 | @Override |
Nathan Harmata | c7e974a | 2015-10-02 22:07:35 +0000 | [diff] [blame] | 257 | public void run() { |
Janak Ramakrishnan | dce5675 | 2015-10-12 20:03:36 +0000 | [diff] [blame] | 258 | for (SkyKey key : keys) { |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 259 | NodeEntry entry = null; |
| 260 | try { |
| 261 | entry = graph.get(null, Reason.OTHER, key); |
| 262 | } catch (InterruptedException e) { |
| 263 | throw new IllegalStateException(e); |
| 264 | } |
Janak Ramakrishnan | dce5675 | 2015-10-12 20:03:36 +0000 | [diff] [blame] | 265 | if (entry == null) { |
| 266 | nodeCreated.add(key); |
Nathan Harmata | c7e974a | 2015-10-02 22:07:35 +0000 | [diff] [blame] | 267 | } |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 268 | } |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 269 | 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 Harmata | c7e974a | 2015-10-02 22:07:35 +0000 | [diff] [blame] | 275 | 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. |
Googler | 7a0b518 | 2016-09-13 14:51:46 +0000 | [diff] [blame] | 280 | try { |
| 281 | if (startEvaluation(entry).equals(DependencyState.NEEDS_SCHEDULING)) { |
lberki | aea56b3 | 2017-05-30 12:35:33 +0200 | [diff] [blame] | 282 | assertThat(valuesSet.add(key)).isTrue(); |
Googler | 7a0b518 | 2016-09-13 14:51:46 +0000 | [diff] [blame] | 283 | // Set to done. |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 284 | entry.setValue(new StringValue("bar" + keyNum), startingVersion); |
Googler | 7a0b518 | 2016-09-13 14:51:46 +0000 | [diff] [blame] | 285 | assertThat(entry.isDone()).isTrue(); |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 286 | } |
Googler | 7a0b518 | 2016-09-13 14:51:46 +0000 | [diff] [blame] | 287 | } catch (InterruptedException e) { |
| 288 | throw new IllegalStateException(key + ", " + entry, e); |
Nathan Harmata | c7e974a | 2015-10-02 22:07:35 +0000 | [diff] [blame] | 289 | } |
| 290 | } |
| 291 | // This shouldn't cause any problems from the other threads. |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 292 | try { |
| 293 | graph.createIfAbsentBatch(null, Reason.OTHER, keys); |
| 294 | } catch (InterruptedException e) { |
| 295 | throw new IllegalStateException(e); |
| 296 | } |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 297 | } |
Nathan Harmata | c7e974a | 2015-10-02 22:07:35 +0000 | [diff] [blame] | 298 | }; |
| 299 | pool.execute(wrapper.wrap(r)); |
| 300 | } |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 301 | } |
| 302 | } |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 303 | wrapper.waitForTasksAndMaybeThrow(); |
lberki | aea56b3 | 2017-05-30 12:35:33 +0200 | [diff] [blame] | 304 | assertThat(ExecutorUtil.interruptibleShutdown(pool)).isFalse(); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 305 | // Check that all the values are as expected. |
| 306 | for (int i = 0; i < numKeys; i++) { |
| 307 | SkyKey key = key("foo" + i); |
lberki | aea56b3 | 2017-05-30 12:35:33 +0200 | [diff] [blame] | 308 | assertThat(nodeCreated).contains(key); |
| 309 | assertThat(valuesSet).contains(key); |
Nathan Harmata | 3b47b1f | 2016-07-26 18:41:41 +0000 | [diff] [blame] | 310 | 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); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 313 | } |
| 314 | } |
| 315 | |
| 316 | /** |
Janak Ramakrishnan | 2553c84 | 2016-07-08 18:43:37 +0000 | [diff] [blame] | 317 | * Initially calling {@link NodeEntry#setValue} and then making sure concurrent calls to {@link |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 318 | * QueryableGraph#get} and {@link QueryableGraph#getBatch} do not interfere with the node. |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 319 | */ |
| 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 Harmata | c7e974a | 2015-10-02 22:07:35 +0000 | [diff] [blame] | 326 | ArrayList<SkyKey> keys = new ArrayList<>(); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 327 | for (int i = 0; i < numKeys; i++) { |
Nathan Harmata | c7e974a | 2015-10-02 22:07:35 +0000 | [diff] [blame] | 328 | keys.add(key("foo" + i)); |
| 329 | } |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 330 | Map<SkyKey, ? extends NodeEntry> entries = graph.createIfAbsentBatch(null, Reason.OTHER, keys); |
Nathan Harmata | c7e974a | 2015-10-02 22:07:35 +0000 | [diff] [blame] | 331 | for (int i = 0; i < numKeys; i++) { |
| 332 | NodeEntry entry = entries.get(key("foo" + i)); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 333 | startEvaluation(entry); |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 334 | entry.setValue(new StringValue("bar"), startingVersion); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 335 | } |
| 336 | |
lberki | aea56b3 | 2017-05-30 12:35:33 +0200 | [diff] [blame] | 337 | assertThat(graph.get(null, Reason.OTHER, key("foo" + 0))).isNotNull(); |
Shreya Bhattarai | cc80878 | 2016-02-10 22:17:00 +0000 | [diff] [blame] | 338 | graph = getGraph(getNextVersion(startingVersion)); |
lberki | aea56b3 | 2017-05-30 12:35:33 +0200 | [diff] [blame] | 339 | assertThat(graph.get(null, Reason.OTHER, key("foo" + 0))).isNotNull(); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 340 | 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 Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 353 | 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 Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 364 | 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 { |
mschaller | 29a7e05 | 2018-06-18 09:15:42 -0700 | [diff] [blame] | 371 | assertThat(entry.markDirty(true).wasCallRedundant()).isFalse(); |
Googler | 7a0b518 | 2016-09-13 14:51:46 +0000 | [diff] [blame] | 372 | |
| 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 Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 379 | entry.setValue(new StringValue("bar" + keyNum), getNextVersion(startingVersion)); |
| 380 | } catch (InterruptedException e) { |
| 381 | throw new IllegalStateException(keyNum + ", " + entry, e); |
| 382 | } |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 383 | } |
| 384 | }; |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 385 | |
| 386 | // Start a bunch of get() calls while the node transitions from dirty to done and back. |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 387 | 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 Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 396 | 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 | } |
lberki | aea56b3 | 2017-05-30 12:35:33 +0200 | [diff] [blame] | 402 | assertThat(entry).isNotNull(); |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 403 | // 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 Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 406 | assertThat(entry.getVersion()) |
| 407 | .isAnyOf(startingVersion, getNextVersion(startingVersion)); |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 408 | getCountDownLatch.countDown(); |
| 409 | } |
| 410 | }; |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 411 | 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 Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 425 | 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 Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 434 | 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 Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 440 | 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 Ramakrishnan | 2553c84 | 2016-07-08 18:43:37 +0000 | [diff] [blame] | 446 | assertThat(entry.getVersion()) |
| 447 | .isAnyOf(startingVersion, getNextVersion(startingVersion)); |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 448 | } |
| 449 | } |
| 450 | }; |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 451 | pool3.execute(wrapper.wrap(r3)); |
| 452 | } |
Janak Ramakrishnan | 5245510 | 2015-08-03 17:05:05 +0000 | [diff] [blame] | 453 | wrapper.waitForTasksAndMaybeThrow(); |
lberki | aea56b3 | 2017-05-30 12:35:33 +0200 | [diff] [blame] | 454 | assertThat(ExecutorUtil.interruptibleShutdown(pool1)).isFalse(); |
| 455 | assertThat(ExecutorUtil.interruptibleShutdown(pool2)).isFalse(); |
| 456 | assertThat(ExecutorUtil.interruptibleShutdown(pool3)).isFalse(); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 457 | for (int i = 0; i < numKeys; i++) { |
Nathan Harmata | 3b47b1f | 2016-07-26 18:41:41 +0000 | [diff] [blame] | 458 | NodeEntry entry = graph.get(null, Reason.OTHER, key("foo" + i)); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 459 | assertThat(entry.getValue()).isEqualTo(new StringValue("bar" + i)); |
Shreya Bhattarai | cc80878 | 2016-02-10 22:17:00 +0000 | [diff] [blame] | 460 | assertThat(entry.getVersion()).isEqualTo(getNextVersion(startingVersion)); |
nharmata | ba14f67 | 2017-12-18 12:36:15 -0800 | [diff] [blame] | 461 | if (checkRdeps()) { |
| 462 | for (SkyKey key : entry.getReverseDepsForDoneEntry()) { |
| 463 | assertThat(key).isEqualTo(key("rdep")); |
| 464 | } |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 465 | } |
| 466 | for (SkyKey key : entry.getDirectDeps()) { |
lberki | aea56b3 | 2017-05-30 12:35:33 +0200 | [diff] [blame] | 467 | assertThat(key).isEqualTo(key("dep")); |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 468 | } |
| 469 | } |
| 470 | } |
| 471 | |
Googler | 2dd4c18 | 2016-10-31 14:54:37 +0000 | [diff] [blame] | 472 | @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 | |
Googler | 7a0b518 | 2016-09-13 14:51:46 +0000 | [diff] [blame] | 485 | private static DependencyState startEvaluation(NodeEntry entry) throws InterruptedException { |
Googler | 2fa2c1e | 2015-06-19 17:16:36 +0000 | [diff] [blame] | 486 | 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 Harmata | 53bd3bf | 2015-04-03 16:12:59 +0000 | [diff] [blame] | 494 | } |