blob: 6cde57318aceda61ee6aab28c7be8aac14fb4460 [file] [log] [blame]
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +00001// Copyright 2016 The Bazel Authors. All rights reserved.
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.
14package com.google.devtools.build.skyframe;
15
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000016import com.google.common.base.Joiner;
Janak Ramakrishnan13221742016-06-15 16:37:22 +000017import com.google.common.base.MoreObjects;
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000018import com.google.common.collect.Maps;
19import com.google.common.collect.Maps.EntryTransformer;
20import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
Janak Ramakrishnanf76c9592016-05-17 21:42:50 +000021import com.google.devtools.build.lib.util.GroupedList;
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000022import java.util.Map;
23import java.util.Set;
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000024import javax.annotation.Nullable;
25
26/**
27 * Class that allows clients to be notified on each access of the graph. Clients can simply track
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000028 * accesses, or they can block to achieve desired synchronization. Clients should call {@link
29 * TrackingAwaiter#INSTANCE#assertNoErrors} at the end of tests in case exceptions were swallowed in
30 * async threads.
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000031 */
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000032public class NotifyingHelper {
33 public static MemoizingEvaluator.GraphTransformerForTesting makeNotifyingTransformer(
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000034 final Listener listener) {
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000035 return new MemoizingEvaluator.GraphTransformerForTesting() {
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000036 @Override
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000037 public InMemoryGraph transform(InMemoryGraph graph) {
38 return new NotifyingInMemoryGraph(graph, listener);
39 }
40
41 @Override
Nathan Harmata33faf912016-08-10 19:15:15 +000042 public QueryableGraph transform(QueryableGraph graph) {
43 return new NotifyingQueryableGraph(graph, listener);
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000044 }
45
46 @Override
47 public ProcessableGraph transform(ProcessableGraph graph) {
48 return new NotifyingProcessableGraph(graph, listener);
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000049 }
50 };
51 }
52
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000053 protected final Listener graphListener;
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000054
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000055 protected final EntryTransformer<SkyKey, ThinNodeEntry, NodeEntry> wrapEntry =
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000056 new EntryTransformer<SkyKey, ThinNodeEntry, NodeEntry>() {
57 @Nullable
58 @Override
59 public NotifyingNodeEntry transformEntry(SkyKey key, @Nullable ThinNodeEntry nodeEntry) {
60 return wrapEntry(key, nodeEntry);
61 }
62 };
63
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000064 NotifyingHelper(Listener graphListener) {
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000065 this.graphListener = new ErrorRecordingDelegatingListener(graphListener);
66 }
67
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000068 /** Subclasses should override if they wish to subclass NotifyingNodeEntry. */
69 @Nullable
70 protected NotifyingNodeEntry wrapEntry(SkyKey key, @Nullable ThinNodeEntry entry) {
71 return entry == null ? null : new NotifyingNodeEntry(key, entry);
72 }
73
Nathan Harmata33faf912016-08-10 19:15:15 +000074 static class NotifyingQueryableGraph implements QueryableGraph {
75 private final QueryableGraph delegate;
76 protected final NotifyingHelper notifyingHelper;
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000077
Nathan Harmata33faf912016-08-10 19:15:15 +000078 NotifyingQueryableGraph(QueryableGraph delegate, Listener graphListener) {
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000079 this.notifyingHelper = new NotifyingHelper(graphListener);
80 this.delegate = delegate;
81 }
82
Nathan Harmata33faf912016-08-10 19:15:15 +000083 NotifyingQueryableGraph(QueryableGraph delegate, NotifyingHelper helper) {
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000084 this.notifyingHelper = helper;
85 this.delegate = delegate;
86 }
87
88 @Override
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +000089 public Map<SkyKey, ? extends NodeEntry> getBatch(
90 @Nullable SkyKey requestor, Reason reason, Iterable<SkyKey> keys)
91 throws InterruptedException {
Nathan Harmata3b47b1f2016-07-26 18:41:41 +000092 return Maps.transformEntries(
Nathan Harmata33faf912016-08-10 19:15:15 +000093 delegate.getBatch(requestor, reason, keys),
Nathan Harmata3b47b1f2016-07-26 18:41:41 +000094 notifyingHelper.wrapEntry);
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000095 }
Nathan Harmata33faf912016-08-10 19:15:15 +000096
97 @Nullable
98 @Override
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +000099 public NodeEntry get(@Nullable SkyKey requestor, Reason reason, SkyKey key)
100 throws InterruptedException {
Nathan Harmata33faf912016-08-10 19:15:15 +0000101 return notifyingHelper.wrapEntry(key, delegate.get(requestor, reason, key));
102 }
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +0000103 }
104
Nathan Harmata33faf912016-08-10 19:15:15 +0000105 static class NotifyingProcessableGraph
106 extends NotifyingQueryableGraph implements ProcessableGraph {
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +0000107 protected final ProcessableGraph delegate;
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +0000108
109 NotifyingProcessableGraph(ProcessableGraph delegate, Listener graphListener) {
Nathan Harmata33faf912016-08-10 19:15:15 +0000110 this(delegate, new NotifyingHelper(graphListener));
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +0000111 }
112
113 NotifyingProcessableGraph(ProcessableGraph delegate, NotifyingHelper helper) {
Nathan Harmata33faf912016-08-10 19:15:15 +0000114 super(delegate, helper);
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +0000115 this.delegate = delegate;
116 }
117
118 @Override
119 public void remove(SkyKey key) {
120 delegate.remove(key);
121 }
122
123 @Override
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000124 public Map<SkyKey, ? extends NodeEntry> createIfAbsentBatch(
125 @Nullable SkyKey requestor, Reason reason, Iterable<SkyKey> keys)
126 throws InterruptedException {
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +0000127 for (SkyKey key : keys) {
128 notifyingHelper.graphListener.accept(key, EventType.CREATE_IF_ABSENT, Order.BEFORE, null);
129 }
Nathan Harmata3b47b1f2016-07-26 18:41:41 +0000130 return Maps.transformEntries(
131 delegate.createIfAbsentBatch(requestor, reason, keys),
132 notifyingHelper.wrapEntry);
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +0000133 }
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +0000134 }
135
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000136 /**
137 * Graph/value entry events that the receiver can be informed of. When writing tests, feel free to
138 * add additional events here if needed.
139 */
140 public enum EventType {
141 CREATE_IF_ABSENT,
142 ADD_REVERSE_DEP,
143 REMOVE_REVERSE_DEP,
Janak Ramakrishnanf76c9592016-05-17 21:42:50 +0000144 GET_TEMPORARY_DIRECT_DEPS,
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000145 SIGNAL,
146 SET_VALUE,
147 MARK_DIRTY,
148 MARK_CLEAN,
149 IS_CHANGED,
150 GET_VALUE_WITH_METADATA,
151 IS_DIRTY,
152 IS_READY,
153 CHECK_IF_DONE,
154 GET_ALL_DIRECT_DEPS_FOR_INCOMPLETE_NODE
155 }
156
157 /**
158 * Whether the given event is about to happen or has just happened. For some events, both will be
159 * published, for others, only one. When writing tests, if you need an additional one to be
160 * published, feel free to add it.
161 */
162 public enum Order {
163 BEFORE,
164 AFTER
165 }
166
167 /** Receiver to be informed when an event for a given key occurs. */
168 public interface Listener {
169 @ThreadSafe
170 void accept(SkyKey key, EventType type, Order order, Object context);
171
172 Listener NULL_LISTENER =
173 new Listener() {
174 @Override
175 public void accept(SkyKey key, EventType type, Order order, Object context) {}
176 };
177 }
178
179 private static class ErrorRecordingDelegatingListener implements Listener {
180 private final Listener delegate;
181
182 private ErrorRecordingDelegatingListener(Listener delegate) {
183 this.delegate = delegate;
184 }
185
186 @Override
187 public void accept(SkyKey key, EventType type, Order order, Object context) {
188 try {
189 delegate.accept(key, type, order, context);
190 } catch (Exception e) {
191 TrackingAwaiter.INSTANCE.injectExceptionAndMessage(
192 e, "In NotifyingGraph: " + Joiner.on(", ").join(key, type, order, context));
193 throw e;
194 }
195 }
196 }
197
198 /** {@link NodeEntry} that informs a {@link Listener} of various method calls. */
199 protected class NotifyingNodeEntry extends DelegatingNodeEntry {
200 private final SkyKey myKey;
201 private final ThinNodeEntry delegate;
202
203 protected NotifyingNodeEntry(SkyKey key, ThinNodeEntry delegate) {
204 myKey = key;
205 this.delegate = delegate;
206 }
207
208 @Override
209 protected NodeEntry getDelegate() {
210 return (NodeEntry) delegate;
211 }
212
213 @Override
214 protected ThinNodeEntry getThinDelegate() {
215 return delegate;
216 }
217
218 @Override
Googler7a0b5182016-09-13 14:51:46 +0000219 public DependencyState addReverseDepAndCheckIfDone(SkyKey reverseDep)
220 throws InterruptedException {
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000221 graphListener.accept(myKey, EventType.ADD_REVERSE_DEP, Order.BEFORE, reverseDep);
222 DependencyState result = super.addReverseDepAndCheckIfDone(reverseDep);
223 graphListener.accept(myKey, EventType.ADD_REVERSE_DEP, Order.AFTER, reverseDep);
224 return result;
225 }
226
227 @Override
Googler7a0b5182016-09-13 14:51:46 +0000228 public void removeReverseDep(SkyKey reverseDep) throws InterruptedException {
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000229 graphListener.accept(myKey, EventType.REMOVE_REVERSE_DEP, Order.BEFORE, reverseDep);
230 super.removeReverseDep(reverseDep);
231 graphListener.accept(myKey, EventType.REMOVE_REVERSE_DEP, Order.AFTER, reverseDep);
232 }
233
234 @Override
Janak Ramakrishnanf76c9592016-05-17 21:42:50 +0000235 public GroupedList<SkyKey> getTemporaryDirectDeps() {
236 graphListener.accept(myKey, EventType.GET_TEMPORARY_DIRECT_DEPS, Order.BEFORE, null);
237 return super.getTemporaryDirectDeps();
238 }
239
240 @Override
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000241 public boolean signalDep(Version childVersion) {
242 graphListener.accept(myKey, EventType.SIGNAL, Order.BEFORE, childVersion);
243 boolean result = super.signalDep(childVersion);
244 graphListener.accept(myKey, EventType.SIGNAL, Order.AFTER, childVersion);
245 return result;
246 }
247
248 @Override
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000249 public Set<SkyKey> setValue(SkyValue value, Version version) throws InterruptedException {
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000250 graphListener.accept(myKey, EventType.SET_VALUE, Order.BEFORE, value);
251 Set<SkyKey> result = super.setValue(value, version);
252 graphListener.accept(myKey, EventType.SET_VALUE, Order.AFTER, value);
253 return result;
254 }
255
256 @Override
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000257 public MarkedDirtyResult markDirty(boolean isChanged) throws InterruptedException {
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000258 graphListener.accept(myKey, EventType.MARK_DIRTY, Order.BEFORE, isChanged);
259 MarkedDirtyResult result = super.markDirty(isChanged);
260 graphListener.accept(myKey, EventType.MARK_DIRTY, Order.AFTER, isChanged);
261 return result;
262 }
263
264 @Override
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000265 public Set<SkyKey> markClean() throws InterruptedException {
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000266 graphListener.accept(myKey, EventType.MARK_CLEAN, Order.BEFORE, this);
267 Set<SkyKey> result = super.markClean();
268 graphListener.accept(myKey, EventType.MARK_CLEAN, Order.AFTER, this);
269 return result;
270 }
271
272 @Override
273 public boolean isChanged() {
274 graphListener.accept(myKey, EventType.IS_CHANGED, Order.BEFORE, this);
275 return super.isChanged();
276 }
277
278 @Override
279 public boolean isDirty() {
280 graphListener.accept(myKey, EventType.IS_DIRTY, Order.BEFORE, this);
281 return super.isDirty();
282 }
283
284 @Override
285 public boolean isReady() {
286 graphListener.accept(myKey, EventType.IS_READY, Order.BEFORE, this);
287 return super.isReady();
288 }
289
290 @Override
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000291 public SkyValue getValueMaybeWithMetadata() throws InterruptedException {
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000292 graphListener.accept(myKey, EventType.GET_VALUE_WITH_METADATA, Order.BEFORE, this);
293 return super.getValueMaybeWithMetadata();
294 }
295
296 @Override
Googler7a0b5182016-09-13 14:51:46 +0000297 public DependencyState checkIfDoneForDirtyReverseDep(SkyKey reverseDep)
298 throws InterruptedException {
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000299 graphListener.accept(myKey, EventType.CHECK_IF_DONE, Order.BEFORE, reverseDep);
300 return super.checkIfDoneForDirtyReverseDep(reverseDep);
301 }
302
303 @Override
304 public Iterable<SkyKey> getAllDirectDepsForIncompleteNode() {
305 graphListener.accept(
306 myKey, EventType.GET_ALL_DIRECT_DEPS_FOR_INCOMPLETE_NODE, Order.BEFORE, this);
307 return super.getAllDirectDepsForIncompleteNode();
308 }
Janak Ramakrishnan13221742016-06-15 16:37:22 +0000309
310 @Override
311 public String toString() {
312 return MoreObjects.toStringHelper(this).add("delegate", getThinDelegate()).toString();
313 }
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000314 }
315}