blob: 2f973b5bcdb37a8d5c911c306388cc1e34356242 [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 +000022
23import java.util.Map;
24import java.util.Set;
25
26import javax.annotation.Nullable;
27
28/**
29 * Class that allows clients to be notified on each access of the graph. Clients can simply track
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000030 * accesses, or they can block to achieve desired synchronization. Clients should call {@link
31 * TrackingAwaiter#INSTANCE#assertNoErrors} at the end of tests in case exceptions were swallowed in
32 * async threads.
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000033 */
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000034public class NotifyingHelper {
35 public static MemoizingEvaluator.GraphTransformerForTesting makeNotifyingTransformer(
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000036 final Listener listener) {
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000037 return new MemoizingEvaluator.GraphTransformerForTesting() {
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000038 @Override
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000039 public InMemoryGraph transform(InMemoryGraph graph) {
40 return new NotifyingInMemoryGraph(graph, listener);
41 }
42
43 @Override
44 public InvalidatableGraph transform(InvalidatableGraph graph) {
45 return new NotifyingInvalidatableGraph(graph, listener);
46 }
47
48 @Override
49 public ProcessableGraph transform(ProcessableGraph graph) {
50 return new NotifyingProcessableGraph(graph, listener);
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000051 }
52 };
53 }
54
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000055 protected final Listener graphListener;
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000056
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000057 protected final EntryTransformer<SkyKey, ThinNodeEntry, NodeEntry> wrapEntry =
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000058 new EntryTransformer<SkyKey, ThinNodeEntry, NodeEntry>() {
59 @Nullable
60 @Override
61 public NotifyingNodeEntry transformEntry(SkyKey key, @Nullable ThinNodeEntry nodeEntry) {
62 return wrapEntry(key, nodeEntry);
63 }
64 };
65
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000066 NotifyingHelper(Listener graphListener) {
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000067 this.graphListener = new ErrorRecordingDelegatingListener(graphListener);
68 }
69
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +000070 /** Subclasses should override if they wish to subclass NotifyingNodeEntry. */
71 @Nullable
72 protected NotifyingNodeEntry wrapEntry(SkyKey key, @Nullable ThinNodeEntry entry) {
73 return entry == null ? null : new NotifyingNodeEntry(key, entry);
74 }
75
Janak Ramakrishnancc7712f2016-07-08 17:38:27 +000076 static class NotifyingInvalidatableGraph implements InvalidatableGraph {
77 private final InvalidatableGraph delegate;
78 private final NotifyingHelper notifyingHelper;
79
80 NotifyingInvalidatableGraph(InvalidatableGraph delegate, Listener graphListener) {
81 this.notifyingHelper = new NotifyingHelper(graphListener);
82 this.delegate = delegate;
83 }
84
85 NotifyingInvalidatableGraph(InvalidatableGraph delegate, NotifyingHelper helper) {
86 this.notifyingHelper = helper;
87 this.delegate = delegate;
88 }
89
90 @Override
91 public Map<SkyKey, NodeEntry> getBatch(Iterable<SkyKey> keys) {
92 return Maps.transformEntries(delegate.getBatch(keys), notifyingHelper.wrapEntry);
93 }
94 }
95
96 static class NotifyingProcessableGraph implements ProcessableGraph {
97 protected final ProcessableGraph delegate;
98 protected final NotifyingHelper notifyingHelper;
99
100 NotifyingProcessableGraph(ProcessableGraph delegate, Listener graphListener) {
101 this.notifyingHelper = new NotifyingHelper(graphListener);
102 this.delegate = delegate;
103 }
104
105 NotifyingProcessableGraph(ProcessableGraph delegate, NotifyingHelper helper) {
106 this.notifyingHelper = helper;
107 this.delegate = delegate;
108 }
109
110 @Override
111 public void remove(SkyKey key) {
112 delegate.remove(key);
113 }
114
115 @Override
116 public Map<SkyKey, NodeEntry> createIfAbsentBatch(Iterable<SkyKey> keys) {
117 for (SkyKey key : keys) {
118 notifyingHelper.graphListener.accept(key, EventType.CREATE_IF_ABSENT, Order.BEFORE, null);
119 }
120 return Maps.transformEntries(delegate.createIfAbsentBatch(keys), notifyingHelper.wrapEntry);
121 }
122
123 @Override
124 public Map<SkyKey, NodeEntry> getBatch(Iterable<SkyKey> keys) {
125 return Maps.transformEntries(delegate.getBatch(keys), notifyingHelper.wrapEntry);
126 }
127
128 @Nullable
129 @Override
130 public NodeEntry get(SkyKey key) {
131 return notifyingHelper.wrapEntry(key, delegate.get(key));
132 }
133 }
134
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000135 /**
136 * Graph/value entry events that the receiver can be informed of. When writing tests, feel free to
137 * add additional events here if needed.
138 */
139 public enum EventType {
140 CREATE_IF_ABSENT,
141 ADD_REVERSE_DEP,
142 REMOVE_REVERSE_DEP,
Janak Ramakrishnanf76c9592016-05-17 21:42:50 +0000143 GET_TEMPORARY_DIRECT_DEPS,
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000144 SIGNAL,
145 SET_VALUE,
146 MARK_DIRTY,
147 MARK_CLEAN,
148 IS_CHANGED,
149 GET_VALUE_WITH_METADATA,
150 IS_DIRTY,
151 IS_READY,
152 CHECK_IF_DONE,
153 GET_ALL_DIRECT_DEPS_FOR_INCOMPLETE_NODE
154 }
155
156 /**
157 * Whether the given event is about to happen or has just happened. For some events, both will be
158 * published, for others, only one. When writing tests, if you need an additional one to be
159 * published, feel free to add it.
160 */
161 public enum Order {
162 BEFORE,
163 AFTER
164 }
165
166 /** Receiver to be informed when an event for a given key occurs. */
167 public interface Listener {
168 @ThreadSafe
169 void accept(SkyKey key, EventType type, Order order, Object context);
170
171 Listener NULL_LISTENER =
172 new Listener() {
173 @Override
174 public void accept(SkyKey key, EventType type, Order order, Object context) {}
175 };
176 }
177
178 private static class ErrorRecordingDelegatingListener implements Listener {
179 private final Listener delegate;
180
181 private ErrorRecordingDelegatingListener(Listener delegate) {
182 this.delegate = delegate;
183 }
184
185 @Override
186 public void accept(SkyKey key, EventType type, Order order, Object context) {
187 try {
188 delegate.accept(key, type, order, context);
189 } catch (Exception e) {
190 TrackingAwaiter.INSTANCE.injectExceptionAndMessage(
191 e, "In NotifyingGraph: " + Joiner.on(", ").join(key, type, order, context));
192 throw e;
193 }
194 }
195 }
196
197 /** {@link NodeEntry} that informs a {@link Listener} of various method calls. */
198 protected class NotifyingNodeEntry extends DelegatingNodeEntry {
199 private final SkyKey myKey;
200 private final ThinNodeEntry delegate;
201
202 protected NotifyingNodeEntry(SkyKey key, ThinNodeEntry delegate) {
203 myKey = key;
204 this.delegate = delegate;
205 }
206
207 @Override
208 protected NodeEntry getDelegate() {
209 return (NodeEntry) delegate;
210 }
211
212 @Override
213 protected ThinNodeEntry getThinDelegate() {
214 return delegate;
215 }
216
217 @Override
218 public DependencyState addReverseDepAndCheckIfDone(SkyKey reverseDep) {
219 graphListener.accept(myKey, EventType.ADD_REVERSE_DEP, Order.BEFORE, reverseDep);
220 DependencyState result = super.addReverseDepAndCheckIfDone(reverseDep);
221 graphListener.accept(myKey, EventType.ADD_REVERSE_DEP, Order.AFTER, reverseDep);
222 return result;
223 }
224
225 @Override
226 public void removeReverseDep(SkyKey reverseDep) {
227 graphListener.accept(myKey, EventType.REMOVE_REVERSE_DEP, Order.BEFORE, reverseDep);
228 super.removeReverseDep(reverseDep);
229 graphListener.accept(myKey, EventType.REMOVE_REVERSE_DEP, Order.AFTER, reverseDep);
230 }
231
232 @Override
Janak Ramakrishnanf76c9592016-05-17 21:42:50 +0000233 public GroupedList<SkyKey> getTemporaryDirectDeps() {
234 graphListener.accept(myKey, EventType.GET_TEMPORARY_DIRECT_DEPS, Order.BEFORE, null);
235 return super.getTemporaryDirectDeps();
236 }
237
238 @Override
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000239 public boolean signalDep(Version childVersion) {
240 graphListener.accept(myKey, EventType.SIGNAL, Order.BEFORE, childVersion);
241 boolean result = super.signalDep(childVersion);
242 graphListener.accept(myKey, EventType.SIGNAL, Order.AFTER, childVersion);
243 return result;
244 }
245
246 @Override
247 public Set<SkyKey> setValue(SkyValue value, Version version) {
248 graphListener.accept(myKey, EventType.SET_VALUE, Order.BEFORE, value);
249 Set<SkyKey> result = super.setValue(value, version);
250 graphListener.accept(myKey, EventType.SET_VALUE, Order.AFTER, value);
251 return result;
252 }
253
254 @Override
255 public MarkedDirtyResult markDirty(boolean isChanged) {
256 graphListener.accept(myKey, EventType.MARK_DIRTY, Order.BEFORE, isChanged);
257 MarkedDirtyResult result = super.markDirty(isChanged);
258 graphListener.accept(myKey, EventType.MARK_DIRTY, Order.AFTER, isChanged);
259 return result;
260 }
261
262 @Override
263 public Set<SkyKey> markClean() {
264 graphListener.accept(myKey, EventType.MARK_CLEAN, Order.BEFORE, this);
265 Set<SkyKey> result = super.markClean();
266 graphListener.accept(myKey, EventType.MARK_CLEAN, Order.AFTER, this);
267 return result;
268 }
269
270 @Override
271 public boolean isChanged() {
272 graphListener.accept(myKey, EventType.IS_CHANGED, Order.BEFORE, this);
273 return super.isChanged();
274 }
275
276 @Override
277 public boolean isDirty() {
278 graphListener.accept(myKey, EventType.IS_DIRTY, Order.BEFORE, this);
279 return super.isDirty();
280 }
281
282 @Override
283 public boolean isReady() {
284 graphListener.accept(myKey, EventType.IS_READY, Order.BEFORE, this);
285 return super.isReady();
286 }
287
288 @Override
289 public SkyValue getValueMaybeWithMetadata() {
290 graphListener.accept(myKey, EventType.GET_VALUE_WITH_METADATA, Order.BEFORE, this);
291 return super.getValueMaybeWithMetadata();
292 }
293
294 @Override
295 public DependencyState checkIfDoneForDirtyReverseDep(SkyKey reverseDep) {
296 graphListener.accept(myKey, EventType.CHECK_IF_DONE, Order.BEFORE, reverseDep);
297 return super.checkIfDoneForDirtyReverseDep(reverseDep);
298 }
299
300 @Override
301 public Iterable<SkyKey> getAllDirectDepsForIncompleteNode() {
302 graphListener.accept(
303 myKey, EventType.GET_ALL_DIRECT_DEPS_FOR_INCOMPLETE_NODE, Order.BEFORE, this);
304 return super.getAllDirectDepsForIncompleteNode();
305 }
Janak Ramakrishnan13221742016-06-15 16:37:22 +0000306
307 @Override
308 public String toString() {
309 return MoreObjects.toStringHelper(this).add("delegate", getThinDelegate()).toString();
310 }
Janak Ramakrishnan8be7fd02016-05-10 20:01:40 +0000311 }
312}