aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java')
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java467
1 files changed, 467 insertions, 0 deletions
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java
new file mode 100644
index 00000000..d244e644
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java
@@ -0,0 +1,467 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Tamas Szabo, Istvan Rath and Daniel Varro
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication;
10
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.List;
14import java.util.Map;
15import java.util.PriorityQueue;
16import java.util.Queue;
17import java.util.Set;
18
19import tools.refinery.viatra.runtime.rete.itc.alg.incscc.IncSCCAlg;
20import tools.refinery.viatra.runtime.rete.itc.alg.misc.topsort.TopologicalSorting;
21import tools.refinery.viatra.runtime.rete.itc.graphimpl.Graph;
22import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
23import tools.refinery.viatra.runtime.rete.aggregation.IAggregatorNode;
24import tools.refinery.viatra.runtime.rete.boundary.ExternalInputEnumeratorNode;
25import tools.refinery.viatra.runtime.rete.eval.RelationEvaluatorNode;
26import tools.refinery.viatra.runtime.rete.index.DualInputNode;
27import tools.refinery.viatra.runtime.rete.index.ExistenceNode;
28import tools.refinery.viatra.runtime.rete.index.Indexer;
29import tools.refinery.viatra.runtime.rete.index.IndexerListener;
30import tools.refinery.viatra.runtime.rete.index.IterableIndexer;
31import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer;
32import tools.refinery.viatra.runtime.rete.network.IGroupable;
33import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode;
34import tools.refinery.viatra.runtime.rete.network.Node;
35import tools.refinery.viatra.runtime.rete.network.ProductionNode;
36import tools.refinery.viatra.runtime.rete.network.Receiver;
37import tools.refinery.viatra.runtime.rete.network.ReteContainer;
38import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyIndexerListenerProxy;
39import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyMailboxProxy;
40import tools.refinery.viatra.runtime.rete.network.mailbox.FallThroughCapableMailbox;
41import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
42import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox;
43import tools.refinery.viatra.runtime.rete.single.TransitiveClosureNode;
44import tools.refinery.viatra.runtime.rete.single.TrimmerNode;
45
46/**
47 * An instance of this class is associated with every {@link ReteContainer}. The tracker serves two purposes: <br>
48 * (1) It allows RETE nodes to register their communication dependencies on-the-fly. These dependencies can be
49 * registered or unregistered when nodes are disposed of. <br>
50 * (2) It allows RETE nodes to register their mailboxes as dirty, that is, they can tell the tracker that they have
51 * something to send to other nodes in the network. The tracker is then responsible for ordering these messages (more
52 * precisely, the mailboxes that contain the messages) for the associated {@link ReteContainer}. The ordering is
53 * governed by the strongly connected components in the dependency network and follows a topological sorting scheme;
54 * those mailboxes will be emptied first whose owner nodes do not depend on other undelivered messages.
55 *
56 * @author Tamas Szabo
57 * @since 1.6
58 *
59 */
60public abstract class CommunicationTracker {
61
62 /**
63 * The minimum group id assigned so far
64 */
65 protected int minGroupId;
66
67 /**
68 * The maximum group id assigned so far
69 */
70 protected int maxGroupId;
71
72 /**
73 * The dependency graph of the communications in the RETE network
74 */
75 protected final Graph<Node> dependencyGraph;
76
77 /**
78 * Incremental SCC information about the dependency graph
79 */
80 protected final IncSCCAlg<Node> sccInformationProvider;
81
82 /**
83 * Precomputed node -> communication group map
84 */
85 protected final Map<Node, CommunicationGroup> groupMap;
86
87 /**
88 * Priority queue of active communication groups
89 */
90 protected final Queue<CommunicationGroup> groupQueue;
91
92 // groups should have a simple integer flag which represents its position in a priority queue
93 // priority queue only contains the ACTIVE groups
94
95 public CommunicationTracker() {
96 this.dependencyGraph = new Graph<Node>();
97 this.sccInformationProvider = new IncSCCAlg<Node>(this.dependencyGraph);
98 this.groupQueue = new PriorityQueue<CommunicationGroup>();
99 this.groupMap = new HashMap<Node, CommunicationGroup>();
100 }
101
102 public Graph<Node> getDependencyGraph() {
103 return dependencyGraph;
104 }
105
106 public CommunicationGroup getGroup(final Node node) {
107 return this.groupMap.get(node);
108 }
109
110 private void precomputeGroups() {
111 groupMap.clear();
112
113 // reconstruct group map from dependency graph
114 final Graph<Node> reducedGraph = sccInformationProvider.getReducedGraph();
115 final List<Node> representatives = TopologicalSorting.compute(reducedGraph);
116
117 for (int i = 0; i < representatives.size(); i++) { // groups for SCC representatives
118 final Node representative = representatives.get(i);
119 createAndStoreGroup(representative, i);
120 }
121
122 minGroupId = 0;
123 maxGroupId = representatives.size() - 1;
124
125 for (final Node node : dependencyGraph.getAllNodes()) { // extend group map to the rest of nodes
126 final Node representative = sccInformationProvider.getRepresentative(node);
127 final CommunicationGroup group = groupMap.get(representative);
128 if (representative != node) {
129 addToGroup(node, group);
130 }
131 }
132
133 for (final Node node : dependencyGraph.getAllNodes()) {
134 // set fall-through flags of default mailboxes
135 precomputeFallThroughFlag(node);
136 // perform further tracker-specific post-processing
137 postProcessNode(node);
138 }
139
140 // reconstruct new queue contents based on new group map
141 if (!groupQueue.isEmpty()) {
142 final Set<CommunicationGroup> oldActiveGroups = new HashSet<CommunicationGroup>(groupQueue);
143 groupQueue.clear();
144 reconstructQueueContents(oldActiveGroups);
145 }
146
147 // post process the groups
148 for (final CommunicationGroup group : groupMap.values()) {
149 postProcessGroup(group);
150 }
151 }
152
153 /**
154 * This method is responsible for reconstructing the active queue contents after the network structure has changed.
155 * It it defined as abstract because the reconstruction logic is specific to each {@link CommunicationTracker}.
156 * @since 2.4
157 */
158 protected abstract void reconstructQueueContents(final Set<CommunicationGroup> oldActiveGroups);
159
160 private void addToGroup(final Node node, final CommunicationGroup group) {
161 groupMap.put(node, group);
162 if (node instanceof Receiver) {
163 ((Receiver) node).getMailbox().setCurrentGroup(group);
164 if (node instanceof IGroupable) {
165 ((IGroupable) node).setCurrentGroup(group);
166 }
167 }
168 }
169
170 /**
171 * Depends on the groups, as well as the parent nodes of the argument, so recomputation is needed if these change
172 */
173 private void precomputeFallThroughFlag(final Node node) {
174 CommunicationGroup group = groupMap.get(node);
175 if (node instanceof Receiver) {
176 IGroupable mailbox = ((Receiver) node).getMailbox();
177 if (mailbox instanceof FallThroughCapableMailbox) {
178 Set<Node> directParents = dependencyGraph.getSourceNodes(node).distinctValues();
179 // decide between using quick&cheap fall-through, or allowing for update cancellation
180 boolean fallThrough =
181 // disallow fallthrough: updates at production nodes should cancel, if they can be trimmed or
182 // disjunctive
183 (!(node instanceof ProductionNode && ( // it is a production node...
184 // with more than one parent
185 directParents.size() > 0 ||
186 // or true trimming in its sole parent
187 directParents.size() == 1 && trueTrimming(directParents.iterator().next())))) &&
188 // disallow fallthrough: external updates should be stored (if updates are delayed)
189 (!(node instanceof ExternalInputEnumeratorNode)) &&
190 // disallow fallthrough: RelationEvaluatorNode needs to be notified in batch-style, and the batching is done by the mailbox
191 // however, it is not the RelationEvaluatorNode itself that is interesting here, as that indirectly uses the BatchingReceiver
192 // so we need to disable fall-through for the BatchingReceiver
193 (!(node instanceof RelationEvaluatorNode.BatchingReceiver));
194 // do additional checks
195 if (fallThrough) {
196 // recursive parent groups generate excess updates that should be cancelled after delete&rederive
197 // phases
198 // aggregator and transitive closure parent nodes also generate excess updates that should be
199 // cancelled
200 directParentLoop: for (Node directParent : directParents) {
201 Set<Node> parentsToCheck = new HashSet<>();
202 // check the case where a direct parent is the reason for mailbox usage
203 parentsToCheck.add(directParent);
204 // check the case where an indirect parent (join slot) is the reason for mailbox usage
205 if (directParent instanceof DualInputNode) {
206 // in case of existence join (typically antijoin), a mailbox should allow
207 // an insertion and deletion (at the secondary slot) to cancel each other out
208 if (directParent instanceof ExistenceNode) {
209 fallThrough = false;
210 break directParentLoop;
211 }
212 // in beta nodes, indexer slots (or their active nodes) are considered indirect parents
213 DualInputNode dualInput = (DualInputNode) directParent;
214 IterableIndexer primarySlot = dualInput.getPrimarySlot();
215 if (primarySlot != null)
216 parentsToCheck.add(primarySlot.getActiveNode());
217 Indexer secondarySlot = dualInput.getSecondarySlot();
218 if (secondarySlot != null)
219 parentsToCheck.add(secondarySlot.getActiveNode());
220 }
221 for (Node parent : parentsToCheck) {
222 CommunicationGroup parentGroup = groupMap.get(parent);
223 if ( // parent is in a different, recursive group
224 (group != parentGroup && parentGroup.isRecursive()) ||
225 // node and parent within the same recursive group, and...
226 (group == parentGroup && group.isRecursive() && (
227 // parent is a transitive closure or aggregator node, or a trimmer
228 // allow trimmed or disjunctive tuple updates to cancel each other
229 (parent instanceof TransitiveClosureNode) || (parent instanceof IAggregatorNode)
230 || trueTrimming(parent)))) {
231 fallThrough = false;
232 break directParentLoop;
233 }
234 }
235 }
236 }
237 // overwrite fallthrough flag with newly computed value
238 ((FallThroughCapableMailbox) mailbox).setFallThrough(fallThrough);
239 }
240 }
241 }
242
243 /**
244 * A trimmer node that actually eliminates some columns (not just reorders)
245 */
246 private boolean trueTrimming(Node node) {
247 if (node instanceof TrimmerNode) {
248 TupleMask mask = ((TrimmerNode) node).getMask();
249 return (mask.indices.length != mask.sourceWidth);
250 }
251 return false;
252 }
253
254 public void activateUnenqueued(final CommunicationGroup group) {
255 groupQueue.add(group);
256 group.isEnqueued = true;
257 }
258
259 public void deactivate(final CommunicationGroup group) {
260 groupQueue.remove(group);
261 group.isEnqueued = false;
262 }
263
264 public CommunicationGroup getAndRemoveFirstGroup() {
265 final CommunicationGroup group = groupQueue.poll();
266 group.isEnqueued = false;
267 return group;
268 }
269
270 public boolean isEmpty() {
271 return groupQueue.isEmpty();
272 }
273
274 protected abstract CommunicationGroup createGroup(final Node representative, final int index);
275
276 protected CommunicationGroup createAndStoreGroup(final Node representative, final int index) {
277 final CommunicationGroup group = createGroup(representative, index);
278 addToGroup(representative, group);
279 return group;
280 }
281
282 /**
283 * Registers the dependency that the target {@link Node} depends on the source {@link Node}. In other words, source
284 * may send messages to target in the RETE network. If the dependency edge is already present, this method call is a
285 * noop.
286 *
287 * @param source
288 * the source node
289 * @param target
290 * the target node
291 */
292 public void registerDependency(final Node source, final Node target) {
293 // nodes can be immediately inserted, if they already exist in the graph, this is a noop
294 dependencyGraph.insertNode(source);
295 dependencyGraph.insertNode(target);
296
297 if (!this.dependencyGraph.getTargetNodes(source).containsNonZero(target)) {
298
299 // query all these information before the actual edge insertion
300 // because SCCs may be unified during the process
301 final Node sourceRepresentative = sccInformationProvider.getRepresentative(source);
302 final Node targetRepresentative = sccInformationProvider.getRepresentative(target);
303 final boolean targetHadOutgoingEdges = sccInformationProvider.hasOutgoingEdges(targetRepresentative);
304
305 // insert the edge
306 dependencyGraph.insertEdge(source, target);
307
308 // create groups if they do not yet exist
309 CommunicationGroup sourceGroup = groupMap.get(sourceRepresentative);
310 if (sourceGroup == null) {
311 // create on-demand with the next smaller group id
312 sourceGroup = createAndStoreGroup(sourceRepresentative, --minGroupId);
313 }
314 final int sourceIndex = sourceGroup.identifier;
315
316 CommunicationGroup targetGroup = groupMap.get(targetRepresentative);
317 if (targetGroup == null) {
318 // create on-demand with the next larger group id
319 targetGroup = createAndStoreGroup(targetRepresentative, ++maxGroupId);
320 }
321 final int targetIndex = targetGroup.identifier;
322
323 if (sourceIndex <= targetIndex) {
324 // indices obey current topological ordering
325 refreshFallThroughFlag(target);
326 postProcessNode(source);
327 postProcessNode(target);
328 postProcessGroup(sourceGroup);
329 if (sourceGroup != targetGroup) {
330 postProcessGroup(targetGroup);
331 }
332 } else if (sourceIndex > targetIndex && !targetHadOutgoingEdges) {
333 // indices violate current topological ordering, but we can simply bump the target index
334 final boolean wasEnqueued = targetGroup.isEnqueued;
335 if (wasEnqueued) {
336 groupQueue.remove(targetGroup);
337 }
338 targetGroup.identifier = ++maxGroupId;
339 if (wasEnqueued) {
340 groupQueue.add(targetGroup);
341 }
342
343 refreshFallThroughFlag(target);
344 postProcessNode(source);
345 postProcessNode(target);
346 postProcessGroup(sourceGroup);
347 postProcessGroup(targetGroup);
348 } else {
349 // needs a full re-computation because of more complex change
350 precomputeGroups();
351 }
352 }
353 }
354
355 /**
356 * Returns true if the given {@link Node} is in a recursive {@link CommunicationGroup}, false otherwise.
357 */
358 public boolean isInRecursiveGroup(final Node node) {
359 final CommunicationGroup group = this.getGroup(node);
360 if (group == null) {
361 return false;
362 } else {
363 return group.isRecursive();
364 }
365 }
366
367 /**
368 * Returns true if the given two {@link Node}s are in the same {@link CommunicationGroup}.
369 */
370 public boolean areInSameGroup(final Node left, final Node right) {
371 final CommunicationGroup leftGroup = this.getGroup(left);
372 final CommunicationGroup rightGroup = this.getGroup(right);
373 return leftGroup != null && leftGroup == rightGroup;
374 }
375
376 /**
377 * Unregisters a dependency between source and target.
378 *
379 * @param source
380 * the source node
381 * @param target
382 * the target node
383 */
384 public void unregisterDependency(final Node source, final Node target) {
385 // delete the edge first, and then query the SCC info provider
386 this.dependencyGraph.deleteEdgeIfExists(source, target);
387
388 final Node sourceRepresentative = sccInformationProvider.getRepresentative(source);
389 final Node targetRepresentative = sccInformationProvider.getRepresentative(target);
390
391 // if they are still in the same SCC,
392 // then this deletion did not affect the SCCs,
393 // and it is sufficient to recompute affected fall-through flags;
394 // otherwise, we need a new pre-computation for the groupMap and groupQueue
395 if (sourceRepresentative.equals(targetRepresentative)) {
396 // this deletion could not have affected the split flags
397 refreshFallThroughFlag(target);
398 postProcessNode(source);
399 postProcessNode(target);
400 } else {
401 // preComputeGroups takes care of the split flag maintenance
402 precomputeGroups();
403 }
404 }
405
406 /**
407 * Refresh fall-through flags if dependencies change for given target, but no SCC change
408 */
409 private void refreshFallThroughFlag(final Node target) {
410 precomputeFallThroughFlag(target);
411 if (target instanceof DualInputNode) {
412 for (final Node indirectTarget : dependencyGraph.getTargetNodes(target).distinctValues()) {
413 precomputeFallThroughFlag(indirectTarget);
414 }
415 }
416 }
417
418 /**
419 * Returns true if the given source-target edge in the communication network acts as a recursion cut point.
420 * The current implementation considers edges leading into {@link ProductionNode}s as cut point iff
421 * both source and target belong to the same group.
422 *
423 * @param source the source node
424 * @param target the target node
425 * @return true if the edge is a cut point, false otherwise
426 * @since 2.4
427 */
428 protected boolean isRecursionCutPoint(final Node source, final Node target) {
429 final Node effectiveSource = source instanceof SpecializedProjectionIndexer
430 ? ((SpecializedProjectionIndexer) source).getActiveNode()
431 : source;
432 final CommunicationGroup sourceGroup = this.getGroup(effectiveSource);
433 final CommunicationGroup targetGroup = this.getGroup(target);
434 return sourceGroup != null && sourceGroup == targetGroup && target instanceof ProductionNode;
435 }
436
437 /**
438 * This hook allows concrete tracker implementations to perform tracker-specific post processing on nodes (cf.
439 * {@link NetworkStructureChangeSensitiveNode} and {@link BehaviorChangingMailbox}). At the time of the invocation,
440 * the network topology has already been updated.
441 */
442 protected abstract void postProcessNode(final Node node);
443
444 /**
445 * This hook allows concrete tracker implementations to perform tracker-specific post processing on groups. At the
446 * time of the invocation, the network topology has already been updated.
447 * @since 2.4
448 */
449 protected abstract void postProcessGroup(final CommunicationGroup group);
450
451 /**
452 * Creates a proxy for the given {@link Mailbox} for the given requester {@link Node}. The proxy creation is
453 * {@link CommunicationTracker}-specific and depends on the identity of the requester. This method is primarily used
454 * to create {@link TimelyMailboxProxy}s depending on the network topology. There is no guarantee that the same
455 * proxy instance is returned when this method is called multiple times with the same arguments.
456 */
457 public abstract Mailbox proxifyMailbox(final Node requester, final Mailbox original);
458
459 /**
460 * Creates a proxy for the given {@link IndexerListener} for the given requester {@link Node}. The proxy creation is
461 * {@link CommunicationTracker}-specific and depends on the identity of the requester. This method is primarily used
462 * to create {@link TimelyIndexerListenerProxy}s depending on the network topology. There is no guarantee that the
463 * same proxy instance is returned when this method is called multiple times with the same arguments.
464 */
465 public abstract IndexerListener proxifyIndexerListener(final Node requester, final IndexerListener original);
466
467}